From 09c9c289349c7fb17277d4904254c59783408fd8 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Sun, 19 Dec 2021 02:34:48 +0100 Subject: [PATCH 1/4] Initial implementation of TomlChecker This is to error when the user specifies an invalid key. Closes #31 --- src/project_parser.cpp | 251 ++++++++++++++++++++++++++--------------- 1 file changed, 159 insertions(+), 92 deletions(-) diff --git a/src/project_parser.cpp b/src/project_parser.cpp index c0f2ac9..cb24558 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -5,6 +5,7 @@ #include #include #include +#include template <> const char *enumStrings::data[] = {"executable", "library", "shared", "static", "interface", "custom"}; @@ -61,12 +62,55 @@ static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destin } } +class TomlChecker { + const TomlBasicValue &m_v; + tsl::ordered_set m_visited; + bool m_checked = false; + + public: + TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {} + TomlChecker(const TomlBasicValue &v) : m_v(v) {} + TomlChecker(const TomlChecker &) = delete; + + ~TomlChecker() noexcept(false) { + if (!m_checked) { + throw std::runtime_error("TomlChecker::check() not called"); + } + } + + template + void optional(const toml::key &ky, T &destination) { + get_optional(m_v, ky, destination); + visit(ky); + } + + template + void required(const toml::key &ky, T &destination) { + destination = toml::find(m_v, ky); + visit(ky); + } + + void visit(const toml::key &ky) { m_visited.insert(ky); } + + void check() { + m_checked = true; + for (const auto &itr : m_v.as_table()) { + const auto &ky = itr.first; + if (m_visited.count(ky) == 0) { + // TODO: nice error messages + throw std::runtime_error("Unknown key '" + ky + "'"); + } + } + } +}; + Project::Project(const Project *parent, const std::string &path, bool build) { const auto toml_path = fs::path(path) / "cmake.toml"; if (!fs::exists(toml_path)) { throw std::runtime_error("No cmake.toml was found!"); } const auto toml = toml::parse(toml_path.string()); + // TODO: TomlChecker for the "cmake" section if (build) { if (toml.contains("cmake")) { const auto &cmake = toml::find(toml, "cmake"); @@ -100,28 +144,34 @@ Project::Project(const Project *parent, const std::string &path, bool build) { } if (toml.contains("project")) { - const auto &project = toml::find(toml, "project"); - project_name = toml::find(project, "name").as_string(); - get_optional(project, "version", project_version); - get_optional(project, "description", project_description); - get_optional(project, "languages", project_languages); - get_optional(project, "cmake-before", cmake_before); - get_optional(project, "cmake-after", cmake_after); - get_optional(project, "include-before", include_before); - get_optional(project, "include-after", include_after); - get_optional(project, "subdirs", project_subdirs); + TomlChecker project(toml, "project"); + project.required("name", project_name); + project.optional("version", project_version); + project.optional("description", project_description); + project.optional("languages", project_languages); + project.optional("cmake-before", cmake_before); + project.optional("cmake-after", cmake_after); + project.optional("include-before", include_before); + project.optional("include-after", include_after); + project.optional("subdirs", project_subdirs); + project.check(); } if (toml.contains("subdir")) { const auto &subs = toml::find(toml, "subdir").as_table(); - for (const auto &sub : subs) { + for (const auto &itr : subs) { Subdir subdir; - subdir.name = sub.first; - get_optional(sub.second, "condition", subdir.condition); - get_optional(sub.second, "cmake-before", subdir.cmake_before); - get_optional(sub.second, "cmake-after", subdir.cmake_after); - get_optional(sub.second, "include-before", subdir.include_before); - get_optional(sub.second, "include-after", subdir.include_after); + subdir.name = itr.first; + + TomlChecker sub(itr.second); + sub.optional("condition", subdir.condition); + sub.optional("condition", subdir.condition); + sub.optional("cmake-before", subdir.cmake_before); + sub.optional("cmake-after", subdir.cmake_after); + sub.optional("include-before", subdir.include_before); + sub.optional("include-after", subdir.include_after); + sub.check(); + subdirs.push_back(subdir); } } @@ -129,25 +179,29 @@ Project::Project(const Project *parent, const std::string &path, bool build) { if (toml.contains("settings")) { using set_map = std::map; const auto &sets = toml::find(toml, "settings"); - for (const auto &set : sets) { + for (const auto &itr : sets) { Setting s; - s.name = set.first; - if (set.second.is_boolean()) { - s.val = set.second.as_boolean(); - } else if (set.second.is_string()) { - s.val = set.second.as_string(); + s.name = itr.first; + const auto& value = itr.second; + if (value.is_boolean()) { + s.val = value.as_boolean(); + } else if (value.is_string()) { + s.val = value.as_string(); } else { - get_optional(set.second, "comment", s.comment); - if (set.second.contains("value")) { - auto v = toml::find(set.second, "value"); + TomlChecker setting(value); + setting.optional("comment", s.comment); + setting.visit("value"); + if (value.contains("value")) { + auto v = toml::find(value, "value"); if (v.is_boolean()) { s.val = v.as_boolean(); } else { s.val = v.as_string(); } } - get_optional(set.second, "cache", s.cache); - get_optional(set.second, "force", s.force); + setting.optional("cache", s.cache); + setting.optional("force", s.force); + setting.check(); } settings.push_back(s); } @@ -156,14 +210,17 @@ Project::Project(const Project *parent, const std::string &path, bool build) { if (toml.contains("options")) { using opts_map = tsl::ordered_map; const auto &opts = toml::find(toml, "options"); - for (const auto &opt : opts) { + for (const auto &itr : opts) { Option o; - o.name = opt.first; - if (opt.second.is_boolean()) { - o.val = opt.second.as_boolean(); + o.name = itr.first; + const auto& value = itr.second; + if (value.is_boolean()) { + o.val = value.as_boolean(); } else { - get_optional(opt.second, "comment", o.comment); - get_optional(opt.second, "value", o.val); + TomlChecker option(value); + option.optional("comment", o.comment); + option.optional("value", o.val); + option.check(); } options.push_back(o); } @@ -172,16 +229,19 @@ Project::Project(const Project *parent, const std::string &path, bool build) { if (toml.contains("find-package")) { using pkg_map = tsl::ordered_map; const auto &pkgs = toml::find(toml, "find-package"); - for (const auto &pkg : pkgs) { + for (const auto &itr : pkgs) { Package p; - p.name = pkg.first; - if (pkg.second.is_string()) { - p.version = pkg.second.as_string(); + p.name = itr.first; + const auto& value = itr.second; + if (itr.second.is_string()) { + p.version = itr.second.as_string(); } else { - get_optional(pkg.second, "version", p.version); - get_optional(pkg.second, "required", p.required); - get_optional(pkg.second, "config", p.config); - get_optional(pkg.second, "components", p.components); + TomlChecker pkg(value); + pkg.optional("version", p.version); + pkg.optional("required", p.required); + pkg.optional("config", p.config); + pkg.optional("components", p.components); + pkg.check(); } packages.push_back(p); } @@ -223,37 +283,42 @@ Project::Project(const Project *parent, const std::string &path, bool build) { const auto &ts = toml::find(toml, "target").as_table(); for (const auto &itr : ts) { - const auto &t = itr.second; + const auto& value = itr.second; + Target target; target.name = itr.first; - target.type = to_enum(toml::find(t, "type").as_string(), "target type"); - get_optional(t, "headers", target.headers); - get_optional(t, "sources", target.sources); + TomlChecker t(value); + std::string type; + t.required("type", type); + target.type = to_enum(type, "target type"); + + t.optional("headers", target.headers); + t.optional("sources", target.sources); - get_optional(t, "compile-definitions", target.compile_definitions); - get_optional(t, "private-compile-definitions", target.private_compile_definitions); + t.optional("compile-definitions", target.compile_definitions); + t.optional("private-compile-definitions", target.private_compile_definitions); - get_optional(t, "compile-features", target.compile_features); - get_optional(t, "private-compile-features", target.private_compile_features); + t.optional("compile-features", target.compile_features); + t.optional("private-compile-features", target.private_compile_features); - get_optional(t, "compile-options", target.compile_options); - get_optional(t, "private-compile-options", target.private_compile_options); + t.optional("compile-options", target.compile_options); + t.optional("private-compile-options", target.private_compile_options); - get_optional(t, "include-directories", target.include_directories); - get_optional(t, "private-include-directories", target.private_include_directories); + t.optional("include-directories", target.include_directories); + t.optional("private-include-directories", target.private_include_directories); - get_optional(t, "link-directories", target.link_directories); - get_optional(t, "private-link-directories", target.private_link_directories); + t.optional("link-directories", target.link_directories); + t.optional("private-link-directories", target.private_link_directories); - get_optional(t, "link-libraries", target.link_libraries); - get_optional(t, "private-link-libraries", target.private_link_libraries); + t.optional("link-libraries", target.link_libraries); + t.optional("private-link-libraries", target.private_link_libraries); - get_optional(t, "link-options", target.link_options); - get_optional(t, "private-link-options", target.private_link_options); + t.optional("link-options", target.link_options); + t.optional("private-link-options", target.private_link_options); - get_optional(t, "precompile-headers", target.precompile_headers); - get_optional(t, "private-precompile-headers", target.private_precompile_headers); + t.optional("precompile-headers", target.precompile_headers); + t.optional("private-precompile-headers", target.private_precompile_headers); if (!target.headers.empty()) { auto &sources = target.sources.nth(0).value(); @@ -261,15 +326,11 @@ Project::Project(const Project *parent, const std::string &path, bool build) { sources.insert(sources.end(), headers.begin(), headers.end()); } - if (t.contains("condition")) { - target.condition = toml::find(t, "condition").as_string(); - } - - if (t.contains("alias")) { - target.alias = toml::find(t, "alias").as_string(); - } + t.optional("condition", target.condition); + t.optional("alias", target.alias); - if (t.contains("properties")) { + t.visit("properties"); + if (value.contains("properties")) { auto store_property = [&target](const toml::key &k, const TomlBasicValue &v, const std::string &condition) { if (v.is_array()) { std::string property_list; @@ -287,7 +348,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) { } }; - const auto &props = toml::find(t, "properties").as_table(); + const auto &props = toml::find(value, "properties").as_table(); for (const auto &propKv : props) { const auto &k = propKv.first; const auto &v = propKv.second; @@ -301,10 +362,11 @@ Project::Project(const Project *parent, const std::string &path, bool build) { } } - get_optional(t, "cmake-before", target.cmake_before); - get_optional(t, "cmake-after", target.cmake_after); - get_optional(t, "include-before", target.include_before); - get_optional(t, "include-after", target.include_after); + t.optional("cmake-before", target.cmake_before); + t.optional("cmake-after", target.cmake_after); + t.optional("include-before", target.include_before); + t.optional("include-after", target.include_after); + t.check(); targets.push_back(target); } @@ -312,35 +374,40 @@ Project::Project(const Project *parent, const std::string &path, bool build) { if (toml.contains("test")) { const auto &ts = toml::find(toml, "test").as_array(); - for (const auto &t : ts) { + for (const auto &value : ts) { + TomlChecker t(value); Test test; - test.name = toml::find(t, "name").as_string(); - get_optional(t, "configurations", test.configurations); - get_optional(t, "working-directory", test.working_directory); - test.command = toml::find(t, "command").as_string(); - get_optional(t, "arguments", test.arguments); + t.required("name", test.name); + t.optional("configurations", test.configurations); + t.optional("working-directory", test.working_directory); + t.required("command", test.command); + t.optional("arguments", test.arguments); + t.check(); tests.push_back(test); } } if (toml.contains("install")) { - const auto &ts = toml::find(toml, "install").as_array(); - for (const auto &t : ts) { + const auto &is = toml::find(toml, "install").as_array(); + for (const auto &value : is) { + TomlChecker i(value); Install inst; - get_optional(t, "targets", inst.targets); - get_optional(t, "files", inst.files); - get_optional(t, "dirs", inst.dirs); - get_optional(t, "configs", inst.configs); - inst.destination = toml::find(t, "destination").as_string(); + i.optional("targets", inst.targets); + i.optional("files", inst.files); + i.optional("dirs", inst.dirs); + i.optional("configs", inst.configs); + i.required("destination", inst.destination); + i.check(); installs.push_back(inst); } } if (toml.contains("vcpkg")) { - const auto &v = toml::find(toml, "vcpkg"); - get_optional(v, "url", vcpkg.url); - get_optional(v, "version", vcpkg.version); - vcpkg.packages = toml::find(v, "packages"); + TomlChecker v(toml, "vcpkg"); + v.optional("url", vcpkg.url); + v.optional("version", vcpkg.version); + v.required("packages", vcpkg.packages); + v.check(); } // Reasonable default conditions (you can override these if you desire) From 77f4bf7da0ddda3daacb54ab3424dfbde75c9738 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 22 Dec 2021 13:30:59 +0100 Subject: [PATCH 2/4] Some minor fixes related to newlines and formatting --- cmake.toml | 2 +- src/cmake_generator.cpp | 10 +++++----- src/project_parser.cpp | 8 ++++---- tests/vcpkg/vcpkg.json | 22 +++++++++++----------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmake.toml b/cmake.toml index be3e694..f7520fa 100644 --- a/cmake.toml +++ b/cmake.toml @@ -35,7 +35,7 @@ link-libraries = [ "ghc_filesystem", "mpark_variant", "ordered_map", - "nlohmann_json" + "nlohmann_json", ] cmake-after = """ generate_resources(${CMKR_TARGET}) diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp index c6975ee..d9c9cc2 100644 --- a/src/cmake_generator.cpp +++ b/src/cmake_generator.cpp @@ -119,7 +119,7 @@ int generate_project(const char *str) { const auto tomlbuf = format(cmake_toml, dir_name.c_str(), dir_name.c_str(), str, installed.c_str(), target.c_str(), dest.c_str()); if (strcmp(str, "interface")) { - std::ofstream ofs("src/main.cpp"); + std::ofstream ofs("src/main.cpp", std::ios::binary); if (ofs.is_open()) { ofs << mainbuf; } @@ -127,7 +127,7 @@ int generate_project(const char *str) { ofs.close(); } - std::ofstream ofs2("cmake.toml"); + std::ofstream ofs2("cmake.toml", std::ios::binary); if (ofs2.is_open()) { ofs2 << tomlbuf; } @@ -607,7 +607,7 @@ int generate_cmake(const char *path, const parser::Project *parent_project) { j["description"] = project.project_description; } - std::ofstream ofs("vcpkg.json"); + std::ofstream ofs("vcpkg.json", std::ios::binary); if (!ofs) { throw std::runtime_error("Failed to create a vcpkg.json manifest file!"); } @@ -900,7 +900,7 @@ int generate_cmake(const char *path, const parser::Project *parent_project) { if (!fs::exists(list_path)) return true; - std::ifstream ifs(list_path, std::ios_base::binary); + std::ifstream ifs(list_path, std::ios::binary); if (!ifs.is_open()) { throw std::runtime_error("Failed to read " + list_path.string()); } @@ -910,7 +910,7 @@ int generate_cmake(const char *path, const parser::Project *parent_project) { }(); if (should_regenerate) { - std::ofstream ofs(list_path, std::ios_base::binary); + std::ofstream ofs(list_path, std::ios::binary); if (ofs.is_open()) { ofs << ss.str(); } else { diff --git a/src/project_parser.cpp b/src/project_parser.cpp index cb24558..2941010 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -182,7 +182,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) { for (const auto &itr : sets) { Setting s; s.name = itr.first; - const auto& value = itr.second; + const auto &value = itr.second; if (value.is_boolean()) { s.val = value.as_boolean(); } else if (value.is_string()) { @@ -213,7 +213,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) { for (const auto &itr : opts) { Option o; o.name = itr.first; - const auto& value = itr.second; + const auto &value = itr.second; if (value.is_boolean()) { o.val = value.as_boolean(); } else { @@ -232,7 +232,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) { for (const auto &itr : pkgs) { Package p; p.name = itr.first; - const auto& value = itr.second; + const auto &value = itr.second; if (itr.second.is_string()) { p.version = itr.second.as_string(); } else { @@ -283,7 +283,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) { const auto &ts = toml::find(toml, "target").as_table(); for (const auto &itr : ts) { - const auto& value = itr.second; + const auto &value = itr.second; Target target; target.name = itr.first; diff --git a/tests/vcpkg/vcpkg.json b/tests/vcpkg/vcpkg.json index ec00969..8c0008a 100644 --- a/tests/vcpkg/vcpkg.json +++ b/tests/vcpkg/vcpkg.json @@ -1,11 +1,11 @@ -{ - "$cmkr": "This file is automatically generated from cmake.toml - DO NOT EDIT", - "$cmkr-url": "https://github.com/build-cpp/cmkr", - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", - "dependencies": [ - "fmt" - ], - "description": "Dependencies from vcpkg", - "name": "vcpkg", - "version-string": "" -} +{ + "$cmkr": "This file is automatically generated from cmake.toml - DO NOT EDIT", + "$cmkr-url": "https://github.com/build-cpp/cmkr", + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "dependencies": [ + "fmt" + ], + "description": "Dependencies from vcpkg", + "name": "vcpkg", + "version-string": "" +} From 9f9934e9a526b71e267f6fa433735e0abe457851 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 22 Dec 2021 13:31:52 +0100 Subject: [PATCH 3/4] Implemented checking of keys in conditions --- src/project_parser.cpp | 111 +++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 31 deletions(-) diff --git a/src/project_parser.cpp b/src/project_parser.cpp index 2941010..828dade 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -62,25 +62,37 @@ static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destin } } +// TODO: construct this from a helper class with state so all the checking can be done implicitly class TomlChecker { const TomlBasicValue &m_v; tsl::ordered_set m_visited; - bool m_checked = false; + tsl::ordered_set m_conditionVisited; public: TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {} TomlChecker(const TomlBasicValue &v) : m_v(v) {} TomlChecker(const TomlChecker &) = delete; - ~TomlChecker() noexcept(false) { - if (!m_checked) { - throw std::runtime_error("TomlChecker::check() not called"); + ~TomlChecker() noexcept(false) {} + + // TOOD: check if the condition is valid during the parsing stage to print better errors! + template + void optional(const toml::key &ky, Condition &destination) { + get_optional(m_v, ky, destination); + for (const auto &itr : destination) { + if (!itr.first.empty()) { + m_conditionVisited.emplace(itr.first); + } } + visit(ky); } template void optional(const toml::key &ky, T &destination) { - get_optional(m_v, ky, destination); + // TODO: this currently doesn't allow you to get an optional map + if (m_v.contains(ky)) { + destination = toml::find(m_v, ky); + } visit(ky); } @@ -92,13 +104,48 @@ class TomlChecker { void visit(const toml::key &ky) { m_visited.insert(ky); } + std::string format_unknown_key(const toml::key &ky, const TomlBasicValue &value) { + auto loc = value.location(); + auto line_number_str = std::to_string(loc.line()); + auto line_width = line_number_str.length(); + auto line_str = loc.line_str(); + + std::ostringstream oss; + oss << "[error] Unknown key: " << ky << '\n'; + oss << " --> " << loc.file_name() << '\n'; + + oss << std::string(line_width + 2, ' ') << "|\n"; + oss << ' ' << line_number_str << " | " << line_str << '\n'; + + oss << std::string(line_width + 2, ' ') << '|'; + auto key_start = line_str.find_last_of(ky, loc.column()); + if (key_start != std::string::npos) { + oss << std::string(key_start - ky.length() + 2, ' ') << std::string(ky.length(), '~'); + } + oss << '\n'; + + return oss.str(); + } + void check() { - m_checked = true; for (const auto &itr : m_v.as_table()) { const auto &ky = itr.first; - if (m_visited.count(ky) == 0) { - // TODO: nice error messages - throw std::runtime_error("Unknown key '" + ky + "'"); + if (m_conditionVisited.count(ky)) { + // TODO: check if condition (ky) exists + for (const auto &jtr : itr.second.as_table()) { + if (m_visited.count(jtr.first) == 0) { + throw std::runtime_error(format_unknown_key(jtr.first, jtr.second)); + } + } + } else if (m_visited.count(ky) == 0) { + if (itr.second.is_table()) { + for (const auto &jtr : itr.second.as_table()) { + if (m_visited.count(jtr.first) == 0) { + throw std::runtime_error(format_unknown_key(jtr.first, jtr.second)); + } + } + } + throw std::runtime_error(format_unknown_key(ky, itr.second)); } } } @@ -126,6 +173,30 @@ Project::Project(const Project *parent, const std::string &path, bool build) { get_optional(cmake, "allow-in-tree", allow_in_tree); } } else { + // Reasonable default conditions (you can override these if you desire) + if (parent == nullptr) { + conditions["windows"] = R"cmake(WIN32)cmake"; + conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake"; + conditions["unix"] = R"cmake(UNIX)cmake"; + conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake"; + conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake"; + conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake"; + conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake"; + conditions["msvc"] = R"cmake(MSVC)cmake"; + } else { + conditions = parent->conditions; + } + + if (toml.contains("conditions")) { + auto conds = toml::find(toml, "conditions"); + for (const auto &cond : conds) { + conditions[cond.first] = cond.second; + } + } + + // TODO: make TomlCheckerFactory + // .check() only once (at the end) + if (toml.contains("cmake")) { const auto &cmake = toml::find(toml, "cmake"); cmake_version = toml::find(cmake, "version").as_string(); @@ -165,7 +236,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) { TomlChecker sub(itr.second); sub.optional("condition", subdir.condition); - sub.optional("condition", subdir.condition); sub.optional("cmake-before", subdir.cmake_before); sub.optional("cmake-after", subdir.cmake_after); sub.optional("include-before", subdir.include_before); @@ -409,27 +479,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) { v.required("packages", vcpkg.packages); v.check(); } - - // Reasonable default conditions (you can override these if you desire) - if (parent == nullptr) { - conditions["windows"] = R"cmake(WIN32)cmake"; - conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake"; - conditions["unix"] = R"cmake(UNIX)cmake"; - conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake"; - conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake"; - conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake"; - conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake"; - conditions["msvc"] = R"cmake(MSVC)cmake"; - } else { - conditions = parent->conditions; - } - - if (toml.contains("conditions")) { - auto conds = toml::find(toml, "conditions"); - for (const auto &cond : conds) { - conditions[cond.first] = cond.second; - } - } } } From 6809e8da41cef7a5e1e1a42da4fb5725cbbb0d9f Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 22 Dec 2021 16:51:55 +0100 Subject: [PATCH 4/4] Refactor and add checking for conditions --- src/build.cpp | 4 +- src/project_parser.cpp | 672 +++++++++++++++++++++-------------------- 2 files changed, 348 insertions(+), 328 deletions(-) diff --git a/src/build.cpp b/src/build.cpp index 5f3d88f..a570721 100644 --- a/src/build.cpp +++ b/src/build.cpp @@ -4,10 +4,10 @@ #include "project_parser.hpp" #include "fs.hpp" -#include #include -#include #include +#include +#include #include namespace cmkr { diff --git a/src/project_parser.cpp b/src/project_parser.cpp index 828dade..b18d827 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -2,6 +2,7 @@ #include "enum_helper.hpp" #include "fs.hpp" +#include #include #include #include @@ -34,35 +35,32 @@ static EnumType to_enum(const std::string &str, const std::string &help_name) { return value; } -template -static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination); - -template -static void get_optional(const TomlBasicValue &v, const toml::key &ky, Condition &destination) { - // TODO: this algorithm in O(n) over the amount of keys, kinda bad - const auto &table = v.as_table(); - for (const auto &itr : table) { - const auto &key = itr.first; - const auto &value = itr.second; - if (value.is_table()) { - if (value.contains(ky)) { - destination[key] = toml::find(value, ky); - } - } else if (key == ky) { - destination[""] = toml::find(v, ky); - } - } -} +static std::string format_key_error(const std::string &error, const toml::key &ky, const TomlBasicValue &value) { + auto loc = value.location(); + auto line_number_str = std::to_string(loc.line()); + auto line_width = line_number_str.length(); + auto line_str = loc.line_str(); + + std::ostringstream oss; + oss << "[error] " << error << '\n'; + oss << " --> " << loc.file_name() << '\n'; + + oss << std::string(line_width + 2, ' ') << "|\n"; + oss << ' ' << line_number_str << " | " << line_str << '\n'; -template -static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination) { - // TODO: this currently doesn't allow you to get an optional map - if (v.contains(ky)) { - destination = toml::find(v, ky); + oss << std::string(line_width + 2, ' ') << '|'; + auto key_start = line_str.substr(0, loc.column() - 1).rfind(ky); + if (key_start == std::string::npos) { + key_start = line_str.find(ky); } + if (key_start != std::string::npos) { + oss << std::string(key_start + 1, ' ') << std::string(ky.length(), '~'); + } + oss << '\n'; + + return oss.str(); } -// TODO: construct this from a helper class with state so all the checking can be done implicitly class TomlChecker { const TomlBasicValue &m_v; tsl::ordered_set m_visited; @@ -72,13 +70,25 @@ class TomlChecker { TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {} TomlChecker(const TomlBasicValue &v) : m_v(v) {} TomlChecker(const TomlChecker &) = delete; + TomlChecker(TomlChecker &&) = delete; - ~TomlChecker() noexcept(false) {} - - // TOOD: check if the condition is valid during the parsing stage to print better errors! template void optional(const toml::key &ky, Condition &destination) { - get_optional(m_v, ky, destination); + // TODO: this algorithm in O(n) over the amount of keys, kinda bad + const auto &table = m_v.as_table(); + for (const auto &itr : table) { + const auto &key = itr.first; + const auto &value = itr.second; + if (value.is_table()) { + if (value.contains(ky)) { + destination[key] = toml::find(value, ky); + } + } else if (key == ky) { + destination[""] = toml::find(m_v, ky); + } + } + + // Handle visiting logic for (const auto &itr : destination) { if (!itr.first.empty()) { m_conditionVisited.emplace(itr.first); @@ -102,384 +112,394 @@ class TomlChecker { visit(ky); } - void visit(const toml::key &ky) { m_visited.insert(ky); } - - std::string format_unknown_key(const toml::key &ky, const TomlBasicValue &value) { - auto loc = value.location(); - auto line_number_str = std::to_string(loc.line()); - auto line_width = line_number_str.length(); - auto line_str = loc.line_str(); - - std::ostringstream oss; - oss << "[error] Unknown key: " << ky << '\n'; - oss << " --> " << loc.file_name() << '\n'; - - oss << std::string(line_width + 2, ' ') << "|\n"; - oss << ' ' << line_number_str << " | " << line_str << '\n'; - - oss << std::string(line_width + 2, ' ') << '|'; - auto key_start = line_str.find_last_of(ky, loc.column()); - if (key_start != std::string::npos) { - oss << std::string(key_start - ky.length() + 2, ' ') << std::string(ky.length(), '~'); - } - oss << '\n'; + bool contains(const toml::key &ky) { + visit(ky); + return m_v.contains(ky); + } - return oss.str(); + const TomlBasicValue &find(const toml::key &ky) { + visit(ky); + return toml::find(m_v, ky); } - void check() { + void visit(const toml::key &ky) { m_visited.insert(ky); } + + void check(const tsl::ordered_map &conditions) const { for (const auto &itr : m_v.as_table()) { const auto &ky = itr.first; - if (m_conditionVisited.count(ky)) { - // TODO: check if condition (ky) exists + if (m_conditionVisited.contains(ky)) { + if (!conditions.contains(ky)) { + throw std::runtime_error(format_key_error("Unknown condition '" + ky + "'", ky, itr.second)); + } + for (const auto &jtr : itr.second.as_table()) { - if (m_visited.count(jtr.first) == 0) { - throw std::runtime_error(format_unknown_key(jtr.first, jtr.second)); + if (!m_visited.contains(jtr.first)) { + throw std::runtime_error(format_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second)); } } - } else if (m_visited.count(ky) == 0) { + } else if (!m_visited.contains(ky)) { if (itr.second.is_table()) { for (const auto &jtr : itr.second.as_table()) { - if (m_visited.count(jtr.first) == 0) { - throw std::runtime_error(format_unknown_key(jtr.first, jtr.second)); + if (!m_visited.contains(jtr.first)) { + throw std::runtime_error(format_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second)); } } } - throw std::runtime_error(format_unknown_key(ky, itr.second)); + throw std::runtime_error(format_key_error("Unknown key '" + ky + "'", ky, itr.second)); } } } }; +class TomlCheckerRoot { + std::deque m_checkers; + bool m_checked = false; + + public: + TomlCheckerRoot() = default; + TomlCheckerRoot(const TomlCheckerRoot &) = delete; + TomlCheckerRoot(TomlCheckerRoot &&) = delete; + + TomlChecker &create(const TomlBasicValue &v) { + m_checkers.emplace_back(v); + return m_checkers.back(); + } + + TomlChecker &create(const TomlBasicValue &v, const toml::key &ky) { + m_checkers.emplace_back(v, ky); + return m_checkers.back(); + } + + void check(const tsl::ordered_map &conditions) { + for (const auto &checker : m_checkers) { + checker.check(conditions); + } + } +}; + Project::Project(const Project *parent, const std::string &path, bool build) { const auto toml_path = fs::path(path) / "cmake.toml"; if (!fs::exists(toml_path)) { throw std::runtime_error("No cmake.toml was found!"); } const auto toml = toml::parse(toml_path.string()); - // TODO: TomlChecker for the "cmake" section - if (build) { - if (toml.contains("cmake")) { - const auto &cmake = toml::find(toml, "cmake"); - if (cmake.contains("bin-dir")) { - throw std::runtime_error("bin-dir has been renamed to build-dir"); - } + TomlCheckerRoot checker; - get_optional(cmake, "build-dir", build_dir); - get_optional(cmake, "generator", generator); - get_optional(cmake, "config", config); - get_optional(cmake, "arguments", gen_args); - get_optional(cmake, "allow-in-tree", allow_in_tree); - } - } else { - // Reasonable default conditions (you can override these if you desire) - if (parent == nullptr) { - conditions["windows"] = R"cmake(WIN32)cmake"; - conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake"; - conditions["unix"] = R"cmake(UNIX)cmake"; - conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake"; - conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake"; - conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake"; - conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake"; - conditions["msvc"] = R"cmake(MSVC)cmake"; - } else { - conditions = parent->conditions; + if (toml.contains("cmake")) { + auto &cmake = checker.create(toml, "cmake"); + + cmake.required("version", cmake_version); + + if (cmake.contains("bin-dir")) { + throw std::runtime_error("bin-dir has been renamed to build-dir"); } - if (toml.contains("conditions")) { - auto conds = toml::find(toml, "conditions"); - for (const auto &cond : conds) { - conditions[cond.first] = cond.second; + cmake.optional("build-dir", build_dir); + cmake.optional("generator", generator); + cmake.optional("config", config); + cmake.optional("arguments", gen_args); + cmake.optional("allow-in-tree", allow_in_tree); + + if (cmake.contains("cmkr-include")) { + const auto &cmkr_include_kv = cmake.find("cmkr-include"); + if (cmkr_include_kv.is_string()) { + cmkr_include = cmkr_include_kv.as_string(); + } else { + // Allow disabling this feature with cmkr-include = false + cmkr_include = ""; } } - // TODO: make TomlCheckerFactory - // .check() only once (at the end) + cmake.optional("cpp-flags", cppflags); + cmake.optional("c-flags", cflags); + cmake.optional("link-flags", linkflags); + } - if (toml.contains("cmake")) { - const auto &cmake = toml::find(toml, "cmake"); - cmake_version = toml::find(cmake, "version").as_string(); - if (cmake.contains("cmkr-include")) { - const auto &cmkr_include_kv = toml::find(cmake, "cmkr-include"); - if (cmkr_include_kv.is_string()) { - cmkr_include = cmkr_include_kv.as_string(); - } else { - // Allow disabling this feature with cmkr-include = false - cmkr_include = ""; - } - } - get_optional(cmake, "cpp-flags", cppflags); - get_optional(cmake, "c-flags", cflags); - get_optional(cmake, "link-flags", linkflags); - } + // Skip the rest of the parsing when building + if (build) { + checker.check(conditions); + return; + } + + // Reasonable default conditions (you can override these if you desire) + if (parent == nullptr) { + conditions["windows"] = R"cmake(WIN32)cmake"; + conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake"; + conditions["unix"] = R"cmake(UNIX)cmake"; + conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake"; + conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake"; + conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake"; + conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake"; + conditions["msvc"] = R"cmake(MSVC)cmake"; + } else { + conditions = parent->conditions; + } - if (toml.contains("project")) { - TomlChecker project(toml, "project"); - project.required("name", project_name); - project.optional("version", project_version); - project.optional("description", project_description); - project.optional("languages", project_languages); - project.optional("cmake-before", cmake_before); - project.optional("cmake-after", cmake_after); - project.optional("include-before", include_before); - project.optional("include-after", include_after); - project.optional("subdirs", project_subdirs); - project.check(); + if (toml.contains("conditions")) { + auto conds = toml::find(toml, "conditions"); + for (const auto &cond : conds) { + conditions[cond.first] = cond.second; } + } - if (toml.contains("subdir")) { - const auto &subs = toml::find(toml, "subdir").as_table(); - for (const auto &itr : subs) { - Subdir subdir; - subdir.name = itr.first; - - TomlChecker sub(itr.second); - sub.optional("condition", subdir.condition); - sub.optional("cmake-before", subdir.cmake_before); - sub.optional("cmake-after", subdir.cmake_after); - sub.optional("include-before", subdir.include_before); - sub.optional("include-after", subdir.include_after); - sub.check(); - - subdirs.push_back(subdir); - } + if (toml.contains("project")) { + auto &project = checker.create(toml, "project"); + project.required("name", project_name); + project.optional("version", project_version); + project.optional("description", project_description); + project.optional("languages", project_languages); + project.optional("cmake-before", cmake_before); + project.optional("cmake-after", cmake_after); + project.optional("include-before", include_before); + project.optional("include-after", include_after); + project.optional("subdirs", project_subdirs); + } + + if (toml.contains("subdir")) { + const auto &subs = toml::find(toml, "subdir").as_table(); + for (const auto &itr : subs) { + Subdir subdir; + subdir.name = itr.first; + + auto &sub = checker.create(itr.second); + sub.optional("condition", subdir.condition); + sub.optional("cmake-before", subdir.cmake_before); + sub.optional("cmake-after", subdir.cmake_after); + sub.optional("include-before", subdir.include_before); + sub.optional("include-after", subdir.include_after); + + subdirs.push_back(subdir); } + } - if (toml.contains("settings")) { - using set_map = std::map; - const auto &sets = toml::find(toml, "settings"); - for (const auto &itr : sets) { - Setting s; - s.name = itr.first; - const auto &value = itr.second; - if (value.is_boolean()) { - s.val = value.as_boolean(); - } else if (value.is_string()) { - s.val = value.as_string(); - } else { - TomlChecker setting(value); - setting.optional("comment", s.comment); - setting.visit("value"); - if (value.contains("value")) { - auto v = toml::find(value, "value"); - if (v.is_boolean()) { - s.val = v.as_boolean(); - } else { - s.val = v.as_string(); - } + if (toml.contains("settings")) { + using set_map = std::map; + const auto &sets = toml::find(toml, "settings"); + for (const auto &itr : sets) { + Setting s; + s.name = itr.first; + const auto &value = itr.second; + if (value.is_boolean()) { + s.val = value.as_boolean(); + } else if (value.is_string()) { + s.val = value.as_string(); + } else { + auto &setting = checker.create(value); + setting.optional("comment", s.comment); + if (setting.contains("value")) { + const auto &v = setting.find("value"); + if (v.is_boolean()) { + s.val = v.as_boolean(); + } else { + s.val = v.as_string(); } - setting.optional("cache", s.cache); - setting.optional("force", s.force); - setting.check(); } - settings.push_back(s); + setting.optional("cache", s.cache); + setting.optional("force", s.force); } + settings.push_back(s); } + } - if (toml.contains("options")) { - using opts_map = tsl::ordered_map; - const auto &opts = toml::find(toml, "options"); - for (const auto &itr : opts) { - Option o; - o.name = itr.first; - const auto &value = itr.second; - if (value.is_boolean()) { - o.val = value.as_boolean(); - } else { - TomlChecker option(value); - option.optional("comment", o.comment); - option.optional("value", o.val); - option.check(); - } - options.push_back(o); + if (toml.contains("options")) { + using opts_map = tsl::ordered_map; + const auto &opts = toml::find(toml, "options"); + for (const auto &itr : opts) { + Option o; + o.name = itr.first; + const auto &value = itr.second; + if (value.is_boolean()) { + o.val = value.as_boolean(); + } else { + auto &option = checker.create(value); + option.optional("comment", o.comment); + option.optional("value", o.val); } + options.push_back(o); } + } - if (toml.contains("find-package")) { - using pkg_map = tsl::ordered_map; - const auto &pkgs = toml::find(toml, "find-package"); - for (const auto &itr : pkgs) { - Package p; - p.name = itr.first; - const auto &value = itr.second; - if (itr.second.is_string()) { - p.version = itr.second.as_string(); - } else { - TomlChecker pkg(value); - pkg.optional("version", p.version); - pkg.optional("required", p.required); - pkg.optional("config", p.config); - pkg.optional("components", p.components); - pkg.check(); - } - packages.push_back(p); + if (toml.contains("find-package")) { + using pkg_map = tsl::ordered_map; + const auto &pkgs = toml::find(toml, "find-package"); + for (const auto &itr : pkgs) { + Package p; + p.name = itr.first; + const auto &value = itr.second; + if (itr.second.is_string()) { + p.version = itr.second.as_string(); + } else { + auto &pkg = checker.create(value); + pkg.optional("version", p.version); + pkg.optional("required", p.required); + pkg.optional("config", p.config); + pkg.optional("components", p.components); } + packages.push_back(p); } + } - if (toml.contains("fetch-content")) { - const auto &fc = toml::find(toml, "fetch-content").as_table(); - for (const auto &itr : fc) { - Content content; - content.name = itr.first; - for (const auto &argItr : itr.second.as_table()) { - auto key = argItr.first; - if (key == "git") { - key = "GIT_REPOSITORY"; - } else if (key == "tag") { - key = "GIT_TAG"; - } else if (key == "svn") { - key = "SVN_REPOSITORY"; - } else if (key == "rev") { - key = "SVN_REVISION"; - } else if (key == "url") { - key = "URL"; - } else if (key == "hash") { - key = "URL_HASH"; - } else { - // don't change arg - } - content.arguments.emplace(key, argItr.second.as_string()); + // TODO: perform checking here + if (toml.contains("fetch-content")) { + const auto &fc = toml::find(toml, "fetch-content").as_table(); + for (const auto &itr : fc) { + Content content; + content.name = itr.first; + for (const auto &argItr : itr.second.as_table()) { + auto key = argItr.first; + if (key == "git") { + key = "GIT_REPOSITORY"; + } else if (key == "tag") { + key = "GIT_TAG"; + } else if (key == "svn") { + key = "SVN_REPOSITORY"; + } else if (key == "rev") { + key = "SVN_REVISION"; + } else if (key == "url") { + key = "URL"; + } else if (key == "hash") { + key = "URL_HASH"; + } else { + // don't change arg } - contents.emplace_back(std::move(content)); + content.arguments.emplace(key, argItr.second.as_string()); } + contents.emplace_back(std::move(content)); } + } - if (toml.contains("bin")) { - throw std::runtime_error("[[bin]] has been renamed to [[target]]"); - } + if (toml.contains("bin")) { + throw std::runtime_error("[[bin]] has been renamed to [[target]]"); + } - if (toml.contains("target")) { - const auto &ts = toml::find(toml, "target").as_table(); + if (toml.contains("target")) { + const auto &ts = toml::find(toml, "target").as_table(); - for (const auto &itr : ts) { - const auto &value = itr.second; + for (const auto &itr : ts) { + const auto &value = itr.second; - Target target; - target.name = itr.first; + Target target; + target.name = itr.first; - TomlChecker t(value); - std::string type; - t.required("type", type); - target.type = to_enum(type, "target type"); + auto &t = checker.create(value); + std::string type; + t.required("type", type); + target.type = to_enum(type, "target type"); - t.optional("headers", target.headers); - t.optional("sources", target.sources); + t.optional("headers", target.headers); + t.optional("sources", target.sources); - t.optional("compile-definitions", target.compile_definitions); - t.optional("private-compile-definitions", target.private_compile_definitions); + t.optional("compile-definitions", target.compile_definitions); + t.optional("private-compile-definitions", target.private_compile_definitions); - t.optional("compile-features", target.compile_features); - t.optional("private-compile-features", target.private_compile_features); + t.optional("compile-features", target.compile_features); + t.optional("private-compile-features", target.private_compile_features); - t.optional("compile-options", target.compile_options); - t.optional("private-compile-options", target.private_compile_options); + t.optional("compile-options", target.compile_options); + t.optional("private-compile-options", target.private_compile_options); - t.optional("include-directories", target.include_directories); - t.optional("private-include-directories", target.private_include_directories); + t.optional("include-directories", target.include_directories); + t.optional("private-include-directories", target.private_include_directories); - t.optional("link-directories", target.link_directories); - t.optional("private-link-directories", target.private_link_directories); + t.optional("link-directories", target.link_directories); + t.optional("private-link-directories", target.private_link_directories); - t.optional("link-libraries", target.link_libraries); - t.optional("private-link-libraries", target.private_link_libraries); + t.optional("link-libraries", target.link_libraries); + t.optional("private-link-libraries", target.private_link_libraries); - t.optional("link-options", target.link_options); - t.optional("private-link-options", target.private_link_options); + t.optional("link-options", target.link_options); + t.optional("private-link-options", target.private_link_options); - t.optional("precompile-headers", target.precompile_headers); - t.optional("private-precompile-headers", target.private_precompile_headers); + t.optional("precompile-headers", target.precompile_headers); + t.optional("private-precompile-headers", target.private_precompile_headers); - if (!target.headers.empty()) { - auto &sources = target.sources.nth(0).value(); - const auto &headers = target.headers.nth(0)->second; - sources.insert(sources.end(), headers.begin(), headers.end()); - } + if (!target.headers.empty()) { + auto &sources = target.sources.nth(0).value(); + const auto &headers = target.headers.nth(0)->second; + sources.insert(sources.end(), headers.begin(), headers.end()); + } - t.optional("condition", target.condition); - t.optional("alias", target.alias); - - t.visit("properties"); - if (value.contains("properties")) { - auto store_property = [&target](const toml::key &k, const TomlBasicValue &v, const std::string &condition) { - if (v.is_array()) { - std::string property_list; - for (const auto &list_val : v.as_array()) { - if (!property_list.empty()) { - property_list += ';'; - } - property_list += list_val.as_string(); + t.optional("condition", target.condition); + t.optional("alias", target.alias); + + if (t.contains("properties")) { + auto store_property = [&target](const toml::key &k, const TomlBasicValue &v, const std::string &condition) { + if (v.is_array()) { + std::string property_list; + for (const auto &list_val : v.as_array()) { + if (!property_list.empty()) { + property_list += ';'; } - target.properties[condition][k] = property_list; - } else if (v.is_boolean()) { - target.properties[condition][k] = v.as_boolean() ? "ON" : "OFF"; - } else { - target.properties[condition][k] = v.as_string(); + property_list += list_val.as_string(); } - }; - - const auto &props = toml::find(value, "properties").as_table(); - for (const auto &propKv : props) { - const auto &k = propKv.first; - const auto &v = propKv.second; - if (v.is_table()) { - for (const auto &condKv : v.as_table()) { - store_property(condKv.first, condKv.second, k); - } - } else { - store_property(k, v, ""); + target.properties[condition][k] = property_list; + } else if (v.is_boolean()) { + target.properties[condition][k] = v.as_boolean() ? "ON" : "OFF"; + } else { + target.properties[condition][k] = v.as_string(); + } + }; + + const auto &props = t.find("properties").as_table(); + for (const auto &propKv : props) { + const auto &k = propKv.first; + const auto &v = propKv.second; + if (v.is_table()) { + for (const auto &condKv : v.as_table()) { + store_property(condKv.first, condKv.second, k); } + } else { + store_property(k, v, ""); } } + } - t.optional("cmake-before", target.cmake_before); - t.optional("cmake-after", target.cmake_after); - t.optional("include-before", target.include_before); - t.optional("include-after", target.include_after); - t.check(); + t.optional("cmake-before", target.cmake_before); + t.optional("cmake-after", target.cmake_after); + t.optional("include-before", target.include_before); + t.optional("include-after", target.include_after); - targets.push_back(target); - } + targets.push_back(target); } + } - if (toml.contains("test")) { - const auto &ts = toml::find(toml, "test").as_array(); - for (const auto &value : ts) { - TomlChecker t(value); - Test test; - t.required("name", test.name); - t.optional("configurations", test.configurations); - t.optional("working-directory", test.working_directory); - t.required("command", test.command); - t.optional("arguments", test.arguments); - t.check(); - tests.push_back(test); - } + if (toml.contains("test")) { + const auto &ts = toml::find(toml, "test").as_array(); + for (const auto &value : ts) { + auto &t = checker.create(value); + Test test; + t.required("name", test.name); + t.optional("configurations", test.configurations); + t.optional("working-directory", test.working_directory); + t.required("command", test.command); + t.optional("arguments", test.arguments); + tests.push_back(test); } + } - if (toml.contains("install")) { - const auto &is = toml::find(toml, "install").as_array(); - for (const auto &value : is) { - TomlChecker i(value); - Install inst; - i.optional("targets", inst.targets); - i.optional("files", inst.files); - i.optional("dirs", inst.dirs); - i.optional("configs", inst.configs); - i.required("destination", inst.destination); - i.check(); - installs.push_back(inst); - } + if (toml.contains("install")) { + const auto &is = toml::find(toml, "install").as_array(); + for (const auto &value : is) { + auto &i = checker.create(value); + Install inst; + i.optional("targets", inst.targets); + i.optional("files", inst.files); + i.optional("dirs", inst.dirs); + i.optional("configs", inst.configs); + i.required("destination", inst.destination); + installs.push_back(inst); } + } - if (toml.contains("vcpkg")) { - TomlChecker v(toml, "vcpkg"); - v.optional("url", vcpkg.url); - v.optional("version", vcpkg.version); - v.required("packages", vcpkg.packages); - v.check(); - } + if (toml.contains("vcpkg")) { + auto &v = checker.create(toml, "vcpkg"); + v.optional("url", vcpkg.url); + v.optional("version", vcpkg.version); + v.required("packages", vcpkg.packages); } + + checker.check(conditions); } bool is_root_path(const std::string &path) {