Compare commits

..

22 Commits

Author SHA1 Message Date
Duncan Ogilvie 264e4ace18 Fix bug checking the root path of subdirectories 2021-06-26 17:15:56 +02:00
Duncan Ogilvie b6ab584a1a Bump CMKR_TAG 2021-06-26 16:28:12 +02:00
Duncan Ogilvie 3cd84a9708 Add basic support for [subdir.mydir] 2021-06-26 16:22:26 +02:00
Duncan Ogilvie 9932e501ce Rename files to make the project easier to navigate 2021-06-25 12:38:48 +02:00
Duncan Ogilvie 1b37dd76a5 Rename things to try to make the code more readable 2021-06-25 12:25:58 +02:00
Duncan Ogilvie 82f2a11311 Bump CMKR_TAG 2021-06-23 22:36:29 +02:00
Duncan Ogilvie eaf03eb785 Add support for target condition 2021-06-15 02:15:06 +02:00
Duncan Ogilvie 8ea5b77ba5 Merge pull request #28 from build-cpp/conditional-properties
Implement conditional properties
2021-06-14 23:38:47 +02:00
Duncan Ogilvie a12b3ae021 Bump CMKR_TAG 2021-06-14 23:36:39 +02:00
Duncan Ogilvie 2450cfb2c9 Implement conditional properties
Closes #8
2021-06-14 23:29:51 +02:00
Duncan Ogilvie 8d024556e3 Fix CI 2021-06-14 04:34:44 +02:00
Duncan Ogilvie af3807ca2b Add support for private- target options 2021-06-14 04:01:02 +02:00
Duncan Ogilvie 17f085c66d Minor improvements to cmkr.cmake 2021-06-01 17:14:08 +02:00
Duncan Ogilvie 5ad6134e07 Merge pull request #27 from build-cpp/manifest
Add support for headers and vcpkg manifest mode
2021-05-31 15:30:34 +02:00
Duncan Ogilvie 7b20d8c54c Restore [vcpkg].version functionality and add a test + documentation 2021-05-31 14:02:13 +02:00
MoAlyousef ffdedbdace fixes 2021-05-29 17:40:44 +03:00
MoAlyousef bb7a0bc0ed signal failure to write a vcpkg.json file 2021-05-28 05:48:23 +03:00
MoAlyousef dd0003f8fe add nlohmann_json to cmake toml 2021-05-28 05:39:00 +03:00
MoAlyousef a32caca8f0 fix fetching vcpkg 2021-05-28 05:08:45 +03:00
MoAlyousef f565ad2af0 fix formatting, add check for project version in manifest mode 2021-05-28 05:03:00 +03:00
MoAlyousef dc4b59f05d prelim vcpkg manifest support 2021-05-28 04:54:04 +03:00
Duncan Ogilvie 4988f141e4 Bump version in cmkr.cmake bootstrapper 2021-05-18 13:14:43 +02:00
21 changed files with 25977 additions and 170 deletions
+9 -6
View File
@@ -71,27 +71,28 @@ generate_documentation()
unset(CMKR_TARGET)
unset(CMKR_SOURCES)
# Target cmkr
set(CMKR_TARGET cmkr)
set(cmkr_SOURCES "")
list(APPEND cmkr_SOURCES
"src/args.cpp"
"src/arguments.cpp"
"src/build.cpp"
"src/cmake.cpp"
"src/cmake_generator.cpp"
"src/error.cpp"
"src/gen.cpp"
"src/help.cpp"
"src/main.cpp"
"include/args.hpp"
"src/project_parser.cpp"
"include/arguments.hpp"
"include/build.hpp"
"include/cmake.hpp"
"include/cmake_generator.hpp"
"include/enum_helper.hpp"
"include/error.hpp"
"include/fs.hpp"
"include/gen.hpp"
"include/help.hpp"
"include/literals.hpp"
"include/project_parser.hpp"
)
list(APPEND cmkr_SOURCES
@@ -125,10 +126,12 @@ target_link_libraries(cmkr PRIVATE
ghc_filesystem
mpark_variant
ordered_map
nlohmann_json
)
unset(CMKR_TARGET)
unset(CMKR_SOURCES)
install(
TARGETS
cmkr
+1 -1
View File
@@ -20,7 +20,7 @@ type = "executable"
sources = ["src/*.cpp", "include/*.hpp"]
include-directories = ["include"]
compile-features = ["cxx_std_11"]
link-libraries = ["toml11", "ghc_filesystem", "mpark_variant", "ordered_map"]
link-libraries = ["toml11", "ghc_filesystem", "mpark_variant", "ordered_map", "nlohmann_json"]
[[install]]
targets = ["cmkr"]
+8 -3
View File
@@ -2,14 +2,14 @@ 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 "archive_7cdf36f3" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_TAG "archive_3cd84a97" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
# Set these from the command line to customize for development/debugging purposes
set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable")
set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation")
# Disable cmkr if generation is disabled
if(CMKR_SKIP_GENERATION)
if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION)
message(STATUS "[cmkr] Skipping automatic cmkr generation")
macro(cmkr)
endmacro()
@@ -132,10 +132,15 @@ macro(cmkr)
file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST)
# Delete the temporary file if it was left for some reason
set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt")
if(EXISTS "${CMKR_TEMP_FILE}")
file(REMOVE "${CMKR_TEMP_FILE}")
endif()
if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST)
# Copy the now-generated CMakeLists.txt to CMakerLists.txt
# This is done because you cannot include() a file you are currently in
set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt")
configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY)
# Add the macro required for the hack at the start of the cmkr macro
+36
View File
@@ -0,0 +1,36 @@
---
# Automatically generated from tests/vcpkg/cmake.toml - DO NOT EDIT
layout: default
title: Dependencies from vcpkg
permalink: /examples/vcpkg
parent: Examples
nav_order: 4
---
# Dependencies from vcpkg
Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) using [vcpkg](https://vcpkg.io/) and links an `example` target to it:
```toml
[project]
name = "vcpkg"
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 = "2021.05.12"
packages = ["fmt"]
[find-package]
fmt = {}
[target.example]
type = "executable"
sources = ["src/main.cpp"]
link-libraries = ["fmt::fmt"]
```
The bootstrapping of vcpkg is fully automated and no user interaction is necessary. You can disable vcpkg by setting `CMKR_DISABLE_VCPKG=ON`.
<sup><sub>This page was automatically generated from [tests/vcpkg/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/vcpkg/cmake.toml).</sub></sup>
@@ -8,7 +8,7 @@
#include <vector>
namespace cmkr {
namespace cmake {
namespace parser {
struct Setting {
std::string name;
@@ -34,6 +34,7 @@ struct Package {
struct Vcpkg {
std::string version;
std::string url;
std::vector<std::string> packages;
};
@@ -55,19 +56,37 @@ struct Target {
std::string name;
TargetType type = {};
// https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands
ConditionVector compile_definitions;
ConditionVector compile_features;
ConditionVector compile_options;
ConditionVector include_directories;
ConditionVector link_directories;
ConditionVector link_libraries;
ConditionVector link_options;
ConditionVector precompile_headers;
ConditionVector headers;
ConditionVector sources;
// https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands
ConditionVector compile_definitions;
ConditionVector private_compile_definitions;
ConditionVector compile_features;
ConditionVector private_compile_features;
ConditionVector compile_options;
ConditionVector private_compile_options;
ConditionVector include_directories;
ConditionVector private_include_directories;
ConditionVector link_directories;
ConditionVector private_link_directories;
ConditionVector link_libraries;
ConditionVector private_link_libraries;
ConditionVector link_options;
ConditionVector private_link_options;
ConditionVector precompile_headers;
ConditionVector private_precompile_headers;
std::string condition;
std::string alias;
tsl::ordered_map<std::string, std::string> properties;
Condition<tsl::ordered_map<std::string, std::string>> properties;
Condition<std::string> cmake_before;
Condition<std::string> cmake_after;
@@ -91,14 +110,23 @@ struct Install {
std::string destination;
};
struct CMake {
struct Subdir {
std::string name;
std::string condition;
Condition<std::string> cmake_before;
Condition<std::string> cmake_after;
ConditionVector include_before;
ConditionVector include_after;
};
struct Project {
// This is the CMake version required to use all cmkr versions.
std::string cmake_version = "3.15";
std::string cmkr_include = "cmkr.cmake";
std::string build_dir = "build";
std::string generator;
std::string config;
Condition<std::vector<std::string>> subdirs;
Condition<std::vector<std::string>> project_subdirs;
std::vector<std::string> cppflags;
std::vector<std::string> cflags;
std::vector<std::string> linkflags;
@@ -121,9 +149,12 @@ struct CMake {
std::vector<Test> tests;
std::vector<Install> installs;
tsl::ordered_map<std::string, std::string> conditions;
std::vector<Subdir> subdirs;
CMake(const std::string &path, bool build);
Project(const std::string &path, bool build);
};
} // namespace cmake
bool is_root_path(const std::string &path);
} // namespace parser
} // namespace cmkr
+2 -2
View File
@@ -1,6 +1,6 @@
#include "args.hpp"
#include "arguments.hpp"
#include "build.hpp"
#include "gen.hpp"
#include "cmake_generator.hpp"
#include "help.hpp"
#include "fs.hpp"
+19 -19
View File
@@ -1,7 +1,7 @@
#include "build.hpp"
#include "cmake.hpp"
#include "project_parser.hpp"
#include "error.hpp"
#include "gen.hpp"
#include "cmake_generator.hpp"
#include "fs.hpp"
#include <sstream>
@@ -14,10 +14,10 @@ namespace cmkr {
namespace build {
int run(int argc, char **argv) {
cmake::CMake cmake(".", true);
parser::Project project(".", true);
if (argc > 2) {
for (int i = 2; i < argc; ++i) {
cmake.build_args.push_back(argv[i]);
project.build_args.push_back(argv[i]);
}
}
std::stringstream ss;
@@ -25,22 +25,22 @@ int run(int argc, char **argv) {
if (gen::generate_cmake(fs::current_path().string().c_str()))
throw std::runtime_error("CMake generation failure!");
ss << "cmake -S. -B" << cmake.build_dir << " ";
ss << "cmake -S. -B" << project.build_dir << " ";
if (!cmake.generator.empty()) {
ss << "-G \"" << cmake.generator << "\" ";
if (!project.generator.empty()) {
ss << "-G \"" << project.generator << "\" ";
}
if (!cmake.config.empty()) {
ss << "-DCMAKE_BUILD_TYPE=" << cmake.config << " ";
if (!project.config.empty()) {
ss << "-DCMAKE_BUILD_TYPE=" << project.config << " ";
}
if (!cmake.gen_args.empty()) {
for (const auto &arg : cmake.gen_args) {
if (!project.gen_args.empty()) {
for (const auto &arg : project.gen_args) {
ss << "-D" << arg << " ";
}
}
ss << "&& cmake --build " << cmake.build_dir << " --parallel";
ss << "&& cmake --build " << project.build_dir << " --parallel";
if (argc > 2) {
for (const auto &arg : cmake.build_args) {
for (const auto &arg : project.build_args) {
ss << " " << arg;
}
}
@@ -50,17 +50,17 @@ int run(int argc, char **argv) {
int clean() {
bool ret = false;
cmake::CMake cmake(".", true);
if (fs::exists(cmake.build_dir)) {
ret = fs::remove_all(cmake.build_dir);
fs::create_directory(cmake.build_dir);
parser::Project project(".", true);
if (fs::exists(project.build_dir)) {
ret = fs::remove_all(project.build_dir);
fs::create_directory(project.build_dir);
}
return !ret;
}
int install() {
cmake::CMake cmake(".", false);
auto cmd = "cmake --install " + cmake.build_dir;
parser::Project project(".", false);
auto cmd = "cmake --install " + project.build_dir;
return ::system(cmd.c_str());
}
} // namespace build
+240 -100
View File
@@ -1,14 +1,17 @@
#include "gen.hpp"
#include "cmake.hpp"
#include "cmake_generator.hpp"
#include "error.hpp"
#include "literals.hpp"
#include "project_parser.hpp"
#include "fs.hpp"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <new>
#include <nlohmann/json.hpp>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
@@ -155,12 +158,14 @@ struct Command {
bool first_arg = true;
bool had_newline = false;
bool generated = false;
std::string post_comment;
Command(std::stringstream &ss, int depth, const std::string &command) : ss(ss), depth(depth), command(command) {}
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)) {}
~Command() {
if (!generated) {
assert(false && "Incorrect usage of cmd()");
assert(false && "Incorrect usage of cmd(), you probably forgot ()");
}
}
@@ -300,7 +305,11 @@ struct Command {
(void)std::initializer_list<bool>{print_arg(values)...};
if (had_newline)
ss << '\n' << indent(depth);
ss << ")\n";
ss << ")";
if (!post_comment.empty()) {
ss << " # " << post_comment;
}
ss << "\n";
return CommandEndl(ss);
}
};
@@ -316,25 +325,25 @@ static std::string tolf(const std::string &str) {
};
struct Generator {
Generator(cmake::CMake &cmake) : cmake(cmake) {}
Generator(parser::Project &project) : project(project) {}
Generator(const Generator &) = delete;
cmake::CMake &cmake;
parser::Project &project;
std::stringstream ss;
int indent = 0;
Command cmd(const std::string &command) {
Command cmd(const std::string &command, const std::string &post_comment = "") {
if (command.empty())
throw std::invalid_argument("command cannot be empty");
if (command == "if") {
indent++;
return Command(ss, indent - 1, command);
return Command(ss, indent - 1, command, post_comment);
} else if (command == "else" || command == "elseif") {
return Command(ss, indent - 1, command);
return Command(ss, indent - 1, command, post_comment);
} else if (command == "endif") {
indent--;
}
return Command(ss, indent, command);
return Command(ss, indent, command, post_comment);
}
CommandEndl comment(const std::string &comment) {
@@ -378,16 +387,16 @@ struct Generator {
}
template <typename T, typename Lambda>
void handle_condition(const cmake::Condition<T> &value, const Lambda &fn) {
void handle_condition(const parser::Condition<T> &value, const Lambda &fn) {
if (!value.empty()) {
for (const auto &itr : value) {
const auto &condition = itr.first;
if (!condition.empty()) {
if (cmake.conditions.count(condition) == 0) {
if (project.conditions.count(condition) == 0) {
// TODO: somehow print line number information here?
throw std::runtime_error("Unknown condition '" + condition + "'");
}
cmd("if")(RawArg(cmake.conditions[condition]));
cmd("if", condition)(RawArg(project.conditions[condition]));
}
if (!itr.second.empty()) {
@@ -403,28 +412,54 @@ struct Generator {
}
};
static std::string vcpkg_escape_identifier(const std::string &name) {
// Do a reasonable effort to escape the project name for use with vcpkg
std::string escaped;
for (char ch : name) {
if ((ch & 0x80) != 0) {
throw std::runtime_error("Non-ASCII characters are not allowed in [project].name when using [vcpkg]");
}
if (ch == '_' || ch == ' ') {
ch = '-';
}
escaped += std::tolower(ch);
}
const std::regex reserved("prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default");
const std::regex ok("[a-z0-9]+(-[a-z0-9]+)*");
std::cmatch m;
if (!std::regex_match(escaped.c_str(), m, reserved) && std::regex_match(escaped.c_str(), m, ok)) {
return escaped;
} else {
throw std::runtime_error("The escaped project name '" + escaped + "' is not usable with [vcpkg]");
}
}
int generate_cmake(const char *path, bool root) {
if (!fs::exists(fs::path(path) / "cmake.toml")) {
throw std::runtime_error("No cmake.toml found!");
}
cmake::CMake cmake(path, false);
Generator gen(cmake);
parser::Project project(path, false);
Generator gen(project);
// Helper lambdas for more convenient CMake generation
auto &ss = gen.ss;
auto cmd = [&gen](const std::string &comment) { return gen.cmd(comment); };
auto cmd = [&gen](const std::string &command) { return gen.cmd(command); };
auto comment = [&gen](const std::string &comment) { return gen.comment(comment); };
auto endl = [&gen]() { gen.endl(); };
auto inject_includes = [&gen](const std::vector<std::string> &includes) { gen.inject_includes(includes); };
auto inject_cmake = [&gen](const std::string &cmake) { gen.inject_cmake(cmake); };
std::string cmkr_url = "https://github.com/build-cpp/cmkr";
comment("This file is automatically generated from cmake.toml - DO NOT EDIT");
comment("See https://github.com/build-cpp/cmkr for more information");
comment("See " + cmkr_url + " for more information");
endl();
if (root) {
cmd("cmake_minimum_required")("VERSION", cmake.cmake_version).endl();
cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl();
comment("Regenerate CMakeLists.txt automatically in the root project");
cmd("set")("CMKR_ROOT_PROJECT", "OFF");
@@ -433,7 +468,7 @@ int generate_cmake(const char *path, bool root) {
cmd("set")("CMKR_ROOT_PROJECT", "ON").endl();
comment("Bootstrap cmkr");
cmd("include")(cmake.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT");
cmd("include")(project.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT");
cmd("if")("CMKR_INCLUDE_RESULT");
cmd("cmkr")();
cmd("endif")().endl();
@@ -452,48 +487,48 @@ int generate_cmake(const char *path, bool root) {
// clang-format on
// TODO: remove support and replace with global compile-features
if (!cmake.cppflags.empty()) {
if (!project.cppflags.empty()) {
ss << "set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} \"";
for (const auto &flag : cmake.cppflags) {
for (const auto &flag : project.cppflags) {
ss << flag << " ";
}
ss << "\")\n\n";
}
// TODO: remove support and replace with global compile-features
if (!cmake.cflags.empty()) {
if (!project.cflags.empty()) {
ss << "set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} \"";
for (const auto &flag : cmake.cflags) {
for (const auto &flag : project.cflags) {
ss << flag << " ";
}
ss << "\")\n\n";
}
// TODO: remove support and replace with global linker-flags
if (!cmake.linkflags.empty()) {
if (!project.linkflags.empty()) {
ss << "set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} \"";
for (const auto &flag : cmake.linkflags) {
for (const auto &flag : project.linkflags) {
ss << flag << " ";
}
ss << "\")\n\n";
}
gen.handle_condition(cmake.include_before, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(cmake.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
gen.handle_condition(project.include_before, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(project.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
if (!cmake.project_name.empty()) {
auto languages = std::make_pair("LANGUAGES", cmake.project_languages);
auto version = std::make_pair("VERSION", cmake.project_version);
auto description = std::make_pair("DESCRIPTION", cmake.project_description);
cmd("project")(cmake.project_name, languages, version, description).endl();
if (!project.project_name.empty()) {
auto languages = std::make_pair("LANGUAGES", project.project_languages);
auto version = std::make_pair("VERSION", project.project_version);
auto description = std::make_pair("DESCRIPTION", project.project_description);
cmd("project")(project.project_name, languages, version, description).endl();
}
gen.handle_condition(cmake.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(cmake.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
gen.handle_condition(project.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(project.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
if (!cmake.contents.empty()) {
if (!project.contents.empty()) {
cmd("include")("FetchContent").endl();
for (const auto &dep : cmake.contents) {
for (const auto &dep : project.contents) {
cmd("message")("STATUS", "Fetching " + dep.first + "...");
ss << "FetchContent_Declare(\n\t" << dep.first << "\n";
for (const auto &arg : dep.second) {
@@ -520,20 +555,51 @@ int generate_cmake(const char *path, bool root) {
}
}
if (!cmake.vcpkg.version.empty()) {
assert("pmm is required in fetch-content for vcpkg to work" && cmake.contents.count("pmm") != 0);
comment("Bootstrap vcpkg");
cmd("include")("${pmm_SOURCE_DIR}/pmm.cmake");
tsl::ordered_map<std::string, std::vector<std::string>> vcpkg_args;
vcpkg_args["REVISION"] = {cmake.vcpkg.version};
vcpkg_args["REQUIRES"] = cmake.vcpkg.packages;
auto vcpkg = std::make_pair("VCPKG", vcpkg_args);
cmd("pmm")(vcpkg).endl();
if (!project.vcpkg.packages.empty()) {
// Allow the user to specify a url or derive it from the version
auto url = project.vcpkg.url;
if (url.empty()) {
if (project.vcpkg.version.empty()) {
throw std::runtime_error("You need either [vcpkg].version or [vcpkg].url");
}
url = "https://github.com/microsoft/vcpkg/archive/refs/tags/" + project.vcpkg.version + ".tar.gz";
}
// CMake to bootstrap vcpkg and download the packages
// clang-format off
cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG");
cmd("include")("FetchContent");
cmd("message")("STATUS", "Fetching vcpkg...");
cmd("FetchContent_Declare")("vcpkg", "URL", url);
cmd("FetchContent_MakeAvailable")("vcpkg");
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")();
endl();
// clang-format on
// Generate vcpkg.json
using namespace nlohmann;
json j;
j["$cmkr"] = "This file is automatically generated from cmake.toml - DO NOT EDIT";
j["$cmkr-url"] = cmkr_url;
j["$schema"] = "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json";
j["name"] = vcpkg_escape_identifier(project.project_name);
j["version-string"] = project.project_version;
j["dependencies"] = project.vcpkg.packages;
if (!project.project_description.empty()) {
j["description"] = project.project_description;
}
std::ofstream ofs("vcpkg.json");
if (!ofs) {
throw std::runtime_error("Failed to create a vcpkg.json manifest file!");
}
ofs << std::setw(2) << j << std::endl;
}
if (!cmake.packages.empty()) {
if (!project.packages.empty()) {
comment("Packages");
for (const auto &dep : cmake.packages) {
for (const auto &dep : project.packages) {
auto version = dep.version;
if (version == "*")
version.clear();
@@ -544,17 +610,17 @@ int generate_cmake(const char *path, bool root) {
}
}
if (!cmake.options.empty()) {
if (!project.options.empty()) {
comment("Options");
for (const auto &opt : cmake.options) {
for (const auto &opt : project.options) {
cmd("option")(opt.name, opt.comment, opt.val ? "ON" : "OFF");
}
endl();
}
if (!cmake.settings.empty()) {
if (!project.settings.empty()) {
comment("Settings");
for (const auto &set : cmake.settings) {
for (const auto &set : project.settings) {
std::string set_val;
if (set.val.index() == 1) {
set_val = mpark::get<1>(set.val);
@@ -573,30 +639,65 @@ int generate_cmake(const char *path, bool root) {
endl();
}
// generate_cmake is called on the subdirectories recursively later
if (!cmake.subdirs.empty()) {
gen.handle_condition(cmake.subdirs, [&](const std::string &, const std::vector<std::string> &subdirs) {
for (const auto &dir : subdirs) {
// clang-format off
comment(dir);
cmd("set")("CMKR_CMAKE_FOLDER", "${CMAKE_FOLDER}");
cmd("if")("CMAKE_FOLDER");
cmd("set")("CMAKE_FOLDER", "${CMAKE_FOLDER}/" + dir);
cmd("else")();
cmd("set")("CMAKE_FOLDER", dir);
cmd("endif")();
// clang-format on
auto add_subdir = [&](const std::string &dir) {
// clang-format off
comment(dir);
cmd("set")("CMKR_CMAKE_FOLDER", "${CMAKE_FOLDER}");
cmd("if")("CMAKE_FOLDER");
cmd("set")("CMAKE_FOLDER", "${CMAKE_FOLDER}/" + dir);
cmd("else")();
cmd("set")("CMAKE_FOLDER", dir);
cmd("endif")();
// clang-format on
cmd("add_subdirectory")(dir);
cmd("set")("CMAKE_FOLDER", "${CMKR_CMAKE_FOLDER}").endl();
cmd("add_subdirectory")(dir);
cmd("set")("CMAKE_FOLDER", "${CMKR_CMAKE_FOLDER}").endl();
};
// generate_cmake is called on the subdirectories recursively later
if (!project.project_subdirs.empty()) {
gen.handle_condition(project.project_subdirs, [&](const std::string &, const std::vector<std::string> &subdirs) {
for (const auto &dir : subdirs) {
add_subdir(dir);
}
});
endl();
}
for (const auto &subdir : project.subdirs) {
if (!subdir.condition.empty()) {
const auto &condition = subdir.condition;
if (project.conditions.count(condition) == 0) {
throw std::runtime_error("Unknown condition '" + condition + "' for [subdir." + subdir.name + "]");
}
gen.cmd("if", condition)(RawArg(project.conditions[condition]));
}
if (!cmake.targets.empty()) {
for (const auto &target : cmake.targets) {
gen.handle_condition(subdir.include_before,
[&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(subdir.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
add_subdir(subdir.name);
gen.handle_condition(subdir.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
gen.handle_condition(subdir.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
if (!subdir.condition.empty()) {
cmd("endif")();
}
}
if (!project.targets.empty()) {
for (const auto &target : project.targets) {
comment("Target " + target.name);
if (!target.condition.empty()) {
const auto &condition = target.condition;
if (project.conditions.count(condition) == 0) {
throw std::runtime_error("Unknown condition '" + condition + "' for [target." + target.name + "]");
}
gen.cmd("if", condition)(RawArg(project.conditions[condition]));
}
cmd("set")("CMKR_TARGET", target.name);
gen.handle_condition(target.include_before,
@@ -620,7 +721,7 @@ int generate_cmake(const char *path, bool root) {
cmd("list")("APPEND", sources_var, sources);
});
if (!added_toml && target.type != cmake::target_interface) {
if (!added_toml && target.type != parser::target_interface) {
cmd("list")("APPEND", sources_var, std::vector<std::string>{"cmake.toml"}).endl();
}
cmd("set")("CMKR_SOURCES", "${" + sources_var + "}");
@@ -629,32 +730,32 @@ int generate_cmake(const char *path, bool root) {
std::string target_type;
std::string target_scope;
switch (target.type) {
case cmake::target_executable:
case parser::target_executable:
add_command = "add_executable";
target_type = "";
target_scope = "PRIVATE";
break;
case cmake::target_library:
case parser::target_library:
add_command = "add_library";
target_type = "";
target_scope = "PUBLIC";
break;
case cmake::target_shared:
case parser::target_shared:
add_command = "add_library";
target_type = "SHARED";
target_scope = "PUBLIC";
break;
case cmake::target_static:
case parser::target_static:
add_command = "add_library";
target_type = "STATIC";
target_scope = "PUBLIC";
break;
case cmake::target_interface:
case parser::target_interface:
add_command = "add_library";
target_type = "INTERFACE";
target_scope = "INTERFACE";
break;
case cmake::target_custom:
case parser::target_custom:
// TODO: add proper support, this is hacky
add_command = "add_custom_target";
target_type = "SOURCES";
@@ -668,12 +769,12 @@ int generate_cmake(const char *path, bool root) {
// clang-format off
cmd("if")(sources_var);
cmd("target_sources")(target.name, target.type == cmake::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
cmd("target_sources")(target.name, target.type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
cmd("endif")().endl();
// clang-format on
// The first executable target will become the Visual Studio startup project
if (target.type == cmake::target_executable) {
if (target.type == parser::target_executable) {
cmd("get_directory_property")("CMKR_VS_STARTUP_PROJECT", "DIRECTORY", "${PROJECT_SOURCE_DIR}", "DEFINITION", "VS_STARTUP_PROJECT");
// clang-format off
cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT");
@@ -683,29 +784,46 @@ int generate_cmake(const char *path, bool root) {
}
if (!target.sources.empty()) {
cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + target.name + "_SOURCES}").endl();
cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + sources_var + "}").endl();
}
if (!target.alias.empty()) {
cmd("add_library")(target.alias, "ALIAS", target.name);
}
auto target_cmd = [&](const char *command, const cmake::ConditionVector &cargs) {
gen.handle_condition(
cargs, [&](const std::string &, const std::vector<std::string> &args) { cmd(command)(target.name, target_scope, args); });
auto target_cmd = [&](const char *command, const parser::ConditionVector &cargs, const std::string &scope) {
gen.handle_condition(cargs,
[&](const std::string &, const std::vector<std::string> &args) { cmd(command)(target.name, scope, args); });
};
target_cmd("target_compile_definitions", target.compile_definitions);
target_cmd("target_compile_features", target.compile_features);
target_cmd("target_compile_options", target.compile_options);
target_cmd("target_include_directories", target.include_directories);
target_cmd("target_link_directories", target.link_directories);
target_cmd("target_link_libraries", target.link_libraries);
target_cmd("target_link_options", target.link_options);
target_cmd("target_precompile_headers", target.precompile_headers);
target_cmd("target_compile_definitions", target.compile_definitions, target_scope);
target_cmd("target_compile_definitions", target.private_compile_definitions, "PRIVATE");
target_cmd("target_compile_features", target.compile_features, target_scope);
target_cmd("target_compile_features", target.private_compile_features, "PRIVATE");
target_cmd("target_compile_options", target.compile_options, target_scope);
target_cmd("target_compile_options", target.private_compile_options, "PRIVATE");
target_cmd("target_include_directories", target.include_directories, target_scope);
target_cmd("target_include_directories", target.private_include_directories, "PRIVATE");
target_cmd("target_link_directories", target.link_directories, target_scope);
target_cmd("target_link_directories", target.private_link_directories, "PRIVATE");
target_cmd("target_link_libraries", target.link_libraries, target_scope);
target_cmd("target_link_libraries", target.private_link_libraries, "PRIVATE");
target_cmd("target_link_options", target.link_options, target_scope);
target_cmd("target_link_options", target.private_link_options, "PRIVATE");
target_cmd("target_precompile_headers", target.precompile_headers, target_scope);
target_cmd("target_precompile_headers", target.private_precompile_headers, "PRIVATE");
if (!target.properties.empty()) {
cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl();
gen.handle_condition(target.properties, [&](const std::string &, const tsl::ordered_map<std::string, std::string> &properties) {
cmd("set_target_properties")(target.name, "PROPERTIES", properties);
});
}
gen.handle_condition(target.include_after,
@@ -714,12 +832,18 @@ int generate_cmake(const char *path, bool root) {
cmd("unset")("CMKR_TARGET");
cmd("unset")("CMKR_SOURCES");
if (!target.condition.empty()) {
cmd("endif")();
}
endl();
}
}
if (!cmake.tests.empty()) {
if (!project.tests.empty()) {
cmd("enable_testing")().endl();
for (const auto &test : cmake.tests) {
for (const auto &test : project.tests) {
auto name = std::make_pair("NAME", test.name);
auto configurations = std::make_pair("CONFIGURATIONS", test.configurations);
auto dir = test.working_directory;
@@ -733,8 +857,8 @@ int generate_cmake(const char *path, bool root) {
}
}
if (!cmake.installs.empty()) {
for (const auto &inst : cmake.installs) {
if (!project.installs.empty()) {
for (const auto &inst : project.installs) {
auto targets = std::make_pair("TARGETS", inst.targets);
auto dirs = std::make_pair("DIRS", inst.dirs);
std::vector<std::string> files_data;
@@ -777,12 +901,28 @@ int generate_cmake(const char *path, bool root) {
}
}
for (const auto &itr : cmake.subdirs) {
for (const auto &sub : itr.second) {
auto subpath = fs::path(path) / fs::path(sub);
if (fs::exists(subpath / "cmake.toml"))
generate_cmake(subpath.string().c_str(), false);
auto generate_subdir = [path](const fs::path &sub) {
// Skip generating for subdirectories that have a cmake.toml with a [project] in it
fs::path subpath;
for (const auto &p : sub) {
subpath /= p;
if (parser::is_root_path((path / subpath).string())) {
return;
}
}
subpath = path / sub;
if (fs::exists(subpath / "cmake.toml")) {
generate_cmake(subpath.string().c_str(), false);
}
};
for (const auto &itr : project.project_subdirs) {
for (const auto &sub : itr.second) {
generate_subdir(sub);
}
}
for (const auto &subdir : project.subdirs) {
generate_subdir(subdir.name);
}
return 0;
+1 -1
View File
@@ -1,4 +1,4 @@
#include "args.hpp"
#include "arguments.hpp"
#include <cstdio>
#include <cstdlib>
+82 -22
View File
@@ -1,4 +1,4 @@
#include "cmake.hpp"
#include "project_parser.hpp"
#include "enum_helper.hpp"
#include "fs.hpp"
@@ -7,10 +7,10 @@
#include <tsl/ordered_map.h>
template <>
const char *enumStrings<cmkr::cmake::TargetType>::data[] = {"executable", "library", "shared", "static", "interface", "custom"};
const char *enumStrings<cmkr::parser::TargetType>::data[] = {"executable", "library", "shared", "static", "interface", "custom"};
namespace cmkr {
namespace cmake {
namespace parser {
using TomlBasicValue = toml::basic_value<toml::preserve_comments, tsl::ordered_map, std::vector>;
@@ -61,11 +61,12 @@ static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destin
}
}
CMake::CMake(const std::string &path, bool build) {
if (!fs::exists(fs::path(path) / "cmake.toml")) {
Project::Project(const std::string &path, bool build) {
const auto toml_path = fs::path(path) / "cmake.toml";
if (!fs::exists(toml_path)) {
throw std::runtime_error("No cmake.toml was found!");
}
const auto toml = toml::parse<toml::preserve_comments, tsl::ordered_map, std::vector>((fs::path(path) / "cmake.toml").string());
const auto toml = toml::parse<toml::preserve_comments, tsl::ordered_map, std::vector>(toml_path.string());
if (build) {
if (toml.contains("cmake")) {
const auto &cmake = toml::find(toml, "cmake");
@@ -99,7 +100,21 @@ CMake::CMake(const std::string &path, bool build) {
get_optional(project, "cmake-after", cmake_after);
get_optional(project, "include-before", include_before);
get_optional(project, "include-after", include_after);
get_optional(project, "subdirs", subdirs);
get_optional(project, "subdirs", project_subdirs);
}
if (toml.contains("subdir")) {
const auto &subs = toml::find(toml, "subdir").as_table();
for (const auto &sub : subs) {
Subdir subdir;
subdir.name = sub.first;
get_optional(sub.second, "condition", subdir.condition);
get_optional(sub.second, "cmake-before", subdir.cmake_before);
get_optional(sub.second, "cmake-after", subdir.cmake_after);
get_optional(sub.second, "include-before", subdir.include_before);
get_optional(sub.second, "include-after", subdir.include_after);
subdirs.push_back(subdir);
}
}
if (toml.contains("settings")) {
@@ -181,34 +196,75 @@ CMake::CMake(const std::string &path, bool build) {
target.name = itr.first;
target.type = to_enum<TargetType>(toml::find(t, "type").as_string(), "target type");
get_optional(t, "headers", target.headers);
get_optional(t, "sources", target.sources);
get_optional(t, "compile-definitions", target.compile_definitions);
get_optional(t, "private-compile-definitions", target.private_compile_definitions);
get_optional(t, "compile-features", target.compile_features);
get_optional(t, "private-compile-features", target.private_compile_features);
get_optional(t, "compile-options", target.compile_options);
get_optional(t, "private-compile-options", target.private_compile_options);
get_optional(t, "include-directories", target.include_directories);
get_optional(t, "private-include-directories", target.private_include_directories);
get_optional(t, "link-directories", target.link_directories);
get_optional(t, "private-link-directories", target.private_link_directories);
get_optional(t, "link-libraries", target.link_libraries);
get_optional(t, "private-link-libraries", target.private_link_libraries);
get_optional(t, "link-options", target.link_options);
get_optional(t, "private-link-options", target.private_link_options);
get_optional(t, "precompile-headers", target.precompile_headers);
get_optional(t, "private-precompile-headers", target.private_precompile_headers);
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());
}
if (t.contains("condition")) {
target.condition = toml::find(t, "condition").as_string();
}
if (t.contains("alias")) {
target.alias = toml::find(t, "alias").as_string();
}
if (t.contains("properties")) {
const auto &props = toml::find(t, "properties").as_table();
for (const auto &propKv : props) {
if (propKv.second.is_array()) {
auto store_property = [&target](const toml::key &k, const TomlBasicValue &v, const std::string &condition) {
if (v.is_array()) {
std::string property_list;
for (const auto &list_val : propKv.second.as_array()) {
for (const auto &list_val : v.as_array()) {
if (!property_list.empty()) {
property_list += ';';
}
property_list += list_val.as_string();
}
target.properties[propKv.first] = property_list;
target.properties[condition][k] = property_list;
} else if (v.is_boolean()) {
target.properties[condition][k] = v.as_boolean() ? "ON" : "OFF";
} else {
target.properties[propKv.first] = propKv.second.as_string();
target.properties[condition][k] = v.as_string();
}
};
const auto &props = toml::find(t, "properties").as_table();
for (const auto &propKv : props) {
const auto &k = propKv.first;
const auto &v = propKv.second;
if (v.is_table()) {
for (const auto &condKv : v.as_table()) {
store_property(condKv.first, condKv.second, k);
}
} else {
store_property(k, v, "");
}
}
}
@@ -250,15 +306,9 @@ CMake::CMake(const std::string &path, bool build) {
if (toml.contains("vcpkg")) {
const auto &v = toml::find(toml, "vcpkg");
vcpkg.version = toml::find(v, "version").as_string();
get_optional(v, "url", vcpkg.url);
get_optional(v, "version", vcpkg.version);
vcpkg.packages = toml::find<decltype(vcpkg.packages)>(v, "packages");
// This allows the user to use a custom pmm version if desired
if (contents.count("pmm") == 0) {
contents["pmm"]["url"] = "https://github.com/vector-of-bool/pmm/archive/refs/tags/1.5.1.tar.gz";
// Hack to not execute pmm's example CMakeLists.txt
contents["pmm"]["SOURCE_SUBDIR"] = "pmm";
}
}
// Reasonable default conditions (you can override these if you desire)
@@ -276,5 +326,15 @@ CMake::CMake(const std::string &path, bool build) {
}
}
}
} // namespace cmake
bool is_root_path(const std::string &path) {
const auto toml_path = fs::path(path) / "cmake.toml";
if (!fs::exists(toml_path)) {
return false;
}
const auto toml = toml::parse<toml::preserve_comments, tsl::ordered_map, std::vector>(toml_path.string());
return toml.contains("project");
}
} // namespace parser
} // namespace cmkr
+10
View File
@@ -48,3 +48,13 @@ add_test(
build
)
add_test(
NAME
vcpkg
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/vcpkg"
COMMAND
$<TARGET_FILE:cmkr>
build
)
+6
View File
@@ -20,4 +20,10 @@ arguments = ["build"]
name = "conditions"
command = "$<TARGET_FILE:cmkr>"
working-directory = "conditions"
arguments = ["build"]
[[test]]
name = "vcpkg"
command = "$<TARGET_FILE:cmkr>"
working-directory = "vcpkg"
arguments = ["build"]
+7 -1
View File
@@ -14,4 +14,10 @@ windows.cmake-after = "message(STATUS win32-after)"
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)"
custom.cmake-after = "message(STATUS custom-after)"
[target.example.properties]
AUTOMOC = false
custom.OUTPUT_NAME = "example2"
custom.AUTORCC = true
AUTOGEN = "ON"
+21
View File
@@ -0,0 +1,21 @@
# Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) using [vcpkg](https://vcpkg.io/) and links an `example` target to it:
[project]
name = "vcpkg"
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 = "2021.05.12"
packages = ["fmt"]
[find-package]
fmt = {}
[target.example]
type = "executable"
sources = ["src/main.cpp"]
link-libraries = ["fmt::fmt"]
# The bootstrapping of vcpkg is fully automated and no user interaction is necessary. You can disable vcpkg by setting `CMKR_DISABLE_VCPKG=ON`.
+6
View File
@@ -0,0 +1,6 @@
#include <fmt/core.h>
int main()
{
fmt::print("Hello, world!\n");
}
+11
View File
@@ -0,0 +1,11 @@
{
"$cmkr": "This file is automatically generated from cmake.toml - DO NOT EDIT",
"$cmkr-url": "https://github.com/build-cpp/cmkr",
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
"dependencies": [
"fmt"
],
"description": "Example of using vcpkg",
"name": "vcpkg",
"version-string": ""
}
Generated Vendored
+4
View File
@@ -16,3 +16,7 @@ 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
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2021 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large Load Diff