diff --git a/.gitignore b/.gitignore index 65fe6fa..a1b9f9c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build*/ .idea/ cmake-build*/ CMakeLists.txt.user +.vscode/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index eb3ec57..1a54bc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,17 +7,14 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build") endif() -# Regenerate CMakeLists.txt automatically in the root project set(CMKR_ROOT_PROJECT OFF) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) set(CMKR_ROOT_PROJECT ON) # Enable folder support set_property(GLOBAL PROPERTY USE_FOLDERS ON) -endif() -# Create a configure-time dependency on cmake.toml to improve IDE support -if(CMKR_ROOT_PROJECT) + # Create a configure-time dependency on cmake.toml to improve IDE support configure_file(cmake.toml cmake.toml COPYONLY) endif() @@ -33,7 +30,7 @@ project(cmkr include("cmake/generate_documentation.cmake") include("cmake/generate_resources.cmake") -# third_party +# Subdirectory: third_party set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER}) if(CMAKE_FOLDER) set(CMAKE_FOLDER "${CMAKE_FOLDER}/third_party") @@ -43,7 +40,7 @@ endif() add_subdirectory(third_party) set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) -# tests +# Subdirectory: tests set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER}) if(CMAKE_FOLDER) set(CMAKE_FOLDER "${CMAKE_FOLDER}/tests") @@ -53,29 +50,14 @@ endif() add_subdirectory(tests) set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) - - -# Target cmkr_generate_documentation -set(CMKR_TARGET cmkr_generate_documentation) -set(cmkr_generate_documentation_SOURCES "") - -set(CMKR_SOURCES ${cmkr_generate_documentation_SOURCES}) +# Target: cmkr_generate_documentation add_library(cmkr_generate_documentation INTERFACE) -if(cmkr_generate_documentation_SOURCES) - target_sources(cmkr_generate_documentation INTERFACE ${cmkr_generate_documentation_SOURCES}) -endif() - +set(CMKR_TARGET cmkr_generate_documentation) generate_documentation() -unset(CMKR_TARGET) -unset(CMKR_SOURCES) - -# Target cmkr -set(CMKR_TARGET cmkr) -set(cmkr_SOURCES "") - -list(APPEND cmkr_SOURCES +# Target: cmkr +set(cmkr_SOURCES "src/arguments.cpp" "src/build.cpp" "src/cmake_generator.cpp" @@ -93,24 +75,12 @@ list(APPEND cmkr_SOURCES "include/project_parser.hpp" "cmake/cmkr.cmake" "cmake/version.hpp.in" -) - -list(APPEND cmkr_SOURCES cmake.toml ) -set(CMKR_SOURCES ${cmkr_SOURCES}) add_executable(cmkr) -if(cmkr_SOURCES) - target_sources(cmkr PRIVATE ${cmkr_SOURCES}) -endif() - -get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT) -if(NOT CMKR_VS_STARTUP_PROJECT) - set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT cmkr) -endif() - +target_sources(cmkr PRIVATE ${cmkr_SOURCES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${cmkr_SOURCES}) target_compile_features(cmkr PRIVATE @@ -129,10 +99,13 @@ target_link_libraries(cmkr PRIVATE nlohmann_json ) -generate_resources(${CMKR_TARGET}) +get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT) +if(NOT CMKR_VS_STARTUP_PROJECT) + set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT cmkr) +endif() -unset(CMKR_TARGET) -unset(CMKR_SOURCES) +set(CMKR_TARGET cmkr) +generate_resources(${CMKR_TARGET}) install( TARGETS diff --git a/README.md b/README.md index 7fe072f..b9387e4 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,10 @@ arguments: ## Credits -- https://github.com/gulrak/filesystem -- https://github.com/Tessil/ordered-map -- https://github.com/ToruNiina/toml11 -- 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 ❤️ +- [gulrak/filesystem](https://github.com/gulrak/filesystem) +- [Tessil/ordered-map](https://github.com/Tessil/ordered-map) +- [ToruNiina/toml11](https://github.com/ToruNiina/toml11) +- [mpark/variant](https://github.com/mpark/variant) +- [SVG Repo Hammer](https://www.svgrepo.com/svg/192268/hammer) +- [can1357](https://github.com/can1357) for buying `cmkr.build` ❤️ +- [JustasMasiulis](https://github.com/JustasMasiulis) for fixing the dark theme ❤️ diff --git a/cmake.toml b/cmake.toml index 64e33ac..a37ce2f 100644 --- a/cmake.toml +++ b/cmake.toml @@ -7,11 +7,11 @@ name = "cmkr" version = "0.2.16" description = "CMakeLists generator from TOML" languages = ["CXX"] -subdirs = ["third_party", "tests"] include-after = [ "cmake/generate_documentation.cmake", "cmake/generate_resources.cmake" ] +subdirs = ["third_party", "tests"] [target.cmkr_generate_documentation] type = "interface" diff --git a/docs/index.md b/docs/index.md index cfcd18f..82afa0d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -70,8 +70,10 @@ arguments: ## Credits -- https://github.com/gulrak/filesystem -- https://github.com/Tessil/ordered-map -- https://github.com/ToruNiina/toml11 -- https://github.com/mpark/variant -- https://www.svgrepo.com/svg/192268/hammer +- [gulrak/filesystem](https://github.com/gulrak/filesystem) +- [Tessil/ordered-map](https://github.com/Tessil/ordered-map) +- [ToruNiina/toml11](https://github.com/ToruNiina/toml11) +- [mpark/variant](https://github.com/mpark/variant) +- [SVG Repo Hammer](https://www.svgrepo.com/svg/192268/hammer) +- [can1357](https://github.com/can1357) for buying `cmkr.build` ❤️ +- [JustasMasiulis](https://github.com/JustasMasiulis) for fixing the dark theme ❤️ diff --git a/include/project_parser.hpp b/include/project_parser.hpp index cbb1cd8..d504e98 100644 --- a/include/project_parser.hpp +++ b/include/project_parser.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace cmkr { diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp index 70ab516..0d7dda6 100644 --- a/src/cmake_generator.cpp +++ b/src/cmake_generator.cpp @@ -4,6 +4,7 @@ #include #include "fs.hpp" +#include "project_parser.hpp" #include #include #include @@ -25,7 +26,7 @@ static std::string format(const char *format, tsl::ordered_map expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool root_project) { +static std::vector expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool is_root_project) { std::vector temp; auto extract_suffix = [](const fs::path &base, const fs::path &full) { @@ -38,7 +39,7 @@ static std::vector expand_cmake_path(const fs::path &name, const fs auto stem = name.filename().stem().string(); auto ext = name.extension(); - if (root_project && stem == "**" && name == name.filename()) { + if (is_root_project && stem == "**" && name == name.filename()) { throw std::runtime_error("Recursive globbing not allowed in project root: " + name.string()); } @@ -66,11 +67,11 @@ static std::vector expand_cmake_path(const fs::path &name, const fs return temp; } -static std::vector expand_cmake_paths(const std::vector &sources, const fs::path &toml_dir, bool root_project) { +static std::vector expand_cmake_paths(const std::vector &sources, const fs::path &toml_dir, bool is_root_project) { // TODO: add duplicate checking std::vector result; for (const auto &src : sources) { - auto expanded = expand_cmake_path(src, toml_dir, root_project); + auto expanded = expand_cmake_path(src, toml_dir, is_root_project); for (const auto &f : expanded) { result.push_back(f); } @@ -416,9 +417,10 @@ struct Generator { } if (!condition.empty()) { - cmd("endif")(); + cmd("endif")().endl(); + } else if (!itr.second.empty()) { + endl(); } - endl(); } } } @@ -505,7 +507,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { } // Root project doesn't have a parent - auto root_project = parent_project == nullptr; + auto is_root_project = parent_project == nullptr; parser::Project project(parent_project, path, false); Generator gen(project); @@ -521,12 +523,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { comment("See " + cmkr_url + " for more information"); endl(); - if (root_project) { + if (is_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(); + cmd("cmake_policy")("SET", "CMP0091", "NEW"); switch (project.project_msvc_runtime) { case parser::msvc_dynamic: @@ -538,6 +540,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { default: break; } + endl(); } // clang-format on @@ -549,14 +552,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { // clang-format on } - comment("Regenerate CMakeLists.txt automatically in the root project"); cmd("set")("CMKR_ROOT_PROJECT", "OFF"); // clang-format off cmd("if")("CMAKE_CURRENT_SOURCE_DIR", "STREQUAL", "CMAKE_SOURCE_DIR"); cmd("set")("CMKR_ROOT_PROJECT", "ON").endl(); if (!project.cmkr_include.empty()) { - comment("Bootstrap cmkr"); + comment("Bootstrap cmkr and automatically regenerate CMakeLists.txt"); cmd("include")(project.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT"); cmd("if")("CMKR_INCLUDE_RESULT"); cmd("cmkr")(); @@ -564,7 +566,10 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { } comment("Enable folder support"); - cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON"); + cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON").endl(); + + comment("Create a configure-time dependency on cmake.toml to improve IDE support"); + cmd("configure_file")("cmake.toml", "cmake.toml", "COPYONLY"); cmd("endif")().endl(); // clang-format on @@ -572,15 +577,15 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { if (!project.cmkr_include.empty() && !fs::exists(cmkr_include) && cmkr_include.is_relative()) { create_file(cmkr_include, resources::cmkr); } + } else { + // clang-format off + comment("Create a configure-time dependency on cmake.toml to improve IDE support"); + cmd("if")("CMKR_ROOT_PROJECT"); + cmd("configure_file")("cmake.toml", "cmake.toml", "COPYONLY"); + cmd("endif")().endl(); + // clang-format on } - // clang-format off - comment("Create a configure-time dependency on cmake.toml to improve IDE support"); - cmd("if")("CMKR_ROOT_PROJECT"); - cmd("configure_file")("cmake.toml", "cmake.toml", "COPYONLY"); - cmd("endif")().endl(); - // clang-format on - // TODO: remove support and replace with global compile-features if (!project.cppflags.empty()) { ss << "set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} \""; @@ -616,6 +621,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { endl(); } + // TODO: rename to 'variables'? if (!project.settings.empty()) { comment("Settings"); for (const auto &set : project.settings) { @@ -645,6 +651,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { 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(); + + for (const auto &language : project.project_languages) { + if (language == "CSharp") { + cmd("include")("CSharpUtilities").endl(); + break; + } + } } gen.conditional_includes(project.include_after); @@ -788,7 +801,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { auto add_subdir = [&](const std::string &dir) { // clang-format off - comment(dir); + comment("Subdirectory: " + dir); cmd("set")("CMKR_CMAKE_FOLDER", "${CMAKE_FOLDER}"); cmd("if")("CMAKE_FOLDER"); cmd("set")("CMAKE_FOLDER", "${CMAKE_FOLDER}/" + dir); @@ -798,17 +811,19 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { // clang-format on cmd("add_subdirectory")(dir); - cmd("set")("CMAKE_FOLDER", "${CMKR_CMAKE_FOLDER}").endl(); + cmd("set")("CMAKE_FOLDER", "${CMKR_CMAKE_FOLDER}"); }; // 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 &subdirs) { - for (const auto &dir : subdirs) { - add_subdir(dir); + for (size_t i = 0; i < subdirs.size(); i++) { + add_subdir(subdirs[i]); + if (i + 1 < subdirs.size()) { + endl(); + } } }); - endl(); } for (const auto &subdir : project.subdirs) { ConditionScope cs(gen, subdir.condition); @@ -817,23 +832,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { gen.conditional_cmake(subdir.cmake_before); add_subdir(subdir.name); + endl(); gen.conditional_includes(subdir.include_after); gen.conditional_cmake(subdir.cmake_after); } if (!project.targets.empty()) { - auto root_project = project.root(); + auto project_root = project.root(); for (size_t i = 0; i < project.targets.size(); i++) { - if (i > 0) { - endl(); - } - const auto &target = project.targets[i]; const parser::Template *tmplate = nullptr; std::unique_ptr tmplate_cs{}; - comment("Target " + target.name); + comment("Target: " + target.name); // Check if this target is using a template. if (target.type == parser::target_template) { @@ -847,56 +859,115 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { ConditionScope cs(gen, target.condition); - cmd("set")("CMKR_TARGET", target.name); + // Detect if there is cmake included before/after the target + auto has_include_before = false; + auto has_include_after = false; + { + auto has_include = [](const parser::ConditionVector &includes) { + for (const auto &itr : includes) { + for (const auto &jtr : itr.second) { + return true; + } + } + return false; + }; + auto has_include_helper = [&](const parser::Target &target) { + if (!target.cmake_before.empty() || has_include(target.include_before)) { + has_include_before = true; + } + if (!target.cmake_after.empty() || has_include(target.include_after)) { + has_include_after = true; + } + }; + if (tmplate != nullptr) { + has_include_helper(tmplate->outline); + } + has_include_helper(target); + } + // Generate the include before + if (has_include_before) { + cmd("set")("CMKR_TARGET", target.name); + } if (tmplate != nullptr) { gen.conditional_includes(tmplate->outline.include_before); gen.conditional_cmake(tmplate->outline.cmake_before); } - gen.conditional_includes(target.include_before); gen.conditional_cmake(target.cmake_before); - auto sources_var = target.name + "_SOURCES"; - - bool added_toml = false; - cmd("set")(sources_var, RawArg("\"\"")).endl(); + // Merge the sources from the template and the target. The sources + // without condition need to be processed first + parser::Condition> msources; + msources[""].clear(); - if (tmplate != nullptr) { - gen.handle_condition(tmplate->outline.sources, [&](const std::string &condition, const std::vector &condition_sources) { - auto sources = expand_cmake_paths(condition_sources, path, root_project); - if (sources.empty()) { - auto source_key = condition.empty() ? "sources" : (condition + ".sources"); - throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files"); + auto merge_sources = [&msources](const parser::ConditionVector &sources) { + for (const auto &itr : sources) { + auto &source_list = msources[itr.first]; + for (const auto &source : itr.second) { + source_list.insert(source); } - cmd("list")("APPEND", sources_var, sources); - }); + } + }; + if (tmplate != nullptr) { + merge_sources(tmplate->outline.sources); + } + merge_sources(target.sources); + + // Improve IDE support + if (target.type != parser::target_interface) { + msources[""].insert("cmake.toml"); + } + + // If there are only conditional sources we generate a 'set' to + // create an empty source list. The rest is then appended using + // 'list(APPEND ...)' + auto has_sources = false; + for (const auto &itr : msources) { + if (!itr.second.empty()) { + has_sources = true; + break; + } + } + auto sources_var = target.name + "_SOURCES"; + auto sources_with_set = true; + if (has_sources && msources[""].empty()) { + sources_with_set = false; + cmd("set")(sources_var, RawArg("\"\"")).endl(); } - gen.handle_condition(target.sources, [&](const std::string &condition, const std::vector &condition_sources) { - auto sources = expand_cmake_paths(condition_sources, path, root_project); + gen.handle_condition(msources, [&](const std::string &condition, const tsl::ordered_set &source_set) { + std::vector condition_sources; + condition_sources.reserve(source_set.size()); + for (const auto &source : source_set) { + condition_sources.push_back(source); + } + auto sources = expand_cmake_paths(condition_sources, path, is_root_project); if (sources.empty()) { auto source_key = condition.empty() ? "sources" : (condition + ".sources"); throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files"); } - // Do not add cmake.toml twice - if (!added_toml && std::find(sources.begin(), sources.end(), "cmake.toml") != sources.end()) { - added_toml = true; + if (sources_with_set) { + // This is a sanity check to make sure the unconditional sources are first + if (!condition.empty()) { + throw std::runtime_error("Unreachable code, make sure unconditional sources are first"); + } + cmd("set")(sources_var, sources); + sources_with_set = false; + } else { + cmd("list")("APPEND", sources_var, sources); } - cmd("list")("APPEND", sources_var, sources); }); auto target_type = target.type; if (tmplate != nullptr) { + if (target_type != parser::target_template) { + throw std::runtime_error("Unreachable code, unexpected target type for template"); + } target_type = tmplate->outline.type; } - if (!added_toml && target_type != parser::target_interface) { - cmd("list")("APPEND", sources_var, std::vector{"cmake.toml"}).endl(); - } - cmd("set")("CMKR_SOURCES", "${" + sources_var + "}"); - std::string add_command; std::string target_type_string; std::string target_scope; @@ -952,34 +1023,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { cmd(add_command)(target.name, target_type_string, "${" + sources_var + "}"); } else { cmd(add_command)(target.name, target_type_string).endl(); - - // clang-format off - cmd("if")(sources_var); - cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}"); - cmd("endif")().endl(); - // clang-format on + if (has_sources) { + cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", + "${" + sources_var + "}"); + } } } else { cmd(add_command)(target.name, target_type_string).endl(); - - // clang-format off - cmd("if")(sources_var); + if (has_sources) { 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 == 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"); - cmd("set_property")("DIRECTORY", "${PROJECT_SOURCE_DIR}", "PROPERTY", "VS_STARTUP_PROJECT", target.name); - cmd("endif")().endl(); - // clang-format on + } } - if (!target.sources.empty()) { + // TODO: support sources from other directories + if (has_sources) { cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + sources_var + "}").endl(); } @@ -1034,7 +1091,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { gen.handle_condition(props, [&](const std::string &, const tsl::ordered_map &properties) { for (const auto &propItr : properties) { if (propItr.first == "MSVC_RUNTIME_LIBRARY") { - if (root_project->project_msvc_runtime == parser::msvc_last) { + if (project_root->project_msvc_runtime == parser::msvc_last) { throw std::runtime_error("You cannot set [target].msvc-runtime without setting the root [project].msvc-runtime"); } } @@ -1043,19 +1100,28 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { }); } + // The first executable target will become the Visual Studio startup project + // TODO: this is not working properly + 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"); + cmd("set_property")("DIRECTORY", "${PROJECT_SOURCE_DIR}", "PROPERTY", "VS_STARTUP_PROJECT", target.name); + cmd("endif")().endl(); + // clang-format on + } + + // Generate the include after + if (!has_include_before && has_include_after) { + cmd("set")("CMKR_TARGET", target.name); + } gen.conditional_includes(target.include_after); gen.conditional_cmake(target.cmake_after); - if (tmplate != nullptr) { gen.conditional_includes(tmplate->outline.include_after); gen.conditional_cmake(tmplate->outline.cmake_after); } - - cmd("unset")("CMKR_TARGET"); - cmd("unset")("CMKR_SOURCES"); } - - endl(); } if (!project.tests.empty()) { @@ -1081,7 +1147,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { auto dirs = std::make_pair("DIRS", inst.dirs); std::vector files_data; if (!inst.files.empty()) { - files_data = expand_cmake_paths(inst.files, path, root_project); + files_data = expand_cmake_paths(inst.files, path, is_root_project); if (files_data.empty()) { throw std::runtime_error("[[install]] files wildcard did not resolve to any files"); } diff --git a/src/project_parser.cpp b/src/project_parser.cpp index bead08f..fdcaf18 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include namespace cmkr { namespace parser {