diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4d25124..de67108 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -35,7 +35,7 @@ jobs:
- name: Test
run: |
cd build/tests
- ctest -C ${{ env.BUILD_TYPE }}
+ ctest -C ${{ env.BUILD_TYPE }} --verbose
- name: Upload artifacts
uses: actions/upload-artifact@v2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff35240..bc72a73 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -86,7 +86,6 @@ list(APPEND cmkr_SOURCES
"include/arguments.hpp"
"include/build.hpp"
"include/cmake_generator.hpp"
- "include/enum_helper.hpp"
"include/error.hpp"
"include/fs.hpp"
"include/help.hpp"
diff --git a/docs/examples/interface.md b/docs/examples/interface.md
index 3e27f87..976ed86 100644
--- a/docs/examples/interface.md
+++ b/docs/examples/interface.md
@@ -19,6 +19,7 @@ description = "Header-only library"
[target.mylib]
type = "interface"
include-directories = ["include"]
+compile-features = ["cxx_std_11"]
[target.example]
type = "executable"
diff --git a/docs/examples/templates.md b/docs/examples/templates.md
new file mode 100644
index 0000000..398f061
--- /dev/null
+++ b/docs/examples/templates.md
@@ -0,0 +1,40 @@
+---
+# Automatically generated from tests/templates/cmake.toml - DO NOT EDIT
+layout: default
+title: Target templates
+permalink: /examples/templates
+parent: Examples
+nav_order: 7
+---
+
+# Target templates
+
+To avoid repeating yourself in targets you can create your own target type (template). All properties of the template are inherited when used as a target type.
+
+```toml
+[project]
+name = "templates"
+description = "Target templates"
+
+[template.app]
+type = "executable"
+sources = ["src/templates.cpp"]
+compile-definitions = ["IS_APP"]
+
+# Unlike interface targets you can also inherit properties
+[template.app.properties]
+CXX_STANDARD = "11"
+CXX_STANDARD_REQUIRED = true
+
+[target.app-a]
+type = "app"
+compile-definitions = ["APP_A"]
+
+[target.app-b]
+type = "app"
+compile-definitions = ["APP_B"]
+```
+
+**Note**: In most cases you probably want to use an [interface](/examples/interface) target instead.
+
+This page was automatically generated from [tests/templates/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/templates/cmake.toml).
diff --git a/include/enum_helper.hpp b/include/enum_helper.hpp
deleted file mode 100644
index 7aafd78..0000000
--- a/include/enum_helper.hpp
+++ /dev/null
@@ -1,69 +0,0 @@
-// https://codereview.stackexchange.com/a/14315
-
-#include
-#include
-#include
-#include
-
-// This is the type that will hold all the strings.
-// Each enumeration type will declare its own specialization.
-// Any enum that does not have a specialization will generate a compiler error
-// indicating that there is no definition of this variable (as there should be
-// be no definition of a generic version).
-template
-struct enumStrings {
- static char const *data[];
-};
-
-// This is a utility type.
-// Created automatically. Should not be used directly.
-template
-struct enumRefHolder {
- T &enumVal;
- enumRefHolder(T &enumVal) : enumVal(enumVal) {}
-};
-template
-struct enumConstRefHolder {
- T const &enumVal;
- enumConstRefHolder(T const &enumVal) : enumVal(enumVal) {}
-};
-
-// The next two functions do the actual work of reading/writing an
-// enum as a string.
-template
-std::ostream &operator<<(std::ostream &str, enumConstRefHolder const &data) {
- return str << enumStrings::data[data.enumVal];
-}
-
-template
-std::istream &operator>>(std::istream &str, enumRefHolder const &data) {
- std::string value;
- str >> value;
-
- // These two can be made easier to read in C++11
- // using std::begin() and std::end()
- //
- static auto begin = std::begin(enumStrings::data);
- static auto end = std::end(enumStrings::data);
-
- auto find = std::find(begin, end, value);
- if (find != end) {
- data.enumVal = static_cast(std::distance(begin, find));
- } else {
- throw std::invalid_argument("");
- }
- return str;
-}
-
-// This is the public interface:
-// use the ability of function to deduce their template type without
-// being explicitly told to create the correct type of enumRefHolder
-template
-enumConstRefHolder enumToString(T const &e) {
- return enumConstRefHolder(e);
-}
-
-template
-enumRefHolder enumFromString(T &e) {
- return enumRefHolder(e);
-}
\ No newline at end of file
diff --git a/include/project_parser.hpp b/include/project_parser.hpp
index bebab1a..7c5ea11 100644
--- a/include/project_parser.hpp
+++ b/include/project_parser.hpp
@@ -56,11 +56,16 @@ enum TargetType {
target_interface,
target_custom,
target_object,
+ target_template,
+ target_last,
};
+extern const char *targetTypeNames[target_last];
+
struct Target {
std::string name;
- TargetType type = {};
+ TargetType type = target_last;
+ std::string type_name;
ConditionVector headers;
ConditionVector sources;
@@ -100,6 +105,12 @@ struct Target {
ConditionVector include_after;
};
+struct Template {
+ Target outline;
+ std::string add_function;
+ bool pass_sources_to_add_function = false;
+};
+
struct Test {
std::string name;
std::string condition;
@@ -160,6 +171,7 @@ struct Project {
std::vector packages;
Vcpkg vcpkg;
std::vector contents;
+ std::vector templates;
std::vector targets;
std::vector tests;
std::vector installs;
diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp
index 5b314d4..aea0a3b 100644
--- a/src/cmake_generator.cpp
+++ b/src/cmake_generator.cpp
@@ -6,6 +6,7 @@
#include "fs.hpp"
#include
#include
+#include
#include
#include
@@ -771,12 +772,31 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
const auto &target = project.targets[i];
+ const parser::Template *tmplate = nullptr;
+ std::unique_ptr tmplate_cs{};
+
comment("Target " + target.name);
+ // Check if this target is using a template.
+ if (target.type == parser::target_template) {
+ for (const auto &t : project.templates) {
+ if (target.type_name == t.outline.name) {
+ tmplate = &t;
+ tmplate_cs = std::unique_ptr(new ConditionScope(gen, tmplate->outline.condition));
+ }
+ }
+ }
+
ConditionScope cs(gen, target.condition);
cmd("set")("CMKR_TARGET", target.name);
+ if (tmplate != nullptr) {
+ gen.handle_condition(tmplate->outline.include_before,
+ [&](const std::string &, const std::vector &includes) { inject_includes(includes); });
+ gen.handle_condition(tmplate->outline.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
+ }
+
gen.handle_condition(target.include_before,
[&](const std::string &, const std::vector &includes) { inject_includes(includes); });
gen.handle_condition(target.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
@@ -785,6 +805,18 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
bool added_toml = false;
cmd("set")(sources_var, RawArg("\"\"")).endl();
+
+ if (tmplate != nullptr) {
+ gen.handle_condition(tmplate->outline.sources, [&](const std::string &condition, const std::vector &condition_sources) {
+ auto sources = expand_cmake_paths(condition_sources, path);
+ if (sources.empty()) {
+ auto source_key = condition.empty() ? "sources" : (condition + ".sources");
+ throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
+ }
+ cmd("list")("APPEND", sources_var, sources);
+ });
+ }
+
gen.handle_condition(target.sources, [&](const std::string &condition, const std::vector &condition_sources) {
auto sources = expand_cmake_paths(condition_sources, path);
if (sources.empty()) {
@@ -798,66 +830,91 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("list")("APPEND", sources_var, sources);
});
- if (!added_toml && target.type != parser::target_interface) {
+ auto target_type = target.type;
+
+ if (tmplate != nullptr) {
+ target_type = tmplate->outline.type;
+ }
+
+ if (!added_toml && target_type != parser::target_interface) {
cmd("list")("APPEND", sources_var, std::vector{"cmake.toml"}).endl();
}
cmd("set")("CMKR_SOURCES", "${" + sources_var + "}");
std::string add_command;
- std::string target_type;
+ std::string target_type_string;
std::string target_scope;
- switch (target.type) {
+
+ switch (target_type) {
case parser::target_executable:
add_command = "add_executable";
- target_type = "";
+ target_type_string = "";
target_scope = "PRIVATE";
break;
case parser::target_library:
add_command = "add_library";
- target_type = "";
+ target_type_string = "";
target_scope = "PUBLIC";
break;
case parser::target_shared:
add_command = "add_library";
- target_type = "SHARED";
+ target_type_string = "SHARED";
target_scope = "PUBLIC";
break;
case parser::target_static:
add_command = "add_library";
- target_type = "STATIC";
+ target_type_string = "STATIC";
target_scope = "PUBLIC";
break;
case parser::target_interface:
add_command = "add_library";
- target_type = "INTERFACE";
+ target_type_string = "INTERFACE";
target_scope = "INTERFACE";
break;
case parser::target_custom:
// TODO: add proper support, this is hacky
add_command = "add_custom_target";
- target_type = "SOURCES";
+ target_type_string = "SOURCES";
target_scope = "PUBLIC";
break;
case parser::target_object:
// NOTE: This is properly supported since 3.12
add_command = "add_library";
- target_type = "OBJECT";
+ target_type_string = "OBJECT";
target_scope = "PUBLIC";
break;
default:
throw std::runtime_error("Unimplemented enum value");
}
- cmd(add_command)(target.name, target_type).endl();
+ // Handle custom add commands from templates.
+ if (tmplate != nullptr && !tmplate->add_function.empty()) {
+ add_command = tmplate->add_function;
+ target_type_string = ""; // TODO: let templates supply options to the add_command here?
+
+ if (tmplate->pass_sources_to_add_function) {
+ cmd(add_command)(target.name, target_type_string, "${" + sources_var + "}");
+ } else {
+ cmd(add_command)(target.name, target_type_string).endl();
+
+ // clang-format off
+ cmd("if")(sources_var);
+ cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
+ cmd("endif")().endl();
+ // clang-format on
+ }
+ } else {
+ cmd(add_command)(target.name, target_type_string).endl();
- // clang-format off
- cmd("if")(sources_var);
- cmd("target_sources")(target.name, target.type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
- cmd("endif")().endl();
- // clang-format on
+ // clang-format off
+ cmd("if")(sources_var);
+ cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
+ cmd("endif")().endl();
+ // clang-format on
+ }
// The first executable target will become the Visual Studio startup project
- if (target.type == parser::target_executable) {
+ if (target_type == parser::target_executable) {
cmd("get_directory_property")("CMKR_VS_STARTUP_PROJECT", "DIRECTORY", "${PROJECT_SOURCE_DIR}", "DEFINITION", "VS_STARTUP_PROJECT");
// clang-format off
cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT");
@@ -879,32 +936,46 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
[&](const std::string &, const std::vector &args) { cmd(command)(target.name, scope, args); });
};
- target_cmd("target_compile_definitions", target.compile_definitions, target_scope);
- target_cmd("target_compile_definitions", target.private_compile_definitions, "PRIVATE");
+ auto gen_target_cmds = [&](const parser::Target &t) {
+ target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
+ target_cmd("target_compile_definitions", t.private_compile_definitions, "PRIVATE");
- target_cmd("target_compile_features", target.compile_features, target_scope);
- target_cmd("target_compile_features", target.private_compile_features, "PRIVATE");
+ target_cmd("target_compile_features", t.compile_features, target_scope);
+ target_cmd("target_compile_features", t.private_compile_features, "PRIVATE");
- target_cmd("target_compile_options", target.compile_options, target_scope);
- target_cmd("target_compile_options", target.private_compile_options, "PRIVATE");
+ target_cmd("target_compile_options", t.compile_options, target_scope);
+ target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
- target_cmd("target_include_directories", target.include_directories, target_scope);
- target_cmd("target_include_directories", target.private_include_directories, "PRIVATE");
+ target_cmd("target_include_directories", t.include_directories, target_scope);
+ target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
- target_cmd("target_link_directories", target.link_directories, target_scope);
- target_cmd("target_link_directories", target.private_link_directories, "PRIVATE");
+ target_cmd("target_link_directories", t.link_directories, target_scope);
+ target_cmd("target_link_directories", t.private_link_directories, "PRIVATE");
- target_cmd("target_link_libraries", target.link_libraries, target_scope);
- target_cmd("target_link_libraries", target.private_link_libraries, "PRIVATE");
+ target_cmd("target_link_libraries", t.link_libraries, target_scope);
+ target_cmd("target_link_libraries", t.private_link_libraries, "PRIVATE");
- target_cmd("target_link_options", target.link_options, target_scope);
- target_cmd("target_link_options", target.private_link_options, "PRIVATE");
+ target_cmd("target_link_options", t.link_options, target_scope);
+ target_cmd("target_link_options", t.private_link_options, "PRIVATE");
- target_cmd("target_precompile_headers", target.precompile_headers, target_scope);
- target_cmd("target_precompile_headers", target.private_precompile_headers, "PRIVATE");
+ target_cmd("target_precompile_headers", t.precompile_headers, target_scope);
+ target_cmd("target_precompile_headers", t.private_precompile_headers, "PRIVATE");
+ };
- if (!target.properties.empty()) {
- gen.handle_condition(target.properties, [&](const std::string &, const tsl::ordered_map &properties) {
+ if (tmplate != nullptr) {
+ gen_target_cmds(tmplate->outline);
+ }
+
+ gen_target_cmds(target);
+
+ if (!target.properties.empty() || (tmplate != nullptr && !tmplate->outline.properties.empty())) {
+ auto props = target.properties;
+
+ if (tmplate != nullptr) {
+ props.insert(tmplate->outline.properties.begin(), tmplate->outline.properties.end());
+ }
+
+ gen.handle_condition(props, [&](const std::string &, const tsl::ordered_map &properties) {
cmd("set_target_properties")(target.name, "PROPERTIES", properties);
});
}
@@ -913,6 +984,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
[&](const std::string &, const std::vector &includes) { inject_includes(includes); });
gen.handle_condition(target.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
+ if (tmplate != nullptr) {
+ gen.handle_condition(tmplate->outline.include_after,
+ [&](const std::string &, const std::vector &includes) { inject_includes(includes); });
+ gen.handle_condition(tmplate->outline.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
+ }
+
cmd("unset")("CMKR_TARGET");
cmd("unset")("CMKR_SOURCES");
}
diff --git a/src/project_parser.cpp b/src/project_parser.cpp
index 0a693b2..713d03c 100644
--- a/src/project_parser.cpp
+++ b/src/project_parser.cpp
@@ -1,39 +1,27 @@
#include "project_parser.hpp"
-#include "enum_helper.hpp"
#include "fs.hpp"
#include
#include
#include
#include
-template <>
-const char *enumStrings::data[] = {"executable", "library", "shared", "static", "interface", "custom", "object"};
-
namespace cmkr {
namespace parser {
-using TomlBasicValue = toml::basic_value;
+const char *targetTypeNames[target_last] = {"executable", "library", "shared", "static", "interface", "custom", "object", "template"};
-template
-static EnumType to_enum(const std::string &str, const std::string &help_name) {
- EnumType value;
- try {
- std::stringstream ss(str);
- ss >> enumFromString(value);
- } catch (std::invalid_argument &) {
- std::string supported;
- for (const auto &s : enumStrings::data) {
- if (!supported.empty()) {
- supported += ", ";
- }
- supported += s;
+static TargetType parse_targetType(const std::string &name) {
+ for (int i = 0; i < target_last; i++) {
+ if (name == targetTypeNames[i]) {
+ return static_cast(i);
}
- throw std::runtime_error("Unknown " + help_name + "'" + str + "'! Supported types are: " + supported);
}
- return value;
+ return target_last;
}
+using TomlBasicValue = toml::basic_value;
+
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());
@@ -42,7 +30,7 @@ static std::string format_key_error(const std::string &error, const toml::key &k
std::ostringstream oss;
oss << "[error] " << error << '\n';
- oss << " --> " << loc.file_name() << '\n';
+ oss << " --> " << loc.file_name() << ':' << loc.line() << '\n';
oss << std::string(line_width + 2, ' ') << "|\n";
oss << ' ' << line_number_str << " | " << line_str << '\n';
@@ -55,7 +43,6 @@ static std::string format_key_error(const std::string &error, const toml::key &k
if (key_start != std::string::npos) {
oss << std::string(key_start + 1, ' ') << std::string(ky.length(), '~');
}
- oss << '\n';
return oss.str();
}
@@ -241,6 +228,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
conditions["x32"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 4)cmake";
} else {
conditions = parent->conditions;
+ templates = parent->templates;
}
if (toml.contains("conditions")) {
@@ -386,94 +374,154 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
throw std::runtime_error("[[bin]] has been renamed to [[target]]");
}
- if (toml.contains("target")) {
- const auto &ts = toml::find(toml, "target").as_table();
+ auto parse_target = [&](const std::string &name, TomlChecker &t, bool isTemplate) {
+ Target target;
+ target.name = name;
- for (const auto &itr : ts) {
- const auto &value = itr.second;
+ t.required("type", target.type_name);
+ target.type = parse_targetType(target.type_name);
- Target target;
- target.name = itr.first;
+ // Users cannot set this target type
+ if (target.type == target_template) {
+ target.type = target_last;
+ }
- auto &t = checker.create(value);
- std::string type;
- t.required("type", type);
- target.type = to_enum(type, "target type");
+ if (!isTemplate && target.type == target_last) {
+ for (const auto &tmplate : templates) {
+ if (target.type_name == tmplate.outline.name) {
+ target.type = target_template;
+ break;
+ }
+ }
+ }
+
+ if (target.type == target_last) {
+ std::string error = "Unknown target type '" + target.type_name + "'\n";
+ error += "Available types:\n";
+ for (std::string type_name : targetTypeNames) {
+ if (type_name != "template") {
+ error += " - " + type_name + "\n";
+ }
+ }
+ if (!isTemplate && !templates.empty()) {
+ error += "Available templates:\n";
+ for (const auto &tmplate : templates) {
+ error += " - " + tmplate.outline.name + "\n";
+ }
+ }
+ error.pop_back(); // Remove last newline
+ throw std::runtime_error(format_key_error(error, target.type_name, t.find("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.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 += ';';
- }
- property_list += list_val.as_string();
+ 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 = 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, "");
+ 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.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);
+
+ return target;
+ };
+
+ if (toml.contains("template")) {
+ const auto &ts = toml::find(toml, "template").as_table();
+ for (const auto &itr : ts) {
+ auto &t = checker.create(itr.second);
+ const auto &name = itr.first;
+
+ for (const auto &type_name : targetTypeNames) {
+ if (name == type_name) {
+ throw std::runtime_error(format_key_error("Reserved template name '" + name + "'", name, itr.second));
+ }
+ }
- targets.push_back(target);
+ for (const auto &tmplate : templates) {
+ if (name == tmplate.outline.name) {
+ throw std::runtime_error(format_key_error("Template '" + name + "' already defined", name, itr.second));
+ }
+ }
+
+ Template tmplate;
+ tmplate.outline = parse_target(name, t, true);
+
+ t.optional("add-function", tmplate.add_function);
+ t.optional("pass-sources-to-add-function", tmplate.pass_sources_to_add_function);
+
+ templates.push_back(tmplate);
+ }
+ }
+
+ if (toml.contains("target")) {
+ const auto &ts = toml::find(toml, "target").as_table();
+ for (const auto &itr : ts) {
+ auto &t = checker.create(itr.second);
+ targets.push_back(parse_target(itr.first, t, false));
}
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2cd8411..0b46624 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -78,3 +78,13 @@ add_test(
build
)
+add_test(
+ NAME
+ templates
+ WORKING_DIRECTORY
+ "${CMAKE_CURRENT_LIST_DIR}/templates"
+ COMMAND
+ $
+ build
+)
+
diff --git a/tests/cmake.toml b/tests/cmake.toml
index 19efbec..10f46ed 100644
--- a/tests/cmake.toml
+++ b/tests/cmake.toml
@@ -1,41 +1,47 @@
[[test]]
name = "basic"
-command = "$"
working-directory = "basic"
+command = "$"
arguments = ["build"]
[[test]]
name = "interface"
-command = "$"
working-directory = "interface"
+command = "$"
arguments = ["build"]
[[test]]
name = "fetch-content"
-command = "$"
working-directory = "fetch-content"
+command = "$"
arguments = ["build"]
[[test]]
name = "conditions"
-command = "$"
working-directory = "conditions"
+command = "$"
arguments = ["build"]
[[test]]
name = "vcpkg"
-command = "$"
working-directory = "vcpkg"
+command = "$"
arguments = ["build"]
[[test]]
name = "cxx-standard"
-command = "$"
working-directory = "cxx-standard"
+command = "$"
arguments = ["build"]
[[test]]
name = "globbing"
-command = "$"
working-directory = "globbing"
+command = "$"
+arguments = ["build"]
+
+[[test]]
+name = "templates"
+working-directory = "templates"
+command = "$"
arguments = ["build"]
diff --git a/tests/interface/cmake.toml b/tests/interface/cmake.toml
index 1f1c0c7..44588c4 100644
--- a/tests/interface/cmake.toml
+++ b/tests/interface/cmake.toml
@@ -5,6 +5,7 @@ description = "Header-only library"
[target.mylib]
type = "interface"
include-directories = ["include"]
+compile-features = ["cxx_std_11"]
[target.example]
type = "executable"
diff --git a/tests/templates/cmake.toml b/tests/templates/cmake.toml
new file mode 100644
index 0000000..9fce97f
--- /dev/null
+++ b/tests/templates/cmake.toml
@@ -0,0 +1,25 @@
+# To avoid repeating yourself in targets you can create your own target type (template). All properties of the template are inherited when used as a target type.
+
+[project]
+name = "templates"
+description = "Target templates"
+
+[template.app]
+type = "executable"
+sources = ["src/templates.cpp"]
+compile-definitions = ["IS_APP"]
+
+# Unlike interface targets you can also inherit properties
+[template.app.properties]
+CXX_STANDARD = "11"
+CXX_STANDARD_REQUIRED = true
+
+[target.app-a]
+type = "app"
+compile-definitions = ["APP_A"]
+
+[target.app-b]
+type = "app"
+compile-definitions = ["APP_B"]
+
+# **Note**: In most cases you probably want to use an [interface](/examples/interface) target instead.
\ No newline at end of file
diff --git a/tests/templates/src/templates.cpp b/tests/templates/src/templates.cpp
new file mode 100644
index 0000000..e4091b3
--- /dev/null
+++ b/tests/templates/src/templates.cpp
@@ -0,0 +1,13 @@
+#include
+
+#if !defined(IS_APP)
+#error Something went wrong with the template
+#endif // IS_APP
+
+int main() {
+#if defined(APP_A)
+ puts("Hello from app A!");
+#elif defined(APP_B)
+ puts("Hello from app B!");
+#endif
+}