From 00969ee77d168d4de4e4a5cbfebcc2c5730cecc5 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Sat, 13 Mar 2021 00:19:34 +0100 Subject: [PATCH] WIP DSL for generating cmake --- .clang-format | 2 +- CMakeLists.txt | 18 ++-- cmake.toml | 4 +- src/cmkrlib/gen.cpp | 244 +++++++++++++++++++++++++++++--------------- src/main.cpp | 5 +- 5 files changed, 176 insertions(+), 97 deletions(-) diff --git a/.clang-format b/.clang-format index f833c75..37c1b1a 100644 --- a/.clang-format +++ b/.clang-format @@ -42,7 +42,7 @@ BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakStringLiterals: true -ColumnLimit: 100 +ColumnLimit: 150 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c2aff5..a48f0df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ include(FetchContent) message(STATUS "Fetching filesystem...") FetchContent_Declare( filesystem - GIT_REPOSITORY https://github.com/gulrak/filesystem + URL https://github.com/gulrak/filesystem/archive/v1.5.2.tar.gz ) FetchContent_MakeAvailable(filesystem) @@ -33,11 +33,11 @@ FetchContent_MakeAvailable(mpark_variant) message(STATUS "Fetching toml11...") FetchContent_Declare( toml11 - GIT_REPOSITORY https://github.com/ToruNiina/toml11 + URL https://github.com/ToruNiina/toml11/archive/v3.6.0.tar.gz ) FetchContent_MakeAvailable(toml11) -set(CMKRLIB_SOURCES +set(cmkrlib_SOURCES src/cmkrlib/args.cpp src/cmkrlib/build.cpp src/cmkrlib/cmake.cpp @@ -55,9 +55,9 @@ set(CMKRLIB_SOURCES cmake.toml ) -add_library(cmkrlib STATIC ${CMKRLIB_SOURCES}) +add_library(cmkrlib STATIC ${cmkrlib_SOURCES}) -source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${CMKRLIB_SOURCES}) +source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${cmkrlib_SOURCES}) target_include_directories(cmkrlib PUBLIC include @@ -73,16 +73,16 @@ target_compile_features(cmkrlib PUBLIC cxx_std_11 ) -set(CMKR_SOURCES +set(cmkr_SOURCES src/main.cpp cmake.toml ) -add_executable(cmkr ${CMKR_SOURCES}) +add_executable(cmkr ${cmkr_SOURCES}) -source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${CMKR_SOURCES}) +source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${cmkr_SOURCES}) -target_link_libraries(cmkr PUBLIC +target_link_libraries(cmkr PRIVATE cmkrlib ) diff --git a/cmake.toml b/cmake.toml index 65556fa..633e051 100644 --- a/cmake.toml +++ b/cmake.toml @@ -6,8 +6,8 @@ name = "cmkr" version = "0.1.3" [fetch-content] -toml11 = { git = "https://github.com/ToruNiina/toml11" } -filesystem = { git = "https://github.com/gulrak/filesystem" } +toml11 = { url = "https://github.com/ToruNiina/toml11/archive/v3.6.0.tar.gz" } +filesystem = { url = "https://github.com/gulrak/filesystem/archive/v1.5.2.tar.gz" } mpark_variant = { url = "https://github.com/mpark/variant/archive/v1.4.0.tar.gz" } [[target]] diff --git a/src/cmkrlib/gen.cpp b/src/cmkrlib/gen.cpp index 21baf6f..e78827c 100644 --- a/src/cmkrlib/gen.cpp +++ b/src/cmkrlib/gen.cpp @@ -4,12 +4,13 @@ #include "literals.h" #include "fs.hpp" +#include +#include +#include #include #include #include #include -#include -#include #include namespace cmkr { @@ -43,15 +44,13 @@ static std::vector expand_cmake_path(const fs::path &p) { auto stem = p.filename().stem().string(); auto ext = p.extension(); if (stem == "*") { - for (const auto &f : fs::directory_iterator( - p.parent_path(), fs::directory_options::follow_directory_symlink)) { + for (const auto &f : fs::directory_iterator(p.parent_path(), fs::directory_options::follow_directory_symlink)) { if (!f.is_directory() && f.path().extension() == ext) { temp.push_back(f.path().string()); } } } else if (stem == "**") { - for (const auto &f : fs::recursive_directory_iterator( - p.parent_path(), fs::directory_options::follow_directory_symlink)) { + for (const auto &f : fs::recursive_directory_iterator(p.parent_path(), fs::directory_options::follow_directory_symlink)) { if (!f.is_directory() && f.path().extension() == ext) { temp.push_back(f.path().string()); } @@ -91,13 +90,11 @@ int generate_project(const char *str) { target = "include/*.h"; dest = "include/" + dir_name; } else { - throw std::runtime_error( - "Unknown project type " + std::string(str) + - "! Supported types are: executable, library, shared, static, interface"); + throw std::runtime_error("Unknown project type " + std::string(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 = detail::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"); @@ -118,26 +115,133 @@ int generate_project(const char *str) { return 0; } +struct CommandEndl { + std::stringstream &ss; + CommandEndl(std::stringstream &ss) : ss(ss) {} + void endl() { ss << '\n'; } +}; + +// Credit: JustMagic +struct Command { + std::stringstream &ss; + int depth = 0; + std::string command; + bool first_arg = true; + bool generated = false; + + Command(std::stringstream &ss, int depth, const std::string &command) : ss(ss), depth(depth), command(command) {} + + ~Command() { + if (!generated) { + assert(false && "Incorrect usage of cmd()"); + } + } + + const char *indent(int n) { + for (int i = 0; i < n; i++) { + ss << '\t'; + } + return ""; + } + + template + bool print_arg(const std::vector &vec) { + if (vec.empty()) { + return true; + } + + ss << '\n'; + for (const auto &value : vec) { + ss << indent(depth + 1) << value << '\n'; + } + return true; + } + + template + bool print_arg(const std::map &map) { + if (map.empty()) { + return true; + } + + ss << '\n'; + for (const auto &itr : map) { + ss << indent(depth + 1) << itr.first << ' ' << itr.second << '\n'; + } + return true; + } + + bool print_arg(const std::string &value) { + if (value.empty()) { + return true; + } + + if (first_arg) { + first_arg = false; + } else { + ss << ' '; + } + ss << value; + return true; + } + + template + bool print_arg(const T &value) { + if (first_arg) { + first_arg = false; + } else { + ss << ' '; + } + ss << value; + return true; + } + + template + CommandEndl operator()(Ts &&... values) { + generated = true; + ss << indent(depth) << command << '('; + std::initializer_list{print_arg(values)...}; + ss << ")\n"; + return CommandEndl(ss); + } +}; + int generate_cmake(const char *path) { if (fs::exists(fs::path(path) / "cmake.toml")) { cmake::CMake cmake(path, false); std::stringstream ss; - ss << "# This file was generated automatically by cmkr.\n"; - ss << "\n"; - ss << "# Regenerate CMakeLists.txt file when necessary\n"; - ss << "include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)\n\n"; - ss << "if(CMKR_INCLUDE_RESULT)\n"; - ss << "\tcmkr()\n"; - ss << "endif()\n"; - ss << "\n"; + int indent = 0; + auto cmd = [&ss, &indent](const std::string &command) { + 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 char *comment) { + ss << "# " << comment << '\n'; + return CommandEndl(ss); + }; + auto endl = [&ss]() { ss << '\n'; }; + + comment("This file was generated automatically by cmkr.").endl(); - ss << "cmake_minimum_required(VERSION " << cmake.cmake_version << ")\n"; - ss << "\n"; + comment("Regenerate CMakeLists.txt file when necessary"); + cmd("include")("cmkr.cmake", "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT").endl(); - ss << "set_property(GLOBAL PROPERTY USE_FOLDERS ON)\n"; - ss << "\n"; + cmd("if")("CMKR_INCLUDE_RESULT"); + cmd("cmkr")(); + cmd("endif")().endl(); + cmd("cmake_minimum_required")("VERSION", cmake.cmake_version).endl(); + + cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON").endl(); + + // 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) { @@ -146,6 +250,7 @@ int generate_cmake(const char *path) { ss << "\")\n\n"; } + // 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) { @@ -154,6 +259,7 @@ int generate_cmake(const char *path) { ss << "\")\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) { @@ -162,23 +268,24 @@ int generate_cmake(const char *path) { ss << "\")\n\n"; } + // TODO: support cmake.toml subdirectory generation if (!cmake.subdirs.empty()) { for (const auto &dir : cmake.subdirs) { - ss << "add_subdirectory(" << dir << ")\n"; + cmd("add_subdirectory")(dir); } - ss << "\n\n"; + ss << '\n'; } if (!cmake.proj_name.empty() && !cmake.proj_version.empty()) { - ss << "set(" << cmake.proj_name << "_PROJECT_VERSION " << cmake.proj_version << ")\n" - << "project(" << cmake.proj_name << " VERSION " - << "${" << cmake.proj_name << "_PROJECT_VERSION}" - << ")\n\n"; + auto name = cmake.proj_name; + auto version = cmake.proj_version; + cmd("set")(name + "_PROJECT_VERSION", version); + cmd("project")(name, "VERSION", "${" + name + "_PROJECT_VERSION}").endl(); } if (!cmake.packages.empty()) { for (const auto &dep : cmake.packages) { - ss << "find_package(" << dep.name << " "; + ss << "find_package(" << dep.name << ' '; if (dep.version != "*") { ss << dep.version << " "; } @@ -226,8 +333,7 @@ int generate_cmake(const char *path) { if (!cmake.options.empty()) { for (const auto &opt : cmake.options) { - ss << "option(" << opt.name << " \"" << opt.comment << "\" " - << (opt.val ? "ON" : "OFF") << ")\n\n"; + ss << "option(" << opt.name << " \"" << opt.comment << "\" " << (opt.val ? "ON" : "OFF") << ")\n\n"; } } @@ -259,104 +365,77 @@ int generate_cmake(const char *path) { for (const auto &target : cmake.targets) { std::string add_command; std::string target_type; + std::string target_scope; if (target.type == "executable") { add_command = "add_executable"; target_type = ""; - } else if (target.type == "shared" || target.type == "static" || - target.type == "interface") { + target_scope = "PRIVATE"; + } else if (target.type == "shared" || target.type == "static" || target.type == "interface") { add_command = "add_library"; target_type = detail::to_upper(target.type); + target_scope = target_type == "INTERFACE" ? target_type : "PUBLIC"; } else if (target.type == "library") { add_command = "add_library"; target_type = ""; + target_scope = "PUBLIC"; } else { - throw std::runtime_error( - "Unknown binary type " + target.type + - "! Supported types are: executable, library, shared, static, interface"); + throw std::runtime_error("Unknown binary type " + target.type + + "! Supported types are: executable, library, shared, static, interface"); } if (!target.sources.empty()) { - ss << "set(" << detail::to_upper(target.name) << "_SOURCES\n"; - int sources_added = 0; + 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) { - ss << "\t" << f << "\n"; - sources_added++; + sources.push_back(f); } } - if (sources_added == 0) { + if (sources.empty()) { throw std::runtime_error(target.name + " sources wildcard found 0 files"); } if (target.type != "interface") { - ss << "\tcmake.toml\n"; + sources.push_back("cmake.toml"); } - ss << ")\n\n"; + cmd("set")(target.name + "_SOURCES", sources).endl(); } - ss << add_command << "(" << target.name; - if (!target_type.empty()) { - ss << " " << target_type; - } + cmd(add_command)(target.name, target_type, "${" + target.name + "_SOURCES}").endl(); if (!target.sources.empty()) { - ss << " ${" << detail::to_upper(target.name) << "_SOURCES})\n\n"; - ss << "source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${" - << detail::to_upper(target.name) << "_SOURCES})\n\n"; - } else { - ss << ")\n\n"; + cmd("source_group")("TREE", "${PROJECT_SOURCE_DIR}", "FILES", "${" + target.name + "_SOURCES}").endl(); } if (!target.alias.empty()) { - ss << "add_library(" << target.alias << " ALIAS " << target.name << ")\n\n"; + cmd("add_library")(target.alias, "ALIAS", target.name); } if (!target.include_directories.empty()) { - ss << "target_include_directories(" << target.name << " PUBLIC\n"; - for (const auto &inc : target.include_directories) { - ss << "\t" << inc << "\n"; - } - ss << ")\n\n"; + cmd("target_include_directories")(target.name, target_scope, target.include_directories).endl(); } if (!target.link_libraries.empty()) { - ss << "target_link_libraries(" << target.name << " PUBLIC\n"; - for (const auto &l : target.link_libraries) { - ss << "\t" << l << "\n"; - } - ss << ")\n\n"; + cmd("target_link_libraries")(target.name, target_scope, target.link_libraries).endl(); } if (!target.compile_features.empty()) { - ss << "target_compile_features(" << target.name << " PUBLIC\n"; - for (const auto &feat : target.compile_features) { - ss << "\t" << feat << "\n"; - } - ss << ")\n\n"; + cmd("target_compile_features")(target.name, target_scope, target.compile_features).endl(); } if (!target.compile_definitions.empty()) { - ss << "target_compile_definitions(" << target.name << " PUBLIC\n"; - for (const auto &def : target.compile_definitions) { - ss << "\t" << def << "\n"; - } - ss << ")\n\n"; + cmd("target_compile_definitions")(target.name, target_scope, target.compile_definitions).endl(); } if (!target.properties.empty()) { - ss << "set_target_properties(" << target.name << " PROPERTIES\n"; - for (const auto &prop : target.properties) { - ss << "\t" << prop.first << " " << prop.second << "\n"; - } - ss << ")\n\n"; + cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl(); } } } if (!cmake.tests.empty()) { - ss << "include(CTest)\n" - << "enable_testing()\n\n"; + 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()) { @@ -394,8 +473,7 @@ int generate_cmake(const char *path) { } } 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()) { diff --git a/src/main.cpp b/src/main.cpp index e27b3a5..fc8d75f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,9 @@ #include "args.h" +#include +#include +#include #include -#include -#include int main(int argc, char **argv) try { auto output = cmkr::args::handle_args(argc, argv);