Compare commits

..

No commits in common. '0a887c08f8371a2590aee5f0e29334e1982401e7' and '58c7de1d603f843658ca5302721f19f4b42fd484' have entirely different histories.

@ -10,7 +10,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [windows-2022, macos-latest, ubuntu-20.04] os: [windows-2022, macos-11, ubuntu-20.04]
env: env:
BUILD_TYPE: 'Release' BUILD_TYPE: 'Release'
CMAKE_GENERATOR: 'Ninja' CMAKE_GENERATOR: 'Ninja'

@ -1,11 +1,9 @@
name: lint name: lint
on: [push, pull_request] on: [push]
jobs: jobs:
clang-format: clang-format:
# Skip building pull requests from the same repository
if: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -29,8 +27,6 @@ jobs:
exit 1 exit 1
editorconfig: editorconfig:
# Skip building pull requests from the same repository
if: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

23
CMakeLists.txt generated

@ -22,7 +22,7 @@ project(cmkr
LANGUAGES LANGUAGES
CXX CXX
VERSION VERSION
0.2.33 0.2.25
DESCRIPTION DESCRIPTION
"CMakeLists generator from TOML" "CMakeLists generator from TOML"
) )
@ -58,9 +58,12 @@ generate_documentation()
# Target: cmkr # Target: cmkr
set(cmkr_SOURCES set(cmkr_SOURCES
cmake.toml "src/arguments.cpp"
"cmake/cmkr.cmake" "src/build.cpp"
"cmake/version.hpp.in" "src/cmake_generator.cpp"
"src/help.cpp"
"src/main.cpp"
"src/project_parser.cpp"
"include/arguments.hpp" "include/arguments.hpp"
"include/build.hpp" "include/build.hpp"
"include/cmake_generator.hpp" "include/cmake_generator.hpp"
@ -68,12 +71,9 @@ set(cmkr_SOURCES
"include/help.hpp" "include/help.hpp"
"include/literals.hpp" "include/literals.hpp"
"include/project_parser.hpp" "include/project_parser.hpp"
"src/arguments.cpp" "cmake/cmkr.cmake"
"src/build.cpp" "cmake/version.hpp.in"
"src/cmake_generator.cpp" cmake.toml
"src/help.cpp"
"src/main.cpp"
"src/project_parser.cpp"
) )
add_executable(cmkr) add_executable(cmkr)
@ -94,6 +94,7 @@ target_link_libraries(cmkr PRIVATE
ghc_filesystem ghc_filesystem
mpark_variant mpark_variant
ordered_map ordered_map
nlohmann_json
) )
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT) get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
@ -102,7 +103,7 @@ if(NOT CMKR_VS_STARTUP_PROJECT)
endif() endif()
set(CMKR_TARGET cmkr) set(CMKR_TARGET cmkr)
include("cmake/custom_targets.cmake") generate_resources(${CMKR_TARGET})
install( install(
TARGETS TARGETS

@ -4,7 +4,7 @@ cmkr-include = false
[project] [project]
name = "cmkr" name = "cmkr"
version = "0.2.33" version = "0.2.25"
description = "CMakeLists generator from TOML" description = "CMakeLists generator from TOML"
languages = ["CXX"] languages = ["CXX"]
include-after = [ include-after = [
@ -36,8 +36,11 @@ link-libraries = [
"ghc_filesystem", "ghc_filesystem",
"mpark_variant", "mpark_variant",
"ordered_map", "ordered_map",
"nlohmann_json",
] ]
include-after = ["cmake/custom_targets.cmake"] cmake-after = """
generate_resources(${CMKR_TARGET})
"""
[[install]] [[install]]
targets = ["cmkr"] targets = ["cmkr"]

@ -2,7 +2,7 @@ include_guard()
# Change these defaults to point to your infrastructure if desired # Change these defaults to point to your infrastructure if desired
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
set(CMKR_TAG "v0.2.33" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) set(CMKR_TAG "v0.2.25" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake

@ -1,18 +0,0 @@
generate_resources(cmkr)
add_custom_target(regenerate-cmake
COMMAND "$<TARGET_FILE:cmkr>" gen
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
)
if(CMAKE_CONFIGURATION_TYPES)
add_custom_target(run-tests
COMMAND "${CMAKE_CTEST_COMMAND}" -C $<CONFIG>
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/tests"
)
else()
add_custom_target(run-tests
COMMAND "${CMAKE_CTEST_COMMAND}"
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/tests"
)
endif()

@ -67,17 +67,15 @@ _Note_: It is generally discouraged to disable the `C` language, unless you are
## Conditions ## Conditions
You can specify your own named conditions and use them in any `condition` field: You can specify your own conditions and use them in any `condition` field:
```toml ```toml
[conditions] [conditions]
ptr64 = "CMAKE_SIZEOF_VOID_P EQUAL 8" arch64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
ptr32 = "CMAKE_SIZEOF_VOID_P EQUAL 4" arch32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
``` ```
This will make the `ptr64` and `ptr32` conditions available with their respective CMake expressions. This will make the `arch64` and `arch32` conditions available with their respective CMake expressions.
**Note**: condition names can only contain lower-case alphanumeric characters (`[0-9a-z]`) and dashes (`-`).
You can also prefix most keys with `condition.` to represent a conditional: You can also prefix most keys with `condition.` to represent a conditional:
@ -85,11 +83,9 @@ You can also prefix most keys with `condition.` to represent a conditional:
[target] [target]
type = "executable" type = "executable"
sources = ["src/main.cpp"] sources = ["src/main.cpp"]
ptr64.sources = ["src/ptr64_only.cpp"] windows.sources = ["src/windows_specific.cpp"]
``` ```
Instead of a named condition you can also specify a [CMake expression](https://cmake.org/cmake/help/latest/command/if.html#condition-syntax) in quotes. Instances of `$<name>` are replaced with the corresponding condition. For example: `"CONDITIONS_BUILD_TESTS AND $<linux>"` becomes `CONDITIONS_BUILD_TESTS AND (CMAKE_SYSTEM_NAME MATCHES "Linux")` in the final `CMakeLists.txt` file.
### Predefined conditions ### Predefined conditions
The following conditions are predefined (you can override them if you desire): The following conditions are predefined (you can override them if you desire):
@ -109,13 +105,6 @@ clang-any = "CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" OR CMAKE_C_COMPILER_ID MATC
root = "CMKR_ROOT_PROJECT" root = "CMKR_ROOT_PROJECT"
x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8" x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
x32 = "CMAKE_SIZEOF_VOID_P EQUAL 4" x32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
android = "ANDROID"
apple = "APPLE"
bsd = "BSD"
cygwin = "CYGWIN"
ios = "IOS"
xcode = "XCODE"
wince = "WINCE"
``` ```
## Subdirectories ## Subdirectories
@ -142,7 +131,7 @@ MYPROJECT_SPECIAL_OPTION = { value = true, help = "Docstring for this option." }
MYPROJECT_BUILD_EXAMPLES = "root" MYPROJECT_BUILD_EXAMPLES = "root"
``` ```
Options correspond to [CMake cache variables](https://cmake.org/cmake/help/book/mastering-cmake/chapter/CMake%20Cache.html) that can be used to customize your project at configure-time. You can configure with `cmake -DMYPROJECT_BUILD_TESTS=ON` to enable the option. Every option automatically gets a corresponding [condition](#conditions). Additionally, a normalized condition is created based on the `[project].name` (i.e. `MYPROJECT_BUILD_TESTS` becomes `build-tests`). Options correspond to [CMake cache variables](https://cmake.org/cmake/help/book/mastering-cmake/chapter/CMake%20Cache.html) that can be used to customize your project at configure-time. You can configure with `cmake -DMYPROJECT_BUILD_TESTS=ON` to enable the option. Every option automatically gets a corresponding [condition](#conditions).
The special value `root` can be used to set the option to `true` if the project is compiled as the root project (it will be `false` if someone is including your project via `[fetch-content]` or `[subdir]`). The special value `root` can be used to set the option to `true` if the project is compiled as the root project (it will be `false` if someone is including your project via `[fetch-content]` or `[subdir]`).
@ -160,8 +149,8 @@ Variables emit a [`set`](https://cmake.org/cmake/help/latest/command/set.html) a
```toml ```toml
[vcpkg] [vcpkg]
version = "2024.03.25" version = "2021.05.12"
url = "https://github.com/microsoft/vcpkg/archive/refs/tags/2024.03.25.tar.gz" url = "https://github.com/microsoft/vcpkg/archive/refs/tags/2021.05.12.tar.gz"
packages = ["fmt", "zlib"] packages = ["fmt", "zlib"]
``` ```
@ -191,7 +180,6 @@ git = "https://github.com/myuser/gitcontent"
tag = "v0.1" tag = "v0.1"
shallow = false shallow = false
system = false system = false
subdir = ""
[fetch-content.svncontent] [fetch-content.svncontent]
condition = "mycondition" condition = "mycondition"
@ -207,8 +195,6 @@ hash = "SHA1 502a4e25b8b209889c99c7fa0732102682c2e4ff"
sha1 = "502a4e25b8b209889c99c7fa0732102682c2e4ff" sha1 = "502a4e25b8b209889c99c7fa0732102682c2e4ff"
``` ```
Table keys that match CMake variable names (`[A-Z_]+`) will be passed to the [`FetchContent_Declare`](https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare) command.
## Targets ## Targets
```toml ```toml

@ -19,7 +19,7 @@ description = "Dependencies from vcpkg"
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions # See https://github.com/microsoft/vcpkg/releases for vcpkg versions
# See https://vcpkg.io/en/packages.html for available packages # See https://vcpkg.io/en/packages.html for available packages
[vcpkg] [vcpkg]
version = "2024.03.25" version = "2022.11.14"
packages = ["fmt"] packages = ["fmt"]
[find-package] [find-package]

@ -155,8 +155,7 @@ struct Content {
Condition<std::string> cmake_after; Condition<std::string> cmake_after;
ConditionVector include_before; ConditionVector include_before;
ConditionVector include_after; ConditionVector include_after;
bool system = false; bool system;
std::string subdir;
}; };
enum MsvcRuntimeType { enum MsvcRuntimeType {
@ -208,7 +207,6 @@ struct Project {
Project(const Project *parent, const std::string &path, bool build); Project(const Project *parent, const std::string &path, bool build);
const Project *root() const; const Project *root() const;
bool cmake_minimum_version(int major, int minor) const; bool cmake_minimum_version(int major, int minor) const;
static bool is_condition_name(const std::string &name);
}; };
bool is_root_path(const std::string &path); bool is_root_path(const std::string &path);

@ -54,85 +54,57 @@ static std::string format(const char *format, const tsl::ordered_map<std::string
return s; return s;
} }
static std::vector<fs::path> expand_cmake_path(const fs::path &source_path, const fs::path &toml_dir, bool is_root_project) { static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool is_root_project) {
auto is_subdir = [](fs::path p, const fs::path &root) { std::vector<std::string> temp;
while (true) {
if (p == root) { auto extract_suffix = [](const fs::path &base, const fs::path &full) {
return true; auto fullpath = full.string();
} auto base_len = base.string().length();
auto parent = p.parent_path(); auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len);
if (parent == p) { return delet;
break;
}
p = parent;
}
return false;
}; };
if (!is_subdir(fs::absolute(toml_dir / source_path), toml_dir)) {
throw std::runtime_error("Path traversal is not allowed: " + source_path.string());
}
// Split the path at the first period (since fs::path::stem() and fs::path::extension() split at the last period) auto stem = name.filename().stem().string();
std::string stem, extension; auto ext = name.extension();
auto filename = source_path.filename().string();
auto dot_position = filename.find('.');
if (dot_position != std::string::npos) {
stem = filename.substr(0, dot_position);
extension = filename.substr(dot_position);
} else {
stem = filename;
}
if (is_root_project && stem == "**" && !source_path.has_parent_path()) { if (is_root_project && stem == "**" && name == name.filename()) {
throw std::runtime_error("Recursive globbing not allowed in project root: " + source_path.string()); throw std::runtime_error("Recursive globbing not allowed in project root: " + name.string());
} }
auto has_extension = [](const fs::path &file_path, const std::string &extension) {
auto path = file_path.string();
return path.rfind(extension) == path.length() - extension.length();
};
std::vector<fs::path> paths;
if (stem == "*") { if (stem == "*") {
for (const auto &f : fs::directory_iterator(toml_dir / source_path.parent_path(), fs::directory_options::follow_directory_symlink)) { for (const auto &f : fs::directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) {
if (!f.is_directory() && has_extension(f.path(), extension)) { if (!f.is_directory() && f.path().extension() == ext) {
paths.push_back(fs::relative(f, toml_dir)); temp.push_back(extract_suffix(toml_dir, f));
} }
} }
} else if (stem == "**") { } else if (stem == "**") {
for (const auto &f : for (const auto &f : fs::recursive_directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) {
fs::recursive_directory_iterator(toml_dir / source_path.parent_path(), fs::directory_options::follow_directory_symlink)) { if (!f.is_directory() && f.path().extension() == ext) {
if (!f.is_directory() && has_extension(f.path(), extension)) { temp.push_back(extract_suffix(toml_dir, f.path()));
paths.push_back(fs::relative(f, toml_dir));
} }
} }
} else { } else {
paths.push_back(source_path); temp.push_back(name.string());
} }
// Normalize all paths to work with CMake (it needs a / on Windows as well)
return paths; for (auto &path : temp) {
std::replace(path.begin(), path.end(), '\\', '/');
}
// Sort paths alphabetically for consistent cross-OS generation
std::sort(temp.begin(), temp.end());
return temp;
} }
static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir, bool is_root_project) { static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir, bool is_root_project) {
std::vector<std::string> paths; // TODO: add duplicate checking
std::vector<std::string> result;
for (const auto &src : sources) { for (const auto &src : sources) {
auto expanded = expand_cmake_path(src, toml_dir, is_root_project); auto expanded = expand_cmake_path(src, toml_dir, is_root_project);
for (const auto &f : expanded) { for (const auto &f : expanded) {
paths.push_back(f.string()); result.push_back(f);
} }
} }
return result;
// Normalize all paths to work with CMake (it needs a / on Windows as well)
for (auto &path : paths) {
std::replace(path.begin(), path.end(), '\\', '/');
}
// Sort paths alphabetically for consistent cross-OS generation
std::sort(paths.begin(), paths.end());
// TODO: remove duplicates
return paths;
} }
static void create_file(const fs::path &path, const std::string &contents) { static void create_file(const fs::path &path, const std::string &contents) {
@ -542,13 +514,15 @@ struct Generator {
if (!value.empty()) { if (!value.empty()) {
for (const auto &itr : value) { for (const auto &itr : value) {
const auto &condition = itr.first; const auto &condition = itr.first;
auto endif = if_condition(condition); if (!condition.empty()) {
cmd("if", condition)(RawArg(project.conditions.at(condition)));
}
if (!itr.second.empty()) { if (!itr.second.empty()) {
fn(condition, itr.second); fn(condition, itr.second);
} }
if (endif) { if (!condition.empty()) {
cmd("endif")().endl(); cmd("endif")().endl();
} else if (!itr.second.empty()) { } else if (!itr.second.empty()) {
endl(); endl();
@ -564,68 +538,6 @@ struct Generator {
void conditional_cmake(const parser::Condition<std::string> &cmake) { void conditional_cmake(const parser::Condition<std::string> &cmake) {
handle_condition(cmake, [this](const std::string &, const std::string &cmake) { inject_cmake(cmake); }); handle_condition(cmake, [this](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
} }
bool if_condition(const std::string &condition) {
if (condition.empty()) {
return false;
}
auto found = project.conditions.find(condition);
if (found == project.conditions.end()) {
if (cmkr::parser::Project::is_condition_name(condition)) {
// NOTE: this should have been caught by the parser already
throw std::runtime_error("Condition '" + condition + "' is not defined");
}
cmd("if", "NOTE: unnamed condition")(RawArg(cmake_condition(condition)));
} else {
cmd("if", condition)(RawArg(cmake_condition(found->second)));
}
return true;
}
private:
std::string cmake_condition(const std::string &condition) {
// HACK: this replaces '$<name>' with the value of the 'name' condition. We can safely
// reuse the generator expression syntax, because it is not valid in CMake conditions.
// TODO: properly handle quoted arguments (using a simple state machine):
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#quoted-argument
std::string result = "";
bool in_replacement = false;
std::string temp;
for (size_t i = 0; i < condition.length(); i++) {
if (in_replacement) {
if (condition[i] == '>') {
in_replacement = false;
if (temp.empty()) {
throw std::runtime_error("Empty replacement in condition '" + condition + "'");
}
auto found = project.conditions.find(temp);
if (found == project.conditions.end()) {
throw std::runtime_error("Unknown condition '" + temp + "' in replacement");
}
auto has_space = found->second.find(' ') != std::string::npos;
if (has_space) {
result += '(';
}
result += found->second;
if (has_space) {
result += ')';
}
temp.clear();
} else {
temp += condition[i];
}
} else if (condition[i] == '$' && i + 1 < condition.length() && condition[i + 1] == '<') {
i++;
in_replacement = true;
} else {
result += condition[i];
}
}
if (!temp.empty()) {
throw std::runtime_error("Unterminated replacement in condition '" + condition + "'");
}
return result;
}
}; };
struct ConditionScope { struct ConditionScope {
@ -633,7 +545,10 @@ struct ConditionScope {
bool endif = false; bool endif = false;
ConditionScope(Generator &gen, const std::string &condition) : gen(gen) { ConditionScope(Generator &gen, const std::string &condition) : gen(gen) {
endif = gen.if_condition(condition); if (!condition.empty()) {
gen.cmd("if", condition)(RawArg(gen.project.conditions.at(condition)));
endif = true;
}
} }
ConditionScope(const ConditionScope &) = delete; ConditionScope(const ConditionScope &) = delete;
@ -681,10 +596,7 @@ static std::string vcpkg_escape_identifier(const std::string &name) {
ch = '-'; ch = '-';
} }
if (ch >= 'A' && ch <= 'Z') { escaped += std::tolower(ch);
ch += ('a' - 'A');
}
escaped += ch;
} }
if (!vcpkg_valid_identifier(escaped)) { if (!vcpkg_valid_identifier(escaped)) {
throw std::runtime_error("The escaped project name '" + escaped + "' is not usable with [vcpkg]"); throw std::runtime_error("The escaped project name '" + escaped + "' is not usable with [vcpkg]");
@ -705,7 +617,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
parser::Project project(parent_project, path, false); parser::Project project(parent_project, path, false);
for (const auto &lang : project.project_languages) { for (auto const &lang : project.project_languages) {
if (known_languages.find(lang) == known_languages.end()) { if (known_languages.find(lang) == known_languages.end()) {
if (project.project_allow_unknown_languages) { if (project.project_allow_unknown_languages) {
printf("[warning] Unknown language '%s' specified\n", lang.c_str()); printf("[warning] Unknown language '%s' specified\n", lang.c_str());
@ -731,6 +643,23 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (is_root_project) { if (is_root_project) {
cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl(); cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl();
if (project.project_msvc_runtime != parser::msvc_last) {
comment("Enable support for MSVC_RUNTIME_LIBRARY");
cmd("cmake_policy")("SET", "CMP0091", "NEW");
switch (project.project_msvc_runtime) {
case parser::msvc_dynamic:
cmd("set")("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL");
break;
case parser::msvc_static:
cmd("set")("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded$<$<CONFIG:Debug>:Debug>");
break;
default:
break;
}
endl();
}
// clang-format on // clang-format on
if (!project.allow_in_tree) { if (!project.allow_in_tree) {
// clang-format off // clang-format off
@ -761,26 +690,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("endif")().endl(); cmd("endif")().endl();
// clang-format on // clang-format on
if (project.project_msvc_runtime != parser::msvc_last) {
comment("Enable support for MSVC_RUNTIME_LIBRARY");
cmd("cmake_policy")("SET", "CMP0091", "NEW");
// clang-format off
cmd("if")("NOT", "DEFINED", "CMAKE_MSVC_RUNTIME_LIBRARY");
switch (project.project_msvc_runtime) {
case parser::msvc_dynamic:
cmd("set")("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL");
break;
case parser::msvc_static:
cmd("set")("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded$<$<CONFIG:Debug>:Debug>");
break;
default:
break;
}
cmd("endif")().endl();
// clang-format on
}
fs::path cmkr_include(project.cmkr_include); fs::path cmkr_include(project.cmkr_include);
if (!project.cmkr_include.empty() && !fs::exists(cmkr_include) && cmkr_include.is_relative()) { if (!project.cmkr_include.empty() && !fs::exists(cmkr_include) && cmkr_include.is_relative()) {
create_file(cmkr_include, resources::cmkr); create_file(cmkr_include, resources::cmkr);
@ -905,20 +814,19 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG"); cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG");
cmd("include")("FetchContent"); cmd("include")("FetchContent");
comment("Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP"); comment("Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP");
// clang-format off
cmd("if")("POLICY", "CMP0135"); cmd("if")("POLICY", "CMP0135");
cmd("cmake_policy")("SET", "CMP0135", "NEW"); cmd("cmake_policy")("SET", "CMP0135", "NEW");
cmd("endif")(); cmd("endif")();
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")..."); // clang-format on
cmd("FetchContent_Declare")("vcpkg", "URL", url); cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt cmd("FetchContent_Declare")("vcpkg", "URL", url);
cmd("FetchContent_GetProperties")("vcpkg"); // Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
cmd("if")("NOT", "vcpkg_POPULATED"); cmd("FetchContent_GetProperties")("vcpkg");
cmd("FetchContent_Populate")("vcpkg"); cmd("if")("NOT", "vcpkg_POPULATED");
cmd("if")("CMAKE_HOST_SYSTEM_NAME", "STREQUAL", "Darwin", "AND", "CMAKE_OSX_ARCHITECTURES", "STREQUAL", RawArg("\"\"")); cmd("FetchContent_Populate")("vcpkg");
cmd("set")("CMAKE_OSX_ARCHITECTURES", "${CMAKE_HOST_SYSTEM_PROCESSOR}", "CACHE", "STRING", RawArg("\"\""), "FORCE"); cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")(); cmd("endif")();
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")();
cmd("endif")(); cmd("endif")();
endl(); endl();
// clang-format on // clang-format on
@ -992,20 +900,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
ofs << "}\n"; ofs << "}\n";
} }
if (!project.packages.empty()) {
comment("Packages");
for (const auto &dep : project.packages) {
auto version = dep.version;
if (version == "*")
version.clear();
auto required = dep.required ? "REQUIRED" : "";
auto config = dep.config ? "CONFIG" : "";
auto components = std::make_pair("COMPONENTS", dep.components);
ConditionScope cs(gen, dep.condition);
cmd("find_package")(dep.name, version, required, config, components).endl();
}
}
if (!project.contents.empty()) { if (!project.contents.empty()) {
cmd("include")("FetchContent").endl(); cmd("include")("FetchContent").endl();
if (!project.root()->vcpkg.enabled()) { if (!project.root()->vcpkg.enabled()) {
@ -1041,6 +935,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
} }
} }
if (!project.packages.empty()) {
comment("Packages");
for (const auto &dep : project.packages) {
auto version = dep.version;
if (version == "*")
version.clear();
auto required = dep.required ? "REQUIRED" : "";
auto config = dep.config ? "CONFIG" : "";
auto components = std::make_pair("COMPONENTS", dep.components);
ConditionScope cs(gen, dep.condition);
cmd("find_package")(dep.name, version, required, config, components).endl();
}
}
auto add_subdir = [&](const std::string &dir) { auto add_subdir = [&](const std::string &dir) {
// clang-format off // clang-format off
comment("Subdirectory: " + dir); comment("Subdirectory: " + dir);

@ -140,7 +140,7 @@ class TomlChecker {
for (const auto &itr : m_v.as_table()) { for (const auto &itr : m_v.as_table()) {
const auto &ky = itr.first; const auto &ky = itr.first;
if (m_conditionVisited.contains(ky)) { if (m_conditionVisited.contains(ky)) {
if (!conditions.contains(ky) && Project::is_condition_name(ky)) { if (!conditions.contains(ky)) {
throw_key_error("Unknown condition '" + ky + "'", ky, itr.second); throw_key_error("Unknown condition '" + ky + "'", ky, itr.second);
} }
@ -160,7 +160,7 @@ class TomlChecker {
throw_key_error("Unknown key '" + ky + "'", ky, itr.second); throw_key_error("Unknown key '" + ky + "'", ky, itr.second);
} else if (ky == "condition") { } else if (ky == "condition") {
std::string condition = itr.second.as_string(); std::string condition = itr.second.as_string();
if (!conditions.contains(condition) && Project::is_condition_name(condition)) { if (!conditions.contains(condition)) {
throw_key_error("Unknown condition '" + condition + "'", condition, itr.second); throw_key_error("Unknown condition '" + condition + "'", condition, itr.second);
} }
} }
@ -274,13 +274,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
conditions["root"] = R"cmake(CMKR_ROOT_PROJECT)cmake"; conditions["root"] = R"cmake(CMKR_ROOT_PROJECT)cmake";
conditions["x64"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 8)cmake"; conditions["x64"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 8)cmake";
conditions["x32"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 4)cmake"; conditions["x32"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 4)cmake";
conditions["android"] = R"cmake(ANDROID)cmake";
conditions["apple"] = R"cmake(APPLE)cmake";
conditions["bsd"] = R"cmake(BSD)cmake";
conditions["cygwin"] = R"cmake(CYGWIN)cmake";
conditions["ios"] = R"cmake(IOS)cmake";
conditions["xcode"] = R"cmake(XCODE)cmake";
conditions["wince"] = R"cmake(WINCE)cmake";
} else { } else {
conditions = parent->conditions; conditions = parent->conditions;
templates = parent->templates; templates = parent->templates;
@ -289,9 +282,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
if (checker.contains("conditions")) { if (checker.contains("conditions")) {
auto conds = toml::find<decltype(conditions)>(toml, "conditions"); auto conds = toml::find<decltype(conditions)>(toml, "conditions");
for (const auto &cond : conds) { for (const auto &cond : conds) {
if (!is_condition_name(cond.first)) {
throw_key_error("Invalid condition name '" + cond.first + "'", cond.first, toml::find(toml::find(toml, "conditions"), cond.first));
}
conditions[cond.first] = cond.second; conditions[cond.first] = cond.second;
} }
} }
@ -382,25 +372,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
} }
if (checker.contains("options")) { if (checker.contains("options")) {
auto normalize = [](const std::string &name) {
std::string normalized;
for (char ch : name) {
if (ch == '_') {
normalized += '-';
} else if (ch >= 'A' && ch <= 'Z') {
ch += ('a' - 'A');
normalized += ch;
} else if (ch == '-' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z')) {
normalized += ch;
} else {
// Ignore all other characters
}
}
return normalized;
};
auto nproject_prefix = normalize(project_name);
nproject_prefix += '-';
using opts_map = tsl::ordered_map<std::string, TomlBasicValue>; using opts_map = tsl::ordered_map<std::string, TomlBasicValue>;
const auto &opts = toml::find<opts_map>(toml, "options"); const auto &opts = toml::find<opts_map>(toml, "options");
for (const auto &itr : opts) { for (const auto &itr : opts) {
@ -438,21 +409,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
throw_key_error(toml::concat_to_string("Unsupported value type: ", itr.second.type()), itr.first, itr.second); throw_key_error(toml::concat_to_string("Unsupported value type: ", itr.second.type()), itr.first, itr.second);
} }
options.push_back(o); options.push_back(o);
// Add a condition matching the option name
conditions.emplace(o.name, o.name); conditions.emplace(o.name, o.name);
// Add an implicit condition for the option
auto ncondition = normalize(o.name);
if (ncondition.find(nproject_prefix) == 0) {
ncondition = ncondition.substr(nproject_prefix.size());
}
if (!ncondition.empty()) {
if (conditions.contains(ncondition)) {
print_key_warning("Option '" + o.name + "' would create a condition '" + ncondition + "' that already exists", o.name, value);
}
conditions.emplace(ncondition, o.name);
}
} }
} }
@ -543,18 +500,13 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
key = "URL"; key = "URL";
} else if (hash_algorithms.contains(key)) { } else if (hash_algorithms.contains(key)) {
std::string algo; std::string algo;
for (auto ch : key) { for (auto c : key) {
if (ch >= 'a' && ch <= 'z') { algo.push_back(std::toupper(c));
ch -= ('a' - 'A');
}
algo.push_back(ch);
} }
key = "URL_HASH"; key = "URL_HASH";
value = algo + "=" + value; value = algo + "=" + value;
} else if (key == "hash") { } else if (key == "hash") {
key = "URL_HASH"; key = "URL_HASH";
} else if (key == "subdir") {
key = "SOURCE_SUBDIR";
} else if (is_cmake_arg(key)) { } else if (is_cmake_arg(key)) {
// allow passthrough of ExternalProject options // allow passthrough of ExternalProject options
} else if (!c.visisted(key)) { } else if (!c.visisted(key)) {
@ -685,28 +637,28 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
Condition<std::string> msvc_runtime; Condition<std::string> msvc_runtime;
t.optional("msvc-runtime", msvc_runtime); t.optional("msvc-runtime", msvc_runtime);
for (const auto &cond_itr : msvc_runtime) { for (const auto &condItr : msvc_runtime) {
switch (parse_msvcRuntimeType(cond_itr.second)) { switch (parse_msvcRuntimeType(condItr.second)) {
case msvc_dynamic: case msvc_dynamic:
target.properties[cond_itr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL"; target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL";
break; break;
case msvc_static: case msvc_static:
target.properties[cond_itr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>"; target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>";
break; break;
default: { default: {
std::string error = "Unknown runtime '" + cond_itr.second + "'\n"; std::string error = "Unknown runtime '" + condItr.second + "'\n";
error += "Available types:\n"; error += "Available types:\n";
for (std::string type_name : msvcRuntimeTypeNames) { for (std::string type_name : msvcRuntimeTypeNames) {
error += " - " + type_name + "\n"; error += " - " + type_name + "\n";
} }
error.pop_back(); // Remove last newline error.pop_back(); // Remove last newline
const TomlBasicValue *report; const TomlBasicValue *report;
if (cond_itr.first.empty()) { if (condItr.first.empty()) {
report = &t.find("msvc-runtime"); report = &t.find("msvc-runtime");
} else { } else {
report = &t.find(cond_itr.first).as_table().find("msvc-runtime").value(); report = &t.find(condItr.first).as_table().find("msvc-runtime").value();
} }
throw_key_error(error, cond_itr.second, *report); throw_key_error(error, condItr.second, *report);
} }
} }
} }
@ -879,15 +831,6 @@ bool Project::cmake_minimum_version(int major, int minor) const {
return std::tie(root_major, root_minor) >= std::tie(major, minor); return std::tie(root_major, root_minor) >= std::tie(major, minor);
} }
bool Project::is_condition_name(const std::string &name) {
for (auto ch : name) {
if (!std::isalnum(ch) && ch != '-' && ch != '_') {
return false;
}
}
return true;
}
bool is_root_path(const std::string &path) { bool is_root_path(const std::string &path) {
const auto toml_path = fs::path(path) / "cmake.toml"; const auto toml_path = fs::path(path) / "cmake.toml";
if (!fs::exists(toml_path)) { if (!fs::exists(toml_path)) {

@ -2,9 +2,6 @@
name = "conditions" name = "conditions"
cmake-after = "set(CUSTOM ON)" cmake-after = "set(CUSTOM ON)"
[options]
CONDITIONS_BUILD_TESTS = "root"
[conditions] [conditions]
custom = "CUSTOM" custom = "CUSTOM"
@ -18,8 +15,6 @@ macos.cmake-after = "message(STATUS macos-after)"
linux.cmake-after = "message(STATUS linux-after)" linux.cmake-after = "message(STATUS linux-after)"
unix.cmake-after = "message(STATUS unix-after)" unix.cmake-after = "message(STATUS unix-after)"
custom.cmake-after = "message(STATUS custom-after)" custom.cmake-after = "message(STATUS custom-after)"
build-tests.cmake-after = "message(STATUS build-tests)"
"CONDITIONS_BUILD_TESTS AND $<linux>".cmake-after = "message(STATUS linux-tests)"
[target.example.properties] [target.example.properties]
AUTOMOC = false AUTOMOC = false

@ -4,4 +4,4 @@
namespace mylib { namespace mylib {
std::string message(); std::string message();
} // namespace mylib }

@ -7,7 +7,7 @@ description = "Dependencies from vcpkg"
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions # See https://github.com/microsoft/vcpkg/releases for vcpkg versions
# See https://vcpkg.io/en/packages.html for available packages # See https://vcpkg.io/en/packages.html for available packages
[vcpkg] [vcpkg]
version = "2024.03.25" version = "2022.11.14"
packages = ["fmt"] packages = ["fmt"]
[find-package] [find-package]

4
third_party/CMakeLists.txt generated vendored

@ -16,3 +16,7 @@ target_include_directories(toml11 INTERFACE toml11-3.6.0)
# https://github.com/mpark/variant (BSL-1.0) # https://github.com/mpark/variant (BSL-1.0)
add_library(mpark_variant INTERFACE) add_library(mpark_variant INTERFACE)
target_include_directories(mpark_variant INTERFACE variant-1.4.0/include) target_include_directories(mpark_variant INTERFACE variant-1.4.0/include)
# https://github.com/nlohmann/json (MIT)
add_library(nlohmann_json INTERFACE)
target_include_directories(nlohmann_json INTERFACE nlohmann-3.9.1/include)

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2021 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save