Refactor cmake generation

vcpkg-wip
Duncan Ogilvie 4 years ago
parent 1af0b5f1cc
commit f2dbd1fcb8

@ -76,8 +76,10 @@ target_link_libraries(cmkr PRIVATE
) )
install( install(
TARGETS cmkr TARGETS
DESTINATION ${CMAKE_INSTALL_PREFIX}/bin cmkr
COMPONENT cmkr DESTINATION
"${CMAKE_INSTALL_PREFIX}/bin"
COMPONENT
cmkr
) )

@ -16,8 +16,6 @@
namespace cmkr { namespace cmkr {
namespace gen { namespace gen {
namespace detail {
inline std::string to_upper(const std::string &str) { inline std::string to_upper(const std::string &str) {
std::string temp; std::string temp;
temp.reserve(str.size()); temp.reserve(str.size());
@ -65,7 +63,18 @@ static std::vector<std::string> expand_cmake_path(const fs::path &p) {
return temp; return temp;
} }
} // namespace detail static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources) {
// TODO: add duplicate checking
std::vector<std::string> result;
for (const auto &src : sources) {
auto path = fs::path(src);
auto expanded = expand_cmake_path(path);
for (const auto &f : expanded) {
result.push_back(f);
}
}
return result;
}
int generate_project(const char *str) { int generate_project(const char *str) {
fs::create_directory("src"); fs::create_directory("src");
@ -76,12 +85,12 @@ int generate_project(const char *str) {
std::string target; std::string target;
std::string dest; std::string dest;
if (!strcmp(str, "executable")) { if (!strcmp(str, "executable")) {
mainbuf = detail::format(hello_world, "main"); mainbuf = format(hello_world, "main");
installed = "targets"; installed = "targets";
target = dir_name; target = dir_name;
dest = "bin"; dest = "bin";
} else if (!strcmp(str, "static") || !strcmp(str, "shared") || !strcmp(str, "library")) { } else if (!strcmp(str, "static") || !strcmp(str, "shared") || !strcmp(str, "library")) {
mainbuf = detail::format(hello_world, "test"); mainbuf = format(hello_world, "test");
installed = "targets"; installed = "targets";
target = dir_name; target = dir_name;
dest = "lib"; dest = "lib";
@ -94,7 +103,7 @@ int generate_project(const char *str) {
"! Supported types are: executable, library, shared, static, interface"); "! Supported types are: executable, library, shared, static, interface");
} }
const auto tomlbuf = detail::format(cmake_toml, dir_name.c_str(), dir_name.c_str(), str, installed.c_str(), target.c_str(), dest.c_str()); const auto tomlbuf = format(cmake_toml, dir_name.c_str(), dir_name.c_str(), str, installed.c_str(), target.c_str(), dest.c_str());
if (strcmp(str, "interface")) { if (strcmp(str, "interface")) {
std::ofstream ofs("src/main.cpp"); std::ofstream ofs("src/main.cpp");
@ -121,15 +130,6 @@ struct CommandEndl {
void endl() { ss << '\n'; } void endl() { ss << '\n'; }
}; };
template <typename T>
struct NamedArg {
std::string name;
T value;
NamedArg() = default;
NamedArg(std::string name, T value) : name(std::move(name)), value(std::move(value)) {}
};
// Credit: JustMagic // Credit: JustMagic
struct Command { struct Command {
std::stringstream &ss; std::stringstream &ss;
@ -182,11 +182,11 @@ struct Command {
return true; return true;
} }
had_newline = true;
for (const auto &value : vec) { for (const auto &value : vec) {
ss << '\n' << indent(depth + 1) << quote(value); print_arg(value);
} }
had_newline = true;
first_arg = false;
return true; return true;
} }
@ -197,25 +197,9 @@ struct Command {
} }
for (const auto &itr : map) { for (const auto &itr : map) {
ss << '\n' << indent(depth + 1) << itr.first; print_arg(itr);
ss << '\n' << indent(depth + 2) << quote(itr.second);
}
had_newline = true;
first_arg = false;
return true;
}
bool print_arg(const std::string &value) {
if (value.empty()) {
return true;
} }
if (first_arg) {
first_arg = false;
} else {
ss << (had_newline ? '\n' : ' ');
}
ss << quote(value);
return true; return true;
} }
@ -225,40 +209,57 @@ struct Command {
return true; return true;
} }
ss << '\n' << indent(depth + 1) << kv.first; had_newline = true;
print_arg(kv.first);
depth++;
for (const auto &s : kv.second) { for (const auto &s : kv.second) {
ss << '\n' << indent(depth + 2) << quote(s); print_arg(s);
} }
had_newline = true; depth--;
first_arg = false;
return true; return true;
} }
template <class K, class V> template <class K, class V>
bool print_arg(const std::pair<K, V> &kv) { bool print_arg(const std::pair<K, V> &kv) {
std::stringstream tmp;
tmp << kv.second;
auto str = tmp.str();
if (kv.second.empty()) { if (kv.second.empty()) {
return true; return true;
} }
ss << '\n' << indent(depth + 1) << kv.first;
ss << '\n' << indent(depth + 2) << quote(kv.second);
had_newline = true; had_newline = true;
first_arg = false; print_arg(kv.first);
depth++;
print_arg(str);
depth--;
return true; return true;
} }
template <class T> template <class T>
bool print_arg(const T &value) { bool print_arg(const T &value, bool indentation = true) {
if (first_arg) { std::stringstream tmp;
tmp << value;
auto str = tmp.str();
if (str.empty()) {
return true;
}
if (indentation) {
if (had_newline) {
first_arg = false;
ss << '\n' << indent(depth + 1);
} else if (first_arg) {
first_arg = false; first_arg = false;
} else { } else {
ss << (had_newline ? '\n' : ' '); ss << ' ';
} }
std::stringstream tmp; }
tmp << value;
ss << quote(tmp.str()); ss << quote(str);
return true; return true;
} }
@ -275,10 +276,12 @@ struct Command {
}; };
int generate_cmake(const char *path, bool root) { int generate_cmake(const char *path, bool root) {
if (fs::exists(fs::path(path) / "cmake.toml")) { if (!fs::exists(fs::path(path) / "cmake.toml")) {
cmake::CMake cmake(path, false); throw std::runtime_error("No cmake.toml found!");
std::stringstream ss; }
// Helper lambdas for more convenient CMake generation
std::stringstream ss;
int indent = 0; int indent = 0;
auto cmd = [&ss, &indent](const std::string &command) { auto cmd = [&ss, &indent](const std::string &command) {
if (command.empty()) if (command.empty())
@ -298,7 +301,6 @@ int generate_cmake(const char *path, bool root) {
return CommandEndl(ss); return CommandEndl(ss);
}; };
auto endl = [&ss]() { ss << '\n'; }; auto endl = [&ss]() { ss << '\n'; };
auto tolf = [](const std::string &str) { auto tolf = [](const std::string &str) {
std::string result; std::string result;
for (char ch : str) { for (char ch : str) {
@ -308,20 +310,25 @@ int generate_cmake(const char *path, bool root) {
} }
return result; return result;
}; };
auto inject_includes = [&cmd, &endl](const std::vector<std::string> &includes) {
comment("This file was generated automatically by cmkr.").endl(); if (!includes.empty()) {
for (const auto &file : includes) {
if (!cmake.cmake_before.empty()) {
ss << tolf(cmake.cmake_before) << "\n\n";
}
if (!cmake.include_before.empty()) {
for (const auto &file : cmake.include_before) {
// TODO: warn/error if file doesn't exist? // TODO: warn/error if file doesn't exist?
cmd("include")(file); cmd("include")(file);
} }
endl(); endl();
} }
};
auto inject_cmake = [&ss, &tolf](const std::string &cmake) {
if (!cmake.empty()) {
ss << tolf(cmake) << "\n\n";
}
};
comment("This file was generated automatically by cmkr.").endl();
// TODO: add link with proper documentation
cmake::CMake cmake(path, false);
if (root) { if (root) {
comment("Regenerate CMakeLists.txt file when necessary"); comment("Regenerate CMakeLists.txt file when necessary");
@ -365,6 +372,9 @@ int generate_cmake(const char *path, bool root) {
ss << "\")\n\n"; ss << "\")\n\n";
} }
inject_includes(cmake.include_before);
inject_cmake(cmake.cmake_before);
if (!cmake.project_name.empty()) { if (!cmake.project_name.empty()) {
auto languages = std::make_pair("LANGUAGES", cmake.project_languages); auto languages = std::make_pair("LANGUAGES", cmake.project_languages);
auto version = std::make_pair("VERSION", cmake.project_version); auto version = std::make_pair("VERSION", cmake.project_version);
@ -372,41 +382,24 @@ int generate_cmake(const char *path, bool root) {
cmd("project")(cmake.project_name, languages, version, description).endl(); cmd("project")(cmake.project_name, languages, version, description).endl();
} }
if (!cmake.cmake_after.empty()) { inject_includes(cmake.include_after);
ss << tolf(cmake.cmake_after) << "\n\n"; inject_cmake(cmake.cmake_after);
}
if (!cmake.include_after.empty()) {
for (const auto &file : cmake.include_after) {
// TODO: warn/error if file doesn't exist?
cmd("include")(file);
}
endl();
}
if (!cmake.packages.empty()) { if (!cmake.packages.empty()) {
for (const auto &dep : cmake.packages) { for (const auto &dep : cmake.packages) {
ss << "find_package(" << dep.name << ' '; auto version = dep.version;
if (dep.version != "*") { if (version == "*")
ss << dep.version << " "; version.clear();
} auto required = dep.required ? "REQUIRED" : "";
if (dep.required) { auto components = std::make_pair("COMPONENTS", dep.components);
ss << "REQUIRED "; cmd("find_package")(dep.name, version, required, components).endl();
}
if (!dep.components.empty()) {
ss << "COMPONENTS ";
for (const auto &comp : dep.components) {
ss << comp << " ";
}
}
ss << ")\n\n";
} }
} }
if (!cmake.contents.empty()) { if (!cmake.contents.empty()) {
ss << "include(FetchContent)\n\n"; cmd("include")("FetchContent").endl();
for (const auto &dep : cmake.contents) { for (const auto &dep : cmake.contents) {
ss << "message(STATUS \"Fetching " << dep.first << "...\")\n"; cmd("message")("STATUS", "Fetching " + dep.first + "...");
ss << "FetchContent_Declare(\n\t" << dep.first << "\n"; ss << "FetchContent_Declare(\n\t" << dep.first << "\n";
for (const auto &arg : dep.second) { for (const auto &arg : dep.second) {
std::string first_arg = arg.first; std::string first_arg = arg.first;
@ -425,17 +418,18 @@ int generate_cmake(const char *path, bool root) {
} else { } else {
// don't change arg // don't change arg
} }
ss << "\t" << first_arg << " " << arg.second << "\n"; ss << "\t" << first_arg << "\n\t\t" << arg.second << "\n";
} }
ss << ")\n" ss << ")\n";
<< "FetchContent_MakeAvailable(" << dep.first << ")\n\n"; cmd("FetchContent_MakeAvailable")(dep.first).endl();
} }
} }
if (!cmake.options.empty()) { if (!cmake.options.empty()) {
for (const auto &opt : cmake.options) { for (const auto &opt : cmake.options) {
ss << "option(" << opt.name << " \"" << opt.comment << "\" " << (opt.val ? "ON" : "OFF") << ")\n\n"; cmd("option")(opt.name, opt.comment, opt.val ? "ON" : "OFF");
} }
endl();
} }
if (!cmake.settings.empty()) { if (!cmake.settings.empty()) {
@ -446,23 +440,19 @@ int generate_cmake(const char *path, bool root) {
} else { } else {
set_val = mpark::get<0>(set.val) ? "ON" : "OFF"; set_val = mpark::get<0>(set.val) ? "ON" : "OFF";
} }
ss << "set(" << set.name << " " << set_val;
;
if (set.cache) { if (set.cache) {
std::string typ; auto typ = set.val.index() == 1 ? "STRING" : "BOOL";
if (set.val.index() == 1) auto force = set.force ? "FORCE" : "";
typ = "STRING"; cmd("set")(set.name, set_val, typ, set.comment, force);
else } else {
typ = "BOOL"; cmd("set")(set.name, set_val);
ss << " CACHE " << typ << " \"" << set.comment << "\"";
if (set.force)
ss << " FORCE";
} }
ss << ")\n\n";
} }
endl();
} }
// generate_cmake is called on these recursively later // generate_cmake is called on the subdirectories recursively later
if (!cmake.subdirs.empty()) { if (!cmake.subdirs.empty()) {
for (const auto &dir : cmake.subdirs) { for (const auto &dir : cmake.subdirs) {
// clang-format off // clang-format off
@ -482,28 +472,11 @@ int generate_cmake(const char *path, bool root) {
if (!cmake.targets.empty()) { if (!cmake.targets.empty()) {
for (const auto &target : cmake.targets) { for (const auto &target : cmake.targets) {
comment("Target " + target.name); comment("Target " + target.name);
if (!target.cmake_before.empty()) { inject_includes(target.include_before);
ss << tolf(target.cmake_before) << "\n\n"; inject_cmake(target.cmake_before);
}
if (!target.include_before.empty()) {
for (const auto &file : target.include_before) {
// TODO: warn/error if file doesn't exist?
cmd("include")(file);
}
endl();
}
if (!target.sources.empty()) { if (!target.sources.empty()) {
// TODO: add duplicate checking auto sources = expand_cmake_paths(target.sources);
std::vector<std::string> sources;
for (const auto &src : target.sources) {
auto path = fs::path(src);
auto expanded = detail::expand_cmake_path(path);
for (const auto &f : expanded) {
sources.push_back(f);
}
}
if (sources.empty()) { if (sources.empty()) {
throw std::runtime_error(target.name + " sources wildcard found 0 files"); throw std::runtime_error(target.name + " sources wildcard found 0 files");
} }
@ -583,17 +556,8 @@ int generate_cmake(const char *path, bool root) {
cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl(); cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl();
} }
if (!target.cmake_after.empty()) { inject_includes(target.include_after);
ss << tolf(target.cmake_after) << "\n\n"; inject_cmake(target.cmake_after);
}
if (!target.include_after.empty()) {
for (const auto &file : target.include_after) {
// TODO: warn/error if file doesn't exist?
cmd("include")(file);
}
endl();
}
} }
} }
@ -601,59 +565,32 @@ int generate_cmake(const char *path, bool root) {
cmd("include")("CTest"); cmd("include")("CTest");
cmd("enable_testing")().endl(); cmd("enable_testing")().endl();
for (const auto &test : cmake.tests) { for (const auto &test : cmake.tests) {
ss << "add_test(NAME " << test.name << " COMMAND " << test.cmd; auto name = std::make_pair("NAME", test.name);
if (!test.args.empty()) { auto command = std::make_pair("COMMAND", test.cmd);
for (const auto &arg : test.args) { cmd("add_test")(name, command, test.args).endl();
ss << " " << arg;
}
}
ss << ")\n\n";
} }
} }
if (!cmake.installs.empty()) { if (!cmake.installs.empty()) {
for (const auto &inst : cmake.installs) { for (const auto &inst : cmake.installs) {
ss << "install(\n"; auto targets = std::make_pair("TARGETS", inst.targets);
if (!inst.targets.empty()) { auto dirs = std::make_pair("DIRS", inst.dirs);
ss << "\tTARGETS "; std::vector<std::string> files_data;
for (const auto &target : inst.targets) {
ss << target << " ";
}
}
if (!inst.dirs.empty()) {
ss << "\tDIRS ";
for (const auto &dir : inst.dirs) {
ss << dir << " ";
}
}
if (!inst.files.empty()) { if (!inst.files.empty()) {
ss << "\tFILES "; files_data = expand_cmake_paths(inst.files);
int files_added = 0; if (files_data.empty()) {
for (const auto &file : inst.files) {
auto path = detail::expand_cmake_path(fs::path(file));
for (const auto &f : path) {
ss << f << " ";
files_added++;
}
}
if (files_added == 0) {
throw std::runtime_error("[[install]] files wildcard did not resolve to any files"); throw std::runtime_error("[[install]] files wildcard did not resolve to any files");
} }
} }
if (!inst.configs.empty()) { auto files = std::make_pair("FILES", inst.files);
ss << "\tCONFIGURATIONS"; auto configs = std::make_pair("CONFIGURATIONS", inst.configs);
for (const auto &conf : inst.configs) { auto destination = std::make_pair("DESTINATION", inst.destination);
ss << conf << " "; auto component = std::make_pair("COMPONENT", inst.targets.empty() ? "" : inst.targets.front());
} cmd("install")(targets, dirs, files, configs, destination, component);
}
ss << "\n\tDESTINATION " << inst.destination << "\n\t";
if (!inst.targets.empty())
ss << "COMPONENT " << inst.targets[0] << "\n)\n\n";
else
ss << "\n)\n\n";
} }
} }
// Generate CMakeLists.txt
auto list_path = fs::path(path) / "CMakeLists.txt"; auto list_path = fs::path(path) / "CMakeLists.txt";
auto should_regenerate = [&list_path, &ss]() { auto should_regenerate = [&list_path, &ss]() {
@ -682,9 +619,7 @@ int generate_cmake(const char *path, bool root) {
if (fs::exists(fs::path(sub) / "cmake.toml")) if (fs::exists(fs::path(sub) / "cmake.toml"))
generate_cmake(sub.c_str(), false); generate_cmake(sub.c_str(), false);
} }
} else {
throw std::runtime_error("No cmake.toml found!");
}
return 0; return 0;
} }
} // namespace gen } // namespace gen

Loading…
Cancel
Save