diff --git a/CMakeLists.txt b/CMakeLists.txt index b873f7e..ab7d0dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,8 +76,10 @@ target_link_libraries(cmkr PRIVATE ) install( - TARGETS cmkr - DESTINATION ${CMAKE_INSTALL_PREFIX}/bin - COMPONENT cmkr + TARGETS + cmkr + DESTINATION + "${CMAKE_INSTALL_PREFIX}/bin" + COMPONENT + cmkr ) - diff --git a/src/cmkrlib/gen.cpp b/src/cmkrlib/gen.cpp index 953df46..e5677bf 100644 --- a/src/cmkrlib/gen.cpp +++ b/src/cmkrlib/gen.cpp @@ -16,8 +16,6 @@ namespace cmkr { namespace gen { -namespace detail { - inline std::string to_upper(const std::string &str) { std::string temp; temp.reserve(str.size()); @@ -65,7 +63,18 @@ static std::vector expand_cmake_path(const fs::path &p) { return temp; } -} // namespace detail +static std::vector expand_cmake_paths(const std::vector &sources) { + // TODO: add duplicate checking + std::vector 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) { fs::create_directory("src"); @@ -76,12 +85,12 @@ int generate_project(const char *str) { std::string target; std::string dest; if (!strcmp(str, "executable")) { - mainbuf = detail::format(hello_world, "main"); + mainbuf = format(hello_world, "main"); installed = "targets"; target = dir_name; dest = "bin"; } else if (!strcmp(str, "static") || !strcmp(str, "shared") || !strcmp(str, "library")) { - mainbuf = detail::format(hello_world, "test"); + mainbuf = format(hello_world, "test"); installed = "targets"; target = dir_name; dest = "lib"; @@ -94,7 +103,7 @@ int generate_project(const char *str) { "! 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")) { std::ofstream ofs("src/main.cpp"); @@ -121,15 +130,6 @@ struct CommandEndl { void endl() { ss << '\n'; } }; -template -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 struct Command { std::stringstream &ss; @@ -182,11 +182,11 @@ struct Command { return true; } + had_newline = true; for (const auto &value : vec) { - ss << '\n' << indent(depth + 1) << quote(value); + print_arg(value); } - had_newline = true; - first_arg = false; + return true; } @@ -197,25 +197,9 @@ struct Command { } for (const auto &itr : map) { - ss << '\n' << indent(depth + 1) << itr.first; - ss << '\n' << indent(depth + 2) << quote(itr.second); + print_arg(itr); } - 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; } @@ -225,40 +209,57 @@ struct Command { return true; } - ss << '\n' << indent(depth + 1) << kv.first; + had_newline = true; + print_arg(kv.first); + depth++; for (const auto &s : kv.second) { - ss << '\n' << indent(depth + 2) << quote(s); + print_arg(s); } - had_newline = true; - first_arg = false; + depth--; return true; } template bool print_arg(const std::pair &kv) { + std::stringstream tmp; + tmp << kv.second; + auto str = tmp.str(); + if (kv.second.empty()) { return true; } - ss << '\n' << indent(depth + 1) << kv.first; - ss << '\n' << indent(depth + 2) << quote(kv.second); had_newline = true; - first_arg = false; + print_arg(kv.first); + depth++; + print_arg(str); + depth--; return true; } template - bool print_arg(const T &value) { - if (first_arg) { - first_arg = false; - } else { - ss << (had_newline ? '\n' : ' '); - } + bool print_arg(const T &value, bool indentation = true) { std::stringstream tmp; tmp << value; - ss << quote(tmp.str()); + 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; + } else { + ss << ' '; + } + } + + ss << quote(str); return true; } @@ -275,416 +276,350 @@ struct Command { }; int generate_cmake(const char *path, bool root) { - if (fs::exists(fs::path(path) / "cmake.toml")) { - cmake::CMake cmake(path, false); - std::stringstream ss; - - int indent = 0; - auto cmd = [&ss, &indent](const std::string &command) { - if (command.empty()) - throw std::invalid_argument("command cannot be empty"); - if (command == "if") { - indent++; - return Command(ss, indent - 1, command); - } else if (command == "else" || command == "elseif") { - return Command(ss, indent - 1, command); - } else if (command == "endif") { - indent--; - } - return Command(ss, indent, command); - }; - auto comment = [&ss](const std::string &comment) { - ss << "# " << comment << '\n'; - return CommandEndl(ss); - }; - auto endl = [&ss]() { ss << '\n'; }; - - auto tolf = [](const std::string &str) { - std::string result; - for (char ch : str) { - if (ch != '\r') { - result += ch; - } - } - return result; - }; - - comment("This file was generated automatically by cmkr.").endl(); + if (!fs::exists(fs::path(path) / "cmake.toml")) { + throw std::runtime_error("No cmake.toml found!"); + } - if (!cmake.cmake_before.empty()) { - ss << tolf(cmake.cmake_before) << "\n\n"; + // Helper lambdas for more convenient CMake generation + std::stringstream ss; + int indent = 0; + auto cmd = [&ss, &indent](const std::string &command) { + if (command.empty()) + throw std::invalid_argument("command cannot be empty"); + if (command == "if") { + indent++; + return Command(ss, indent - 1, command); + } else if (command == "else" || command == "elseif") { + return Command(ss, indent - 1, command); + } else if (command == "endif") { + indent--; + } + return Command(ss, indent, command); + }; + auto comment = [&ss](const std::string &comment) { + ss << "# " << comment << '\n'; + return CommandEndl(ss); + }; + auto endl = [&ss]() { ss << '\n'; }; + auto tolf = [](const std::string &str) { + std::string result; + for (char ch : str) { + if (ch != '\r') { + result += ch; + } } - - if (!cmake.include_before.empty()) { - for (const auto &file : cmake.include_before) { + return result; + }; + auto inject_includes = [&cmd, &endl](const std::vector &includes) { + if (!includes.empty()) { + for (const auto &file : includes) { // TODO: warn/error if file doesn't exist? cmd("include")(file); } endl(); } + }; + auto inject_cmake = [&ss, &tolf](const std::string &cmake) { + if (!cmake.empty()) { + ss << tolf(cmake) << "\n\n"; + } + }; - if (root) { - comment("Regenerate CMakeLists.txt file when necessary"); - cmd("include")(cmake.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT").endl(); + comment("This file was generated automatically by cmkr.").endl(); + // TODO: add link with proper documentation - // clang-format off - cmd("if")("CMKR_INCLUDE_RESULT"); - cmd("cmkr")(); - cmd("endif")().endl(); - // clang-format on + cmake::CMake cmake(path, false); - cmd("cmake_minimum_required")("VERSION", cmake.cmake_version).endl(); + if (root) { + comment("Regenerate CMakeLists.txt file when necessary"); + cmd("include")(cmake.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT").endl(); - cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON").endl(); - } + // clang-format off + cmd("if")("CMKR_INCLUDE_RESULT"); + cmd("cmkr")(); + cmd("endif")().endl(); + // clang-format on - // TODO: remove support and replace with global compile-features - if (!cmake.cppflags.empty()) { - ss << "set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} \""; - for (const auto &flag : cmake.cppflags) { - ss << flag << " "; - } - ss << "\")\n\n"; - } + cmd("cmake_minimum_required")("VERSION", cmake.cmake_version).endl(); - // TODO: remove support and replace with global compile-features - if (!cmake.cflags.empty()) { - ss << "set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} \""; - for (const auto &flag : cmake.cflags) { - ss << flag << " "; - } - ss << "\")\n\n"; - } + cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON").endl(); + } - // TODO: remove support and replace with global linker-flags - if (!cmake.linkflags.empty()) { - ss << "set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} \""; - for (const auto &flag : cmake.linkflags) { - ss << flag << " "; - } - ss << "\")\n\n"; + // TODO: remove support and replace with global compile-features + if (!cmake.cppflags.empty()) { + ss << "set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} \""; + for (const auto &flag : cmake.cppflags) { + ss << flag << " "; } + ss << "\")\n\n"; + } - 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(); + // TODO: remove support and replace with global compile-features + if (!cmake.cflags.empty()) { + ss << "set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} \""; + for (const auto &flag : cmake.cflags) { + ss << flag << " "; } + ss << "\")\n\n"; + } - if (!cmake.cmake_after.empty()) { - ss << tolf(cmake.cmake_after) << "\n\n"; + // TODO: remove support and replace with global linker-flags + if (!cmake.linkflags.empty()) { + ss << "set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} \""; + for (const auto &flag : cmake.linkflags) { + ss << flag << " "; } + ss << "\")\n\n"; + } - if (!cmake.include_after.empty()) { - for (const auto &file : cmake.include_after) { - // TODO: warn/error if file doesn't exist? - cmd("include")(file); - } - endl(); + inject_includes(cmake.include_before); + inject_cmake(cmake.cmake_before); + + 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(); + } + + inject_includes(cmake.include_after); + inject_cmake(cmake.cmake_after); + + if (!cmake.packages.empty()) { + for (const auto &dep : cmake.packages) { + auto version = dep.version; + if (version == "*") + version.clear(); + auto required = dep.required ? "REQUIRED" : ""; + auto components = std::make_pair("COMPONENTS", dep.components); + cmd("find_package")(dep.name, version, required, components).endl(); } + } - if (!cmake.packages.empty()) { - for (const auto &dep : cmake.packages) { - ss << "find_package(" << dep.name << ' '; - if (dep.version != "*") { - ss << dep.version << " "; - } - if (dep.required) { - ss << "REQUIRED "; - } - if (!dep.components.empty()) { - ss << "COMPONENTS "; - for (const auto &comp : dep.components) { - ss << comp << " "; - } + if (!cmake.contents.empty()) { + cmd("include")("FetchContent").endl(); + for (const auto &dep : cmake.contents) { + cmd("message")("STATUS", "Fetching " + dep.first + "..."); + ss << "FetchContent_Declare(\n\t" << dep.first << "\n"; + for (const auto &arg : dep.second) { + std::string first_arg = arg.first; + if (first_arg == "git") { + first_arg = "GIT_REPOSITORY"; + } else if (first_arg == "tag") { + first_arg = "GIT_TAG"; + } else if (first_arg == "svn") { + first_arg = "SVN_REPOSITORY"; + } else if (first_arg == "rev") { + first_arg = "SVN_REVISION"; + } else if (first_arg == "url") { + first_arg = "URL"; + } else if (first_arg == "hash") { + first_arg = "URL_HASH"; + } else { + // don't change arg } - ss << ")\n\n"; + ss << "\t" << first_arg << "\n\t\t" << arg.second << "\n"; } + ss << ")\n"; + cmd("FetchContent_MakeAvailable")(dep.first).endl(); } + } - if (!cmake.contents.empty()) { - ss << "include(FetchContent)\n\n"; - for (const auto &dep : cmake.contents) { - ss << "message(STATUS \"Fetching " << dep.first << "...\")\n"; - ss << "FetchContent_Declare(\n\t" << dep.first << "\n"; - for (const auto &arg : dep.second) { - std::string first_arg = arg.first; - if (first_arg == "git") { - first_arg = "GIT_REPOSITORY"; - } else if (first_arg == "tag") { - first_arg = "GIT_TAG"; - } else if (first_arg == "svn") { - first_arg = "SVN_REPOSITORY"; - } else if (first_arg == "rev") { - first_arg = "SVN_REVISION"; - } else if (first_arg == "url") { - first_arg = "URL"; - } else if (first_arg == "hash") { - first_arg = "URL_HASH"; - } else { - // don't change arg - } - ss << "\t" << first_arg << " " << arg.second << "\n"; - } - ss << ")\n" - << "FetchContent_MakeAvailable(" << dep.first << ")\n\n"; - } + if (!cmake.options.empty()) { + for (const auto &opt : cmake.options) { + cmd("option")(opt.name, opt.comment, opt.val ? "ON" : "OFF"); } + endl(); + } - if (!cmake.options.empty()) { - for (const auto &opt : cmake.options) { - ss << "option(" << opt.name << " \"" << opt.comment << "\" " << (opt.val ? "ON" : "OFF") << ")\n\n"; + if (!cmake.settings.empty()) { + for (const auto &set : cmake.settings) { + std::string set_val; + if (set.val.index() == 1) { + set_val = mpark::get<1>(set.val); + } else { + set_val = mpark::get<0>(set.val) ? "ON" : "OFF"; } - } - if (!cmake.settings.empty()) { - for (const auto &set : cmake.settings) { - std::string set_val; - if (set.val.index() == 1) { - set_val = mpark::get<1>(set.val); - } else { - set_val = mpark::get<0>(set.val) ? "ON" : "OFF"; - } - ss << "set(" << set.name << " " << set_val; - ; - if (set.cache) { - std::string typ; - if (set.val.index() == 1) - typ = "STRING"; - else - typ = "BOOL"; - ss << " CACHE " << typ << " \"" << set.comment << "\""; - if (set.force) - ss << " FORCE"; - } - ss << ")\n\n"; + if (set.cache) { + auto typ = set.val.index() == 1 ? "STRING" : "BOOL"; + auto force = set.force ? "FORCE" : ""; + cmd("set")(set.name, set_val, typ, set.comment, force); + } else { + cmd("set")(set.name, set_val); } } + endl(); + } - // generate_cmake is called on these recursively later - if (!cmake.subdirs.empty()) { - for (const auto &dir : cmake.subdirs) { - // clang-format off - 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(); - } - endl(); + // generate_cmake is called on the subdirectories recursively later + if (!cmake.subdirs.empty()) { + for (const auto &dir : cmake.subdirs) { + // clang-format off + 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(); } + endl(); + } - if (!cmake.targets.empty()) { - for (const auto &target : cmake.targets) { - comment("Target " + target.name); - if (!target.cmake_before.empty()) { - ss << tolf(target.cmake_before) << "\n\n"; - } + if (!cmake.targets.empty()) { + for (const auto &target : cmake.targets) { + comment("Target " + target.name); + inject_includes(target.include_before); + 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()) { + auto sources = expand_cmake_paths(target.sources); + if (sources.empty()) { + throw std::runtime_error(target.name + " sources wildcard found 0 files"); } - - if (!target.sources.empty()) { - // TODO: add duplicate checking - std::vector 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()) { - throw std::runtime_error(target.name + " sources wildcard found 0 files"); + if (target.type != cmake::target_interface) { + // Do not add cmake.toml twice + if (std::find(sources.begin(), sources.end(), "cmake.toml") == sources.end()) { + sources.push_back("cmake.toml"); } - if (target.type != cmake::target_interface) { - // Do not add cmake.toml twice - if (std::find(sources.begin(), sources.end(), "cmake.toml") == sources.end()) { - sources.push_back("cmake.toml"); - } - } - cmd("set")(target.name + "_SOURCES", sources).endl(); - } - - std::string add_command; - std::string target_type; - std::string target_scope; - switch (target.type) { - case cmake::target_executable: - add_command = "add_executable"; - target_type = ""; - target_scope = "PRIVATE"; - break; - case cmake::target_library: - add_command = "add_library"; - target_type = ""; - target_scope = "PUBLIC"; - break; - case cmake::target_shared: - add_command = "add_library"; - target_type = "SHARED"; - target_scope = "PUBLIC"; - break; - case cmake::target_static: - add_command = "add_library"; - target_type = "STATIC"; - target_scope = "PUBLIC"; - break; - case cmake::target_interface: - add_command = "add_library"; - target_type = "INTERFACE"; - target_scope = "INTERFACE"; - break; - case cmake::target_custom: - // TODO: add proper support, this is hacky - add_command = "add_custom_target"; - target_type = "SOURCES"; - target_scope = "PUBLIC"; - break; - default: - assert("Unimplemented enum value" && false); } + cmd("set")(target.name + "_SOURCES", sources).endl(); + } - cmd(add_command)(target.name, target_type, "${" + target.name + "_SOURCES}").endl(); + std::string add_command; + std::string target_type; + std::string target_scope; + switch (target.type) { + case cmake::target_executable: + add_command = "add_executable"; + target_type = ""; + target_scope = "PRIVATE"; + break; + case cmake::target_library: + add_command = "add_library"; + target_type = ""; + target_scope = "PUBLIC"; + break; + case cmake::target_shared: + add_command = "add_library"; + target_type = "SHARED"; + target_scope = "PUBLIC"; + break; + case cmake::target_static: + add_command = "add_library"; + target_type = "STATIC"; + target_scope = "PUBLIC"; + break; + case cmake::target_interface: + add_command = "add_library"; + target_type = "INTERFACE"; + target_scope = "INTERFACE"; + break; + case cmake::target_custom: + // TODO: add proper support, this is hacky + add_command = "add_custom_target"; + target_type = "SOURCES"; + target_scope = "PUBLIC"; + break; + default: + assert("Unimplemented enum value" && false); + } - if (!target.sources.empty()) { - cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + target.name + "_SOURCES}").endl(); - } + cmd(add_command)(target.name, target_type, "${" + target.name + "_SOURCES}").endl(); - if (!target.alias.empty()) { - cmd("add_library")(target.alias, "ALIAS", target.name); - } - - auto target_cmd = [&](const char *command, const std::vector &args) { - if (!args.empty()) { - cmd(command)(target.name, target_scope, args).endl(); - } - }; - - 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_precompile_headers", target.precompile_headers); - - if (!target.properties.empty()) { - cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl(); - } + if (!target.sources.empty()) { + cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + target.name + "_SOURCES}").endl(); + } - if (!target.cmake_after.empty()) { - ss << tolf(target.cmake_after) << "\n\n"; - } + if (!target.alias.empty()) { + cmd("add_library")(target.alias, "ALIAS", target.name); + } - if (!target.include_after.empty()) { - for (const auto &file : target.include_after) { - // TODO: warn/error if file doesn't exist? - cmd("include")(file); - } - endl(); + auto target_cmd = [&](const char *command, const std::vector &args) { + if (!args.empty()) { + cmd(command)(target.name, target_scope, args).endl(); } + }; + + 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_precompile_headers", target.precompile_headers); + + if (!target.properties.empty()) { + cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl(); } + + inject_includes(target.include_after); + inject_cmake(target.cmake_after); } + } - if (!cmake.tests.empty()) { - cmd("include")("CTest"); - cmd("enable_testing")().endl(); - for (const auto &test : cmake.tests) { - ss << "add_test(NAME " << test.name << " COMMAND " << test.cmd; - if (!test.args.empty()) { - for (const auto &arg : test.args) { - ss << " " << arg; - } - } - ss << ")\n\n"; - } + if (!cmake.tests.empty()) { + cmd("include")("CTest"); + cmd("enable_testing")().endl(); + for (const auto &test : cmake.tests) { + auto name = std::make_pair("NAME", test.name); + auto command = std::make_pair("COMMAND", test.cmd); + cmd("add_test")(name, command, test.args).endl(); } + } - if (!cmake.installs.empty()) { - for (const auto &inst : cmake.installs) { - ss << "install(\n"; - if (!inst.targets.empty()) { - ss << "\tTARGETS "; - 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()) { - ss << "\tFILES "; - int files_added = 0; - 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"); - } + if (!cmake.installs.empty()) { + for (const auto &inst : cmake.installs) { + auto targets = std::make_pair("TARGETS", inst.targets); + auto dirs = std::make_pair("DIRS", inst.dirs); + std::vector files_data; + if (!inst.files.empty()) { + files_data = expand_cmake_paths(inst.files); + if (files_data.empty()) { + throw std::runtime_error("[[install]] files wildcard did not resolve to any files"); } - if (!inst.configs.empty()) { - ss << "\tCONFIGURATIONS"; - for (const auto &conf : inst.configs) { - ss << conf << " "; - } - } - ss << "\n\tDESTINATION " << inst.destination << "\n\t"; - if (!inst.targets.empty()) - ss << "COMPONENT " << inst.targets[0] << "\n)\n\n"; - else - ss << "\n)\n\n"; } + auto files = std::make_pair("FILES", inst.files); + auto configs = std::make_pair("CONFIGURATIONS", inst.configs); + auto destination = std::make_pair("DESTINATION", inst.destination); + auto component = std::make_pair("COMPONENT", inst.targets.empty() ? "" : inst.targets.front()); + cmd("install")(targets, dirs, files, configs, destination, component); } + } - auto list_path = fs::path(path) / "CMakeLists.txt"; + // Generate CMakeLists.txt + auto list_path = fs::path(path) / "CMakeLists.txt"; - auto should_regenerate = [&list_path, &ss]() { - if (!fs::exists(list_path)) - return true; + auto should_regenerate = [&list_path, &ss]() { + if (!fs::exists(list_path)) + return true; - std::ifstream ifs(list_path, std::ios_base::binary); - if (!ifs.is_open()) { - throw std::runtime_error("Failed to read " + list_path.string()); - } + std::ifstream ifs(list_path, std::ios_base::binary); + if (!ifs.is_open()) { + throw std::runtime_error("Failed to read " + list_path.string()); + } - std::string data((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - return data != ss.str(); - }(); + std::string data((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + return data != ss.str(); + }(); - if (should_regenerate) { - std::ofstream ofs(list_path, std::ios_base::binary); - if (ofs.is_open()) { - ofs << ss.str(); - } else { - throw std::runtime_error("Failed to write " + list_path.string()); - } + if (should_regenerate) { + std::ofstream ofs(list_path, std::ios_base::binary); + if (ofs.is_open()) { + ofs << ss.str(); + } else { + throw std::runtime_error("Failed to write " + list_path.string()); } + } - for (const auto &sub : cmake.subdirs) { - if (fs::exists(fs::path(sub) / "cmake.toml")) - generate_cmake(sub.c_str(), false); - } - } else { - throw std::runtime_error("No cmake.toml found!"); + for (const auto &sub : cmake.subdirs) { + if (fs::exists(fs::path(sub) / "cmake.toml")) + generate_cmake(sub.c_str(), false); } + return 0; } } // namespace gen