Compare commits

...

36 Commits

Author SHA1 Message Date
Duncan Ogilvie 0a887c08f8 Bump to 0.2.33
5 months ago
Duncan Ogilvie 9f2cfe4083
Merge pull request #146 from build-cpp/improved-conditions
5 months ago
Duncan Ogilvie 15ad1dc6a7 Support nested condition syntax for named conditions
5 months ago
Duncan Ogilvie dabffeaed9 Add back the option name as a named condition
5 months ago
Duncan Ogilvie 59fe5c38b8 Consider PROJECT_OPTION to be a valid condition
5 months ago
Duncan Ogilvie dcdc7ac410 Add a few more default named conditions
5 months ago
Duncan Ogilvie 88377a8bfb Bump to 0.2.32
5 months ago
Duncan Ogilvie 3a9685be82
Merge pull request #145 from build-cpp/system-bug
5 months ago
Duncan Ogilvie 5576789c18 Fix uninitialized 'system' field for fetch-content
5 months ago
Duncan Ogilvie a362978f99 Bump to 0.2.31
6 months ago
Duncan Ogilvie a2f33297e4
Merge pull request #144 from build-cpp/msvc-runtime-subproject
6 months ago
Duncan Ogilvie 674c976647 Do not set CMAKE_MSVC_RUNTIME_LIBRARY if it's already set
6 months ago
Duncan Ogilvie 714a666e88
Merge pull request #143 from build-cpp/project-improvements
6 months ago
Duncan Ogilvie d613e433d5 Skip linting on pull requests for the same repository
6 months ago
Duncan Ogilvie 7c828d8740 Add custom targets for running cmkr/tests
6 months ago
Duncan Ogilvie 559f750c89 Fix undefined behavior with std::tolower/toupper
6 months ago
Duncan Ogilvie 4b6b72874e Remove nlohmann_json
6 months ago
Duncan Ogilvie 6bffa7cc71 Bump to 0.2.30
6 months ago
Duncan Ogilvie f415b432ed Also run linting on pull requests
6 months ago
Duncan Ogilvie 6c46664336
Merge pull request #142 from anthonyprintup/globbing-extension-fix
6 months ago
Duncan Ogilvie f32aac63aa Review improvements
6 months ago
Anthony Printup 33d4cc4156
Fixed globbing when multiple extensions are present in the file name
6 months ago
Duncan Ogilvie 9288c8a87d Bump to 0.2.29
8 months ago
Duncan Ogilvie e36eee4420
Merge pull request #140 from build-cpp/improved-conditions
8 months ago
Duncan Ogilvie 53820c9f65 Support $<condition> in unnamed conditions
8 months ago
Duncan Ogilvie ff7e4b8f23 Allow arbitrary CMake expressions as conditions
8 months ago
Duncan Ogilvie 1072cb44e2 Bump to 0.2.28
8 months ago
Duncan Ogilvie 926fc15fa2
Merge pull request #139 from build-cpp/vcpkg-bump
8 months ago
Duncan Ogilvie e2c929adb0 Fix out-of-the-box vcpkg experience on macos
8 months ago
Duncan Ogilvie 0dec7ca4d5 Bump to the latest vcpkg
8 months ago
Duncan Ogilvie 45e346b4a3 Bump to 0.2.27
8 months ago
Duncan Ogilvie 7165986190
Merge pull request #138 from build-cpp/find-package-first
8 months ago
Duncan Ogilvie be0ef6d615 Generate find-package before fetch-content
8 months ago
Duncan Ogilvie d607b9028c Bump to 0.2.26
1 year ago
Duncan Ogilvie 099b14552c
Merge pull request #131 from build-cpp/fetch-subdir
1 year ago
Duncan Ogilvie 771c80a41a Add [fetch-content].subdir
1 year ago

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

@ -1,9 +1,11 @@
name: lint name: lint
on: [push] on: [push, pull_request]
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:
@ -27,6 +29,8 @@ 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.25 0.2.33
DESCRIPTION DESCRIPTION
"CMakeLists generator from TOML" "CMakeLists generator from TOML"
) )
@ -58,12 +58,9 @@ generate_documentation()
# Target: cmkr # Target: cmkr
set(cmkr_SOURCES set(cmkr_SOURCES
"src/arguments.cpp" cmake.toml
"src/build.cpp" "cmake/cmkr.cmake"
"src/cmake_generator.cpp" "cmake/version.hpp.in"
"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"
@ -71,9 +68,12 @@ set(cmkr_SOURCES
"include/help.hpp" "include/help.hpp"
"include/literals.hpp" "include/literals.hpp"
"include/project_parser.hpp" "include/project_parser.hpp"
"cmake/cmkr.cmake" "src/arguments.cpp"
"cmake/version.hpp.in" "src/build.cpp"
cmake.toml "src/cmake_generator.cpp"
"src/help.cpp"
"src/main.cpp"
"src/project_parser.cpp"
) )
add_executable(cmkr) add_executable(cmkr)
@ -94,7 +94,6 @@ 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)
@ -103,7 +102,7 @@ if(NOT CMKR_VS_STARTUP_PROJECT)
endif() endif()
set(CMKR_TARGET cmkr) set(CMKR_TARGET cmkr)
generate_resources(${CMKR_TARGET}) include("cmake/custom_targets.cmake")
install( install(
TARGETS TARGETS

@ -4,7 +4,7 @@ cmkr-include = false
[project] [project]
name = "cmkr" name = "cmkr"
version = "0.2.25" version = "0.2.33"
description = "CMakeLists generator from TOML" description = "CMakeLists generator from TOML"
languages = ["CXX"] languages = ["CXX"]
include-after = [ include-after = [
@ -36,11 +36,8 @@ link-libraries = [
"ghc_filesystem", "ghc_filesystem",
"mpark_variant", "mpark_variant",
"ordered_map", "ordered_map",
"nlohmann_json",
] ]
cmake-after = """ include-after = ["cmake/custom_targets.cmake"]
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.25" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) set(CMKR_TAG "v0.2.33" 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

@ -0,0 +1,18 @@
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,15 +67,17 @@ _Note_: It is generally discouraged to disable the `C` language, unless you are
## Conditions ## Conditions
You can specify your own conditions and use them in any `condition` field: You can specify your own named conditions and use them in any `condition` field:
```toml ```toml
[conditions] [conditions]
arch64 = "CMAKE_SIZEOF_VOID_P EQUAL 8" ptr64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
arch32 = "CMAKE_SIZEOF_VOID_P EQUAL 4" ptr32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
``` ```
This will make the `arch64` and `arch32` conditions available with their respective CMake expressions. This will make the `ptr64` and `ptr32` 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:
@ -83,9 +85,11 @@ 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"]
windows.sources = ["src/windows_specific.cpp"] ptr64.sources = ["src/ptr64_only.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):
@ -105,6 +109,13 @@ 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
@ -131,7 +142,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). 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`).
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]`).
@ -149,8 +160,8 @@ Variables emit a [`set`](https://cmake.org/cmake/help/latest/command/set.html) a
```toml ```toml
[vcpkg] [vcpkg]
version = "2021.05.12" version = "2024.03.25"
url = "https://github.com/microsoft/vcpkg/archive/refs/tags/2021.05.12.tar.gz" url = "https://github.com/microsoft/vcpkg/archive/refs/tags/2024.03.25.tar.gz"
packages = ["fmt", "zlib"] packages = ["fmt", "zlib"]
``` ```
@ -180,6 +191,7 @@ 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"
@ -195,6 +207,8 @@ 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 = "2022.11.14" version = "2024.03.25"
packages = ["fmt"] packages = ["fmt"]
[find-package] [find-package]

@ -155,7 +155,8 @@ 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; bool system = false;
std::string subdir;
}; };
enum MsvcRuntimeType { enum MsvcRuntimeType {
@ -207,6 +208,7 @@ 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,57 +54,85 @@ static std::string format(const char *format, const tsl::ordered_map<std::string
return s; return s;
} }
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool is_root_project) { static std::vector<fs::path> expand_cmake_path(const fs::path &source_path, const fs::path &toml_dir, bool is_root_project) {
std::vector<std::string> temp; auto is_subdir = [](fs::path p, const fs::path &root) {
while (true) {
auto extract_suffix = [](const fs::path &base, const fs::path &full) { if (p == root) {
auto fullpath = full.string(); return true;
auto base_len = base.string().length(); }
auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len); auto parent = p.parent_path();
return delet; if (parent == p) {
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());
}
auto stem = name.filename().stem().string(); // Split the path at the first period (since fs::path::stem() and fs::path::extension() split at the last period)
auto ext = name.extension(); std::string stem, 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 == "**" && name == name.filename()) { if (is_root_project && stem == "**" && !source_path.has_parent_path()) {
throw std::runtime_error("Recursive globbing not allowed in project root: " + name.string()); throw std::runtime_error("Recursive globbing not allowed in project root: " + source_path.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 / name.parent_path(), fs::directory_options::follow_directory_symlink)) { for (const auto &f : fs::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)); paths.push_back(fs::relative(f, toml_dir));
} }
} }
} else if (stem == "**") { } else if (stem == "**") {
for (const auto &f : fs::recursive_directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) { for (const auto &f :
if (!f.is_directory() && f.path().extension() == ext) { fs::recursive_directory_iterator(toml_dir / source_path.parent_path(), fs::directory_options::follow_directory_symlink)) {
temp.push_back(extract_suffix(toml_dir, f.path())); if (!f.is_directory() && has_extension(f.path(), extension)) {
paths.push_back(fs::relative(f, toml_dir));
} }
} }
} else { } else {
temp.push_back(name.string()); paths.push_back(source_path);
}
// Normalize all paths to work with CMake (it needs a / on Windows as well)
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 paths;
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) {
// TODO: add duplicate checking std::vector<std::string> paths;
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) {
result.push_back(f); paths.push_back(f.string());
} }
} }
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) {
@ -514,15 +542,13 @@ 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;
if (!condition.empty()) { auto endif = if_condition(condition);
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 (!condition.empty()) { if (endif) {
cmd("endif")().endl(); cmd("endif")().endl();
} else if (!itr.second.empty()) { } else if (!itr.second.empty()) {
endl(); endl();
@ -538,6 +564,68 @@ 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 {
@ -545,10 +633,7 @@ 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) {
if (!condition.empty()) { endif = gen.if_condition(condition);
gen.cmd("if", condition)(RawArg(gen.project.conditions.at(condition)));
endif = true;
}
} }
ConditionScope(const ConditionScope &) = delete; ConditionScope(const ConditionScope &) = delete;
@ -596,7 +681,10 @@ static std::string vcpkg_escape_identifier(const std::string &name) {
ch = '-'; ch = '-';
} }
escaped += std::tolower(ch); if (ch >= 'A' && ch <= 'Z') {
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]");
@ -617,7 +705,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 (auto const &lang : project.project_languages) { for (const auto &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());
@ -643,23 +731,6 @@ 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
@ -690,6 +761,26 @@ 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);
@ -814,19 +905,20 @@ 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")();
// clang-format on cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")..."); cmd("FetchContent_Declare")("vcpkg", "URL", url);
cmd("FetchContent_Declare")("vcpkg", "URL", url); // Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt cmd("FetchContent_GetProperties")("vcpkg");
cmd("FetchContent_GetProperties")("vcpkg"); cmd("if")("NOT", "vcpkg_POPULATED");
cmd("if")("NOT", "vcpkg_POPULATED"); cmd("FetchContent_Populate")("vcpkg");
cmd("FetchContent_Populate")("vcpkg"); cmd("if")("CMAKE_HOST_SYSTEM_NAME", "STREQUAL", "Darwin", "AND", "CMAKE_OSX_ARCHITECTURES", "STREQUAL", RawArg("\"\""));
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake"); cmd("set")("CMAKE_OSX_ARCHITECTURES", "${CMAKE_HOST_SYSTEM_PROCESSOR}", "CACHE", "STRING", RawArg("\"\""), "FORCE");
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
@ -900,6 +992,20 @@ 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()) {
@ -935,20 +1041,6 @@ 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)) { if (!conditions.contains(ky) && Project::is_condition_name(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)) { if (!conditions.contains(condition) && Project::is_condition_name(condition)) {
throw_key_error("Unknown condition '" + condition + "'", condition, itr.second); throw_key_error("Unknown condition '" + condition + "'", condition, itr.second);
} }
} }
@ -274,6 +274,13 @@ 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;
@ -282,6 +289,9 @@ 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;
} }
} }
@ -372,6 +382,25 @@ 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) {
@ -409,7 +438,21 @@ 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);
}
} }
} }
@ -500,13 +543,18 @@ 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 c : key) { for (auto ch : key) {
algo.push_back(std::toupper(c)); if (ch >= 'a' && ch <= 'z') {
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)) {
@ -637,28 +685,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 &condItr : msvc_runtime) { for (const auto &cond_itr : msvc_runtime) {
switch (parse_msvcRuntimeType(condItr.second)) { switch (parse_msvcRuntimeType(cond_itr.second)) {
case msvc_dynamic: case msvc_dynamic:
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL"; target.properties[cond_itr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL";
break; break;
case msvc_static: case msvc_static:
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>"; target.properties[cond_itr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>";
break; break;
default: { default: {
std::string error = "Unknown runtime '" + condItr.second + "'\n"; std::string error = "Unknown runtime '" + cond_itr.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 (condItr.first.empty()) { if (cond_itr.first.empty()) {
report = &t.find("msvc-runtime"); report = &t.find("msvc-runtime");
} else { } else {
report = &t.find(condItr.first).as_table().find("msvc-runtime").value(); report = &t.find(cond_itr.first).as_table().find("msvc-runtime").value();
} }
throw_key_error(error, condItr.second, *report); throw_key_error(error, cond_itr.second, *report);
} }
} }
} }
@ -831,6 +879,15 @@ 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,6 +2,9 @@
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"
@ -15,6 +18,8 @@ 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 = "2022.11.14" version = "2024.03.25"
packages = ["fmt"] packages = ["fmt"]
[find-package] [find-package]

4
third_party/CMakeLists.txt generated vendored

@ -16,7 +16,3 @@ 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)

@ -1,21 +0,0 @@
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