Skip to content
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
5 changes: 5 additions & 0 deletions docs/schema-compiler-tool-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Options:
--root-file-stem NAME Root namespace file stem (default: schema)
--file-extension EXT Generated file extension (default: .generated.hpp)
--list-outputs Print generated output paths without writing files
--emit-qbs PATH Emit the resolved schema as a deterministic QBS image
--qbs-reflective Include reflective record, field, and enum type names in QBS
--print-generated-code-api-version
Print the generated-code API compatibility version and exit
--version Show version information
Expand All @@ -46,6 +48,9 @@ Current behavior:
* defaults to `schema` for the root file stem
* defaults to `.generated.hpp` for generated file extensions
* supports `--list-outputs` as a read-only output-inventory query
* supports `--emit-qbs PATH` to emit minimal QBS by default
* supports `--qbs-reflective` with `--emit-qbs` to include optional reflective names;
without `--qbs-reflective`, QBS remains minimal
* supports `--print-generated-code-api-version` as a terminal machine-readable
compatibility query
* writes diagnostics and tool errors to stderr
Expand Down
62 changes: 62 additions & 0 deletions tests/tools/schema_compiler_tool_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,68 @@ TEST(SchemaCompilerToolTest, EmitsDeterministicQbsForBenchmarkWorkload) {
QUARRY_GENERIC_OK);
}

TEST(SchemaCompilerToolTest, EmitsDeterministicReflectiveQbsWhenRequested) {
const std::filesystem::path root = make_temp_directory("emit-qbs-reflective");
const std::filesystem::path input =
std::filesystem::path{QUARRY_TEST_SOURCE_DIR} / "benchmarks/schemas/workload.brd";
const auto minimal = root / "minimal.qbs";
const auto first = root / "first.qbs";
const auto second = root / "second.qbs";
ASSERT_EQ(run_tool({"--emit-qbs", minimal.string(), input.string()}, root).status, 0);
ASSERT_EQ(run_tool({"--emit-qbs", first.string(), "--qbs-reflective", input.string()}, root).status,
0);
ASSERT_EQ(run_tool({"--qbs-reflective", "--emit-qbs", second.string(), input.string()}, root).status,
0);
std::ifstream minimal_stream(minimal, std::ios::binary);
std::ifstream first_stream(first, std::ios::binary);
std::ifstream second_stream(second, std::ios::binary);
const std::vector<uint8_t> minimal_bytes{std::istreambuf_iterator<char>(minimal_stream), {}};
const std::vector<uint8_t> first_bytes{std::istreambuf_iterator<char>(first_stream), {}};
const std::vector<uint8_t> second_bytes{std::istreambuf_iterator<char>(second_stream), {}};
EXPECT_NE(first_bytes, minimal_bytes);
EXPECT_EQ(first_bytes, second_bytes);

quarry_qbs_record_view_t records[128]{}; quarry_qbs_field_view_t fields[128]{};
quarry_qbs_type_view_t types[128]{}; quarry_qbs_enum_view_t enums[128]{}; uint64_t values[128]{};
quarry_workspace_t workspace{};
workspace.records = records; workspace.record_capacity = 128;
workspace.fields = fields; workspace.field_capacity = 128;
workspace.types = types; workspace.type_capacity = 128;
workspace.enums = enums; workspace.enum_capacity = 128;
workspace.enum_values = values; workspace.enum_value_capacity = 128;
quarry_qbs_view_t view{};
ASSERT_EQ(quarry_qbs_parse(first_bytes.data(), first_bytes.size(), &view, &workspace, nullptr),
QUARRY_GENERIC_OK);
EXPECT_NE(view.strings_offset, 0U);
quarry_string_view_t name{};
ASSERT_EQ(quarry_qbs_record_name(&view, &view.records[0], &name), QUARRY_GENERIC_OK);
EXPECT_EQ(std::string(name.data, name.size), "Workload");
ASSERT_EQ(quarry_qbs_field_name(&view, &view.records[0], 0U, &name), QUARRY_GENERIC_OK);
EXPECT_EQ(std::string(name.data, name.size), "sequence");
}

TEST(SchemaCompilerToolTest, QbsReflectiveRequiresQbsOutput) {
const std::filesystem::path root = make_temp_directory("qbs-reflective-validation");
const std::filesystem::path input = root / "schema.brd";
write_text_file(input,
"namespace: quarry.test\nrecord: Sample\nversion: 1\ntype: data\nfields:\n"
" value:\n type: uint32\n");
const CommandResult result = run_tool({"--qbs-reflective", input.string()}, root);
EXPECT_EQ(result.status, 2);
EXPECT_NE(result.stderr_text.find("--qbs-reflective requires --emit-qbs"), std::string::npos);
}

TEST(SchemaCompilerToolTest, DuplicateQbsReflectiveFails) {
const std::filesystem::path root = make_temp_directory("qbs-reflective-duplicate");
const std::filesystem::path input = root / "schema.brd";
write_text_file(input,
"namespace: quarry.test\nrecord: Sample\nversion: 1\ntype: data\nfields:\n"
" value:\n type: uint32\n");
const CommandResult result = run_tool({"--qbs-reflective", "--qbs-reflective", input.string()}, root);
EXPECT_EQ(result.status, 2);
EXPECT_NE(result.stderr_text.find("duplicate --qbs-reflective option"), std::string::npos);
}

TEST(SchemaCompilerToolTest, HelpIsTerminalBeforeListOutputs) {
const std::filesystem::path root = make_temp_directory("help-list-outputs");

Expand Down
30 changes: 27 additions & 3 deletions tools/schema_compiler/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct CommandLine {
bool show_version = false;
bool show_generated_code_api_version = false;
bool list_outputs = false;
bool qbs_reflective = false;
std::string qbs_output;
Language language = Language::Cpp;
std::string input_path;
Expand All @@ -64,6 +65,7 @@ struct CommandLine {
" (default: .generated.hpp)\n"
" --language {cpp,c,python} Target backend language (default: cpp)\n"
" --emit-qbs PATH Emit the resolved schema as a deterministic QBS image\n"
" --qbs-reflective Include reflective record, field, and enum names in QBS\n"
" --list-outputs Print generated output paths without writing files\n"
" --print-generated-code-api-version\n"
" Print the generated-code API compatibility version and "
Expand Down Expand Up @@ -96,6 +98,7 @@ void print_generated_code_api_version(std::ostream& output) {
bool saw_file_extension = false;
bool saw_language = false;
bool saw_qbs_output = false;
bool saw_qbs_reflective = false;

for (int index = 1; index < argc; ++index) {
const std::string_view argument{argv[index]};
Expand All @@ -119,6 +122,15 @@ void print_generated_code_api_version(std::ostream& output) {
command_line.list_outputs = true;
continue;
}
if (argument == "--qbs-reflective") {
if (saw_qbs_reflective) {
errors << "error: duplicate --qbs-reflective option\n";
return std::nullopt;
}
saw_qbs_reflective = true;
command_line.qbs_reflective = true;
continue;
}

if (option_requires_value(argument)) {
if (index + 1 >= argc) {
Expand Down Expand Up @@ -201,7 +213,8 @@ void print_generated_code_api_version(std::ostream& output) {
command_line.codegen_options.output_directory != "generated" ||
command_line.codegen_options.root_file_stem != "schema" ||
command_line.codegen_options.file_extension != ".generated.hpp" ||
command_line.language != Language::Cpp || !command_line.qbs_output.empty();
command_line.language != Language::Cpp || !command_line.qbs_output.empty() ||
command_line.qbs_reflective;
if (has_generation_arguments) {
errors << "error: --print-generated-code-api-version does not accept generation "
"options or an input file\n";
Expand All @@ -214,6 +227,13 @@ void print_generated_code_api_version(std::ostream& output) {
return std::nullopt;
}

if (command_line.qbs_reflective && command_line.qbs_output.empty() &&
!command_line.show_help && !command_line.show_version &&
!command_line.show_generated_code_api_version) {
errors << "error: --qbs-reflective requires --emit-qbs\n";
return std::nullopt;
}

if (command_line.language == Language::Python && saw_file_extension) {
errors << "error: --file-extension is not supported with --language python\n";
return std::nullopt;
Expand Down Expand Up @@ -423,8 +443,12 @@ template <typename CodegenResultT>
}
if (!layout_diagnostics.empty()) return exit_failure;
quarry::compiler::qbs::QbsModelBuilder builder;
auto model = builder.build(*compilation_result.schema_ir, layout,
quarry::compiler::qbs::QbsBuildOptions{}, layout_diagnostics);
const quarry::compiler::qbs::QbsBuildOptions qbs_options{
.mode = command_line.qbs_reflective
? quarry::compiler::qbs::BuildMode::Reflective
: quarry::compiler::qbs::BuildMode::Minimal};
auto model = builder.build(*compilation_result.schema_ir, layout, qbs_options,
layout_diagnostics);
if (!model) {
errors << diagnostics::DiagnosticFormatter::format_all(
layout_diagnostics, compiler_context.source_manager());
Expand Down
Loading