| auto end_time = std::chrono::steady_clock::now(); | auto end_time = std::chrono::steady_clock::now(); | ||||
| auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | 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()) { | if (!compile_res.okay()) { | ||||
| spdlog::error("Compilation failed: {}", _source.path.string()); | spdlog::error("Compilation failed: {}", _source.path.string()); |
| #include <dds/logging.hpp> | #include <dds/logging.hpp> | ||||
| #include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
| #include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
| #include <dds/toolchain/from_dds.hpp> | |||||
| #include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
| #include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
| #include <dds/util/signal.hpp> | #include <dds/util/signal.hpp> | ||||
| } | } | ||||
| return std::move(*tc); | return std::move(*tc); | ||||
| } else { | } else { | ||||
| return dds::toolchain::load_from_file(tc_path); | |||||
| return dds::parse_toolchain_dds(dds::slurp_file(tc_path)); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; |
| #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(); | |||||
| } |
| #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 |
| #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; |
| #include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
| #include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
| #include <libman/parse.hpp> | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <cassert> | |||||
| #include <optional> | #include <optional> | ||||
| #include <string> | #include <string> | ||||
| #include <vector> | #include <vector> | ||||
| using std::vector; | using std::vector; | ||||
| using opt_string = optional<string>; | 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 toolchain::realize(const toolchain_prep& prep) { | ||||
| toolchain ret; | toolchain ret; | ||||
| ret._c_compile = prep.c_compile; | ret._c_compile = prep.c_compile; | ||||
| return ret; | 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) { | vector<string> dds::split_shell_string(std::string_view shell) { | ||||
| char cur_quote = 0; | char cur_quote = 0; | ||||
| bool is_escaped = false; | bool is_escaped = false; |
| public: | public: | ||||
| toolchain() = default; | 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 realize(const toolchain_prep&); | ||||
| static toolchain load_from_file(fs::path); | |||||
| auto& archive_suffix() const noexcept { return _archive_suffix; } | auto& archive_suffix() const noexcept { return _archive_suffix; } | ||||
| auto& object_suffix() const noexcept { return _object_suffix; } | auto& object_suffix() const noexcept { return _object_suffix; } |
| #pragma once | #pragma once | ||||
| #include <libman/parse_fwd.hpp> | |||||
| #include <cassert> | #include <cassert> | ||||
| #include <filesystem> | #include <filesystem> | ||||
| #include <optional> | #include <optional> | ||||
| : _key(key) | : _key(key) | ||||
| , _ref(ref) {} | , _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) { | if (key != _key) { | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| : _key(key) | : _key(key) | ||||
| , _ref(ref) {} | , _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) { | if (key != _key) { | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| _ref = T(value); | _ref = T(value); | ||||
| return 1; | 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 { | class read_check_eq { | ||||
| : _key(key) | : _key(key) | ||||
| , _expect(value) {} | , _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) { | if (key != _key) { | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| return 1; | return 1; | ||||
| } | } | ||||
| void validate(std::string_view) {} | |||||
| }; | }; | ||||
| template <typename Container> | template <typename Container> | ||||
| : _key(key) | : _key(key) | ||||
| , _items(c) {} | , _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) { | if (key == _key) { | ||||
| _items.emplace_back(value); | _items.emplace_back(value); | ||||
| return 1; | return 1; | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| void validate(std::string_view) {} | |||||
| }; | }; | ||||
| class reject_unknown { | class reject_unknown { | ||||
| public: | 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) + "'"); | 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> | 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; | std::vector<pair> bad_pairs; | ||||
| for (auto [key, value] : pairs.items()) { | 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) { | if (did_read) { | ||||
| bad_pairs.emplace_back(key, value); | bad_pairs.emplace_back(key, value); | ||||
| } | } | ||||
| } | } | ||||
| (is.validate(context), ...); | |||||
| (validate_reader(is, context, 0), ...); | |||||
| return bad_pairs; | return bad_pairs; | ||||
| } | } | ||||
| #pragma once | |||||
| namespace lm { | |||||
| class pair; | |||||
| class pair_iterator; | |||||
| class pair_list; | |||||
| } // namespace lm |