projgen: Working!
The test-gmod project now builds without any hand-written Makefile, building it by using projgen to generate a makefile then make to build it now works! I'm pretty happy with it, there's a couple things I might want to fix/add, but for now it's functionally ready. Now I can probably focus on building a Containerfile for the build image.
This commit is contained in:
parent
568064068e
commit
92a62322f9
|
@ -4,3 +4,9 @@ module_build/
|
||||||
|
|
||||||
.cache/
|
.cache/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# generated products of the GMOD test program
|
||||||
|
test-gmod/Makefile
|
||||||
|
test-gmod/obj
|
||||||
|
test-gmod/*.elf
|
||||||
|
test-gmod/*.bin
|
||||||
|
|
35
ideas.md
35
ideas.md
|
@ -11,7 +11,7 @@ This is basically the working ideas for the LCPU project.
|
||||||
|
|
||||||
## Code upload
|
## Code upload
|
||||||
|
|
||||||
- Upload a raw binary to execute, generated from any user tooling (goes into server `data/lcpu/users/[steamid]/`)
|
- Upload a raw binary to execute, generated from any user tooling (goes into the server `data/lcpu/users/[steamid]/`)
|
||||||
- Yes, this means you can run Linux in GMod. No, I'm not sorry.
|
- Yes, this means you can run Linux in GMod. No, I'm not sorry.
|
||||||
|
|
||||||
## Integrated simple project workflow (WIP, not in the addon yet)
|
## Integrated simple project workflow (WIP, not in the addon yet)
|
||||||
|
@ -23,21 +23,18 @@ This is basically the working ideas for the LCPU project.
|
||||||
- At the root of a project, a `project.json` file is expected to exist, with contents like:
|
- At the root of a project, a `project.json` file is expected to exist, with contents like:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Project": {
|
|
||||||
"Name": "test",
|
"Name": "test",
|
||||||
|
|
||||||
// Define all build configurations here
|
|
||||||
"Configurations": {
|
"Configurations": {
|
||||||
"Debug": {
|
"Debug": {
|
||||||
"CCompileFlags": "-O0 ${BaseCCompileFlags}",
|
"CCompileFlags": "-O0",
|
||||||
"CppCompileFlags": "-O0 ${BaseCppCompileFlags}"
|
"CppCompileFlags": "-O0",
|
||||||
|
"LinkerFlags": ""
|
||||||
},
|
},
|
||||||
"Release": {
|
"Release": {
|
||||||
"CCompileFlags": "-O2 ${BaseCCompileFlags}",
|
"CCompileFlags": "-O2",
|
||||||
"CppCompileFlags": "-O2 ${BaseCppCompileFlags}",
|
"CppCompileFlags": "-O2",
|
||||||
// If a variable is unset it will usually default to
|
"LinkerFlags": "-Wl,--gc-sections"
|
||||||
// ${Base${VariableName}}
|
|
||||||
"LinkerFlags": "-Wl,--gc-sections ${BaseLinkerFlags}"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -47,32 +44,26 @@ This is basically the working ideas for the LCPU project.
|
||||||
"main.c"
|
"main.c"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
- This will be transpiled into a `Makefile` when built.
|
||||||
- `BaseCCompileFlags` and `BaseCppCompileFlags` are defaulted to sane values for each language.
|
|
||||||
|
|
||||||
- This will be transpiled into a `Makefile` by the addon.
|
|
||||||
- A standalone tool will be provided and used for transpiling `project.json` to a `Makefile` (and maybe even built into the container and transpiled there, to reduce the actions on the host to just the podman run?)
|
- A standalone tool will be provided and used for transpiling `project.json` to a `Makefile` (and maybe even built into the container and transpiled there, to reduce the actions on the host to just the podman run?)
|
||||||
- which, when a Build is done in GMod; is then run with `make` in a temporary podman container which only has access to the source code folder for the project (and nothing else, besides riscv tools which are in the image).
|
- which, when a Build is requested in GMod; is then run with `make` in a temporary podman container which only has access to the source code folder for the project (and nothing else, besides riscv tools which are in the image).
|
||||||
- Command line is probably something like `make CONFIG=${config}`
|
- Command line is probably something like `podman run localhost/lcpu-build:latest -v /project:[host project dir] make CONFIG=${config}`
|
||||||
- the output binary will be stored alongside the source code on the server side, with a name like `${name}-${config}.bin`
|
- the output binary will be stored alongside the source code on the server side, with a name like `${name}_${config}.bin`
|
||||||
- This file can then be selected for loading (without needing to be uploaded from the client).
|
- This file can then be selected for loading (without needing to be uploaded from the client).
|
||||||
|
|
||||||
|
|
||||||
- There is no conditional compilation in the `project.json` system
|
- There is no conditional compilation in the `project.json` system
|
||||||
- All files in a project are always built by that project.
|
|
||||||
|
|
||||||
- No notion of subprojects/build dependencies
|
- No notion of subprojects/build dependencies
|
||||||
- This is meant to be simple for easy development in GMod. If you want complex build features you can export the project onto your own computer and use `lcpu_projgen` to generate Makefiles (which you can then maintain)
|
|
||||||
|
|
||||||
- Text editor used to edit project source files
|
- Text editor used to edit project source files in GMod
|
||||||
- Use the Wire editor? (we need wiremod anyways, and the text editor is.. OK I suppose.)
|
- Use the Wire editor? (we need wiremod anyways, and the text editor is.. OK I suppose.)
|
||||||
- Or: https://github.com/Metastruct/gmod-monaco
|
- Or: https://github.com/Metastruct/gmod-monaco
|
||||||
- https://github.com/JustMrPhoenix/Noir/tree/master
|
- https://github.com/JustMrPhoenix/Noir/tree/master
|
||||||
|
|
||||||
- Some example projects?
|
- Some example projects?
|
||||||
- A simple bare metal "Hello World"
|
- A simple bare metal "Hello World" using the SDK headers
|
||||||
- I joke about it, but an RTOS would be really nice and a good stress test of the project system (for usage in "real" projects.)
|
- I joke about it, but an RTOS would be really nice and a good stress test of the project system (for usage in "real" projects.)
|
||||||
|
|
||||||
## Moderation/administration tools
|
## Moderation/administration tools
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
//! These are the hardcoded defaults projgen uses for configuring Makefiles.
|
||||||
|
//! Only change these if you know what they do.
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
/// These are the hardcoded defaults
|
|
||||||
#define PROJGEN_CC "riscv32-unknown-elf-gcc"
|
#define PROJGEN_CC "riscv32-unknown-elf-gcc"
|
||||||
#define PROJGEN_CXX "riscv32-unknown-elf-g++"
|
#define PROJGEN_CXX "riscv32-unknown-elf-g++"
|
||||||
|
#define PROJGEN_LD "riscv32-unknown-elf-gcc"
|
||||||
|
#define PROJGEN_OBJCOPY "riscv32-unknown-elf-objcopy"
|
||||||
#define PROJGEN_BASE_C_FLAGS "-ffreestanding -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32"
|
#define PROJGEN_BASE_C_FLAGS "-ffreestanding -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32"
|
||||||
#define PROJGEN_BASE_CC_FLAGS "-ffreestanding -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32"
|
#define PROJGEN_BASE_CC_FLAGS "-ffreestanding -fno-rtti -fno-exceptions -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32"
|
||||||
#define PROJGEN_BASE_LD_FLAGS "-nostdlib"
|
#define PROJGEN_BASE_LD_FLAGS "-nostdlib"
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <daw/json/daw_json_link.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -7,13 +11,13 @@ namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace projgen::util {
|
namespace projgen::util {
|
||||||
|
|
||||||
using unique_file_ptr = std::unique_ptr<std::FILE, decltype(&std::fclose)>;
|
using UniqueFilePtr = std::unique_ptr<std::FILE, decltype(&std::fclose)>;
|
||||||
|
|
||||||
unique_file_ptr UniqueFopen(std::string_view path, std::string_view mode) {
|
inline UniqueFilePtr UniqueFopen(std::string_view path, std::string_view mode) {
|
||||||
return unique_file_ptr(std::fopen(path.data(), mode.data()), &std::fclose);
|
return UniqueFilePtr(std::fopen(path.data(), mode.data()), &std::fclose);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ReadFileAsString(const fs::path& path) {
|
inline std::string ReadFileAsString(const fs::path& path) {
|
||||||
auto file = UniqueFopen(path.string(), "r");
|
auto file = UniqueFopen(path.string(), "r");
|
||||||
std::string data;
|
std::string data;
|
||||||
if(file) {
|
if(file) {
|
||||||
|
@ -27,4 +31,10 @@ namespace projgen::util {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline T ParseJsonFromFile(const fs::path& path) {
|
||||||
|
auto data = projgen::util::ReadFileAsString(path);
|
||||||
|
return daw::json::from_json<T>(data);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace projgen::util
|
} // namespace projgen::util
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <lucore/Assert.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "BaseConfig.hpp"
|
||||||
|
#include "FsUtils.hpp"
|
||||||
|
#include "Project.hpp"
|
||||||
|
|
||||||
|
namespace projgen::make {
|
||||||
|
|
||||||
|
// base class for makefile rules
|
||||||
|
struct MakefileGeneratable {
|
||||||
|
virtual ~MakefileGeneratable() = default;
|
||||||
|
virtual std::string Generate() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileGlobalVariables : public MakefileGeneratable {
|
||||||
|
std::unordered_map<std::string, std::string> values;
|
||||||
|
|
||||||
|
// initalize for the global collection
|
||||||
|
MakefileGlobalVariables(const std::string& projectName, const std::string& objects) {
|
||||||
|
values["NAME"] = projectName;
|
||||||
|
values["CC"] = PROJGEN_CC;
|
||||||
|
values["CXX"] = PROJGEN_CXX;
|
||||||
|
values["LD"] = PROJGEN_LD;
|
||||||
|
values["OBJCOPY"] = PROJGEN_OBJCOPY;
|
||||||
|
|
||||||
|
values["BASE_CCFLAGS"] = PROJGEN_BASE_C_FLAGS;
|
||||||
|
values["BASE_CXXFLAGS"] = PROJGEN_BASE_CC_FLAGS;
|
||||||
|
values["BASE_LDFLAGS"] = PROJGEN_BASE_LD_FLAGS;
|
||||||
|
|
||||||
|
values["OBJS"] = objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Generate() override {
|
||||||
|
auto str = std::string();
|
||||||
|
for(auto& kv : values) {
|
||||||
|
str += std::format("{} = {}\n", kv.first, kv.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileConfiguration : public MakefileGeneratable {
|
||||||
|
MakefileConfiguration(const std::unordered_map<std::string, projgen::Project::Configuration>& configs) : configs(configs) {}
|
||||||
|
|
||||||
|
std::string Generate() override {
|
||||||
|
auto string = std::string();
|
||||||
|
|
||||||
|
for(auto& config : configs) {
|
||||||
|
string += std::format("ifeq ($(CONFIG),{})\n", config.first);
|
||||||
|
string += std::format("{}_Valid = yes\n", config.first);
|
||||||
|
string += std::format("{}_CCFLAGS = $(BASE_CCFLAGS) {}\n", config.first, config.second.cCompileFlags);
|
||||||
|
string += std::format("{}_CXXFLAGS = $(BASE_CXXFLAGS) {}\n", config.first, config.second.cppCompileFlags);
|
||||||
|
string += std::format("{}_LDFLAGS = $(BASE_LDFLAGS) {}\n", config.first, config.second.linkerFlags.value_or(""));
|
||||||
|
string += std::format("endif\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
string += std::format("ifeq ($(CONFIG),)\n$(error Please specify a build configuration)\nendif\n");
|
||||||
|
string += std::format("ifneq ($($(CONFIG)_Valid), yes)\n$(error Invalid configuration $(CONFIG))\nendif\n");
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::unordered_map<std::string, projgen::Project::Configuration>& configs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// a general product rule
|
||||||
|
struct MakefileProductRule : public MakefileGeneratable {
|
||||||
|
std::string product;
|
||||||
|
std::string consumeDeps;
|
||||||
|
|
||||||
|
MakefileProductRule(const std::string& product, const std::string& consumeDeps) : product(product), consumeDeps(consumeDeps) {}
|
||||||
|
|
||||||
|
std::string Generate() override { return std::format("{}: {}\n\t{}", product, consumeDeps, GenerateRuleCommand()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual std::string GenerateRuleCommand() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileAllRule : MakefileGeneratable {
|
||||||
|
std::string product;
|
||||||
|
std::string consumeDeps;
|
||||||
|
|
||||||
|
MakefileAllRule() : product("all"), consumeDeps("obj/$(CONFIG)/ $(NAME)_$(CONFIG).bin") {}
|
||||||
|
|
||||||
|
std::string Generate() override { return std::format("{}: {}\n", product, consumeDeps); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileCleanRule : MakefileProductRule {
|
||||||
|
MakefileCleanRule() : MakefileProductRule("clean", "") {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string GenerateRuleCommand() override { return "rm -rf $(NAME)_$(CONFIG).elf $(NAME)_$(CONFIG).bin obj/$(CONFIG)"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileObjDirRule : MakefileProductRule {
|
||||||
|
MakefileObjDirRule() : MakefileProductRule("obj/$(CONFIG)/", "") {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string GenerateRuleCommand() override { return "mkdir -p $@"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// a pattern rule
|
||||||
|
// e.g "%.o: %.c" or such
|
||||||
|
struct MakefilePatternRule : public MakefileGeneratable {
|
||||||
|
std::string productExtension;
|
||||||
|
std::string consumeExtension;
|
||||||
|
|
||||||
|
MakefilePatternRule(const std::string& productExtension, const std::string& consumeExtension)
|
||||||
|
: productExtension(productExtension), consumeExtension(consumeExtension) {}
|
||||||
|
|
||||||
|
std::string Generate() override { return std::format("{}: {}\n\t{}", productExtension, consumeExtension, GenerateRuleCommand()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual std::string GenerateRuleCommand() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileAsmRule : public MakefilePatternRule {
|
||||||
|
MakefileAsmRule() : MakefilePatternRule("obj/$(CONFIG)/%.o", "%.S") {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string GenerateRuleCommand() override { return "$(CC) -xassembler-with-cpp -c $($(CONFIG)_CCFLAGS) $< -o $@"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileCRule : public MakefilePatternRule {
|
||||||
|
MakefileCRule() : MakefilePatternRule("obj/$(CONFIG)/%.o", "%.c") {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string GenerateRuleCommand() override { return "$(CC) -c $($(CONFIG)_CCFLAGS) $< -o $@"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileCXXRule : public MakefilePatternRule {
|
||||||
|
MakefileCXXRule() : MakefilePatternRule("obj/$(CONFIG)/%.o", "%.cpp") {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string GenerateRuleCommand() override { return "$(CXX) -c $($(CONFIG)_CXXFLAGS) $< -o $@"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileLinkRule : public MakefileProductRule {
|
||||||
|
MakefileLinkRule() : MakefileProductRule("$(NAME)_$(CONFIG).elf", "$(OBJS)") {};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string GenerateRuleCommand() override { return "$(LD) $($(CONFIG)_LDFLAGS) $(OBJS) -o $@"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileFlatBinaryRule : MakefileProductRule {
|
||||||
|
MakefileFlatBinaryRule() : MakefileProductRule("$(NAME)_$(CONFIG).bin", "$(NAME)_$(CONFIG).elf") {};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string GenerateRuleCommand() override { return "$(OBJCOPY) $^ -O binary $@"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakefileWriter {
|
||||||
|
MakefileWriter(const fs::path& path) {
|
||||||
|
file = projgen::util::UniqueFopen((path / "Makefile").string(), "w");
|
||||||
|
LUCORE_CHECK(file, "Could not open {} for writing", (path / "Makefile").string());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Write(const std::vector<std::unique_ptr<MakefileGeneratable>>& g) {
|
||||||
|
for(auto& p : g) {
|
||||||
|
auto generated_data = p->Generate();
|
||||||
|
if(std::fwrite(generated_data.data(), 1, generated_data.length(), file.get()) != generated_data.length())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!fputc('\n', file.get()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
projgen::util::UniqueFilePtr file { nullptr, std::fclose };
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace projgen::make
|
|
@ -1,10 +1,12 @@
|
||||||
#include <daw/json/daw_json_link.h>
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "FsUtils.hpp"
|
||||||
|
|
||||||
namespace projgen {
|
namespace projgen {
|
||||||
struct Project {
|
struct Project {
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
|
@ -20,6 +22,52 @@ namespace projgen {
|
||||||
std::vector<std::string> sourceFileNames;
|
std::vector<std::string> sourceFileNames;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// this describes a source file
|
||||||
|
struct SourceFile {
|
||||||
|
enum class Type {
|
||||||
|
Invalid, // invalid file
|
||||||
|
AsmSourceFile, // Assembly source file
|
||||||
|
CSourceFile, // C source code
|
||||||
|
CppSourceFile, // C++ source code
|
||||||
|
LinkerScript, // prepended to linker flags with a -T (only one can exist in a project)
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr Type TypeFromExtension(std::string_view extension) {
|
||||||
|
if(extension == ".S")
|
||||||
|
return Type::AsmSourceFile;
|
||||||
|
else if(extension == ".c")
|
||||||
|
return Type::CSourceFile;
|
||||||
|
else if(extension == ".cpp")
|
||||||
|
return Type::CppSourceFile;
|
||||||
|
else if(extension == ".ld")
|
||||||
|
return Type::LinkerScript;
|
||||||
|
else
|
||||||
|
return Type::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit SourceFile(const fs::path& path) {
|
||||||
|
if(path.has_extension())
|
||||||
|
type = TypeFromExtension(path.extension().string());
|
||||||
|
else
|
||||||
|
type = Type::Invalid;
|
||||||
|
|
||||||
|
this->path = path.filename();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<SourceFile> MakeArray(const fs::path& sourcePath, const std::vector<std::string>& filenames) {
|
||||||
|
auto vec = std::vector<SourceFile>();
|
||||||
|
vec.reserve(filenames.size());
|
||||||
|
|
||||||
|
for(auto& filename : filenames)
|
||||||
|
vec.emplace_back((sourcePath / filename));
|
||||||
|
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
fs::path path;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace projgen
|
} // namespace projgen
|
||||||
|
|
||||||
/// DAW JSON Link bindings
|
/// DAW JSON Link bindings
|
||||||
|
@ -36,8 +84,8 @@ namespace daw::json {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct json_data_contract<projgen::Project> {
|
struct json_data_contract<projgen::Project> {
|
||||||
using type =
|
using type = json_member_list<
|
||||||
json_member_list<json_string<"Name">,
|
json_string<"Name">,
|
||||||
json_key_value<"Configurations", std::unordered_map<std::string, projgen::Project::Configuration>, projgen::Project::Configuration>,
|
json_key_value<"Configurations", std::unordered_map<std::string, projgen::Project::Configuration>, projgen::Project::Configuration>,
|
||||||
json_array<"Sources", std::string> >;
|
json_array<"Sources", std::string> >;
|
||||||
|
|
||||||
|
|
|
@ -1,89 +1,87 @@
|
||||||
//! Main for the LCPU project generator
|
//! Main for the LCPU project generator
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <lucore/Assert.hpp>
|
||||||
#include <lucore/Logger.hpp>
|
#include <lucore/Logger.hpp>
|
||||||
#include <lucore/StdoutSink.hpp>
|
#include <lucore/StdoutSink.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "BaseConfig.hpp"
|
#include "BaseConfig.hpp"
|
||||||
#include "FsUtils.hpp"
|
#include "FsUtils.hpp"
|
||||||
|
#include "Makefile.hpp"
|
||||||
#include "Project.hpp"
|
#include "Project.hpp"
|
||||||
|
|
||||||
template <class T>
|
// TODO:
|
||||||
T ParseJsonFromFile(const fs::path& path) {
|
// Once there's better C++23 ranges support, this can/should be replaced with:
|
||||||
auto data = projgen::util::ReadFileAsString(path);
|
// (objects | views::join_with(' ')
|
||||||
return daw::json::from_json<T>(data);
|
// | std::ranges::to<std::string>())
|
||||||
|
// I want better ranges support in gcc and clang, this already works in MSVC :(
|
||||||
|
inline auto join(const std::vector<std::string>& values, const std::string_view seperator = " ") {
|
||||||
|
auto string = std::string {};
|
||||||
|
for(auto& value : values)
|
||||||
|
string += std::format("{}{}", value, seperator);
|
||||||
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this describes a source file
|
|
||||||
struct SourceFile {
|
|
||||||
enum class Type {
|
|
||||||
Invalid, // invalid file
|
|
||||||
AsmSourceFile, // Assembly source file
|
|
||||||
CSourceFile, // C source code
|
|
||||||
CppSourceFile, // C++ source code
|
|
||||||
LinkerScript, // prepended to linker flags with a -T (only one can exist in a project)
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr Type TypeFromExtension(std::string_view extension) {
|
|
||||||
if(extension == "s" || extension == "S")
|
|
||||||
return Type::AsmSourceFile;
|
|
||||||
|
|
||||||
if(extension == "c")
|
|
||||||
return Type::CSourceFile;
|
|
||||||
if(extension == "cpp" || extension == "cc")
|
|
||||||
return Type::CppSourceFile;
|
|
||||||
if(extension == "ld")
|
|
||||||
return Type::LinkerScript;
|
|
||||||
|
|
||||||
return Type::Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit SourceFile(const std::string& filename) : filename(filename) {
|
|
||||||
if(auto pos = filename.rfind('.'); pos != std::string::npos) {
|
|
||||||
type = TypeFromExtension(filename.substr(pos + 1));
|
|
||||||
} else {
|
|
||||||
type = Type::Invalid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::vector<SourceFile> MakeArray(const std::vector<std::string>& filenames) {
|
|
||||||
auto vec = std::vector<SourceFile>();
|
|
||||||
vec.reserve(filenames.size());
|
|
||||||
|
|
||||||
for(auto& filename : filenames)
|
|
||||||
vec.emplace_back(filename);
|
|
||||||
|
|
||||||
return vec;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type type;
|
|
||||||
std::string filename;
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
lucore::LoggerAttachStdout();
|
lucore::LoggerAttachStdout();
|
||||||
|
lucore::LogInfo("LCPU project generator");
|
||||||
lucore::LogInfo("LCPU project generator!");
|
|
||||||
|
|
||||||
auto project_json_path = (fs::current_path() / "project.json");
|
auto project_json_path = (fs::current_path() / "project.json");
|
||||||
|
|
||||||
if(!fs::exists(project_json_path)) {
|
if(!fs::exists(project_json_path)) {
|
||||||
lucore::LogFatal("The directory \"{}\" does not seem like it's a project to me", fs::current_path().string());
|
lucore::LogFatal("The directory \"{}\" does not seem like it's a LCPU project to me", fs::current_path().string());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto project = ParseJsonFromFile<projgen::Project>(project_json_path);
|
auto project = projgen::util::ParseJsonFromFile<projgen::Project>(project_json_path);
|
||||||
|
|
||||||
for(auto& pair : project.configurations) {
|
lucore::LogInfo("Generating Makefile for project \"{}\".", project.name);
|
||||||
std::printf("%s: %s %s %s\n", pair.first.c_str(), pair.second.cCompileFlags.c_str(), pair.second.cppCompileFlags.c_str(),
|
|
||||||
pair.second.linkerFlags.value_or(PROJGEN_BASE_LD_FLAGS).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sourceFiles = SourceFile::MakeArray(project.sourceFileNames);
|
auto sourceFiles = projgen::SourceFile::MakeArray(fs::current_path(), project.sourceFileNames);
|
||||||
|
auto objects = std::vector<std::string> {};
|
||||||
|
bool foundLdScript = false;
|
||||||
|
|
||||||
for(auto& source : sourceFiles) {
|
for(auto& source : sourceFiles) {
|
||||||
std::printf("%s -> %d\n", source.filename.c_str(), source.type);
|
LUCORE_CHECK(source.type != projgen::SourceFile::Type::Invalid,
|
||||||
|
"Source file {} with Invalid type in source files. Refusing to generate project", source.path.string());
|
||||||
|
|
||||||
|
if(source.type == projgen::SourceFile::Type::LinkerScript) {
|
||||||
|
LUCORE_CHECK(!foundLdScript, "Project invalid; has more than 1 .ld script file");
|
||||||
|
foundLdScript = true;
|
||||||
|
for(auto& kv : project.configurations) {
|
||||||
|
if(kv.second.linkerFlags.has_value()) {
|
||||||
|
kv.second.linkerFlags = std::format("-T {} {}", source.path.string(), *kv.second.linkerFlags);
|
||||||
|
} else {
|
||||||
|
kv.second.linkerFlags = std::format("-T {}", source.path.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
objects.push_back(std::format("obj/$(CONFIG)/{}", source.path.replace_extension(".o").string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
projgen::make::MakefileWriter writer(fs::current_path());
|
||||||
|
|
||||||
|
auto generators = std::vector<std::unique_ptr<projgen::make::MakefileGeneratable>> {};
|
||||||
|
generators.emplace_back(new projgen::make::MakefileGlobalVariables(project.name, join(objects)));
|
||||||
|
generators.emplace_back(new projgen::make::MakefileConfiguration(project.configurations));
|
||||||
|
generators.emplace_back(new projgen::make::MakefileAllRule());
|
||||||
|
generators.emplace_back(new projgen::make::MakefileCleanRule());
|
||||||
|
generators.emplace_back(new projgen::make::MakefileObjDirRule());
|
||||||
|
generators.emplace_back(new projgen::make::MakefileAsmRule());
|
||||||
|
generators.emplace_back(new projgen::make::MakefileCRule());
|
||||||
|
generators.emplace_back(new projgen::make::MakefileCXXRule());
|
||||||
|
generators.emplace_back(new projgen::make::MakefileFlatBinaryRule());
|
||||||
|
generators.emplace_back(new projgen::make::MakefileLinkRule());
|
||||||
|
|
||||||
|
if(!writer.Write(generators)) {
|
||||||
|
lucore::LogError("Could not generate project");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
lucore::LogInfo("Generated Makefile \"{}\".", (fs::current_path() / "Makefile").string());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch(daw::json::json_exception& ex) {
|
} catch(daw::json::json_exception& ex) {
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
PROJECT = test
|
|
||||||
|
|
||||||
# where your rv32 toolchain is
|
|
||||||
TCPATH = /home/lily/bin/riscv/bin
|
|
||||||
PREFIX = $(TCPATH)/riscv32-unknown-elf
|
|
||||||
|
|
||||||
CC = $(PREFIX)-gcc
|
|
||||||
CXX = $(PREFIX)-g++
|
|
||||||
|
|
||||||
ARCHFLAGS = -ffreestanding -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32
|
|
||||||
CCFLAGS = -g -Os $(ARCHFLAGS) -std=c18
|
|
||||||
CXXFLAGS = $(ARCHFLAGS) -g -Os -std=c++20 -fno-exceptions -fno-rtti
|
|
||||||
LDFLAGS = -T binary.ld -nostdlib -Wl,--gc-sections
|
|
||||||
|
|
||||||
OBJS = start.o \
|
|
||||||
main.o
|
|
||||||
|
|
||||||
.PHONY: all test clean
|
|
||||||
|
|
||||||
all: $(PROJECT).bin $(PROJECT).debug.txt
|
|
||||||
|
|
||||||
# this assumes the lcpu project build dir you're using is
|
|
||||||
# [lcpu repo root]/build
|
|
||||||
test: $(PROJECT).bin $(PROJECT).debug.txt
|
|
||||||
../../../../build/projects/riscv_test_harness/rvtest $<
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm $(PROJECT).elf $(PROJECT).bin $(PROJECT).debug.txt $(OBJS)
|
|
||||||
|
|
||||||
# Link rules
|
|
||||||
|
|
||||||
$(PROJECT).elf: $(OBJS)
|
|
||||||
$(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJS)
|
|
||||||
|
|
||||||
$(PROJECT).bin : $(PROJECT).elf
|
|
||||||
$(PREFIX)-objcopy $^ -O binary $@
|
|
||||||
|
|
||||||
$(PROJECT).debug.txt : $(PROJECT).elf
|
|
||||||
$(PREFIX)-objdump -t $^ > $@
|
|
||||||
$(PREFIX)-objdump -S $^ >> $@
|
|
||||||
|
|
||||||
# Compile rules
|
|
||||||
|
|
||||||
%.o: %.cpp
|
|
||||||
$(CXX) -c $(CXXFLAGS) $< -o $@
|
|
||||||
|
|
||||||
%.o: %.c
|
|
||||||
$(CC) -c $(CCFLAGS) $< -o $@
|
|
||||||
|
|
||||||
%.o: %.S
|
|
||||||
$(CC) -x assembler-with-cpp -march=rv32ima -mabi=ilp32 -c $< -o $@
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue