Skip to content

feat(build): merge multiple build recipes #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 63 additions & 20 deletions ecsact/cli/commands/build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ constexpr auto USAGE = R"docopt(Ecsact Build Command

Usage:
ecsact build (-h | --help)
ecsact build <files>... --recipe=<name> --output=<path> [--format=<type>] [--temp_dir=<path>] [--compiler_config=<path>] [--report_filter=<filter>]
ecsact build <files>... --recipe=<name>... --output=<path> [--allow-unresolved-imports] [--format=<type>] [--temp_dir=<path>] [--compiler_config=<path>] [--report_filter=<filter>]

Options:
<files> Ecsact files used to build Ecsact Runtime
Expand Down Expand Up @@ -86,8 +86,57 @@ auto ecsact::cli::detail::build_command( //

auto output_path = fs::path{args.at("--output").asString()};

auto recipe_path = fs::path{args.at("--recipe").asString()};
auto recipe = build_recipe::from_yaml_file(recipe_path);
auto recipe_composite = std::optional<build_recipe>{};
auto recipe_paths = args.at("--recipe").asStringList();
for(auto recipe_path : args.at("--recipe").asStringList()) {
auto recipe_result = build_recipe::from_yaml_file(recipe_path);

if(std::holds_alternative<build_recipe_parse_error>(recipe_result)) {
auto recipe_error = std::get<build_recipe_parse_error>(recipe_result);
ecsact::cli::report_error(
"Recipe Error {}",
magic_enum::enum_name(recipe_error)
);
return 1;
}

auto recipe = std::move(std::get<build_recipe>(recipe_result));
if(!recipe_composite) {
recipe_composite.emplace(std::move(recipe));
} else {
auto merge_result = build_recipe::merge(*recipe_composite, recipe);
if(std::holds_alternative<build_recipe_merge_error>(merge_result)) {
auto merge_error = std::get<build_recipe_merge_error>(merge_result);
ecsact::cli::report_error(
"Recipe Merge Error {}",
magic_enum::enum_name(merge_error)
);
return 1;
}

recipe_composite.emplace(std::get<build_recipe>(std::move(merge_result)));
}
}

if(!recipe_composite) {
ecsact::cli::report_error("No recipe");
return 1;
}

if(!args["--allow-unresolved-imports"].asBool()) {
if(!recipe_composite->imports().empty()) {
for(auto imp : recipe_composite->imports()) {
ecsact::cli::report_error("Unresolved import {}", imp);
}
ecsact::cli::report_error(
"Build recipes do not resolve all imports. Make sure all imported "
"functions in provided recipes are also exported by another recipe. If "
"you would like to allow unresolved imports you may provide the "
"--allow-unresolved-imports flag to suppress this error."
);
return 1;
}
}

auto temp_dir = args["--temp_dir"].isString() //
? fs::path{args["--temp_dir"].asString()}
Expand All @@ -114,15 +163,6 @@ auto ecsact::cli::detail::build_command( //
file_paths.emplace_back(file);
}

if(std::holds_alternative<build_recipe_parse_error>(recipe)) {
auto recipe_error = std::get<build_recipe_parse_error>(recipe);
ecsact::cli::report_error(
"Recipe Error {}",
magic_enum::enum_name(recipe_error)
);
return 1;
}

auto eval_errors = ecsact::eval_files(file_paths);

if(!eval_errors.empty()) {
Expand Down Expand Up @@ -172,26 +212,29 @@ auto ecsact::cli::detail::build_command( //
compiler->compiler_version
);

auto additional_plugin_dirs = std::vector<fs::path>{};
for(fs::path recipe_path : recipe_paths) {
if(recipe_path.has_parent_path()) {
additional_plugin_dirs.emplace_back(recipe_path.parent_path());
}
}

auto cook_options = cook_recipe_options{
.files = file_paths,
.work_dir = work_dir,
.output_path = output_path,
.additional_plugin_dirs = {recipe_path.parent_path()},
.additional_plugin_dirs = additional_plugin_dirs,
};
auto runtime_output_path = cook_recipe(
argv[0],
std::get<build_recipe>(recipe),
*compiler,
cook_options
);
auto runtime_output_path =
cook_recipe(argv[0], *recipe_composite, *compiler, cook_options);

if(!runtime_output_path) {
ecsact::cli::report_error("Failed to cook recipe");
return 1;
}

auto exit_code = ecsact::cli::taste_recipe( //
std::get<build_recipe>(recipe),
*recipe_composite,
*runtime_output_path
);

Expand Down
96 changes: 96 additions & 0 deletions ecsact/cli/commands/build/build_recipe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <fstream>
#include <filesystem>
#include <cassert>
#include <ranges>
#include <iostream>
#include <algorithm>
#include <ranges>
Expand All @@ -12,6 +13,15 @@

namespace fs = std::filesystem;

static auto range_contains(auto& r, const auto& v) -> bool {
for(auto& rv : r) {
if(rv == v) {
return true;
}
}
return false;
}

ecsact::build_recipe::build_recipe() = default;
ecsact::build_recipe::build_recipe(build_recipe&&) = default;
ecsact::build_recipe::~build_recipe() = default;
Expand Down Expand Up @@ -227,3 +237,89 @@ auto ecsact::build_recipe::from_yaml_file( //
return build_recipe_parse_error::bad_file;
}
}

auto ecsact::build_recipe::merge( //
const build_recipe& base,
const build_recipe& target
) -> std::variant<build_recipe, build_recipe_merge_error> {
auto merged_build_recipe = build_recipe{};

for(auto base_export_name : base._exports) {
for(auto target_export_name : target._exports) {
if(target_export_name == base_export_name) {
ecsact::cli::report_error(
"Multiple recipes export {}",
target_export_name
);
return build_recipe_merge_error::conflicting_export;
}
}
}

merged_build_recipe._system_libs.reserve(
merged_build_recipe._system_libs.size() + target._system_libs.size()
);
merged_build_recipe._system_libs.insert(
merged_build_recipe._system_libs.end(),
base._system_libs.begin(),
base._system_libs.end()
);
merged_build_recipe._system_libs.insert(
merged_build_recipe._system_libs.end(),
target._system_libs.begin(),
target._system_libs.end()
);

merged_build_recipe._exports.reserve(
merged_build_recipe._exports.size() + target._exports.size()
);
merged_build_recipe._exports.insert(
merged_build_recipe._exports.end(),
base._exports.begin(),
base._exports.end()
);
merged_build_recipe._exports.insert(
merged_build_recipe._exports.end(),
target._exports.begin(),
target._exports.end()
);

merged_build_recipe._imports.reserve(
merged_build_recipe._imports.size() + target._imports.size()
);
merged_build_recipe._imports.insert(
merged_build_recipe._imports.end(),
base._imports.begin(),
base._imports.end()
);
merged_build_recipe._imports.insert(
merged_build_recipe._imports.end(),
target._imports.begin(),
target._imports.end()
);

for(auto itr = merged_build_recipe._imports.begin();
itr != merged_build_recipe._imports.end();) {
if(range_contains(merged_build_recipe._exports, *itr)) {
itr = merged_build_recipe._imports.erase(itr);
continue;
}
++itr;
}

merged_build_recipe._sources.reserve(
merged_build_recipe._sources.size() + target._sources.size()
);
merged_build_recipe._sources.insert(
merged_build_recipe._sources.end(),
base._sources.begin(),
base._sources.end()
);
merged_build_recipe._sources.insert(
merged_build_recipe._sources.end(),
target._sources.begin(),
target._sources.end()
);

return merged_build_recipe;
}
9 changes: 9 additions & 0 deletions ecsact/cli/commands/build/build_recipe.hh
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ enum class build_recipe_parse_error {
conflicting_import_export_method_modules,
};

enum class build_recipe_merge_error {
conflicting_export,
};

class build_recipe {
public:
static auto from_yaml_file( //
std::filesystem::path p
) -> std::variant<build_recipe, build_recipe_parse_error>;

static auto merge( //
const build_recipe& a,
const build_recipe& b
) -> std::variant<build_recipe, build_recipe_merge_error>;

struct source_path {
std::filesystem::path path;
std::optional<std::string> outdir;
Expand Down
4 changes: 2 additions & 2 deletions test/MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/build_recipe/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ cc_test(
copts = copts,
data = [
"ecsact_build_test.cc",
"ecsact_build_test_merge.cc",
"test-recipe.yml",
"test-merge-recipe.yml",
"//:test.ecsact",
"//:test_codegen_plugin",
"@ecsact_cli",
Expand All @@ -24,12 +26,14 @@ cc_test(
"@platforms//os:windows": {
"TEST_ECSACT_CLI": "$(rootpath @ecsact_cli)",
"TEST_ECSACT_BUILD_RECIPE_PATH": "$(rootpath test-recipe.yml)",
"TEST_ECSACT_BUILD_MERGE_RECIPE_PATH": "$(rootpath test-merge-recipe.yml)",
"TEST_ECSACT_FILE_PATH": "$(rootpath //:test.ecsact)",
"TEST_CODEGEN_PLUGIN_PATH": "$(rootpath //:test_codegen_plugin)",
},
"//conditions:default": {
"TEST_ECSACT_CLI": "$(rootpath @ecsact_cli)",
"TEST_ECSACT_BUILD_RECIPE_PATH": "$(rootpath test-recipe.yml)",
"TEST_ECSACT_BUILD_MERGE_RECIPE_PATH": "$(rootpath test-merge-recipe.yml)",
"TEST_ECSACT_FILE_PATH": "$(rootpath //:test.ecsact)",
"TEST_CODEGEN_PLUGIN_PATH": "$(rootpath //:test_codegen_plugin)",
"CC": "$(rootpath @llvm_toolchain_llvm//:bin/clang)",
Expand Down
7 changes: 7 additions & 0 deletions test/build_recipe/ecsact_build_test_merge.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "ecsact/runtime/dynamic.h"

void ecsact_system_execution_context_get(struct ecsact_system_execution_context*, ecsact_component_like_id, void*) {
}

void ecsact_system_execution_context_action(struct ecsact_system_execution_context*, void*) {
}
11 changes: 11 additions & 0 deletions test/build_recipe/test-merge-recipe.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$schema: ../../schema/ecsact-build-recipe.schema.json

name: Example Recipe 2

sources:
- ecsact_build_test_merge.cc

exports:
- ecsact_system_execution_context_get
- ecsact_system_execution_context_action

15 changes: 15 additions & 0 deletions test/build_recipe/test_build_recipe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ TEST(Build, Success) {
auto test_codegen_plugin_path = std::getenv("TEST_CODEGEN_PLUGIN_PATH");
auto test_ecsact_file_path = std::getenv("TEST_ECSACT_FILE_PATH");
auto test_build_recipe_path = std::getenv("TEST_ECSACT_BUILD_RECIPE_PATH");
auto test_build_merge_recipe_path =
std::getenv("TEST_ECSACT_BUILD_MERGE_RECIPE_PATH");

ASSERT_NE(test_ecsact_cli, nullptr);
ASSERT_NE(test_codegen_plugin_path, nullptr);
ASSERT_NE(test_ecsact_file_path, nullptr);
ASSERT_NE(test_build_recipe_path, nullptr);
ASSERT_NE(test_build_merge_recipe_path, nullptr);

ASSERT_TRUE(fs::exists(test_codegen_plugin_path));
ASSERT_TRUE(fs::exists(test_ecsact_file_path));
Expand All @@ -30,10 +33,22 @@ TEST(Build, Success) {
auto exit_code = build_command(std::vector{
"ecsact"s,
"build"s,
"--allow-unresolved-imports"s,
std::string{test_ecsact_file_path},
std::format("--recipe={}", test_build_recipe_path),
std::format("--output=test_ecsact_runtime"),
});

ASSERT_EQ(exit_code, 0);

exit_code = build_command(std::vector{
"ecsact"s,
"build"s,
std::string{test_ecsact_file_path},
std::format("--recipe={}", test_build_recipe_path),
std::format("--recipe={}", test_build_merge_recipe_path),
std::format("--output=test_ecsact_runtime_merged"),
});

ASSERT_EQ(exit_code, 0);
}