Compare commits

..

48 Commits

Author SHA1 Message Date
Duncan Ogilvie 88377a8bfb Bump to 0.2.32 2024-07-06 14:03:55 +02:00
Duncan Ogilvie 3a9685be82 Merge pull request #145 from build-cpp/system-bug
Fix uninitialized 'system' field for fetch-content
2024-07-06 14:03:08 +02:00
Duncan Ogilvie 5576789c18 Fix uninitialized 'system' field for fetch-content 2024-07-06 14:02:33 +02:00
Duncan Ogilvie a362978f99 Bump to 0.2.31 2024-06-21 00:33:33 +02:00
Duncan Ogilvie a2f33297e4 Merge pull request #144 from build-cpp/msvc-runtime-subproject
Do not set CMAKE_MSVC_RUNTIME_LIBRARY if it's already set
2024-06-21 00:32:50 +02:00
Duncan Ogilvie 674c976647 Do not set CMAKE_MSVC_RUNTIME_LIBRARY if it's already set 2024-06-21 00:32:30 +02:00
Duncan Ogilvie 714a666e88 Merge pull request #143 from build-cpp/project-improvements
Project improvements
2024-06-01 01:25:26 +02:00
Duncan Ogilvie d613e433d5 Skip linting on pull requests for the same repository 2024-06-01 00:55:31 +02:00
Duncan Ogilvie 7c828d8740 Add custom targets for running cmkr/tests 2024-06-01 00:55:31 +02:00
Duncan Ogilvie 559f750c89 Fix undefined behavior with std::tolower/toupper 2024-06-01 00:55:29 +02:00
Duncan Ogilvie 4b6b72874e Remove nlohmann_json 2024-05-31 22:45:27 +02:00
Duncan Ogilvie 6bffa7cc71 Bump to 0.2.30 2024-05-31 22:36:39 +02:00
Duncan Ogilvie f415b432ed Also run linting on pull requests 2024-05-31 22:34:27 +02:00
Duncan Ogilvie 6c46664336 Merge pull request #142 from anthonyprintup/globbing-extension-fix
Fixed globbing when multiple extensions are present in the file name
2024-05-31 22:31:18 +02:00
Duncan Ogilvie f32aac63aa Review improvements 2024-05-31 22:24:01 +02:00
Anthony Printup 33d4cc4156 Fixed globbing when multiple extensions are present in the file name 2024-05-30 00:36:14 +02:00
Duncan Ogilvie 9288c8a87d Bump to 0.2.29 2024-04-15 03:08:33 +02:00
Duncan Ogilvie e36eee4420 Merge pull request #140 from build-cpp/improved-conditions
Allow arbitrary CMake expressions as conditions
2024-04-15 03:08:02 +02:00
Duncan Ogilvie 53820c9f65 Support $<condition> in unnamed conditions 2024-04-15 03:01:51 +02:00
Duncan Ogilvie ff7e4b8f23 Allow arbitrary CMake expressions as conditions 2024-04-15 02:12:02 +02:00
Duncan Ogilvie 1072cb44e2 Bump to 0.2.28 2024-04-14 00:08:11 +02:00
Duncan Ogilvie 926fc15fa2 Merge pull request #139 from build-cpp/vcpkg-bump
Bump to the latest vcpkg
2024-04-14 00:06:00 +02:00
Duncan Ogilvie e2c929adb0 Fix out-of-the-box vcpkg experience on macos 2024-04-13 23:57:30 +02:00
Duncan Ogilvie 0dec7ca4d5 Bump to the latest vcpkg 2024-04-13 23:21:25 +02:00
Duncan Ogilvie 45e346b4a3 Bump to 0.2.27 2024-03-28 11:38:01 +01:00
Duncan Ogilvie 7165986190 Merge pull request #138 from build-cpp/find-package-first
Generate find-package before fetch-content
2024-03-28 11:37:31 +01:00
Duncan Ogilvie be0ef6d615 Generate find-package before fetch-content 2024-03-28 11:36:15 +01:00
Duncan Ogilvie d607b9028c Bump to 0.2.26 2023-11-09 13:32:05 +01:00
Duncan Ogilvie 099b14552c Merge pull request #131 from build-cpp/fetch-subdir
Add [fetch-content].subdir
2023-11-09 13:31:48 +01:00
Duncan Ogilvie 771c80a41a Add [fetch-content].subdir
Closes #98
2023-11-09 13:28:29 +01:00
Duncan Ogilvie 58c7de1d60 Merge pull request #129 from build-cpp/gitignore-improvement
Improve the default .gitignore that's generated
2023-10-18 13:22:44 +02:00
Duncan Ogilvie a61e4bb999 Merge pull request #128 from build-cpp/language-documentation
Document the supported languages
2023-10-18 13:22:32 +02:00
Duncan Ogilvie 47aaeaf8ce Document the supported languages 2023-10-18 12:27:04 +02:00
Duncan Ogilvie 62609a0088 Improve the default .gitignore that's generated 2023-10-18 12:26:38 +02:00
Duncan Ogilvie e0d8a085db Merge pull request #121 from anthonyprintup/relative-paths
Added support for relative paths in `link-libraries`
2023-09-22 16:13:03 +02:00
Duncan Ogilvie 56da4144ae Merge pull request #125 from anthonyprintup/fix-124
fix(generator): Prevent calling `std::string::back` on an empty string
2023-09-22 16:12:20 +02:00
Duncan Ogilvie 9388b3f29d Merge pull request #123 from anthonyprintup/quotes-fix
Prevent wrapping test command arguments in quotes
2023-09-22 16:12:07 +02:00
Anthony Printup 079644cfeb fix(generator): Prevent calling std::string::back on an empty string
Fixes #124
2023-09-20 02:34:02 +02:00
Anthony Printup a7ca9f04c0 fix(relative-paths): Replaced has_parent_path with a simpler path check 2023-09-20 00:35:41 +02:00
Anthony Printup 339c2aac94 fix(generator): Prevent command arguments from being quoted when generating tests
Fixes #122
2023-09-18 18:36:58 +02:00
Anthony Printup c573c9a76d test(relative-paths): Add the test-library file to libs 2023-09-18 17:49:21 +02:00
Anthony Printup b24a0a2fdc test(relative-paths): Make link-libraries conditional to the Windows platform 2023-09-18 17:49:20 +02:00
Anthony Printup 854e8817c4 test(relative-paths): Remove inlined CMake from cmake.toml 2023-09-18 17:49:11 +02:00
Anthony Printup 1c2947b2fc fix(relative-paths): Prevent processing paths which may contain CMake macros, added a check to see if the library that's being linked exists on disk 2023-09-18 17:35:46 +02:00
Anthony Printup 19e61aa1aa test(relative-paths): Attempt to fix the tests workflow 2023-09-18 16:41:37 +02:00
Anthony Printup bfa1e1fe0d test(relative-paths): Added the latest relative-paths test to the tests workflow 2023-09-18 15:49:17 +02:00
Anthony Printup 56bd78f7ad feat(project-parser): Added support for relative paths in link-libraries
Fixes #116
2023-09-18 15:44:44 +02:00
Anthony Printup 4362b3547f test(relative-paths): Added tests for #116 2023-09-18 15:43:46 +02:00
23 changed files with 416 additions and 25604 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [windows-2022, macos-11, ubuntu-20.04]
os: [windows-2022, macos-latest, ubuntu-20.04]
env:
BUILD_TYPE: 'Release'
CMAKE_GENERATOR: 'Ninja'
+5 -1
View File
@@ -1,9 +1,11 @@
name: lint
on: [push]
on: [push, pull_request]
jobs:
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
steps:
@@ -27,6 +29,8 @@ jobs:
exit 1
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
steps:
+11 -12
View File
@@ -22,7 +22,7 @@ project(cmkr
LANGUAGES
CXX
VERSION
0.2.25
0.2.32
DESCRIPTION
"CMakeLists generator from TOML"
)
@@ -58,12 +58,9 @@ generate_documentation()
# Target: cmkr
set(cmkr_SOURCES
"src/arguments.cpp"
"src/build.cpp"
"src/cmake_generator.cpp"
"src/help.cpp"
"src/main.cpp"
"src/project_parser.cpp"
cmake.toml
"cmake/cmkr.cmake"
"cmake/version.hpp.in"
"include/arguments.hpp"
"include/build.hpp"
"include/cmake_generator.hpp"
@@ -71,9 +68,12 @@ set(cmkr_SOURCES
"include/help.hpp"
"include/literals.hpp"
"include/project_parser.hpp"
"cmake/cmkr.cmake"
"cmake/version.hpp.in"
cmake.toml
"src/arguments.cpp"
"src/build.cpp"
"src/cmake_generator.cpp"
"src/help.cpp"
"src/main.cpp"
"src/project_parser.cpp"
)
add_executable(cmkr)
@@ -94,7 +94,6 @@ target_link_libraries(cmkr PRIVATE
ghc_filesystem
mpark_variant
ordered_map
nlohmann_json
)
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()
set(CMKR_TARGET cmkr)
generate_resources(${CMKR_TARGET})
include("cmake/custom_targets.cmake")
install(
TARGETS
+2 -5
View File
@@ -4,7 +4,7 @@ cmkr-include = false
[project]
name = "cmkr"
version = "0.2.25"
version = "0.2.32"
description = "CMakeLists generator from TOML"
languages = ["CXX"]
include-after = [
@@ -36,11 +36,8 @@ link-libraries = [
"ghc_filesystem",
"mpark_variant",
"ordered_map",
"nlohmann_json",
]
cmake-after = """
generate_resources(${CMKR_TARGET})
"""
include-after = ["cmake/custom_targets.cmake"]
[[install]]
targets = ["cmkr"]
+1 -1
View File
@@ -2,7 +2,7 @@ include_guard()
# 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_TAG "v0.2.25" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_TAG "v0.2.32" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake
+18
View File
@@ -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()
+41 -8
View File
@@ -39,17 +39,45 @@ include-before = ["cmake/before-project.cmake"]
include-after = ["cmake/after-project.cmake"]
```
### Languages
Supported languages are (see [`enable_language`](https://cmake.org/cmake/help/latest/command/enable_language.html) for more information):
- `C`
- `CXX` → C++
- `CSharp` → C#
- `CUDA`
- `OBJC` → Objective-C
- `OBJCXX` → Objective-C++
- `Fortran`
- `HIP`
- `ISPC`
- `Swift`
- `ASM`
- `ASM_MASM` → [Microsoft Macro Assembler (MASM)](https://learn.microsoft.com/en-US/cpp/assembler/masm/masm-for-x64-ml64-exe)
- `ASM_NASM` → [Netwide Assembler (NASM)](https://www.nasm.us)
- `ASM_MARMASM` [Microsoft ARM Assembler](https://learn.microsoft.com/en-us/cpp/assembler/arm/arm-assembler-command-line-reference)
- `ASM-ATT`
- `Java` (undocumented)
- `RC` (undocumented)
After a language is enabled, adding sources files with the corresponding extension to your target will automatically use the appropriate compiler/assembler for it.
_Note_: It is generally discouraged to disable the `C` language, unless you are absolutely sure it is not used. Sometimes projects added with `fetch-content` implicitly require it and the error messages can be extremely confusing.
## 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
[conditions]
arch64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
arch32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
ptr64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
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:
@@ -57,9 +85,11 @@ You can also prefix most keys with `condition.` to represent a conditional:
[target]
type = "executable"
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
The following conditions are predefined (you can override them if you desire):
@@ -105,7 +135,7 @@ MYPROJECT_SPECIAL_OPTION = { value = true, help = "Docstring for this option." }
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]`).
@@ -123,8 +153,8 @@ Variables emit a [`set`](https://cmake.org/cmake/help/latest/command/set.html) a
```toml
[vcpkg]
version = "2021.05.12"
url = "https://github.com/microsoft/vcpkg/archive/refs/tags/2021.05.12.tar.gz"
version = "2024.03.25"
url = "https://github.com/microsoft/vcpkg/archive/refs/tags/2024.03.25.tar.gz"
packages = ["fmt", "zlib"]
```
@@ -154,6 +184,7 @@ git = "https://github.com/myuser/gitcontent"
tag = "v0.1"
shallow = false
system = false
subdir = ""
[fetch-content.svncontent]
condition = "mycondition"
@@ -169,6 +200,8 @@ hash = "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
```toml
+1 -1
View File
@@ -19,7 +19,7 @@ description = "Dependencies from vcpkg"
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions
# See https://vcpkg.io/en/packages.html for available packages
[vcpkg]
version = "2022.11.14"
version = "2024.03.25"
packages = ["fmt"]
[find-package]
+3 -1
View File
@@ -155,7 +155,8 @@ struct Content {
Condition<std::string> cmake_after;
ConditionVector include_before;
ConditionVector include_after;
bool system;
bool system = false;
std::string subdir;
};
enum MsvcRuntimeType {
@@ -207,6 +208,7 @@ struct Project {
Project(const Project *parent, const std::string &path, bool build);
const Project *root() 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);
+183 -86
View File
@@ -54,57 +54,85 @@ static std::string format(const char *format, const tsl::ordered_map<std::string
return s;
}
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool is_root_project) {
std::vector<std::string> temp;
auto extract_suffix = [](const fs::path &base, const fs::path &full) {
auto fullpath = full.string();
auto base_len = base.string().length();
auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len);
return delet;
static std::vector<fs::path> expand_cmake_path(const fs::path &source_path, const fs::path &toml_dir, bool is_root_project) {
auto is_subdir = [](fs::path p, const fs::path &root) {
while (true) {
if (p == root) {
return true;
}
auto parent = p.parent_path();
if (parent == p) {
break;
}
p = parent;
}
return false;
};
auto stem = name.filename().stem().string();
auto ext = name.extension();
if (is_root_project && stem == "**" && name == name.filename()) {
throw std::runtime_error("Recursive globbing not allowed in project root: " + name.string());
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)
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 == "**" && !source_path.has_parent_path()) {
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 == "*") {
for (const auto &f : fs::directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) {
if (!f.is_directory() && f.path().extension() == ext) {
temp.push_back(extract_suffix(toml_dir, f));
for (const auto &f : fs::directory_iterator(toml_dir / source_path.parent_path(), fs::directory_options::follow_directory_symlink)) {
if (!f.is_directory() && has_extension(f.path(), extension)) {
paths.push_back(fs::relative(f, toml_dir));
}
}
} else if (stem == "**") {
for (const auto &f : fs::recursive_directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) {
if (!f.is_directory() && f.path().extension() == ext) {
temp.push_back(extract_suffix(toml_dir, f.path()));
for (const auto &f :
fs::recursive_directory_iterator(toml_dir / source_path.parent_path(), fs::directory_options::follow_directory_symlink)) {
if (!f.is_directory() && has_extension(f.path(), extension)) {
paths.push_back(fs::relative(f, toml_dir));
}
}
} 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 temp;
return paths;
}
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> result;
std::vector<std::string> paths;
for (const auto &src : sources) {
auto expanded = expand_cmake_path(src, toml_dir, is_root_project);
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) {
@@ -215,7 +243,7 @@ void generate_project(const std::string &type) {
generate_gitfile(".gitattributes", {"/**/CMakeLists.txt linguist-generated", "/**/cmkr.cmake linguist-vendored"});
// Generate .gitignore with reasonable defaults for CMake
generate_gitfile(".gitignore", {"build*/", "cmake-build*/", ".idea/", ".vscode/"});
generate_gitfile(".gitignore", {"build*/", "cmake-build*/", "CMakerLists.txt", "CMakeLists.txt.user"});
tsl::ordered_map<std::string, std::string> variables = {
{"@name", name},
@@ -493,7 +521,7 @@ struct Generator {
throw std::runtime_error("Detected additional \" at the end of cmake block");
}
auto cmake_lf = tolf(cmake);
while (cmake_lf.back() == '\n')
while (!cmake_lf.empty() && cmake_lf.back() == '\n')
cmake_lf.pop_back();
bool did_indent = false;
for (char ch : cmake_lf) {
@@ -514,15 +542,13 @@ struct Generator {
if (!value.empty()) {
for (const auto &itr : value) {
const auto &condition = itr.first;
if (!condition.empty()) {
cmd("if", condition)(RawArg(project.conditions.at(condition)));
}
auto endif = if_condition(condition);
if (!itr.second.empty()) {
fn(condition, itr.second);
}
if (!condition.empty()) {
if (endif) {
cmd("endif")().endl();
} else if (!itr.second.empty()) {
endl();
@@ -538,6 +564,68 @@ struct Generator {
void conditional_cmake(const parser::Condition<std::string> &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(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 {
@@ -545,10 +633,7 @@ struct ConditionScope {
bool endif = false;
ConditionScope(Generator &gen, const std::string &condition) : gen(gen) {
if (!condition.empty()) {
gen.cmd("if", condition)(RawArg(gen.project.conditions.at(condition)));
endif = true;
}
endif = gen.if_condition(condition);
}
ConditionScope(const ConditionScope &) = delete;
@@ -596,7 +681,10 @@ static std::string vcpkg_escape_identifier(const std::string &name) {
ch = '-';
}
escaped += std::tolower(ch);
if (ch >= 'A' && ch <= 'Z') {
ch += ('a' - 'A');
}
escaped += ch;
}
if (!vcpkg_valid_identifier(escaped)) {
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);
for (auto const &lang : project.project_languages) {
for (const auto &lang : project.project_languages) {
if (known_languages.find(lang) == known_languages.end()) {
if (project.project_allow_unknown_languages) {
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) {
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
if (!project.allow_in_tree) {
// clang-format off
@@ -690,6 +761,26 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("endif")().endl();
// 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);
if (!project.cmkr_include.empty() && !fs::exists(cmkr_include) && cmkr_include.is_relative()) {
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("include")("FetchContent");
comment("Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP");
// clang-format off
cmd("if")("POLICY", "CMP0135");
cmd("cmake_policy")("SET", "CMP0135", "NEW");
cmd("endif")();
// clang-format on
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
cmd("FetchContent_Declare")("vcpkg", "URL", url);
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
cmd("FetchContent_GetProperties")("vcpkg");
cmd("if")("NOT", "vcpkg_POPULATED");
cmd("FetchContent_Populate")("vcpkg");
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")();
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
cmd("FetchContent_Declare")("vcpkg", "URL", url);
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
cmd("FetchContent_GetProperties")("vcpkg");
cmd("if")("NOT", "vcpkg_POPULATED");
cmd("FetchContent_Populate")("vcpkg");
cmd("if")("CMAKE_HOST_SYSTEM_NAME", "STREQUAL", "Darwin", "AND", "CMAKE_OSX_ARCHITECTURES", "STREQUAL", RawArg("\"\""));
cmd("set")("CMAKE_OSX_ARCHITECTURES", "${CMAKE_HOST_SYSTEM_PROCESSOR}", "CACHE", "STRING", RawArg("\"\""), "FORCE");
cmd("endif")();
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")();
cmd("endif")();
endl();
// clang-format on
@@ -900,6 +992,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
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()) {
cmd("include")("FetchContent").endl();
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) {
// clang-format off
comment("Subdirectory: " + dir);
@@ -1347,7 +1439,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
auto working_directory = std::make_pair("WORKING_DIRECTORY", dir);
auto command = std::make_pair("COMMAND", test.command);
auto arguments = std::make_pair("", test.arguments);
// Transform the provided arguments into raw arguments to prevent them from being quoted when the generator runs
std::vector<RawArg> raw_arguments{};
for (const auto &argument : test.arguments)
raw_arguments.emplace_back(argument);
auto arguments = std::make_pair("", raw_arguments);
ConditionScope cs(gen, test.condition);
cmd("add_test")(name, configurations, working_directory, command, arguments).endl();
}
+93 -13
View File
@@ -140,7 +140,7 @@ class TomlChecker {
for (const auto &itr : m_v.as_table()) {
const auto &ky = itr.first;
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);
}
@@ -160,7 +160,7 @@ class TomlChecker {
throw_key_error("Unknown key '" + ky + "'", ky, itr.second);
} else if (ky == "condition") {
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);
}
}
@@ -282,6 +282,9 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
if (checker.contains("conditions")) {
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
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;
}
}
@@ -372,6 +375,25 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
}
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>;
const auto &opts = toml::find<opts_map>(toml, "options");
for (const auto &itr : opts) {
@@ -409,7 +431,18 @@ 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);
}
options.push_back(o);
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 +533,18 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
key = "URL";
} else if (hash_algorithms.contains(key)) {
std::string algo;
for (auto c : key) {
algo.push_back(std::toupper(c));
for (auto ch : key) {
if (ch >= 'a' && ch <= 'z') {
ch -= ('a' - 'A');
}
algo.push_back(ch);
}
key = "URL_HASH";
value = algo + "=" + value;
} else if (key == "hash") {
key = "URL_HASH";
} else if (key == "subdir") {
key = "SOURCE_SUBDIR";
} else if (is_cmake_arg(key)) {
// allow passthrough of ExternalProject options
} else if (!c.visisted(key)) {
@@ -597,6 +635,38 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
t.optional("link-libraries", target.link_libraries);
t.optional("private-link-libraries", target.private_link_libraries);
// Add support for relative paths for (private-)link-libraries
const auto fix_relative_paths = [&name, &path](ConditionVector &libraries, const char *key) {
for (const auto &library_entries : libraries) {
for (auto &library_path : libraries[library_entries.first]) {
// Skip processing paths with potential CMake macros in them (this check isn't perfect)
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#variable-references
if ((library_path.find("${") != std::string::npos || library_path.find("$ENV{") != std::string::npos ||
library_path.find("$CACHE{") != std::string::npos) &&
library_path.find('}') != std::string::npos) {
continue;
}
// Skip paths that don't contain backwards or forwards slashes
if (library_path.find_first_of(R"(\/)") == std::string::npos) {
continue;
}
// Check if the new file path exists, otherwise emit an error
const auto expected_library_file_path = fs::path{path} / library_path;
if (!fs::exists(expected_library_file_path)) {
throw std::runtime_error("Attempted to link against a library file that doesn't exist for target \"" + name + "\" in \"" +
key + "\": " + library_path);
}
// Prepend ${CMAKE_CURRENT_SOURCE_DIR} to the path
library_path.insert(0, "${CMAKE_CURRENT_SOURCE_DIR}/");
}
}
};
fix_relative_paths(target.link_libraries, "link-libraries");
fix_relative_paths(target.private_link_libraries, "private-link-libraries");
t.optional("link-options", target.link_options);
t.optional("private-link-options", target.private_link_options);
@@ -605,28 +675,28 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
Condition<std::string> msvc_runtime;
t.optional("msvc-runtime", msvc_runtime);
for (const auto &condItr : msvc_runtime) {
switch (parse_msvcRuntimeType(condItr.second)) {
for (const auto &cond_itr : msvc_runtime) {
switch (parse_msvcRuntimeType(cond_itr.second)) {
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;
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;
default: {
std::string error = "Unknown runtime '" + condItr.second + "'\n";
std::string error = "Unknown runtime '" + cond_itr.second + "'\n";
error += "Available types:\n";
for (std::string type_name : msvcRuntimeTypeNames) {
error += " - " + type_name + "\n";
}
error.pop_back(); // Remove last newline
const TomlBasicValue *report;
if (condItr.first.empty()) {
if (cond_itr.first.empty()) {
report = &t.find("msvc-runtime");
} 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);
}
}
}
@@ -799,6 +869,16 @@ bool Project::cmake_minimum_version(int major, int minor) const {
return std::tie(root_major, root_minor) >= std::tie(major, minor);
}
bool Project::is_condition_name(const std::string &name) {
auto is_named_condition = true;
for (auto ch : name) {
if (!(ch == '-' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z'))) {
return false;
}
}
return true;
}
bool is_root_path(const std::string &path) {
const auto toml_path = fs::path(path) / "cmake.toml";
if (!fs::exists(toml_path)) {
+13
View File
@@ -109,3 +109,16 @@ add_test(
"$<TARGET_FILE:cmkr>"
build
)
if(WIN32) # windows
add_test(
NAME
relative-paths
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/relative-paths"
COMMAND
"$<TARGET_FILE:cmkr>"
build
)
endif()
+8 -1
View File
@@ -57,4 +57,11 @@ arguments = ["build"]
name = "compile-options"
working-directory = "compile-options"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
arguments = ["build"]
[[test]]
condition = "windows"
name = "relative-paths"
working-directory = "relative-paths"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
+5
View File
@@ -2,6 +2,9 @@
name = "conditions"
cmake-after = "set(CUSTOM ON)"
[options]
CONDITIONS_BUILD_TESTS = "root"
[conditions]
custom = "CUSTOM"
@@ -15,6 +18,8 @@ macos.cmake-after = "message(STATUS macos-after)"
linux.cmake-after = "message(STATUS linux-after)"
unix.cmake-after = "message(STATUS unix-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]
AUTOMOC = false
+1 -1
View File
@@ -4,4 +4,4 @@
namespace mylib {
std::string message();
}
} // namespace mylib
+11
View File
@@ -0,0 +1,11 @@
[project]
name = "relative-paths"
[target.test-library]
type = "static"
sources = ["src/library-code.cpp"]
[target.example]
type = "executable"
sources = ["src/main.cpp"]
windows.link-libraries = ["libs/test-library-x64-Release.lib"]
@@ -0,0 +1,6 @@
// Created by Anthony Printup on 9/18/2023.
#include <cstdio>
extern "C" void library_function() {
std::puts("Hello from library_function!");
}
+12
View File
@@ -0,0 +1,12 @@
// Created by Anthony Printup on 9/18/2023.
#include <cstdio>
#ifdef WIN32
extern "C" void library_function();
#endif
int main() {
puts("Hello from cmkr(relative-paths)!");
#ifdef WIN32
library_function();
#endif
}
+1 -1
View File
@@ -7,7 +7,7 @@ description = "Dependencies from vcpkg"
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions
# See https://vcpkg.io/en/packages.html for available packages
[vcpkg]
version = "2022.11.14"
version = "2024.03.25"
packages = ["fmt"]
[find-package]
Generated Vendored
-4
View File
@@ -16,7 +16,3 @@ target_include_directories(toml11 INTERFACE toml11-3.6.0)
# https://github.com/mpark/variant (BSL-1.0)
add_library(mpark_variant INTERFACE)
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)
-21
View File
@@ -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