Initial support for conditional arguments

vcpkg-wip archive_7cdf36f3
Duncan Ogilvie 4 years ago
parent bf14f069a7
commit 7cdf36f317

@ -1,4 +1,5 @@
# This file was generated automatically by cmkr. # This file is automatically generated from cmake.toml - DO NOT EDIT
# See https://github.com/MoAlyousef/cmkr for more information
cmake_minimum_required(VERSION 2.8...3.8) cmake_minimum_required(VERSION 2.8...3.8)
@ -26,7 +27,6 @@ endif()
if(CMAKE_BUILD_TYPE) if(CMAKE_BUILD_TYPE)
endif() endif()
project(cmkr project(cmkr
LANGUAGES LANGUAGES
CXX CXX
@ -58,7 +58,9 @@ set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
# Target cmkrlib # Target cmkrlib
set(cmkrlib_SOURCES unset(cmkrlib_SOURCES)
list(APPEND cmkrlib_SOURCES
"src/cmkrlib/args.cpp" "src/cmkrlib/args.cpp"
"src/cmkrlib/build.cpp" "src/cmkrlib/build.cpp"
"src/cmkrlib/cmake.cpp" "src/cmkrlib/cmake.cpp"
@ -74,6 +76,9 @@ set(cmkrlib_SOURCES
"include/gen.h" "include/gen.h"
"include/help.h" "include/help.h"
"include/literals.h" "include/literals.h"
)
list(APPEND cmkrlib_SOURCES
cmake.toml cmake.toml
) )
@ -97,8 +102,13 @@ target_link_libraries(cmkrlib PUBLIC
) )
# Target cmkr # Target cmkr
set(cmkr_SOURCES unset(cmkr_SOURCES)
list(APPEND cmkr_SOURCES
"src/main.cpp" "src/main.cpp"
)
list(APPEND cmkr_SOURCES
cmake.toml cmake.toml
) )

@ -79,4 +79,3 @@ Header-only library. Equivalent to [add_library(name INTERFACE)](https://cmake.o
## Roadmap ## Roadmap
- Support more cmake fields. - Support more cmake fields.
- Support conditional cmake args somehow!

@ -33,6 +33,26 @@ static EnumType to_enum(const std::string &str, const std::string &help_name) {
return value; return value;
} }
template <typename T>
static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination);
template <typename T>
static void get_optional(const TomlBasicValue &v, const toml::key &ky, Condition<T> &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<T>(value, ky);
}
} else if (key == ky) {
destination[""] = toml::find<T>(v, ky);
}
}
}
template <typename T> template <typename T>
static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination) { static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination) {
if (v.contains(ky)) { if (v.contains(ky)) {
@ -237,6 +257,19 @@ CMake::CMake(const std::string &path, bool build) {
contents["pmm"]["SOURCE_SUBDIR"] = "pmm"; contents["pmm"]["SOURCE_SUBDIR"] = "pmm";
} }
} }
// Reasonable default conditions (you can override these if you desire)
conditions["win32"] = conditions["windows"] = conditions["win"] = "WIN32";
conditions["macos"] = conditions["macosx"] = conditions["osx"] = conditions["mac"] = R"cond("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")cond";
conditions["unix"] = "UNIX";
conditions["linux"] = R"cond("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")cond";
if (toml.contains("conditions")) {
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
for (const auto &cond : conds) {
conditions[cond.first] = cond.second;
}
}
} }
} }
} // namespace cmake } // namespace cmake

@ -46,28 +46,33 @@ enum TargetType {
target_custom, target_custom,
}; };
template <typename T>
using Condition = tsl::ordered_map<std::string, T>;
using ConditionVector = Condition<std::vector<std::string>>;
struct Target { struct Target {
std::string name; std::string name;
TargetType type = {}; TargetType type = {};
// https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands // https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands
std::vector<std::string> compile_definitions; ConditionVector compile_definitions;
std::vector<std::string> compile_features; ConditionVector compile_features;
std::vector<std::string> compile_options; ConditionVector compile_options;
std::vector<std::string> include_directories; ConditionVector include_directories;
std::vector<std::string> link_directories; ConditionVector link_directories;
std::vector<std::string> link_libraries; ConditionVector link_libraries;
std::vector<std::string> link_options; ConditionVector link_options;
std::vector<std::string> precompile_headers; ConditionVector precompile_headers;
std::vector<std::string> sources; ConditionVector sources;
std::string alias; std::string alias;
tsl::ordered_map<std::string, std::string> properties; tsl::ordered_map<std::string, std::string> properties;
std::string cmake_before; Condition<std::string> cmake_before;
std::string cmake_after; Condition<std::string> cmake_after;
std::vector<std::string> include_before; ConditionVector include_before;
std::vector<std::string> include_after; ConditionVector include_after;
}; };
struct Test { struct Test {
@ -103,10 +108,10 @@ struct CMake {
std::string project_version; std::string project_version;
std::string project_description; std::string project_description;
std::vector<std::string> project_languages; std::vector<std::string> project_languages;
std::string cmake_before; Condition<std::string> cmake_before;
std::string cmake_after; Condition<std::string> cmake_after;
std::vector<std::string> include_before; ConditionVector include_before;
std::vector<std::string> include_after; ConditionVector include_after;
std::vector<Setting> settings; std::vector<Setting> settings;
std::vector<Option> options; std::vector<Option> options;
std::vector<Package> packages; std::vector<Package> packages;
@ -115,6 +120,8 @@ struct CMake {
std::vector<Target> targets; std::vector<Target> targets;
std::vector<Test> tests; std::vector<Test> tests;
std::vector<Install> installs; std::vector<Install> installs;
tsl::ordered_map<std::string, std::string> conditions;
CMake(const std::string &path, bool build); CMake(const std::string &path, bool build);
}; };

@ -140,6 +140,13 @@ struct CommandEndl {
void endl() { ss << '\n'; } void endl() { ss << '\n'; }
}; };
struct RawArg {
RawArg() = default;
RawArg(std::string arg) : arg(std::move(arg)) {}
std::string arg;
};
// Credit: JustMagic // Credit: JustMagic
struct Command { struct Command {
std::stringstream &ss; std::stringstream &ss;
@ -246,8 +253,26 @@ struct Command {
return true; return true;
} }
bool print_arg(const RawArg &arg) {
if (arg.arg.empty()) {
return true;
}
if (had_newline) {
first_arg = false;
ss << '\n' << indent(depth + 1);
} else if (first_arg) {
first_arg = false;
} else {
ss << ' ';
}
ss << arg.arg;
return true;
}
template <class T> template <class T>
bool print_arg(const T &value, bool indentation = true) { bool print_arg(const T &value) {
std::stringstream tmp; std::stringstream tmp;
tmp << value; tmp << value;
auto str = tmp.str(); auto str = tmp.str();
@ -255,15 +280,13 @@ struct Command {
return true; return true;
} }
if (indentation) { if (had_newline) {
if (had_newline) { first_arg = false;
first_arg = false; ss << '\n' << indent(depth + 1);
ss << '\n' << indent(depth + 1); } else if (first_arg) {
} else if (first_arg) { first_arg = false;
first_arg = false; } else {
} else { ss << ' ';
ss << ' ';
}
} }
ss << quote(str); ss << quote(str);
@ -276,21 +299,31 @@ struct Command {
ss << indent(depth) << command << '('; ss << indent(depth) << command << '(';
(void)std::initializer_list<bool>{print_arg(values)...}; (void)std::initializer_list<bool>{print_arg(values)...};
if (had_newline) if (had_newline)
ss << '\n'; ss << '\n' << indent(depth);
ss << ")\n"; ss << ")\n";
return CommandEndl(ss); return CommandEndl(ss);
} }
}; };
int generate_cmake(const char *path, bool root) { static std::string tolf(const std::string &str) {
if (!fs::exists(fs::path(path) / "cmake.toml")) { std::string result;
throw std::runtime_error("No cmake.toml found!"); for (char ch : str) {
if (ch != '\r') {
result += ch;
}
} }
return result;
};
// Helper lambdas for more convenient CMake generation struct Generator {
Generator(cmake::CMake &cmake) : cmake(cmake) {}
Generator(const Generator &) = delete;
cmake::CMake &cmake;
std::stringstream ss; std::stringstream ss;
int indent = 0; int indent = 0;
auto cmd = [&ss, &indent](const std::string &command) {
Command cmd(const std::string &command) {
if (command.empty()) if (command.empty())
throw std::invalid_argument("command cannot be empty"); throw std::invalid_argument("command cannot be empty");
if (command == "if") { if (command == "if") {
@ -302,22 +335,16 @@ int generate_cmake(const char *path, bool root) {
indent--; indent--;
} }
return Command(ss, indent, command); return Command(ss, indent, command);
}; }
auto comment = [&ss, &indent](const std::string &comment) {
CommandEndl comment(const std::string &comment) {
ss << Command::indent(indent) << "# " << comment << '\n'; ss << Command::indent(indent) << "# " << comment << '\n';
return CommandEndl(ss); return CommandEndl(ss);
}; }
auto endl = [&ss]() { ss << '\n'; };
auto tolf = [](const std::string &str) { void endl() { ss << '\n'; }
std::string result;
for (char ch : str) { void inject_includes(const std::vector<std::string> &includes) {
if (ch != '\r') {
result += ch;
}
}
return result;
};
auto inject_includes = [&cmd, &endl](const std::vector<std::string> &includes) {
if (!includes.empty()) { if (!includes.empty()) {
for (const auto &file : includes) { for (const auto &file : includes) {
if (!fs::is_regular_file(file)) { if (!fs::is_regular_file(file)) {
@ -325,22 +352,74 @@ int generate_cmake(const char *path, bool root) {
} }
cmd("include")(file); cmd("include")(file);
} }
endl();
} }
}; }
auto inject_cmake = [&ss, &tolf](const std::string &cmake) {
void inject_cmake(const std::string &cmake) {
if (!cmake.empty()) { if (!cmake.empty()) {
if (cmake.back() == '\"') { if (cmake.back() == '\"') {
throw std::runtime_error("Detected additional \" at the end of cmake block"); throw std::runtime_error("Detected additional \" at the end of cmake block");
} }
ss << tolf(cmake) << "\n\n"; auto cmake_lf = tolf(cmake);
while (cmake_lf.back() == '\n')
cmake_lf.pop_back();
bool did_indent = false;
for (char ch : cmake_lf) {
if (!did_indent) {
ss << Command::indent(indent);
did_indent = true;
} else if (ch == '\n') {
did_indent = false;
}
ss << ch;
}
ss << '\n';
} }
}; }
// TODO: add link with proper documentation template <typename T, typename Lambda>
comment("This file is automatically generated from cmake.toml - DO NOT EDIT").endl(); void handle_condition(const cmake::Condition<T> &value, const Lambda &fn) {
if (!value.empty()) {
for (const auto &itr : value) {
const auto &condition = itr.first;
if (!condition.empty()) {
if (cmake.conditions.count(condition) == 0) {
// TODO: somehow print line number information here?
throw std::runtime_error("Unknown condition '" + condition + "'");
}
cmd("if")(RawArg(cmake.conditions[condition]));
}
fn(condition, itr.second);
if (!condition.empty()) {
cmd("endif")();
}
endl();
}
}
}
};
int generate_cmake(const char *path, bool root) {
if (!fs::exists(fs::path(path) / "cmake.toml")) {
throw std::runtime_error("No cmake.toml found!");
}
cmake::CMake cmake(path, false); cmake::CMake cmake(path, false);
Generator gen(cmake);
// Helper lambdas for more convenient CMake generation
auto &ss = gen.ss;
auto cmd = [&gen](const std::string &comment) { return gen.cmd(comment); };
auto comment = [&gen](const std::string &comment) { return gen.comment(comment); };
auto endl = [&gen]() { gen.endl(); };
auto inject_includes = [&gen](const std::vector<std::string> &includes) { gen.inject_includes(includes); };
auto inject_cmake = [&gen](const std::string &cmake) { gen.inject_cmake(cmake); };
comment("This file is automatically generated from cmake.toml - DO NOT EDIT");
comment("See https://github.com/MoAlyousef/cmkr for more information");
endl();
if (root) { if (root) {
cmd("cmake_minimum_required")("VERSION", cmake.cmake_version).endl(); cmd("cmake_minimum_required")("VERSION", cmake.cmake_version).endl();
@ -397,8 +476,8 @@ int generate_cmake(const char *path, bool root) {
ss << "\")\n\n"; ss << "\")\n\n";
} }
inject_includes(cmake.include_before); gen.handle_condition(cmake.include_before, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
inject_cmake(cmake.cmake_before); gen.handle_condition(cmake.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
if (!cmake.project_name.empty()) { if (!cmake.project_name.empty()) {
auto languages = std::make_pair("LANGUAGES", cmake.project_languages); auto languages = std::make_pair("LANGUAGES", cmake.project_languages);
@ -407,8 +486,8 @@ int generate_cmake(const char *path, bool root) {
cmd("project")(cmake.project_name, languages, version, description).endl(); cmd("project")(cmake.project_name, languages, version, description).endl();
} }
inject_includes(cmake.include_after); gen.handle_condition(cmake.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
inject_cmake(cmake.cmake_after); gen.handle_condition(cmake.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
if (!cmake.contents.empty()) { if (!cmake.contents.empty()) {
cmd("include")("FetchContent").endl(); cmd("include")("FetchContent").endl();
@ -513,21 +592,29 @@ int generate_cmake(const char *path, bool root) {
if (!cmake.targets.empty()) { if (!cmake.targets.empty()) {
for (const auto &target : cmake.targets) { for (const auto &target : cmake.targets) {
comment("Target " + target.name); comment("Target " + target.name);
inject_includes(target.include_before); gen.handle_condition(target.include_before,
inject_cmake(target.cmake_before); [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(target.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
if (!target.sources.empty()) { auto sources_var = target.name + "_SOURCES";
auto sources = expand_cmake_paths(target.sources, path);
bool added_toml = false;
cmd("unset")(sources_var).endl();
gen.handle_condition(target.sources, [&](const std::string &condition, const std::vector<std::string> &condition_sources) {
auto sources = expand_cmake_paths(condition_sources, path);
if (sources.empty()) { if (sources.empty()) {
throw std::runtime_error(target.name + " sources wildcard found 0 files"); auto source_key = condition.empty() ? "sources" : (condition + ".sources");
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
} }
if (target.type != cmake::target_interface) { // Do not add cmake.toml twice
// Do not add cmake.toml twice if (!added_toml && std::find(sources.begin(), sources.end(), "cmake.toml") != sources.end()) {
if (std::find(sources.begin(), sources.end(), "cmake.toml") == sources.end()) { added_toml = true;
sources.push_back("cmake.toml");
}
} }
cmd("set")(target.name + "_SOURCES", sources).endl(); cmd("list")("APPEND", sources_var, sources);
});
if (!added_toml && target.type != cmake::target_interface) {
cmd("list")("APPEND", sources_var, std::vector<std::string>{"cmake.toml"}).endl();
} }
std::string add_command; std::string add_command;
@ -589,10 +676,9 @@ int generate_cmake(const char *path, bool root) {
cmd("add_library")(target.alias, "ALIAS", target.name); cmd("add_library")(target.alias, "ALIAS", target.name);
} }
auto target_cmd = [&](const char *command, const std::vector<std::string> &args) { auto target_cmd = [&](const char *command, const cmake::ConditionVector &cargs) {
if (!args.empty()) { gen.handle_condition(
cmd(command)(target.name, target_scope, args).endl(); cargs, [&](const std::string &, const std::vector<std::string> &args) { cmd(command)(target.name, target_scope, args); });
}
}; };
target_cmd("target_compile_definitions", target.compile_definitions); target_cmd("target_compile_definitions", target.compile_definitions);
@ -607,8 +693,9 @@ int generate_cmake(const char *path, bool root) {
cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl(); cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl();
} }
inject_includes(target.include_after); gen.handle_condition(target.include_after,
inject_cmake(target.cmake_after); [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(target.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
} }
} }

2
tests/.gitignore vendored

@ -0,0 +1,2 @@
# These will be generated by cmkr, so no point in tracking them
**/CMakeLists.txt

@ -1,4 +1,5 @@
# This file was generated automatically by cmkr. # This file is automatically generated from cmake.toml - DO NOT EDIT
# See https://github.com/MoAlyousef/cmkr for more information
# Create a configure-time dependency on cmake.toml to improve IDE support # Create a configure-time dependency on cmake.toml to improve IDE support
if(CMKR_ROOT_PROJECT) if(CMKR_ROOT_PROJECT)
@ -17,3 +18,13 @@ add_test(
build build
) )
add_test(
NAME
conditions
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/conditions"
COMMAND
$<TARGET_FILE:cmkr>
build
)

@ -1,41 +0,0 @@
# This file was generated automatically by cmkr.
cmake_minimum_required(VERSION 3.5)
# Regenerate CMakeLists.txt automatically in the root project
set(CMKR_ROOT_PROJECT OFF)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(CMKR_ROOT_PROJECT ON)
# Bootstrap cmkr
include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)
if(CMKR_INCLUDE_RESULT)
cmkr()
endif()
# Enable folder support
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
endif()
# Create a configure-time dependency on cmake.toml to improve IDE support
if(CMKR_ROOT_PROJECT)
configure_file(cmake.toml cmake.toml COPYONLY)
endif()
project(basic)
# Target basic
set(basic_SOURCES
"src/basic.cpp"
cmake.toml
)
add_executable(basic ${basic_SOURCES})
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
if(NOT CMKR_VS_STARTUP_PROJECT)
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT basic)
endif()
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${basic_SOURCES})

@ -3,3 +3,9 @@ name = "basic"
command = "$<TARGET_FILE:cmkr>" command = "$<TARGET_FILE:cmkr>"
working-directory = "${CMAKE_CURRENT_LIST_DIR}/basic" working-directory = "${CMAKE_CURRENT_LIST_DIR}/basic"
arguments = ["build"] arguments = ["build"]
[[test]]
name = "conditions"
command = "$<TARGET_FILE:cmkr>"
working-directory = "${CMAKE_CURRENT_LIST_DIR}/conditions"
arguments = ["build"]

@ -0,0 +1,17 @@
[project]
name = "conditions"
cmake-after = "set(CUSTOM ON)"
[conditions]
custom = "CUSTOM"
[target.example]
type = "executable"
sources = ["src/main.cpp"]
win32.sources = ["src/win32.cpp"]
cmake-after = "message(STATUS cmake-after)"
win32.cmake-after = "message(STATUS win32-after)"
unix.cmake-after = "message(STATUS unix-after)"
macos.cmake-after = "message(STATUS macos-after)"
custom.cmake-after = "message(STATUS custom-after)"
linux.cmake-after = "message(STATUS linux-after)"

@ -0,0 +1,3 @@
#include <Windows.h>
void foo() { }
Loading…
Cancel
Save