Skip to content

Commit cc68d8d

Browse files
authored
feat(build): merge multiple build recipes (#84)
1 parent 2370eb7 commit cc68d8d

File tree

9 files changed

+209
-24
lines changed

9 files changed

+209
-24
lines changed

MODULE.bazel.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ecsact/cli/commands/build.cc

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ constexpr auto USAGE = R"docopt(Ecsact Build Command
2929
3030
Usage:
3131
ecsact build (-h | --help)
32-
ecsact build <files>... --recipe=<name> --output=<path> [--format=<type>] [--temp_dir=<path>] [--compiler_config=<path>] [--report_filter=<filter>]
32+
ecsact build <files>... --recipe=<name>... --output=<path> [--allow-unresolved-imports] [--format=<type>] [--temp_dir=<path>] [--compiler_config=<path>] [--report_filter=<filter>]
3333
3434
Options:
3535
<files> Ecsact files used to build Ecsact Runtime
@@ -86,8 +86,57 @@ auto ecsact::cli::detail::build_command( //
8686

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

89-
auto recipe_path = fs::path{args.at("--recipe").asString()};
90-
auto recipe = build_recipe::from_yaml_file(recipe_path);
89+
auto recipe_composite = std::optional<build_recipe>{};
90+
auto recipe_paths = args.at("--recipe").asStringList();
91+
for(auto recipe_path : args.at("--recipe").asStringList()) {
92+
auto recipe_result = build_recipe::from_yaml_file(recipe_path);
93+
94+
if(std::holds_alternative<build_recipe_parse_error>(recipe_result)) {
95+
auto recipe_error = std::get<build_recipe_parse_error>(recipe_result);
96+
ecsact::cli::report_error(
97+
"Recipe Error {}",
98+
magic_enum::enum_name(recipe_error)
99+
);
100+
return 1;
101+
}
102+
103+
auto recipe = std::move(std::get<build_recipe>(recipe_result));
104+
if(!recipe_composite) {
105+
recipe_composite.emplace(std::move(recipe));
106+
} else {
107+
auto merge_result = build_recipe::merge(*recipe_composite, recipe);
108+
if(std::holds_alternative<build_recipe_merge_error>(merge_result)) {
109+
auto merge_error = std::get<build_recipe_merge_error>(merge_result);
110+
ecsact::cli::report_error(
111+
"Recipe Merge Error {}",
112+
magic_enum::enum_name(merge_error)
113+
);
114+
return 1;
115+
}
116+
117+
recipe_composite.emplace(std::get<build_recipe>(std::move(merge_result)));
118+
}
119+
}
120+
121+
if(!recipe_composite) {
122+
ecsact::cli::report_error("No recipe");
123+
return 1;
124+
}
125+
126+
if(!args["--allow-unresolved-imports"].asBool()) {
127+
if(!recipe_composite->imports().empty()) {
128+
for(auto imp : recipe_composite->imports()) {
129+
ecsact::cli::report_error("Unresolved import {}", imp);
130+
}
131+
ecsact::cli::report_error(
132+
"Build recipes do not resolve all imports. Make sure all imported "
133+
"functions in provided recipes are also exported by another recipe. If "
134+
"you would like to allow unresolved imports you may provide the "
135+
"--allow-unresolved-imports flag to suppress this error."
136+
);
137+
return 1;
138+
}
139+
}
91140

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

117-
if(std::holds_alternative<build_recipe_parse_error>(recipe)) {
118-
auto recipe_error = std::get<build_recipe_parse_error>(recipe);
119-
ecsact::cli::report_error(
120-
"Recipe Error {}",
121-
magic_enum::enum_name(recipe_error)
122-
);
123-
return 1;
124-
}
125-
126166
auto eval_errors = ecsact::eval_files(file_paths);
127167

128168
if(!eval_errors.empty()) {
@@ -172,26 +212,29 @@ auto ecsact::cli::detail::build_command( //
172212
compiler->compiler_version
173213
);
174214

215+
auto additional_plugin_dirs = std::vector<fs::path>{};
216+
for(fs::path recipe_path : recipe_paths) {
217+
if(recipe_path.has_parent_path()) {
218+
additional_plugin_dirs.emplace_back(recipe_path.parent_path());
219+
}
220+
}
221+
175222
auto cook_options = cook_recipe_options{
176223
.files = file_paths,
177224
.work_dir = work_dir,
178225
.output_path = output_path,
179-
.additional_plugin_dirs = {recipe_path.parent_path()},
226+
.additional_plugin_dirs = additional_plugin_dirs,
180227
};
181-
auto runtime_output_path = cook_recipe(
182-
argv[0],
183-
std::get<build_recipe>(recipe),
184-
*compiler,
185-
cook_options
186-
);
228+
auto runtime_output_path =
229+
cook_recipe(argv[0], *recipe_composite, *compiler, cook_options);
187230

188231
if(!runtime_output_path) {
189232
ecsact::cli::report_error("Failed to cook recipe");
190233
return 1;
191234
}
192235

193236
auto exit_code = ecsact::cli::taste_recipe( //
194-
std::get<build_recipe>(recipe),
237+
*recipe_composite,
195238
*runtime_output_path
196239
);
197240

ecsact/cli/commands/build/build_recipe.cc

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <fstream>
44
#include <filesystem>
55
#include <cassert>
6+
#include <ranges>
67
#include <iostream>
78
#include <algorithm>
89
#include <ranges>
@@ -12,6 +13,15 @@
1213

1314
namespace fs = std::filesystem;
1415

16+
static auto range_contains(auto& r, const auto& v) -> bool {
17+
for(auto& rv : r) {
18+
if(rv == v) {
19+
return true;
20+
}
21+
}
22+
return false;
23+
}
24+
1525
ecsact::build_recipe::build_recipe() = default;
1626
ecsact::build_recipe::build_recipe(build_recipe&&) = default;
1727
ecsact::build_recipe::~build_recipe() = default;
@@ -227,3 +237,89 @@ auto ecsact::build_recipe::from_yaml_file( //
227237
return build_recipe_parse_error::bad_file;
228238
}
229239
}
240+
241+
auto ecsact::build_recipe::merge( //
242+
const build_recipe& base,
243+
const build_recipe& target
244+
) -> std::variant<build_recipe, build_recipe_merge_error> {
245+
auto merged_build_recipe = build_recipe{};
246+
247+
for(auto base_export_name : base._exports) {
248+
for(auto target_export_name : target._exports) {
249+
if(target_export_name == base_export_name) {
250+
ecsact::cli::report_error(
251+
"Multiple recipes export {}",
252+
target_export_name
253+
);
254+
return build_recipe_merge_error::conflicting_export;
255+
}
256+
}
257+
}
258+
259+
merged_build_recipe._system_libs.reserve(
260+
merged_build_recipe._system_libs.size() + target._system_libs.size()
261+
);
262+
merged_build_recipe._system_libs.insert(
263+
merged_build_recipe._system_libs.end(),
264+
base._system_libs.begin(),
265+
base._system_libs.end()
266+
);
267+
merged_build_recipe._system_libs.insert(
268+
merged_build_recipe._system_libs.end(),
269+
target._system_libs.begin(),
270+
target._system_libs.end()
271+
);
272+
273+
merged_build_recipe._exports.reserve(
274+
merged_build_recipe._exports.size() + target._exports.size()
275+
);
276+
merged_build_recipe._exports.insert(
277+
merged_build_recipe._exports.end(),
278+
base._exports.begin(),
279+
base._exports.end()
280+
);
281+
merged_build_recipe._exports.insert(
282+
merged_build_recipe._exports.end(),
283+
target._exports.begin(),
284+
target._exports.end()
285+
);
286+
287+
merged_build_recipe._imports.reserve(
288+
merged_build_recipe._imports.size() + target._imports.size()
289+
);
290+
merged_build_recipe._imports.insert(
291+
merged_build_recipe._imports.end(),
292+
base._imports.begin(),
293+
base._imports.end()
294+
);
295+
merged_build_recipe._imports.insert(
296+
merged_build_recipe._imports.end(),
297+
target._imports.begin(),
298+
target._imports.end()
299+
);
300+
301+
for(auto itr = merged_build_recipe._imports.begin();
302+
itr != merged_build_recipe._imports.end();) {
303+
if(range_contains(merged_build_recipe._exports, *itr)) {
304+
itr = merged_build_recipe._imports.erase(itr);
305+
continue;
306+
}
307+
++itr;
308+
}
309+
310+
merged_build_recipe._sources.reserve(
311+
merged_build_recipe._sources.size() + target._sources.size()
312+
);
313+
merged_build_recipe._sources.insert(
314+
merged_build_recipe._sources.end(),
315+
base._sources.begin(),
316+
base._sources.end()
317+
);
318+
merged_build_recipe._sources.insert(
319+
merged_build_recipe._sources.end(),
320+
target._sources.begin(),
321+
target._sources.end()
322+
);
323+
324+
return merged_build_recipe;
325+
}

ecsact/cli/commands/build/build_recipe.hh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ enum class build_recipe_parse_error {
1919
conflicting_import_export_method_modules,
2020
};
2121

22+
enum class build_recipe_merge_error {
23+
conflicting_export,
24+
};
25+
2226
class build_recipe {
2327
public:
2428
static auto from_yaml_file( //
2529
std::filesystem::path p
2630
) -> std::variant<build_recipe, build_recipe_parse_error>;
2731

32+
static auto merge( //
33+
const build_recipe& a,
34+
const build_recipe& b
35+
) -> std::variant<build_recipe, build_recipe_merge_error>;
36+
2837
struct source_path {
2938
std::filesystem::path path;
3039
std::optional<std::string> outdir;

test/MODULE.bazel.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/build_recipe/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ cc_test(
99
copts = copts,
1010
data = [
1111
"ecsact_build_test.cc",
12+
"ecsact_build_test_merge.cc",
1213
"test-recipe.yml",
14+
"test-merge-recipe.yml",
1315
"//:test.ecsact",
1416
"//:test_codegen_plugin",
1517
"@ecsact_cli",
@@ -24,12 +26,14 @@ cc_test(
2426
"@platforms//os:windows": {
2527
"TEST_ECSACT_CLI": "$(rootpath @ecsact_cli)",
2628
"TEST_ECSACT_BUILD_RECIPE_PATH": "$(rootpath test-recipe.yml)",
29+
"TEST_ECSACT_BUILD_MERGE_RECIPE_PATH": "$(rootpath test-merge-recipe.yml)",
2730
"TEST_ECSACT_FILE_PATH": "$(rootpath //:test.ecsact)",
2831
"TEST_CODEGEN_PLUGIN_PATH": "$(rootpath //:test_codegen_plugin)",
2932
},
3033
"//conditions:default": {
3134
"TEST_ECSACT_CLI": "$(rootpath @ecsact_cli)",
3235
"TEST_ECSACT_BUILD_RECIPE_PATH": "$(rootpath test-recipe.yml)",
36+
"TEST_ECSACT_BUILD_MERGE_RECIPE_PATH": "$(rootpath test-merge-recipe.yml)",
3337
"TEST_ECSACT_FILE_PATH": "$(rootpath //:test.ecsact)",
3438
"TEST_CODEGEN_PLUGIN_PATH": "$(rootpath //:test_codegen_plugin)",
3539
"CC": "$(rootpath @llvm_toolchain_llvm//:bin/clang)",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "ecsact/runtime/dynamic.h"
2+
3+
void ecsact_system_execution_context_get(struct ecsact_system_execution_context*, ecsact_component_like_id, void*) {
4+
}
5+
6+
void ecsact_system_execution_context_action(struct ecsact_system_execution_context*, void*) {
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
$schema: ../../schema/ecsact-build-recipe.schema.json
2+
3+
name: Example Recipe 2
4+
5+
sources:
6+
- ecsact_build_test_merge.cc
7+
8+
exports:
9+
- ecsact_system_execution_context_get
10+
- ecsact_system_execution_context_action
11+

test/build_recipe/test_build_recipe.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ TEST(Build, Success) {
1717
auto test_codegen_plugin_path = std::getenv("TEST_CODEGEN_PLUGIN_PATH");
1818
auto test_ecsact_file_path = std::getenv("TEST_ECSACT_FILE_PATH");
1919
auto test_build_recipe_path = std::getenv("TEST_ECSACT_BUILD_RECIPE_PATH");
20+
auto test_build_merge_recipe_path =
21+
std::getenv("TEST_ECSACT_BUILD_MERGE_RECIPE_PATH");
2022

2123
ASSERT_NE(test_ecsact_cli, nullptr);
2224
ASSERT_NE(test_codegen_plugin_path, nullptr);
2325
ASSERT_NE(test_ecsact_file_path, nullptr);
2426
ASSERT_NE(test_build_recipe_path, nullptr);
27+
ASSERT_NE(test_build_merge_recipe_path, nullptr);
2528

2629
ASSERT_TRUE(fs::exists(test_codegen_plugin_path));
2730
ASSERT_TRUE(fs::exists(test_ecsact_file_path));
@@ -30,10 +33,22 @@ TEST(Build, Success) {
3033
auto exit_code = build_command(std::vector{
3134
"ecsact"s,
3235
"build"s,
36+
"--allow-unresolved-imports"s,
3337
std::string{test_ecsact_file_path},
3438
std::format("--recipe={}", test_build_recipe_path),
3539
std::format("--output=test_ecsact_runtime"),
3640
});
3741

3842
ASSERT_EQ(exit_code, 0);
43+
44+
exit_code = build_command(std::vector{
45+
"ecsact"s,
46+
"build"s,
47+
std::string{test_ecsact_file_path},
48+
std::format("--recipe={}", test_build_recipe_path),
49+
std::format("--recipe={}", test_build_merge_recipe_path),
50+
std::format("--output=test_ecsact_runtime_merged"),
51+
});
52+
53+
ASSERT_EQ(exit_code, 0);
3954
}

0 commit comments

Comments
 (0)