@@ -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 |