Skip to content

Commit e6ded07

Browse files
authored
Merge pull request #65 from gmh5225/feature-msvc-static
[feature] msvc-static
2 parents 07d99c5 + 3615cca commit e6ded07

File tree

10 files changed

+177
-13
lines changed

10 files changed

+177
-13
lines changed

cmake/generate_documentation.cmake

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
option(CMKR_GENERATE_DOCUMENTATION "Generate cmkr documentation" ${CMKR_ROOT_PROJECT})
2-
set(CMKR_TESTS "" CACHE INTERNAL "List of test directories in the order declared in tests/cmake.toml")
3-
4-
if(CMKR_GENERATE_DOCUMENTATION)
5-
# Hook the add_test function to capture the tests in the order declared in tests/cmake.toml
6-
function(add_test)
7-
cmake_parse_arguments(TEST "" "WORKING_DIRECTORY" "" ${ARGN})
8-
get_filename_component(TEST_WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}" NAME)
9-
list(APPEND CMKR_TESTS "${TEST_WORKING_DIRECTORY}")
10-
set(CMKR_TESTS "${CMKR_TESTS}" CACHE INTERNAL "")
11-
_add_test(${test} ${ARGN})
12-
endfunction()
13-
endif()
142

153
function(generate_documentation)
164
if(CMKR_GENERATE_DOCUMENTATION)
175
message(STATUS "[cmkr] Generating documentation...")
6+
7+
# Extract the order of the tests
8+
set(CMKR_TESTS "")
9+
file(READ "${PROJECT_SOURCE_DIR}/tests/cmake.toml" tests_toml NO_HEX_CONVERSION)
10+
string(REGEX MATCHALL "working-directory = \"([^\"]+)\"" tests_match "${tests_toml}")
11+
foreach(match ${tests_match})
12+
if(match MATCHES "working-directory = \"([^\"]+)\"")
13+
list(APPEND CMKR_TESTS "${CMAKE_MATCH_1}")
14+
else()
15+
message(FATAL_ERROR "This should not happen (wrong regex?)")
16+
endif()
17+
endforeach()
1818

1919
# Delete previously generated examples
2020
set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples")

docs/cmake-toml.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ name = "myproject"
2727
version = "1.0.0"
2828
description = "Description of the project"
2929
languages = ["C", "CXX"]
30+
msvc-runtime = "" # dynamic (implicit default), static
3031
cmake-before = """
3132
message(STATUS "CMake injected before the project() call")
3233
"""
@@ -58,6 +59,8 @@ sources = ["src/main.cpp"]
5859
windows.sources = ["src/windows_specific.cpp"]
5960
```
6061

62+
### Predefined conditions
63+
6164
The following conditions are predefined (you can override them if you desire):
6265

6366
```toml
@@ -144,6 +147,7 @@ alias = "mytarget::mytarget"
144147
type = "static" # executable, shared (DLL), static, interface, object, library, custom
145148
headers = ["src/mytarget.h"]
146149
sources = ["src/mytarget.cpp"]
150+
msvc-runtime = "" # dynamic (implicit default), static
147151

148152
# The keys below match the target_xxx CMake commands
149153
# Keys prefixed with private- will get PRIVATE visibility

docs/examples/msvc-runtime.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
# Automatically generated from tests/msvc-runtime/cmake.toml - DO NOT EDIT
3+
layout: default
4+
title: Static MSVC runtime
5+
permalink: /examples/msvc-runtime
6+
parent: Examples
7+
nav_order: 8
8+
---
9+
10+
# Static MSVC runtime
11+
12+
13+
14+
```toml
15+
[project]
16+
name = "msvc-runtime"
17+
description = "Static MSVC runtime"
18+
msvc-runtime = "static"
19+
20+
# This target will compile with a static runtime
21+
[target.static-runtime]
22+
type = "executable"
23+
sources = ["src/main.cpp"]
24+
25+
# This target overrides the [project].msvc-runtime
26+
[target.dynamic-runtime]
27+
type = "executable"
28+
sources = ["src/main.cpp"]
29+
msvc-runtime = "dynamic"
30+
```
31+
32+
33+
34+
<sup><sub>This page was automatically generated from [tests/msvc-runtime/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/msvc-runtime/cmake.toml).</sub></sup>

include/project_parser.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,17 @@ struct Content {
151151
ConditionVector include_after;
152152
};
153153

154+
enum MsvcRuntimeType {
155+
msvc_dynamic,
156+
msvc_static,
157+
msvc_last,
158+
};
159+
160+
extern const char *msvcRuntimeTypeNames[msvc_last];
161+
154162
struct Project {
163+
const Project *parent;
164+
155165
// This is the CMake version required to use all cmkr versions.
156166
std::string cmake_version = "3.15";
157167
std::string cmkr_include = "cmkr.cmake";
@@ -169,6 +179,7 @@ struct Project {
169179
std::string project_version;
170180
std::string project_description;
171181
std::vector<std::string> project_languages;
182+
MsvcRuntimeType project_msvc_runtime = msvc_last;
172183
Condition<std::string> cmake_before;
173184
Condition<std::string> cmake_after;
174185
ConditionVector include_before;
@@ -186,6 +197,7 @@ struct Project {
186197
std::vector<Subdir> subdirs;
187198

188199
Project(const Project *parent, const std::string &path, bool build);
200+
const Project *root() const;
189201
};
190202

191203
bool is_root_path(const std::string &path);

src/cmake_generator.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
524524
if (root_project) {
525525
cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl();
526526

527+
if (project.project_msvc_runtime != parser::msvc_last) {
528+
comment("Enable support for MSVC_RUNTIME_LIBRARY");
529+
cmd("cmake_policy")("SET", "CMP0091", "NEW").endl();
530+
}
531+
532+
// clang-format on
527533
if (!project.allow_in_tree) {
528534
// clang-format off
529535
cmd("if")("CMAKE_SOURCE_DIR", "STREQUAL", "CMAKE_BINARY_DIR");
@@ -806,6 +812,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
806812
}
807813

808814
if (!project.targets.empty()) {
815+
auto root_project = project.root();
809816
for (size_t i = 0; i < project.targets.size(); i++) {
810817
if (i > 0) {
811818
endl();
@@ -1014,6 +1021,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
10141021
}
10151022

10161023
gen.handle_condition(props, [&](const std::string &, const tsl::ordered_map<std::string, std::string> &properties) {
1024+
for (const auto &propItr : properties) {
1025+
if (propItr.first == "MSVC_RUNTIME_LIBRARY") {
1026+
if (root_project->project_msvc_runtime == parser::msvc_last) {
1027+
throw std::runtime_error("You cannot set [target].msvc-runtime without setting the root [project].msvc-runtime");
1028+
}
1029+
}
1030+
}
10171031
cmd("set_target_properties")(target.name, "PROPERTIES", properties);
10181032
});
10191033
}

src/project_parser.cpp

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ static TargetType parse_targetType(const std::string &name) {
2121
return target_last;
2222
}
2323

24+
const char *msvcRuntimeTypeNames[msvc_last] = {"dynamic", "static"};
25+
26+
static MsvcRuntimeType parse_msvcRuntimeType(const std::string &name) {
27+
for (int i = 0; i < msvc_last; i++) {
28+
if (name == msvcRuntimeTypeNames[i]) {
29+
return static_cast<MsvcRuntimeType>(i);
30+
}
31+
}
32+
return msvc_last;
33+
}
34+
2435
using TomlBasicValue = toml::basic_value<toml::discard_comments, tsl::ordered_map, std::vector>;
2536

2637
static std::string format_key_error(const std::string &error, const toml::key &ky, const TomlBasicValue &value) {
@@ -192,7 +203,7 @@ class TomlCheckerRoot {
192203
}
193204
};
194205

195-
Project::Project(const Project *parent, const std::string &path, bool build) {
206+
Project::Project(const Project *parent, const std::string &path, bool build) : parent(parent) {
196207
const auto toml_path = fs::path(path) / "cmake.toml";
197208
if (!fs::exists(toml_path)) {
198209
throw std::runtime_error("File not found '" + toml_path.string() + "'");
@@ -276,6 +287,21 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
276287
project.optional("include-before", include_before);
277288
project.optional("include-after", include_after);
278289
project.optional("subdirs", project_subdirs);
290+
291+
std::string msvc_runtime;
292+
project.optional("msvc-runtime", msvc_runtime);
293+
if (!msvc_runtime.empty()) {
294+
project_msvc_runtime = parse_msvcRuntimeType(msvc_runtime);
295+
if (project_msvc_runtime == msvc_last) {
296+
std::string error = "Unknown runtime '" + msvc_runtime + "'\n";
297+
error += "Available types:\n";
298+
for (std::string type_name : msvcRuntimeTypeNames) {
299+
error += " - " + type_name + "\n";
300+
}
301+
error.pop_back(); // Remove last newline
302+
throw std::runtime_error(format_key_error(error, msvc_runtime, project.find("msvc-runtime")));
303+
}
304+
}
279305
}
280306

281307
if (checker.contains("subdir")) {
@@ -523,6 +549,34 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
523549
t.optional("precompile-headers", target.precompile_headers);
524550
t.optional("private-precompile-headers", target.private_precompile_headers);
525551

552+
Condition<std::string> msvc_runtime;
553+
t.optional("msvc-runtime", msvc_runtime);
554+
for (const auto &condItr : msvc_runtime) {
555+
switch (parse_msvcRuntimeType(condItr.second)) {
556+
case msvc_dynamic:
557+
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL";
558+
break;
559+
case msvc_static:
560+
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>";
561+
break;
562+
default: {
563+
std::string error = "Unknown runtime '" + condItr.second + "'\n";
564+
error += "Available types:\n";
565+
for (std::string type_name : msvcRuntimeTypeNames) {
566+
error += " - " + type_name + "\n";
567+
}
568+
error.pop_back(); // Remove last newline
569+
const TomlBasicValue *report;
570+
if (condItr.first.empty()) {
571+
report = &t.find("msvc-runtime");
572+
} else {
573+
report = &t.find(condItr.first).as_table().find("msvc-runtime").value();
574+
}
575+
throw std::runtime_error(format_key_error(error, condItr.second, *report));
576+
}
577+
}
578+
}
579+
526580
t.optional("condition", target.condition);
527581
t.optional("alias", target.alias);
528582

@@ -664,6 +718,13 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
664718
checker.check(conditions, true);
665719
}
666720

721+
const Project *Project::root() const {
722+
auto root = this;
723+
while (root->parent != nullptr)
724+
root = root->parent;
725+
return root;
726+
}
727+
667728
bool is_root_path(const std::string &path) {
668729
const auto toml_path = fs::path(path) / "cmake.toml";
669730
if (!fs::exists(toml_path)) {

tests/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/cmake.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,10 @@ name = "templates"
4545
working-directory = "templates"
4646
command = "$<TARGET_FILE:cmkr>"
4747
arguments = ["build"]
48+
49+
[[test]]
50+
condition = "msvc"
51+
name = "msvc-runtime"
52+
working-directory = "msvc-runtime"
53+
command = "$<TARGET_FILE:cmkr>"
54+
arguments = ["build"]

tests/msvc-runtime/cmake.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[project]
2+
name = "msvc-runtime"
3+
description = "Static MSVC runtime"
4+
msvc-runtime = "static"
5+
6+
# This target will compile with a static runtime
7+
[target.static-runtime]
8+
type = "executable"
9+
sources = ["src/main.cpp"]
10+
11+
# This target overrides the [project].msvc-runtime
12+
[target.dynamic-runtime]
13+
type = "executable"
14+
sources = ["src/main.cpp"]
15+
msvc-runtime = "dynamic"

tests/msvc-runtime/src/main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <cstdio>
2+
3+
int main() {
4+
puts("Hello from cmkr(msvc-static)!");
5+
}

0 commit comments

Comments
 (0)