Compare commits

..

1 Commits

Author SHA1 Message Date
Duncan Ogilvie c76956450a Initial draft of [package] support 2022-03-20 21:19:07 +01:00
23 changed files with 262 additions and 318 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortFunctionsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
+1 -1
View File
@@ -25,7 +25,7 @@ project(cmkr
LANGUAGES
CXX
VERSION
0.2.16
0.2.12
DESCRIPTION
"CMakeLists generator from TOML"
)
-1
View File
@@ -70,4 +70,3 @@ arguments:
- https://github.com/mpark/variant
- https://www.svgrepo.com/svg/192268/hammer
- https://github.com/can1357 for buying `cmkr.build` ❤️
- https://github.com/JustasMasiulis for fixing the dark theme ❤️
+1 -1
View File
@@ -4,7 +4,7 @@ cmkr-include = false
[project]
name = "cmkr"
version = "0.2.16"
version = "0.2.12"
description = "CMakeLists generator from TOML"
languages = ["CXX"]
subdirs = ["third_party", "tests"]
+5 -22
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.16" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_TAG "v0.2.12" 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
@@ -58,7 +58,7 @@ else()
endif()
# Use cached cmkr if found
if(DEFINED ENV{CMKR_CACHE})
if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}")
string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}")
if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$")
@@ -72,27 +72,10 @@ endif()
set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}")
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")
# Helper function to check if a string starts with a prefix
# Cannot use MATCHES, see: https://github.com/build-cpp/cmkr/issues/61
function(cmkr_startswith str prefix result)
string(LENGTH "${prefix}" prefix_length)
string(LENGTH "${str}" str_length)
if(prefix_length LESS_EQUAL str_length)
string(SUBSTRING "${str}" 0 ${prefix_length} str_prefix)
if(prefix STREQUAL str_prefix)
set("${result}" ON PARENT_SCOPE)
return()
endif()
endif()
set("${result}" OFF PARENT_SCOPE)
endfunction()
# Handle upgrading logic
if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE)
cmkr_startswith("${CMKR_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/_cmkr" CMKR_STARTSWITH_BUILD)
cmkr_startswith("${CMKR_EXECUTABLE}" "${CMKR_DIRECTORY_PREFIX}" CMKR_STARTSWITH_CACHE)
if(CMKR_STARTSWITH_BUILD)
if(DEFINED ENV{CMKR_CACHE})
if(CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr")
if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'")
if(EXISTS "${CMKR_CACHED_EXECUTABLE}")
set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE)
@@ -103,7 +86,7 @@ if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE)
message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
unset(CMKR_EXECUTABLE CACHE)
endif()
elseif(DEFINED ENV{CMKR_CACHE} AND CMKR_STARTSWITH_CACHE)
elseif(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}" AND CMKR_EXECUTABLE MATCHES "^${CMKR_DIRECTORY_PREFIX}")
message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
unset(CMKR_EXECUTABLE CACHE)
endif()
+12 -12
View File
@@ -1,20 +1,20 @@
option(CMKR_GENERATE_DOCUMENTATION "Generate cmkr documentation" ${CMKR_ROOT_PROJECT})
set(CMKR_TESTS "" CACHE INTERNAL "List of test directories in the order declared in tests/cmake.toml")
if(CMKR_GENERATE_DOCUMENTATION)
# Hook the add_test function to capture the tests in the order declared in tests/cmake.toml
function(add_test)
cmake_parse_arguments(TEST "" "WORKING_DIRECTORY" "" ${ARGN})
get_filename_component(TEST_WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}" NAME)
list(APPEND CMKR_TESTS "${TEST_WORKING_DIRECTORY}")
set(CMKR_TESTS "${CMKR_TESTS}" CACHE INTERNAL "")
_add_test(${test} ${ARGN})
endfunction()
endif()
function(generate_documentation)
if(CMKR_GENERATE_DOCUMENTATION)
message(STATUS "[cmkr] Generating documentation...")
# Extract the order of the tests
set(CMKR_TESTS "")
file(READ "${PROJECT_SOURCE_DIR}/tests/cmake.toml" tests_toml NO_HEX_CONVERSION)
string(REGEX MATCHALL "working-directory = \"([^\"]+)\"" tests_match "${tests_toml}")
foreach(match ${tests_match})
if(match MATCHES "working-directory = \"([^\"]+)\"")
list(APPEND CMKR_TESTS "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR "This should not happen (wrong regex?)")
endif()
endforeach()
# Delete previously generated examples
set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples")
+2 -8
View File
@@ -27,7 +27,6 @@ name = "myproject"
version = "1.0.0"
description = "Description of the project"
languages = ["C", "CXX"]
msvc-runtime = "" # dynamic (implicit default), static
cmake-before = """
message(STATUS "CMake injected before the project() call")
"""
@@ -59,8 +58,6 @@ sources = ["src/main.cpp"]
windows.sources = ["src/windows_specific.cpp"]
```
### Predefined conditions
The following conditions are predefined (you can override them if you desire):
```toml
@@ -147,7 +144,6 @@ alias = "mytarget::mytarget"
type = "static" # executable, shared (DLL), static, interface, object, library, custom
headers = ["src/mytarget.h"]
sources = ["src/mytarget.cpp"]
msvc-runtime = "" # dynamic (implicit default), static
# The keys below match the target_xxx CMake commands
# Keys prefixed with private- will get PRIVATE visibility
@@ -204,9 +200,7 @@ working-directory = "mytest-dir"
condition = "mycondition"
targets = ["mytarget", "mytest"]
destination = ["bin"]
component = "mycomponent"
files = ["content/my.png"]
dirs = ["include"]
configs = ["Release", "Debug"]
optional = false
dirs = [""]
configs = [""]
```
-34
View File
@@ -1,34 +0,0 @@
---
# Automatically generated from tests/msvc-runtime/cmake.toml - DO NOT EDIT
layout: default
title: Static MSVC runtime
permalink: /examples/msvc-runtime
parent: Examples
nav_order: 8
---
# Static MSVC runtime
```toml
[project]
name = "msvc-runtime"
description = "Static MSVC runtime"
msvc-runtime = "static"
# This target will compile with a static runtime
[target.static-runtime]
type = "executable"
sources = ["src/main.cpp"]
# This target overrides the [project].msvc-runtime
[target.dynamic-runtime]
type = "executable"
sources = ["src/main.cpp"]
msvc-runtime = "dynamic"
```
<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>
+10 -13
View File
@@ -27,7 +27,7 @@ struct Option {
bool val = false;
};
struct Package {
struct FindPackage {
std::string name;
std::string condition;
std::string version;
@@ -67,6 +67,7 @@ struct Target {
TargetType type = target_last;
std::string type_name;
ConditionVector headers;
ConditionVector sources;
// https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands
@@ -127,7 +128,6 @@ struct Install {
std::vector<std::string> configs;
std::string destination;
std::string component;
bool optional = false;
};
struct Subdir {
@@ -151,17 +151,15 @@ struct Content {
ConditionVector include_after;
};
enum MsvcRuntimeType {
msvc_dynamic,
msvc_static,
msvc_last,
struct Package {
std::string name;
std::vector<std::string> targets;
std::vector<std::string> headers;
std::vector<std::string> dependencies;
std::string namespace_name;
};
extern const char *msvcRuntimeTypeNames[msvc_last];
struct Project {
const Project *parent;
// This is the CMake version required to use all cmkr versions.
std::string cmake_version = "3.15";
std::string cmkr_include = "cmkr.cmake";
@@ -179,15 +177,15 @@ struct Project {
std::string project_version;
std::string project_description;
std::vector<std::string> project_languages;
MsvcRuntimeType project_msvc_runtime = msvc_last;
Condition<std::string> cmake_before;
Condition<std::string> cmake_after;
ConditionVector include_before;
ConditionVector include_after;
std::vector<Setting> settings;
std::vector<Option> options;
std::vector<Package> packages;
std::vector<FindPackage> packages;
Vcpkg vcpkg;
Package package;
std::vector<Content> contents;
std::vector<Template> templates;
std::vector<Target> targets;
@@ -197,7 +195,6 @@ struct Project {
std::vector<Subdir> subdirs;
Project(const Project *parent, const std::string &path, bool build);
const Project *root() const;
};
bool is_root_path(const std::string &path);
+127 -76
View File
@@ -25,16 +25,16 @@ static std::string format(const char *format, tsl::ordered_map<std::string, std:
return s;
}
static std::string 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<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool 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;
};
auto stem = name.filename().stem().string();
auto ext = name.extension();
@@ -89,24 +89,8 @@ static void create_file(const fs::path &path, const std::string &contents) {
ofs << contents;
}
// CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-]
// TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-]
// We replace all non-TOML bare key characters with _
static std::string escape_project_name(const std::string &name) {
std::string escaped;
escaped.reserve(name.length());
for (auto ch : name) {
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-') {
escaped += ch;
} else {
escaped += '_';
}
}
return escaped;
}
void generate_project(const std::string &type) {
const auto name = escape_project_name(fs::current_path().stem().string());
const auto name = fs::current_path().stem().string();
if (fs::exists(fs::current_path() / "cmake.toml")) {
throw std::runtime_error("Cannot initialize a project when cmake.toml already exists!");
}
@@ -142,17 +126,13 @@ void generate_project(const std::string &type) {
struct CommandEndl {
std::stringstream &ss;
CommandEndl(std::stringstream &ss) : ss(ss) {
}
void endl() {
ss << '\n';
}
CommandEndl(std::stringstream &ss) : ss(ss) {}
void endl() { ss << '\n'; }
};
struct RawArg {
RawArg() = default;
RawArg(std::string arg) : arg(std::move(arg)) {
}
RawArg(std::string arg) : arg(std::move(arg)) {}
std::string arg;
};
@@ -168,8 +148,7 @@ struct Command {
std::string post_comment;
Command(std::stringstream &ss, int depth, std::string command, std::string post_comment)
: ss(ss), depth(depth), command(std::move(command)), post_comment(std::move(post_comment)) {
}
: ss(ss), depth(depth), command(std::move(command)), post_comment(std::move(post_comment)) {}
~Command() noexcept(false) {
if (!generated) {
@@ -183,11 +162,10 @@ struct Command {
return "\"\"";
}
// Don't quote arguments that don't need quoting
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#unquoted-argument
// NOTE: Normally '/' does not require quoting according to the documentation but this has been the case here
// previously, so for backwards compatibility its still here.
if (str.find_first_of("()#\"\\'> |/;") == str.npos)
if (str.find(' ') == std::string::npos && str.find('\"') == std::string::npos && str.find('/') == std::string::npos &&
str.find(';') == std::string::npos) {
return str;
}
std::string result;
result += "\"";
for (char ch : str) {
@@ -256,9 +234,16 @@ struct Command {
return true;
}
template <class T>
static bool is_empty(const T &v) {
return v.empty();
}
static bool is_empty(const char *v) { return *v == '\0'; }
template <class K, class V>
bool print_arg(const std::pair<K, V> &kv) {
if (kv.second.empty()) {
if (is_empty(kv.second)) {
return true;
}
@@ -272,7 +257,7 @@ struct Command {
}
bool print_arg(const RawArg &arg) {
if (arg.arg.empty()) {
if (is_empty(arg.arg)) {
return true;
}
@@ -338,8 +323,7 @@ static std::string tolf(const std::string &str) {
};
struct Generator {
Generator(const parser::Project &project) : project(project) {
}
Generator(const parser::Project &project) : project(project) {}
Generator(const Generator &) = delete;
const parser::Project &project;
@@ -365,9 +349,7 @@ struct Generator {
return CommandEndl(ss);
}
void endl() {
ss << '\n';
}
void endl() { ss << '\n'; }
void inject_includes(const std::vector<std::string> &includes) {
if (!includes.empty()) {
@@ -524,21 +506,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (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").endl();
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;
}
}
// clang-format on
if (!project.allow_in_tree) {
// clang-format off
cmd("if")("CMAKE_SOURCE_DIR", "STREQUAL", "CMAKE_BINARY_DIR");
@@ -649,6 +616,9 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
gen.conditional_cmake(project.cmake_after);
if (!project.vcpkg.packages.empty()) {
if (!root_project) {
throw std::runtime_error("[vcpkg] has to be in the project root");
}
// Allow the user to specify a url or derive it from the version
auto url = project.vcpkg.url;
auto version_name = url;
@@ -673,12 +643,8 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("include")("FetchContent");
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("FetchContent_MakeAvailable")("vcpkg");
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")();
endl();
// clang-format on
@@ -821,7 +787,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
if (!project.targets.empty()) {
auto root_project = project.root();
for (size_t i = 0; i < project.targets.size(); i++) {
if (i > 0) {
endl();
@@ -1000,7 +965,23 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
target_cmd("target_compile_options", t.compile_options, target_scope);
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
target_cmd("target_include_directories", t.include_directories, target_scope);
cmkr::parser::ConditionVector include_directories;
for (const auto &itr : t.include_directories) {
auto directories = itr.second;
if (target_scope == "PUBLIC" || target_scope == "INTERFACE") {
for (auto &directory : directories) {
// TODO: properly detect if this is a relative path
if (directory[0] != '$') {
directory = "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/" + directory + ">";
}
}
}
include_directories[itr.first] = directories;
}
// TODO: this needs to be done cleaner, maybe the install command has some magic?
include_directories[""].push_back("$<INSTALL_INTERFACE:include>");
target_cmd("target_include_directories", include_directories, target_scope);
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
target_cmd("target_link_directories", t.link_directories, target_scope);
@@ -1030,13 +1011,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
gen.handle_condition(props, [&](const std::string &, const tsl::ordered_map<std::string, std::string> &properties) {
for (const auto &propItr : properties) {
if (propItr.first == "MSVC_RUNTIME_LIBRARY") {
if (root_project->project_msvc_runtime == parser::msvc_last) {
throw std::runtime_error("You cannot set [target].msvc-runtime without setting the root [project].msvc-runtime");
}
}
}
cmd("set_target_properties")(target.name, "PROPERTIES", properties);
});
}
@@ -1092,12 +1066,89 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
component_name = inst.targets.front();
}
auto component = std::make_pair("COMPONENT", component_name);
auto optional = inst.optional ? "OPTIONAL" : "";
ConditionScope cs(gen, inst.condition);
cmd("install")(targets, dirs, files, configs, destination, component, optional);
cmd("install")(targets, dirs, files, configs, destination, component);
}
}
if (!project.package.targets.empty()) {
//TODO: generate an option to enable/disable installation
const auto &package = project.package;
if (!root_project) {
throw std::runtime_error("[package] has to be in the project root");
}
comment("Project packaging");
cmd("include")("CMakePackageConfigHelpers").endl();
comment("Install headers");
tsl::ordered_map<std::string, std::vector<std::string>> header_tree;
for (const auto &pattern : package.headers) {
auto pattern_path = fs::path(pattern);
auto pattern_parent = pattern_path.parent_path();
auto headers = expand_cmake_path(pattern_path, path, root_project);
for (const auto &header : headers) {
auto install_path = extract_suffix(pattern_parent, header);
auto install_destination = "include/" + fs::path(install_path).parent_path().string();
header_tree[install_destination].push_back(header);
}
}
for (const auto &itr : header_tree) {
cmd("install")(std::make_pair("FILES", itr.second), std::make_pair("DESTINATION", itr.first));
printf("Install destination: %s\n", itr.first.c_str());
for (const auto &header : itr.second) {
printf(" Header: %s\n", header.c_str());
}
}
endl();
comment("Install target outputs and generate target exports");
auto export_name = package.name + "Targets";
cmd("install")(std::make_pair("TARGETS", package.targets), std::make_pair("EXPORT", export_name), "LIBRARY", "ARCHIVE", "RUNTIME");
endl();
comment("Install generated target exports");
auto cmake_destination = "lib/cmake/" + package.name;
cmd("install")(std::make_pair("EXPORT", export_name), std::make_pair("FILE", export_name + ".cmake"),
std::make_pair("NAMESPACE", package.namespace_name), std::make_pair("DESTINATION", cmake_destination));
endl();
auto config_base = "${CMAKE_CURRENT_BINARY_DIR}/" + package.name;
std::vector<std::string> generated_scripts;
if (!project.project_version.empty()) {
comment("Generate package version scripts");
auto version_cmake = config_base + "ConfigVersion.cmake";
cmd("write_basic_package_version_file")(version_cmake, std::make_pair("VERSION", "${PROJECT_VERSION}"),
std::make_pair("COMPATIBILITY", "SameMajorVersion"));
endl();
generated_scripts.push_back(version_cmake);
}
comment("Generate package config scripts");
cmd("set")("PACKAGE_NAME", package.name);
// TODO: this needs some actual logic
cmd("set")("PACKAGE_DEPENDENCIES", package.dependencies);
auto config_cmake = config_base + "Config.cmake";
auto config_in = config_cmake + ".in";
cmd("file")("WRITE", config_in, RawArg(R"("include(CMakeFindDependencyMacro)\n\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("string(REGEX MATCHALL \"[^;]+\" SEPARATE_DEPENDENCIES \"@PACKAGE_DEPENDENCIES@\")\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("foreach(dependency ${SEPARATE_DEPENDENCIES})\n")"));
cmd("file")("APPEND", config_in, RawArg(R"(" string(REPLACE \" \" \";\" args \"\${dependency}\")\n")"));
cmd("file")("APPEND", config_in, RawArg(R"(" find_dependency(\${args})\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("endforeach()\n\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("include(\"\${CMAKE_CURRENT_LIST_DIR}/@PACKAGE_NAME@Targets.cmake\")\n")"));
cmd("configure_file")(config_in, config_cmake, "@ONLY");
generated_scripts.push_back(config_cmake);
endl();
comment("Install generated package scripts");
cmd("install")(std::make_pair("FILES", generated_scripts), std::make_pair("DESTINATION", cmake_destination));
endl();
}
// Generate CMakeLists.txt
auto list_path = fs::path(path) / "CMakeLists.txt";
+4 -10
View File
@@ -1,21 +1,15 @@
#include "error.hpp"
#include <cassert>
#include <cstddef>
namespace cmkr {
namespace error {
Status::Status(Code ec) noexcept : ec_(ec) {
}
Status::Status(Code ec) noexcept : ec_(ec) {}
Status::operator int() const noexcept {
return static_cast<int>(ec_);
}
Status::operator int() const noexcept { return static_cast<int>(ec_); }
Status::Code Status::code() const noexcept {
return ec_;
}
Status::Code Status::code() const noexcept { return ec_; }
} // namespace error
} // namespace cmkr
@@ -26,6 +20,6 @@ static const char *err_string[] = {
};
const char *cmkr_error_status(int i) {
assert(i >= 0 && static_cast<size_t>(i) < (sizeof(err_string) / sizeof(*(err_string))));
assert(i >= 0 && i < (sizeof(err_string) / sizeof(*(err_string))));
return err_string[i];
}
+3 -9
View File
@@ -4,9 +4,7 @@
namespace cmkr {
namespace help {
const char *version() noexcept {
return "cmkr version " CMKR_VERSION;
}
const char *version() noexcept { return "cmkr version " CMKR_VERSION; }
const char *message() noexcept {
return R"lit(
@@ -24,10 +22,6 @@ arguments:
} // namespace help
} // namespace cmkr
const char *cmkr_help_version(void) {
return cmkr::help::version();
}
const char *cmkr_help_version(void) { return cmkr::help::version(); }
const char *cmkr_help_message(void) {
return cmkr::help::message();
}
const char *cmkr_help_message(void) { return cmkr::help::message(); }
+38 -87
View File
@@ -21,17 +21,6 @@ static TargetType parse_targetType(const std::string &name) {
return target_last;
}
const char *msvcRuntimeTypeNames[msvc_last] = {"dynamic", "static"};
static MsvcRuntimeType parse_msvcRuntimeType(const std::string &name) {
for (int i = 0; i < msvc_last; i++) {
if (name == msvcRuntimeTypeNames[i]) {
return static_cast<MsvcRuntimeType>(i);
}
}
return msvc_last;
}
using TomlBasicValue = toml::basic_value<toml::discard_comments, tsl::ordered_map, std::vector>;
static std::string format_key_error(const std::string &error, const toml::key &ky, const TomlBasicValue &value) {
@@ -65,15 +54,14 @@ class TomlChecker {
tsl::ordered_set<toml::key> m_conditionVisited;
public:
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {
}
TomlChecker(const TomlBasicValue &v) : m_v(v) {
}
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
TomlChecker(const TomlBasicValue &v) : m_v(v) {}
TomlChecker(const TomlChecker &) = delete;
TomlChecker(TomlChecker &&) = delete;
template <typename T>
void optional(const toml::key &ky, Condition<T> &destination) {
bool optional(const toml::key &ky, Condition<T> &destination) {
auto found = false;
// TODO: this algorithm in O(n) over the amount of keys, kinda bad
const auto &table = m_v.as_table();
for (const auto &itr : table) {
@@ -82,9 +70,11 @@ class TomlChecker {
if (value.is_table()) {
if (value.contains(ky)) {
destination[key] = toml::find<T>(value, ky);
found = true;
}
} else if (key == ky) {
destination[""] = toml::find<T>(m_v, ky);
found = true;
}
}
@@ -95,15 +85,19 @@ class TomlChecker {
}
}
visit(ky);
return found;
}
template <typename T>
void optional(const toml::key &ky, T &destination) {
bool optional(const toml::key &ky, T &destination) {
auto found = false;
// TODO: this currently doesn't allow you to get an optional map<string, X>
if (m_v.contains(ky)) {
destination = toml::find<T>(m_v, ky);
found = true;
}
visit(ky);
return found;
}
template <typename T>
@@ -122,13 +116,9 @@ class TomlChecker {
return toml::find(m_v, ky);
}
void visit(const toml::key &ky) {
m_visited.emplace(ky);
}
void visit(const toml::key &ky) { m_visited.emplace(ky); }
bool visisted(const toml::key &ky) const {
return m_visited.contains(ky);
}
bool visisted(const toml::key &ky) const { return m_visited.contains(ky); }
void check(const tsl::ordered_map<std::string, std::string> &conditions) const {
for (const auto &itr : m_v.as_table()) {
@@ -169,8 +159,7 @@ class TomlCheckerRoot {
bool m_checked = false;
public:
TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) {
}
TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) {}
TomlCheckerRoot(const TomlCheckerRoot &) = delete;
TomlCheckerRoot(TomlCheckerRoot &&) = delete;
@@ -203,7 +192,7 @@ class TomlCheckerRoot {
}
};
Project::Project(const Project *parent, const std::string &path, bool build) : parent(parent) {
Project::Project(const Project *parent, const std::string &path, bool build) {
const auto toml_path = fs::path(path) / "cmake.toml";
if (!fs::exists(toml_path)) {
throw std::runtime_error("File not found '" + toml_path.string() + "'");
@@ -287,21 +276,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
project.optional("include-before", include_before);
project.optional("include-after", include_after);
project.optional("subdirs", project_subdirs);
std::string msvc_runtime;
project.optional("msvc-runtime", msvc_runtime);
if (!msvc_runtime.empty()) {
project_msvc_runtime = parse_msvcRuntimeType(msvc_runtime);
if (project_msvc_runtime == msvc_last) {
std::string error = "Unknown runtime '" + msvc_runtime + "'\n";
error += "Available types:\n";
for (std::string type_name : msvcRuntimeTypeNames) {
error += " - " + type_name + "\n";
}
error.pop_back(); // Remove last newline
throw std::runtime_error(format_key_error(error, msvc_runtime, project.find("msvc-runtime")));
}
}
}
if (checker.contains("subdir")) {
@@ -372,7 +346,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
using pkg_map = tsl::ordered_map<std::string, TomlBasicValue>;
const auto &pkgs = toml::find<pkg_map>(toml, "find-package");
for (const auto &itr : pkgs) {
Package p;
FindPackage p;
p.name = itr.first;
const auto &value = itr.second;
if (itr.second.is_string()) {
@@ -513,18 +487,9 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
throw std::runtime_error(format_key_error(error, target.type_name, t.find("type")));
}
t.optional("headers", target.headers);
t.optional("sources", target.sources);
// Merge the headers into the sources
ConditionVector headers;
t.optional("headers", headers);
for (const auto &itr : headers) {
auto &dest = target.sources[itr.first];
for (const auto &jtr : itr.second) {
dest.push_back(jtr);
}
}
t.optional("compile-definitions", target.compile_definitions);
t.optional("private-compile-definitions", target.private_compile_definitions);
@@ -549,32 +514,10 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
t.optional("precompile-headers", target.precompile_headers);
t.optional("private-precompile-headers", target.private_precompile_headers);
Condition<std::string> msvc_runtime;
t.optional("msvc-runtime", msvc_runtime);
for (const auto &condItr : msvc_runtime) {
switch (parse_msvcRuntimeType(condItr.second)) {
case msvc_dynamic:
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL";
break;
case msvc_static:
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>";
break;
default: {
std::string error = "Unknown runtime '" + condItr.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()) {
report = &t.find("msvc-runtime");
} else {
report = &t.find(condItr.first).as_table().find("msvc-runtime").value();
}
throw std::runtime_error(format_key_error(error, condItr.second, *report));
}
}
if (!target.headers.empty()) {
auto &sources = target.sources.nth(0).value();
const auto &headers = target.headers.nth(0)->second;
sources.insert(sources.end(), headers.begin(), headers.end());
}
t.optional("condition", target.condition);
@@ -683,7 +626,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
i.optional("configs", inst.configs);
i.required("destination", inst.destination);
i.optional("component", inst.component);
i.optional("optional", inst.optional);
installs.push_back(inst);
}
}
@@ -692,7 +634,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
auto &v = checker.create(toml, "vcpkg");
v.optional("url", vcpkg.url);
v.optional("version", vcpkg.version);
for (const auto &p : v.find("packages").as_array()) {
Vcpkg::Package package;
const auto &package_str = p.as_string().str;
@@ -715,14 +656,24 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
}
}
checker.check(conditions, true);
}
if(checker.contains("package")) {
auto& p = checker.create(toml, "package");
if(!p.optional("name", package.name)) {
package.name = project_name;
}
p.required("targets", package.targets);
// TODO: should this be optional?
p.required("headers", package.headers);
// TODO: how to detect this should default to all dependencies vs no dependencies?
p.optional("dependencies", package.dependencies);
if(!p.optional("namespace", package.namespace_name)) {
package.namespace_name = project_name + "::";
} else {
// TODO: check if the namespace ends with "::"?
}
}
const Project *Project::root() const {
auto root = this;
while (root->parent != nullptr)
root = root->parent;
return root;
checker.check(conditions, true);
}
bool is_root_path(const std::string &path) {
+8 -20
View File
@@ -14,7 +14,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/basic"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
@@ -24,7 +24,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/interface"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
@@ -34,7 +34,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/fetch-content"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
@@ -44,7 +44,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/conditions"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
@@ -54,7 +54,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/vcpkg"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
@@ -64,7 +64,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/cxx-standard"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
@@ -74,7 +74,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/globbing"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
@@ -84,19 +84,7 @@ add_test(
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/templates"
COMMAND
"$<TARGET_FILE:cmkr>"
$<TARGET_FILE:cmkr>
build
)
if(MSVC) # msvc
add_test(
NAME
msvc-runtime
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/msvc-runtime"
COMMAND
"$<TARGET_FILE:cmkr>"
build
)
endif()
+2 -3
View File
@@ -47,8 +47,7 @@ command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
[[test]]
condition = "msvc"
name = "msvc-runtime"
working-directory = "msvc-runtime"
name = "package"
working-directory = "package"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
-15
View File
@@ -1,15 +0,0 @@
[project]
name = "msvc-runtime"
description = "Static MSVC runtime"
msvc-runtime = "static"
# This target will compile with a static runtime
[target.static-runtime]
type = "executable"
sources = ["src/main.cpp"]
# This target overrides the [project].msvc-runtime
[target.dynamic-runtime]
type = "executable"
sources = ["src/main.cpp"]
msvc-runtime = "dynamic"
-5
View File
@@ -1,5 +0,0 @@
#include <cstdio>
int main() {
puts("Hello from cmkr(msvc-static)!");
}
+20
View File
@@ -0,0 +1,20 @@
[project]
name = "Utility"
version = "1.2"
[target.Utility]
type = "static"
alias = "Utility::Utility" # autogenerate this alias as ${PROJECT_NAME}::TargetName
sources = [
"src/Utility/*.cpp",
"include/Utility/*.hpp",
]
include-directories = ["include"]
compile-features = ["cxx_std_11"]
[package]
name = "Utility" # defaults to ${PROJECT_NAME}
targets = ["Utility"] # required
headers = ["include/**.hpp", "include/**.h"] # required
dependencies = [] # defaults to all the required packages
namespace = "Utility::" # defaults to ${PROJECT_NAME}::
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <string>
namespace String
{
bool startsWith(const std::string& str, const std::string& prefix);
std::string reverse(const std::string& str);
}
+19
View File
@@ -0,0 +1,19 @@
#include <cctype>
#include <algorithm>
#include <Utility/String.hpp>
namespace String
{
bool startsWith(const std::string& str, const std::string& prefix)
{
return str.find(prefix) == 0;
}
std::string reverse(const std::string& str)
{
auto result = str;
std::reverse(result.begin(), result.end());
return result;
}
}