Compare commits

...

5 Commits

Author SHA1 Message Date
Duncan Ogilvie ea49a3acb7 Bump to 0.2.20 2023-02-28 21:31:18 +01:00
Duncan Ogilvie 257e14c869 Merge pull request #93 from build-cpp/gitattributes
Automatically generate .gitattributes and .gitignore with cmkr init
2023-02-28 21:30:34 +01:00
Duncan Ogilvie 7508816fe7 Merge pull request #94 from build-cpp/user-experience
Add a special "root" value for `[options]`
2023-02-28 21:27:11 +01:00
Duncan Ogilvie 45678bc5bc Automatically generate .gitattributes and .gitignore with cmkr init 2023-02-28 21:26:07 +01:00
Duncan Ogilvie b869e5646f Add a special "root" value for [options] 2023-02-28 19:34:31 +01:00
7 changed files with 116 additions and 9 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ project(cmkr
LANGUAGES
CXX
VERSION
0.2.19
0.2.20
DESCRIPTION
"CMakeLists generator from TOML"
)
+1 -1
View File
@@ -4,7 +4,7 @@ cmkr-include = false
[project]
name = "cmkr"
version = "0.2.19"
version = "0.2.20"
description = "CMakeLists generator from TOML"
languages = ["CXX"]
include-after = [
+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.19" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_TAG "v0.2.20" 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
+4 -1
View File
@@ -100,9 +100,12 @@ include-after = ["cmake/after-subdir.cmake"]
[options]
MYPROJECT_BUILD_TESTS = false
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 options 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).
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]`).
## Variables
+1 -1
View File
@@ -25,7 +25,7 @@ struct Variable {
struct Option {
std::string name;
std::string help;
bool value = false;
mpark::variant<bool, std::string> value;
};
struct Package {
+83 -2
View File
@@ -119,6 +119,19 @@ static void create_file(const fs::path &path, const std::string &contents) {
ofs << contents;
}
static std::string read_file(const fs::path &path) {
std::ifstream ifs(path, std::ios::binary);
if (!ifs) {
throw std::runtime_error("Failed to read " + path.string());
}
std::string contents;
ifs.seekg(0, std::ios::end);
contents.resize(ifs.tellg());
ifs.seekg(0, std::ios::beg);
ifs.read(&contents[0], contents.size());
return 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 _
@@ -135,12 +148,74 @@ static std::string escape_project_name(const std::string &name) {
return escaped;
}
static void generate_gitfile(const char *gitfile, const std::vector<std::string> &desired_lines) {
// Reference: https://github.com/github/linguist/blob/master/docs/overrides.md#summary
auto lines = desired_lines;
auto generate = [&lines](const char *newline) {
std::string generated;
generated += "# cmkr";
generated += newline;
for (const auto &line : lines) {
generated += line;
generated += newline;
}
return generated;
};
if (!fs::exists(gitfile)) {
create_file(gitfile, generate("\n"));
} else {
auto contents = read_file(gitfile);
std::string line;
auto cr = 0, lf = 0;
auto flush_line = [&line, &lines]() {
auto itr = std::find(lines.begin(), lines.end(), line);
if (itr != lines.end()) {
printf("erase:%s\n", line.c_str());
lines.erase(itr);
}
line.clear();
};
for (size_t i = 0; i < contents.length(); i++) {
if (contents[i] == '\r') {
cr++;
continue;
}
if (contents[i] == '\n') {
lf++;
flush_line();
} else {
line += contents[i];
}
}
if (!line.empty()) {
flush_line();
}
if (!lines.empty()) {
// Append the cmkr .gitattributes using the detected newline
auto newline = cr == lf ? "\r\n" : "\n";
if (!contents.empty() && contents.back() != '\n') {
contents += newline;
}
contents += newline;
contents += generate(newline);
create_file(gitfile, contents);
}
}
}
void generate_project(const std::string &type) {
const auto name = escape_project_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!");
}
// Automatically generate .gitattributes to not skew the statistics of the repo
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/"});
tsl::ordered_map<std::string, std::string> variables = {
{"@name", name},
{"@type", type},
@@ -656,7 +731,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (!project.options.empty()) {
comment("Options");
for (const auto &opt : project.options) {
cmd("option")(opt.name, RawArg(Command::quote(opt.help)), opt.value ? "ON" : "OFF");
std::string default_val;
if (opt.value.index() == 0) {
default_val = mpark::get<0>(opt.value) ? "ON" : "OFF";
} else {
default_val = mpark::get<1>(opt.value);
}
cmd("option")(opt.name, RawArg(Command::quote(opt.help)), default_val);
}
endl();
}
@@ -892,7 +973,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
}
auto contains_language_source = [&project_extensions](const std::vector<std::string>& sources) {
auto contains_language_source = [&project_extensions](const std::vector<std::string> &sources) {
for (const auto &source : sources) {
auto extension = fs::path(source).extension().string();
if (project_extensions.count(extension) > 0) {
+25 -2
View File
@@ -377,10 +377,33 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
const auto &value = itr.second;
if (value.is_boolean()) {
o.value = value.as_boolean();
} else {
} else if (value.is_string()) {
auto str = std::string(value.as_string());
if (str == "root") {
o.value = std::string("${CMKR_ROOT_PROJECT}");
} else {
throw_key_error("Unsupported option value '" + str + "'", str, value);
}
} else if (value.is_table()) {
auto &option = checker.create(value);
option.optional("help", o.help);
option.optional("value", o.value);
if (option.contains("value")) {
const auto &ovalue = option.find("value");
if (ovalue.is_boolean()) {
o.value = ovalue.as_boolean();
} else if (ovalue.is_string()) {
auto str = std::string(ovalue.as_string());
if (str == "root") {
o.value = std::string("${CMKR_ROOT_PROJECT}");
} else {
throw_key_error("Unsupported option value '" + str + "'", str, value);
}
} else {
throw_key_error(toml::concat_to_string("Unsupported value type: ", ovalue.type()), "value", value);
}
}
} else {
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);