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 |