From 1e8b75627673c998fbfb3b8d971bdd69a8425864 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 31 Mar 2021 14:55:16 +0200 Subject: [PATCH] Move target type handling to an enum for refactor --- CMakeLists.txt | 1 + src/cmkrlib/cmake.cpp | 22 ++++++++++- src/cmkrlib/cmake.hpp | 23 ++++++++--- src/cmkrlib/enum_helper.hpp | 69 ++++++++++++++++++++++++++++++++ src/cmkrlib/gen.cpp | 79 +++++++++++++++++++++++-------------- 5 files changed, 159 insertions(+), 35 deletions(-) create mode 100644 src/cmkrlib/enum_helper.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d560211..e550b2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ set(cmkrlib_SOURCES src/cmkrlib/gen.cpp src/cmkrlib/help.cpp src/cmkrlib/cmake.hpp + src/cmkrlib/enum_helper.hpp src/cmkrlib/fs.hpp include/args.h include/build.h diff --git a/src/cmkrlib/cmake.cpp b/src/cmkrlib/cmake.cpp index c9395ca..5791752 100644 --- a/src/cmkrlib/cmake.cpp +++ b/src/cmkrlib/cmake.cpp @@ -17,6 +17,26 @@ std::vector to_string_vec(const std::vector &vals) temp.push_back(val.as_string()); return temp; } + +template +EnumType to_enum(const std::string &str, const std::string &help_name) { + EnumType value; + try { + std::stringstream ss(str); + ss >> enumFromString(value); + } catch (std::invalid_argument &) { + std::string supported; + for (const auto &s : enumStrings::data) { + if (!supported.empty()) { + supported += ", "; + } + supported += s; + } + throw std::runtime_error("Unknown " + help_name + "'" + str + "'! Supported types are: " + supported); + } + return value; +} + } // namespace detail CMake::CMake(const std::string &path, bool build) { @@ -180,7 +200,7 @@ CMake::CMake(const std::string &path, bool build) { const auto &t = itr.second; Target target; target.name = itr.first; - target.type = toml::find(t, "type").as_string(); + target.type = detail::to_enum(toml::find(t, "type").as_string(), "target type"); target.sources = detail::to_string_vec(toml::find(t, "sources").as_array()); diff --git a/src/cmkrlib/cmake.hpp b/src/cmkrlib/cmake.hpp index 13db5ec..7c86fd7 100644 --- a/src/cmkrlib/cmake.hpp +++ b/src/cmkrlib/cmake.hpp @@ -1,11 +1,12 @@ #pragma once +#include "enum_helper.hpp" #include +#include #include #include -#include -#include #include +#include namespace cmkr { namespace cmake { @@ -31,9 +32,18 @@ struct Package { std::vector components; }; +enum TargetType { + target_executable, + target_library, + target_shared, + target_static, + target_interface, + target_custom, +}; + struct Target { std::string name; - std::string type; + TargetType type; // https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands std::vector compile_definitions; @@ -45,7 +55,7 @@ struct Target { std::vector link_options; std::vector precompile_headers; std::vector sources; - + std::string alias; tsl::ordered_map properties; @@ -97,4 +107,7 @@ struct CMake { }; } // namespace cmake -} // namespace cmkr \ No newline at end of file +} // namespace cmkr + +template <> +const char *enumStrings::data[] = {"executable", "library", "shared", "static", "internface", "custom"}; \ No newline at end of file diff --git a/src/cmkrlib/enum_helper.hpp b/src/cmkrlib/enum_helper.hpp new file mode 100644 index 0000000..7aafd78 --- /dev/null +++ b/src/cmkrlib/enum_helper.hpp @@ -0,0 +1,69 @@ +// https://codereview.stackexchange.com/a/14315 + +#include +#include +#include +#include + +// This is the type that will hold all the strings. +// Each enumeration type will declare its own specialization. +// Any enum that does not have a specialization will generate a compiler error +// indicating that there is no definition of this variable (as there should be +// be no definition of a generic version). +template +struct enumStrings { + static char const *data[]; +}; + +// This is a utility type. +// Created automatically. Should not be used directly. +template +struct enumRefHolder { + T &enumVal; + enumRefHolder(T &enumVal) : enumVal(enumVal) {} +}; +template +struct enumConstRefHolder { + T const &enumVal; + enumConstRefHolder(T const &enumVal) : enumVal(enumVal) {} +}; + +// The next two functions do the actual work of reading/writing an +// enum as a string. +template +std::ostream &operator<<(std::ostream &str, enumConstRefHolder const &data) { + return str << enumStrings::data[data.enumVal]; +} + +template +std::istream &operator>>(std::istream &str, enumRefHolder const &data) { + std::string value; + str >> value; + + // These two can be made easier to read in C++11 + // using std::begin() and std::end() + // + static auto begin = std::begin(enumStrings::data); + static auto end = std::end(enumStrings::data); + + auto find = std::find(begin, end, value); + if (find != end) { + data.enumVal = static_cast(std::distance(begin, find)); + } else { + throw std::invalid_argument(""); + } + return str; +} + +// This is the public interface: +// use the ability of function to deduce their template type without +// being explicitly told to create the correct type of enumRefHolder +template +enumConstRefHolder enumToString(T const &e) { + return enumConstRefHolder(e); +} + +template +enumRefHolder enumFromString(T &e) { + return enumRefHolder(e); +} \ No newline at end of file diff --git a/src/cmkrlib/gen.cpp b/src/cmkrlib/gen.cpp index 47dec19..f6f654d 100644 --- a/src/cmkrlib/gen.cpp +++ b/src/cmkrlib/gen.cpp @@ -233,6 +233,8 @@ int generate_cmake(const char *path, bool root) { 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); @@ -432,24 +434,16 @@ int generate_cmake(const char *path, bool root) { if (!cmake.targets.empty()) { 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 = ""; - 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 target type " + target.type + - "! Supported types are: executable, library, shared, static, interface"); + if (!target.cmake_before.empty()) { + ss << tolf(target.cmake_before) << "\n\n"; + } + + 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()) { @@ -464,22 +458,49 @@ int generate_cmake(const char *path, bool root) { if (sources.empty()) { throw std::runtime_error(target.name + " sources wildcard found 0 files"); } - if (target.type != "interface") { + if (target.type != cmake::target_interface) { sources.push_back("cmake.toml"); } cmd("set")(target.name + "_SOURCES", sources).endl(); } - if (!target.cmake_before.empty()) { - ss << tolf(target.cmake_before) << "\n\n"; - } - - if (!target.include_before.empty()) { - for (const auto &file : target.include_before) { - // TODO: warn/error if file doesn't exist? - cmd("include")(file); - } - 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 + add_command = "add_custom_target"; + target_type = ""; + target_scope = "PUBLIC"; + break; + default: + assert("Unimplemented enum value" && false); } cmd(add_command)(target.name, target_type, "${" + target.name + "_SOURCES}").endl();