| @@ -39,7 +39,7 @@ void compile_file_plan::compile(const build_env& env) const { | |||
| auto end_time = std::chrono::steady_clock::now(); | |||
| auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||
| spdlog::info("{} - {:>5n}ms", msg, dur_ms.count()); | |||
| spdlog::info("{} - {:>7n}ms", msg, dur_ms.count()); | |||
| if (!compile_res.okay()) { | |||
| spdlog::error("Compilation failed: {}", _source.path.string()); | |||
| @@ -2,6 +2,7 @@ | |||
| #include <dds/logging.hpp> | |||
| #include <dds/repo/repo.hpp> | |||
| #include <dds/sdist.hpp> | |||
| #include <dds/toolchain/from_dds.hpp> | |||
| #include <dds/util/fs.hpp> | |||
| #include <dds/util/paths.hpp> | |||
| #include <dds/util/signal.hpp> | |||
| @@ -40,7 +41,7 @@ struct toolchain_flag : string_flag { | |||
| } | |||
| return std::move(*tc); | |||
| } else { | |||
| return dds::toolchain::load_from_file(tc_path); | |||
| return dds::parse_toolchain_dds(dds::slurp_file(tc_path)); | |||
| } | |||
| } | |||
| }; | |||
| @@ -0,0 +1,484 @@ | |||
| #include "./from_dds.hpp" | |||
| #include <dds/toolchain/prep.hpp> | |||
| #include <dds/toolchain/toolchain.hpp> | |||
| #include <dds/util/algo.hpp> | |||
| #include <libman/parse.hpp> | |||
| #include <spdlog/fmt/fmt.h> | |||
| #include <map> | |||
| #include <optional> | |||
| #include <tuple> | |||
| #include <vector> | |||
| using namespace dds; | |||
| using fmt::format; | |||
| using std::optional; | |||
| using std::string; | |||
| using std::vector; | |||
| using string_seq = vector<string>; | |||
| using opt_string = optional<string>; | |||
| using opt_str_seq = optional<string_seq>; | |||
| using strv = std::string_view; | |||
| toolchain dds::parse_toolchain_dds(strv str, strv context) { | |||
| auto kvs = lm::parse_string(str); | |||
| return parse_toolchain_dds(kvs, context); | |||
| } | |||
| struct read_argv_acc { | |||
| strv my_key; | |||
| opt_str_seq& out; | |||
| bool operator()(strv, strv key, strv value) const { | |||
| if (key != my_key) { | |||
| return false; | |||
| } | |||
| if (!out) { | |||
| out.emplace(); | |||
| } | |||
| auto cmd = split_shell_string(value); | |||
| extend(*out, cmd); | |||
| return true; | |||
| } | |||
| }; | |||
| struct read_argv { | |||
| strv my_key; | |||
| opt_str_seq& out; | |||
| bool operator()(strv ctx, strv key, strv value) const { | |||
| if (key != my_key) { | |||
| return false; | |||
| } | |||
| if (out.has_value()) { | |||
| throw std::runtime_error( | |||
| format("{}: More than one value provided for key '{}'", ctx, key)); | |||
| } | |||
| out.emplace(split_shell_string(value)); | |||
| return true; | |||
| } | |||
| }; | |||
| template <typename T, typename Func> | |||
| T read_opt(const std::optional<T>& what, Func&& fn) { | |||
| if (!what.has_value()) { | |||
| return fn(); | |||
| } | |||
| return *what; | |||
| } | |||
| toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) { | |||
| opt_string compiler_id; | |||
| opt_string c_compiler_fpath; | |||
| opt_string cxx_compiler_fpath; | |||
| opt_string c_version; | |||
| opt_string cxx_version; | |||
| opt_string archive_prefix; | |||
| opt_string archive_suffix; | |||
| opt_string obj_prefix; | |||
| opt_string obj_suffix; | |||
| opt_string exe_prefix; | |||
| opt_string exe_suffix; | |||
| optional<bool> do_debug; | |||
| optional<bool> do_optimize; | |||
| opt_str_seq include_template; | |||
| opt_str_seq define_template; | |||
| opt_str_seq warning_flags; | |||
| opt_str_seq flags; | |||
| opt_str_seq c_flags; | |||
| opt_str_seq cxx_flags; | |||
| opt_str_seq c_compile_file; | |||
| opt_str_seq cxx_compile_file; | |||
| opt_str_seq create_archive; | |||
| opt_str_seq link_executable; | |||
| opt_str_seq compile_launcher; | |||
| lm::read(context, | |||
| pairs, | |||
| // Base compile info: | |||
| lm::read_opt("Compiler-ID", compiler_id), | |||
| lm::read_opt("C-Compiler", c_compiler_fpath), | |||
| lm::read_opt("C++-Compiler", cxx_compiler_fpath), | |||
| // Language options | |||
| lm::read_opt("C-Version", c_version), | |||
| lm::read_opt("C++-Version", cxx_version), | |||
| // Flag templates | |||
| read_argv{"Include-Template", include_template}, | |||
| read_argv{"Define-Template", define_template}, | |||
| // Flags | |||
| read_argv_acc{"Warning-Flags", warning_flags}, | |||
| read_argv_acc{"Flags", flags}, | |||
| read_argv_acc{"C-Flags", c_flags}, | |||
| read_argv_acc{"C++-Flags", cxx_flags}, | |||
| // Options for flags | |||
| lm::read_bool("Optimize", do_optimize), | |||
| lm::read_bool("Debug", do_debug), | |||
| // Miscellaneous | |||
| read_argv{"Compiler-Launcher", compile_launcher}, | |||
| // Command templates | |||
| read_argv{"C-Compile-File", c_compile_file}, | |||
| read_argv{"C++-Compile-File", cxx_compile_file}, | |||
| read_argv{"Create-Archive", create_archive}, | |||
| read_argv{"Link-Executable", link_executable}, | |||
| // Filename affixes | |||
| lm::read_opt("Archive-Prefix", archive_prefix), | |||
| lm::read_opt("Archive-Suffix", archive_suffix), | |||
| lm::read_opt("Object-Prefix", obj_prefix), | |||
| lm::read_opt("Object-Suffix", obj_suffix), | |||
| lm::read_opt("Executable-Prefix", exe_prefix), | |||
| lm::read_opt("Executable-Suffix", exe_suffix), | |||
| // Die: | |||
| lm::reject_unknown()); | |||
| toolchain_prep tc; | |||
| auto fail = [&](auto msg, auto... args)[[noreturn]]->void { | |||
| throw std::runtime_error( | |||
| format("{}: Failed to generate toolchain: {}", context, msg, args...)); | |||
| }; | |||
| enum compiler_id_e_t { | |||
| no_comp_id, | |||
| msvc, | |||
| clang, | |||
| gnu, | |||
| } compiler_id_e | |||
| = [&] { | |||
| if (!compiler_id) { | |||
| return no_comp_id; | |||
| } else if (compiler_id == "MSVC") { | |||
| return msvc; | |||
| } else if (compiler_id == "GNU") { | |||
| return gnu; | |||
| } else if (compiler_id == "Clang") { | |||
| return clang; | |||
| } else { | |||
| fail("Unknown Compiler-ID '{}'", *compiler_id); | |||
| } | |||
| }(); | |||
| bool is_gnu = compiler_id_e == gnu; | |||
| bool is_clang = compiler_id_e == clang; | |||
| bool is_msvc = compiler_id_e == msvc; | |||
| bool is_gnu_like = is_gnu || is_clang; | |||
| // Now convert the flags we've been given into a real toolchain | |||
| auto get_compiler = [&](bool is_cxx) -> string { | |||
| if (is_cxx && cxx_compiler_fpath) { | |||
| return *cxx_compiler_fpath; | |||
| } | |||
| if (!is_cxx && c_compiler_fpath) { | |||
| return *c_compiler_fpath; | |||
| } | |||
| if (!compiler_id.has_value()) { | |||
| fail("Unable to determine what compiler to use."); | |||
| } | |||
| if (is_gnu) { | |||
| return is_cxx ? "g++" : "gcc"; | |||
| } | |||
| if (is_clang) { | |||
| return is_cxx ? "clang++" : "clang"; | |||
| } | |||
| if (is_msvc) { | |||
| return "cl.exe"; | |||
| } | |||
| assert(false && "Compiler name deduction failed"); | |||
| std::terminate(); | |||
| }; | |||
| enum c_version_e_t { | |||
| c_none, | |||
| c89, | |||
| c99, | |||
| c11, | |||
| c18, | |||
| } c_version_e | |||
| = [&] { | |||
| if (!c_version) { | |||
| return c_none; | |||
| } else if (c_version == "c89") { | |||
| return c89; | |||
| } else if (c_version == "c99") { | |||
| return c99; | |||
| } else if (c_version == "c11") { | |||
| return c11; | |||
| } else if (c_version == "c18") { | |||
| return c18; | |||
| } else { | |||
| fail("Unknown C-Version '{}'", *c_version); | |||
| } | |||
| }(); | |||
| enum cxx_version_e_t { | |||
| cxx_none, | |||
| cxx98, | |||
| cxx03, | |||
| cxx11, | |||
| cxx14, | |||
| cxx17, | |||
| cxx20, | |||
| } cxx_version_e | |||
| = [&] { | |||
| if (!cxx_version) { | |||
| return cxx_none; | |||
| } else if (cxx_version == "c++98") { | |||
| return cxx98; | |||
| } else if (cxx_version == "c++03") { | |||
| return cxx03; | |||
| } else if (cxx_version == "c++11") { | |||
| return cxx11; | |||
| } else if (cxx_version == "c++14") { | |||
| return cxx14; | |||
| } else if (cxx_version == "c++17") { | |||
| return cxx17; | |||
| } else if (cxx_version == "c++20") { | |||
| return cxx20; | |||
| } else { | |||
| fail("Unknown C++-Version '{}'", *cxx_version); | |||
| } | |||
| }(); | |||
| std::map<std::tuple<compiler_id_e_t, c_version_e_t>, string_seq> c_version_flag_table = { | |||
| {{msvc, c_none}, {}}, | |||
| {{msvc, c89}, {}}, | |||
| {{msvc, c99}, {}}, | |||
| {{msvc, c11}, {}}, | |||
| {{msvc, c18}, {}}, | |||
| {{gnu, c_none}, {}}, | |||
| {{gnu, c89}, {"-std=c89"}}, | |||
| {{gnu, c99}, {"-std=c99"}}, | |||
| {{gnu, c11}, {"-std=c11"}}, | |||
| {{gnu, c18}, {"-std=c18"}}, | |||
| {{clang, c_none}, {}}, | |||
| {{clang, c89}, {"-std=c89"}}, | |||
| {{clang, c99}, {"-std=c99"}}, | |||
| {{clang, c11}, {"-std=c11"}}, | |||
| {{clang, c18}, {"-std=c18"}}, | |||
| }; | |||
| auto get_c_version_flags = [&]() -> string_seq { | |||
| if (!compiler_id.has_value()) { | |||
| fail("Unable to deduce flags for 'C-Version' without setting 'Compiler-ID'"); | |||
| } | |||
| auto c_ver_iter = c_version_flag_table.find({compiler_id_e, c_version_e}); | |||
| assert(c_ver_iter != c_version_flag_table.end()); | |||
| return c_ver_iter->second; | |||
| }; | |||
| std::map<std::tuple<compiler_id_e_t, cxx_version_e_t>, string_seq> cxx_version_flag_table = { | |||
| {{msvc, cxx_none}, {}}, | |||
| {{msvc, cxx98}, {}}, | |||
| {{msvc, cxx03}, {}}, | |||
| {{msvc, cxx11}, {}}, | |||
| {{msvc, cxx14}, {"/std:c++14"}}, | |||
| {{msvc, cxx17}, {"/std:c++17"}}, | |||
| {{msvc, cxx20}, {"/std:c++latest"}}, | |||
| {{gnu, cxx_none}, {}}, | |||
| {{gnu, cxx98}, {"-std=c++98"}}, | |||
| {{gnu, cxx03}, {"-std=c++03"}}, | |||
| {{gnu, cxx11}, {"-std=c++11"}}, | |||
| {{gnu, cxx14}, {"-std=c++14"}}, | |||
| {{gnu, cxx17}, {"-std=c++17"}}, | |||
| {{gnu, cxx20}, {"-std=c++20"}}, | |||
| {{clang, cxx_none}, {}}, | |||
| {{clang, cxx98}, {"-std=c++98"}}, | |||
| {{clang, cxx03}, {"-std=c++03"}}, | |||
| {{clang, cxx11}, {"-std=c++11"}}, | |||
| {{clang, cxx14}, {"-std=c++14"}}, | |||
| {{clang, cxx17}, {"-std=c++17"}}, | |||
| {{clang, cxx20}, {"-std=c++20"}}, | |||
| }; | |||
| auto get_cxx_version_flags = [&]() -> string_seq { | |||
| if (!compiler_id.has_value()) { | |||
| fail("Unable to deduce flags for 'C++-Version' without setting 'Compiler-ID'"); | |||
| } | |||
| auto cxx_ver_iter = cxx_version_flag_table.find({compiler_id_e, cxx_version_e}); | |||
| assert(cxx_ver_iter != cxx_version_flag_table.end()); | |||
| return cxx_ver_iter->second; | |||
| }; | |||
| auto get_flags = [&](bool is_cxx) -> string_seq { | |||
| string_seq ret; | |||
| if (flags) { | |||
| extend(ret, *flags); | |||
| } | |||
| if (is_cxx && cxx_flags) { | |||
| extend(ret, *cxx_flags); | |||
| } | |||
| if (!is_cxx && c_flags) { | |||
| extend(ret, *c_flags); | |||
| } | |||
| if (!is_cxx && c_version) { | |||
| extend(ret, get_c_version_flags()); | |||
| } | |||
| if (is_cxx && cxx_version) { | |||
| extend(ret, get_cxx_version_flags()); | |||
| } | |||
| if (is_msvc) { | |||
| strv rt_lib = "/MT"; | |||
| if (do_optimize.has_value() && *do_optimize) { | |||
| extend(ret, {"/O2"}); | |||
| } | |||
| if (do_debug.has_value() && *do_debug) { | |||
| extend(ret, {"/Z7", "/DEBUG", "/MTd"}); | |||
| rt_lib = "/MTd"; | |||
| } | |||
| ret.emplace_back(rt_lib); | |||
| extend(ret, {"/nologo", "<FLAGS>", "/c", "<IN>", "/permissive-", "/Fo<OUT>"}); | |||
| if (is_cxx) { | |||
| extend(ret, {"/EHsc"}); | |||
| } | |||
| } else if (is_gnu_like) { | |||
| if (do_optimize.has_value() && *do_optimize) { | |||
| extend(ret, {"-O2"}); | |||
| } | |||
| if (do_debug.has_value() && *do_debug) { | |||
| extend(ret, {"-g"}); | |||
| } | |||
| extend(ret, | |||
| {"-fPIC", | |||
| "-fdiagnostics-color", | |||
| "-pthread", | |||
| "<FLAGS>", | |||
| "-c", | |||
| "<IN>", | |||
| "-o<OUT>"}); | |||
| } | |||
| return ret; | |||
| }; | |||
| tc.c_compile = read_opt(c_compile_file, [&] { | |||
| string_seq c; | |||
| if (compile_launcher) { | |||
| extend(c, *compile_launcher); | |||
| } | |||
| c.push_back(get_compiler(false)); | |||
| extend(c, get_flags(false)); | |||
| return c; | |||
| }); | |||
| tc.cxx_compile = read_opt(cxx_compile_file, [&] { | |||
| string_seq cxx; | |||
| if (compile_launcher) { | |||
| extend(cxx, *compile_launcher); | |||
| } | |||
| cxx.push_back(get_compiler(true)); | |||
| extend(cxx, get_flags(true)); | |||
| return cxx; | |||
| }); | |||
| tc.include_template = read_opt(include_template, [&]() -> string_seq { | |||
| if (!compiler_id) { | |||
| fail("Cannot deduce 'Include-Template' without 'Compiler-ID'"); | |||
| } | |||
| if (is_gnu_like) { | |||
| return {"-I", "<PATH>"}; | |||
| } else if (compiler_id == "MSVC") { | |||
| return {"/I", "<PATH>"}; | |||
| } | |||
| assert(false && "Include-Template deduction failed"); | |||
| std::terminate(); | |||
| }); | |||
| tc.define_template = read_opt(define_template, [&]() -> string_seq { | |||
| if (!compiler_id) { | |||
| fail("Cannot deduce 'Define-Template' without 'Compiler-ID'"); | |||
| } | |||
| if (is_gnu_like) { | |||
| return {"-D", "<DEF>"}; | |||
| } else if (compiler_id == "MSVC") { | |||
| return {"/D", "<DEF>"}; | |||
| } | |||
| assert(false && "Define-Template deduction failed"); | |||
| std::terminate(); | |||
| }); | |||
| tc.archive_prefix = archive_prefix.value_or("lib"); | |||
| tc.archive_suffix = read_opt(archive_suffix, [&] { | |||
| if (!compiler_id) { | |||
| fail("Cannot deduce library file extension without Compiler-ID"); | |||
| } | |||
| if (is_gnu) { | |||
| return ".a"; | |||
| } else if (is_msvc) { | |||
| return ".lib"; | |||
| } | |||
| assert(false && "No archive suffix"); | |||
| std::terminate(); | |||
| }); | |||
| tc.object_prefix = obj_prefix.value_or(""); | |||
| tc.object_suffix = read_opt(obj_suffix, [&] { | |||
| if (!compiler_id) { | |||
| fail("Cannot deduce object file extension without Compiler-ID"); | |||
| } | |||
| if (is_gnu) { | |||
| return ".o"; | |||
| } else if (is_msvc) { | |||
| return ".obj"; | |||
| } | |||
| assert(false && "No object file suffix"); | |||
| std::terminate(); | |||
| }); | |||
| tc.exe_prefix = exe_prefix.value_or(""); | |||
| tc.exe_suffix = read_opt(exe_suffix, [&] { | |||
| #ifdef _WIN32 | |||
| return ".exe"; | |||
| #else | |||
| return ""; | |||
| #endif | |||
| }); | |||
| tc.warning_flags = read_opt(warning_flags, [&]() -> string_seq { | |||
| if (!compiler_id) { | |||
| // No error. Just no warning flags | |||
| return {}; | |||
| } | |||
| if (is_msvc) { | |||
| return {"/W4", "/WX"}; | |||
| } else if (is_gnu_like) { | |||
| return {"-Wall", "-Wextra", "-Wpedantic", "-Wconversion"}; | |||
| } | |||
| assert(false && "No warning flags"); | |||
| std::terminate(); | |||
| }); | |||
| tc.link_archive = read_opt(create_archive, [&]() -> string_seq { | |||
| if (!compiler_id) { | |||
| fail("Unable to deduce archive creation rules without a Compiler-ID"); | |||
| } | |||
| if (is_msvc) { | |||
| return {"lib", "/nologo", "/OUT:<OUT>", "<IN>"}; | |||
| } else if (is_gnu_like) { | |||
| return {"ar", "rcs", "<OUT>", "<IN>"}; | |||
| } | |||
| assert(false && "No archive command"); | |||
| std::terminate(); | |||
| }); | |||
| tc.link_exe = read_opt(link_executable, [&]() -> string_seq { | |||
| if (!compiler_id) { | |||
| fail("Unable to deduce how to link executables without a Compiler-ID"); | |||
| } | |||
| if (is_msvc) { | |||
| return {get_compiler(true), "/nologo", "/EHsc", "<IN>", "/Fe<OUT>"}; | |||
| } else if (is_gnu_like) { | |||
| return {get_compiler(true), | |||
| "-fPIC", | |||
| "-fdiagnostics-color", | |||
| "<IN>", | |||
| "-pthread", | |||
| "-lstdc++fs", | |||
| "-o<OUT>"}; | |||
| } | |||
| assert(false && "No link-exe command"); | |||
| std::terminate(); | |||
| }); | |||
| return tc.realize(); | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| #pragma once | |||
| #include <dds/toolchain/toolchain.hpp> | |||
| #include <libman/parse_fwd.hpp> | |||
| #include <string_view> | |||
| namespace dds { | |||
| class toolchain; | |||
| toolchain parse_toolchain_dds(std::string_view str, | |||
| std::string_view context = "Loading toolchain file"); | |||
| toolchain parse_toolchain_dds(const lm::pair_list&, | |||
| std::string_view context = "Loading toolchain file"); | |||
| } // namespace dds | |||
| @@ -0,0 +1,127 @@ | |||
| #include <dds/toolchain/from_dds.hpp> | |||
| #include <dds/proc.hpp> | |||
| #include <dds/util.test.hpp> | |||
| namespace { | |||
| void check_tc_compile(std::string_view tc_content, | |||
| std::string_view compile, | |||
| std::string_view compile_warnings, | |||
| std::string_view ar, | |||
| std::string_view exe) { | |||
| auto tc = dds::parse_toolchain_dds(tc_content); | |||
| bool any_error = false; | |||
| dds::compile_file_spec cf; | |||
| cf.source_path = "foo.cpp"; | |||
| cf.out_path = "foo.o"; | |||
| auto cf_cmd = tc.create_compile_command(cf); | |||
| auto cf_cmd_str = dds::quote_command(cf_cmd); | |||
| if (cf_cmd_str != compile) { | |||
| std::cerr << "Compile command came out incorrect!\n"; | |||
| std::cerr << " Expected: " << compile << '\n'; | |||
| std::cerr << " Actual: " << cf_cmd_str << "\n\n"; | |||
| any_error = true; | |||
| } | |||
| cf.enable_warnings = true; | |||
| cf_cmd = tc.create_compile_command(cf); | |||
| cf_cmd_str = dds::quote_command(cf_cmd); | |||
| if (cf_cmd_str != compile_warnings) { | |||
| std::cerr << "Compile command (with warnings) came out incorrect!\n"; | |||
| std::cerr << " Expected: " << compile_warnings << '\n'; | |||
| std::cerr << " Actual: " << cf_cmd_str << "\n\n"; | |||
| any_error = true; | |||
| } | |||
| dds::archive_spec ar_spec; | |||
| ar_spec.input_files.push_back("foo.o"); | |||
| ar_spec.input_files.push_back("bar.o"); | |||
| ar_spec.out_path = "stuff.a"; | |||
| auto ar_cmd = tc.create_archive_command(ar_spec); | |||
| auto ar_cmd_str = dds::quote_command(ar_cmd); | |||
| if (ar_cmd_str != ar) { | |||
| std::cerr << "Archive command came out incorrect!\n"; | |||
| std::cerr << " Expected: " << ar << '\n'; | |||
| std::cerr << " Actual: " << ar_cmd_str << "\n\n"; | |||
| any_error = true; | |||
| } | |||
| dds::link_exe_spec exe_spec; | |||
| exe_spec.inputs.push_back("foo.o"); | |||
| exe_spec.inputs.push_back("bar.a"); | |||
| exe_spec.output = "meow.exe"; | |||
| auto exe_cmd = tc.create_link_executable_command(exe_spec); | |||
| auto exe_cmd_str = dds::quote_command(exe_cmd); | |||
| if (exe_cmd_str != exe) { | |||
| std::cerr << "Executable linking command came out incorrect!\n"; | |||
| std::cerr << " Expected: " << exe << '\n'; | |||
| std::cerr << " Actual: " << exe_cmd_str << "\n\n"; | |||
| any_error = true; | |||
| } | |||
| if (any_error) { | |||
| std::cerr << "The error-producing toolchain file content:\n" << tc_content << '\n'; | |||
| dds::S_failed_checks++; | |||
| } | |||
| } | |||
| void run_tests() { | |||
| check_tc_compile("Compiler-ID: GNU", | |||
| "g++ -fPIC -fdiagnostics-color -pthread -c foo.cpp -ofoo.o", | |||
| "g++ -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion " | |||
| "-c foo.cpp -ofoo.o", | |||
| "ar rcs stuff.a foo.o bar.o", | |||
| "g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -lstdc++fs -omeow.exe"); | |||
| auto tc = dds::parse_toolchain_dds(R"( | |||
| Compiler-ID: GNU | |||
| )"); | |||
| dds::compile_file_spec cfs; | |||
| cfs.source_path = "foo.cpp"; | |||
| cfs.out_path = "foo.o"; | |||
| auto cmd = tc.create_compile_command(cfs); | |||
| CHECK(cmd | |||
| == std::vector<std::string>{"g++", | |||
| "-fPIC", | |||
| "-fdiagnostics-color", | |||
| "-pthread", | |||
| "-c", | |||
| "foo.cpp", | |||
| "-ofoo.o"}); | |||
| cfs.definitions.push_back("FOO=BAR"); | |||
| cmd = tc.create_compile_command(cfs); | |||
| CHECK(cmd | |||
| == std::vector<std::string>{"g++", | |||
| "-fPIC", | |||
| "-fdiagnostics-color", | |||
| "-pthread", | |||
| "-D", | |||
| "FOO=BAR", | |||
| "-c", | |||
| "foo.cpp", | |||
| "-ofoo.o"}); | |||
| cfs.include_dirs.push_back("fake-dir"); | |||
| cmd = tc.create_compile_command(cfs); | |||
| CHECK(cmd | |||
| == std::vector<std::string>{"g++", | |||
| "-fPIC", | |||
| "-fdiagnostics-color", | |||
| "-pthread", | |||
| "-I", | |||
| "fake-dir", | |||
| "-D", | |||
| "FOO=BAR", | |||
| "-c", | |||
| "foo.cpp", | |||
| "-ofoo.o"}); | |||
| } | |||
| } // namespace | |||
| DDS_TEST_MAIN; | |||
| @@ -4,10 +4,7 @@ | |||
| #include <dds/util/algo.hpp> | |||
| #include <dds/util/string.hpp> | |||
| #include <libman/parse.hpp> | |||
| #include <spdlog/fmt/fmt.h> | |||
| #include <cassert> | |||
| #include <optional> | |||
| #include <string> | |||
| #include <vector> | |||
| @@ -20,14 +17,6 @@ using std::string_view; | |||
| using std::vector; | |||
| using opt_string = optional<string>; | |||
| namespace { | |||
| struct invalid_toolchain : std::runtime_error { | |||
| using std::runtime_error::runtime_error; | |||
| }; | |||
| } // namespace | |||
| toolchain toolchain::realize(const toolchain_prep& prep) { | |||
| toolchain ret; | |||
| ret._c_compile = prep.c_compile; | |||
| @@ -46,96 +35,6 @@ toolchain toolchain::realize(const toolchain_prep& prep) { | |||
| return ret; | |||
| } | |||
| toolchain toolchain::load_from_file(fs::path p) { | |||
| opt_string inc_template; | |||
| opt_string def_template; | |||
| opt_string c_compile_template; | |||
| opt_string cxx_compile_template; | |||
| opt_string create_archive_template; | |||
| opt_string link_exe_template; | |||
| opt_string warning_flags; | |||
| opt_string archive_prefix; | |||
| opt_string archive_suffix; | |||
| opt_string object_suffix; | |||
| opt_string exe_suffix; | |||
| auto require_key = [](auto k, auto& opt) { | |||
| if (!opt.has_value()) { | |||
| throw invalid_toolchain( | |||
| fmt::format("Toolchain file is missing a required key '{}'", k)); | |||
| } | |||
| }; | |||
| auto kvs = lm::parse_file(p); | |||
| for (auto&& pair : kvs.items()) { | |||
| auto& key = pair.key(); | |||
| auto& value = pair.value(); | |||
| auto try_single = [&](auto k, auto& opt) { | |||
| if (key == k) { | |||
| if (opt.has_value()) { | |||
| throw invalid_toolchain(fmt::format("Duplicated key '{}'", key)); | |||
| } | |||
| opt = value; | |||
| return true; | |||
| } | |||
| return false; | |||
| }; | |||
| // clang-format off | |||
| bool found_single = false // Bool to force alignment | |||
| // Argument templates | |||
| || try_single("Include-Template", inc_template) | |||
| || try_single("Define-Template", def_template) | |||
| // Command templates | |||
| || try_single("Compile-C-Template", c_compile_template) | |||
| || try_single("Compile-C++-Template", cxx_compile_template) | |||
| || try_single("Create-Archive-Template", create_archive_template) | |||
| || try_single("Link-Executable-Template", link_exe_template) | |||
| || try_single("Warning-Flags", warning_flags) | |||
| || try_single("Archive-Prefix", archive_prefix) | |||
| || try_single("Archive-Suffix", archive_suffix) | |||
| || try_single("Object-Suffix", object_suffix) | |||
| || try_single("Executable-Suffix", exe_suffix) | |||
| || false; | |||
| // clang-format on | |||
| if (found_single) { | |||
| continue; | |||
| } | |||
| throw invalid_toolchain(fmt::format("Unknown toolchain file key '{}'", key)); | |||
| } | |||
| require_key("Include-Template", inc_template); | |||
| require_key("Define-Template", def_template); | |||
| require_key("Compile-C-Template", c_compile_template); | |||
| require_key("Compile-C++-Template", cxx_compile_template); | |||
| require_key("Create-Archive-Template", create_archive_template); | |||
| require_key("Link-Executable-Template", link_exe_template); | |||
| require_key("Archive-Suffix", archive_suffix); | |||
| require_key("Object-Suffix", object_suffix); | |||
| require_key("Executable-Suffix", exe_suffix); | |||
| return toolchain{ | |||
| c_compile_template.value(), | |||
| cxx_compile_template.value(), | |||
| inc_template.value(), | |||
| def_template.value(), | |||
| create_archive_template.value(), | |||
| link_exe_template.value(), | |||
| warning_flags.value_or(""), | |||
| archive_prefix.value_or("lib"), | |||
| archive_suffix.value(), | |||
| object_suffix.value(), | |||
| exe_suffix.value(), | |||
| }; | |||
| } | |||
| vector<string> dds::split_shell_string(std::string_view shell) { | |||
| char cur_quote = 0; | |||
| bool is_escaped = false; | |||
| @@ -58,31 +58,7 @@ class toolchain { | |||
| public: | |||
| toolchain() = default; | |||
| toolchain(std::string_view c_compile, | |||
| std::string_view cxx_compile, | |||
| std::string_view inc_template, | |||
| std::string_view def_template, | |||
| std::string_view archive_template, | |||
| std::string_view link_exe_template, | |||
| std::string_view warning_flags, | |||
| std::string_view archive_prefix, | |||
| std::string_view archive_suffix, | |||
| std::string_view object_suffix, | |||
| std::string_view exe_suffix) | |||
| : _c_compile(split_shell_string(c_compile)) | |||
| , _cxx_compile(split_shell_string(cxx_compile)) | |||
| , _inc_template(split_shell_string(inc_template)) | |||
| , _def_template(split_shell_string(def_template)) | |||
| , _link_archive(split_shell_string(archive_template)) | |||
| , _link_exe(split_shell_string(link_exe_template)) | |||
| , _warning_flags(split_shell_string(warning_flags)) | |||
| , _archive_prefix(archive_prefix) | |||
| , _archive_suffix(archive_suffix) | |||
| , _object_suffix(object_suffix) | |||
| , _exe_suffix(exe_suffix) {} | |||
| static toolchain realize(const toolchain_prep&); | |||
| static toolchain load_from_file(fs::path); | |||
| auto& archive_suffix() const noexcept { return _archive_suffix; } | |||
| auto& object_suffix() const noexcept { return _object_suffix; } | |||
| @@ -1,5 +1,7 @@ | |||
| #pragma once | |||
| #include <libman/parse_fwd.hpp> | |||
| #include <cassert> | |||
| #include <filesystem> | |||
| #include <optional> | |||
| @@ -134,7 +136,7 @@ public: | |||
| : _key(key) | |||
| , _ref(ref) {} | |||
| int read_one(std::string_view context, std::string_view key, std::string_view value) { | |||
| int operator()(std::string_view context, std::string_view key, std::string_view value) { | |||
| if (key != _key) { | |||
| return 0; | |||
| } | |||
| @@ -166,7 +168,7 @@ public: | |||
| : _key(key) | |||
| , _ref(ref) {} | |||
| int read_one(std::string_view context, std::string_view key, std::string_view value) { | |||
| int operator()(std::string_view context, std::string_view key, std::string_view value) { | |||
| if (key != _key) { | |||
| return 0; | |||
| } | |||
| @@ -177,8 +179,39 @@ public: | |||
| _ref = T(value); | |||
| return 1; | |||
| } | |||
| }; | |||
| template <typename T> | |||
| class read_bool { | |||
| std::string_view _key; | |||
| T& _ref; | |||
| bool _did_read = false; | |||
| void validate(std::string_view) {} | |||
| public: | |||
| read_bool(std::string_view key, T& ref) | |||
| : _key(key) | |||
| , _ref(ref) {} | |||
| bool operator()(std::string_view context, std::string_view key, std::string_view value) { | |||
| if (key != _key) { | |||
| return false; | |||
| } | |||
| if (_did_read) { | |||
| throw std::runtime_error(std::string(context) + ": Duplicate key '" + std::string(key) | |||
| + "' is not allowed."); | |||
| } | |||
| if (value == "true" || value == "True") { | |||
| _ref = true; | |||
| } else if (value == "false" || value == "False") { | |||
| _ref = false; | |||
| } else { | |||
| throw std::runtime_error(std::string(context) + ": Invalid value '" + std::string(value) | |||
| + "' for key '" + std::string(key) | |||
| + ".' Expected `true` or `false`."); | |||
| } | |||
| _did_read = true; | |||
| return true; | |||
| } | |||
| }; | |||
| class read_check_eq { | |||
| @@ -190,7 +223,7 @@ public: | |||
| : _key(key) | |||
| , _expect(value) {} | |||
| int read_one(std::string_view context, std::string_view key, std::string_view value) const { | |||
| int operator()(std::string_view context, std::string_view key, std::string_view value) const { | |||
| if (key != _key) { | |||
| return 0; | |||
| } | |||
| @@ -201,8 +234,6 @@ public: | |||
| } | |||
| return 1; | |||
| } | |||
| void validate(std::string_view) {} | |||
| }; | |||
| template <typename Container> | |||
| @@ -215,36 +246,40 @@ public: | |||
| : _key(key) | |||
| , _items(c) {} | |||
| int read_one(std::string_view, std::string_view key, std::string_view value) const { | |||
| int operator()(std::string_view, std::string_view key, std::string_view value) const { | |||
| if (key == _key) { | |||
| _items.emplace_back(value); | |||
| return 1; | |||
| } | |||
| return 0; | |||
| } | |||
| void validate(std::string_view) {} | |||
| }; | |||
| class reject_unknown { | |||
| public: | |||
| int read_one(std::string_view context, std::string_view key, std::string_view) const { | |||
| int operator()(std::string_view context, std::string_view key, std::string_view) const { | |||
| throw std::runtime_error(std::string(context) + ": Unknown key '" + std::string(key) + "'"); | |||
| } | |||
| void validate(std::string_view) {} | |||
| }; | |||
| template <typename T> | |||
| auto validate_reader(T&& t, std::string_view context, int) -> decltype(t.validate(context)) { | |||
| t.validate(context); | |||
| } | |||
| template <typename T> | |||
| void validate_reader(T&&, std::string_view, ...) {} | |||
| template <typename... Items> | |||
| auto read(std::string_view context [[maybe_unused]], const pair_list& pairs, Items... is) { | |||
| auto read(std::string_view context[[maybe_unused]], const pair_list& pairs, Items... is) { | |||
| std::vector<pair> bad_pairs; | |||
| for (auto [key, value] : pairs.items()) { | |||
| auto did_read = (is.read_one(context, key, value) || ...); | |||
| auto did_read = (is(context, key, value) || ...); | |||
| if (did_read) { | |||
| bad_pairs.emplace_back(key, value); | |||
| } | |||
| } | |||
| (is.validate(context), ...); | |||
| (validate_reader(is, context, 0), ...); | |||
| return bad_pairs; | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| #pragma once | |||
| namespace lm { | |||
| class pair; | |||
| class pair_iterator; | |||
| class pair_list; | |||
| } // namespace lm | |||