diff --git a/include/project_parser.hpp b/include/project_parser.hpp index 33e4d7a..d85861e 100644 --- a/include/project_parser.hpp +++ b/include/project_parser.hpp @@ -180,6 +180,7 @@ struct Project { std::string project_version; std::string project_description; std::vector project_languages; + bool project_allow_unknown_languages = false; MsvcRuntimeType project_msvc_runtime = msvc_last; Condition cmake_before; Condition cmake_after; diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp index 216e0e1..933827c 100644 --- a/src/cmake_generator.cpp +++ b/src/cmake_generator.cpp @@ -14,6 +14,35 @@ namespace cmkr { namespace gen { +/* +Location: CMake/share/cmake-3.26/Modules +rg "set\(CMAKE_(.+)_SOURCE_FILE_EXTENSIONS" + +Links: +- https://gitlab.kitware.com/cmake/cmake/-/issues/24340 +- https://cmake.org/cmake/help/latest/command/enable_language.html +*/ + +static tsl::ordered_map> known_languages = { + {"ASM", {".s", ".S", ".asm", ".abs", ".msa", ".s90", ".s43", ".s85", ".s51"}}, + {"ASM-ATT", {".s", ".asm"}}, + {"ASM_MARMASM", {".asm"}}, + {"ASM_MASM", {".asm"}}, + {"ASM_NASM", {".nasm", ".asm"}}, + {"C", {".c", ".m"}}, + {"CSharp", {".cs"}}, + {"CUDA", {".cu"}}, + {"CXX", {".C", ".M", ".c++", ".cc", ".cpp", ".cxx", ".m", ".mm", ".mpp", ".CPP", ".ixx", ".cppm"}}, + {"Fortran", {".f", ".F", ".fpp", ".FPP", ".f77", ".F77", ".f90", ".F90", ".for", ".For", ".FOR", ".f95", ".F95", ".cuf", ".CUF"}}, + {"HIP", {".hip"}}, + {"ISPC", {".ispc"}}, + {"Java", {".java"}}, + {"OBJC", {".m"}}, + {"OBJCXX", {".M", ".m", ".mm"}}, + {"RC", {".rc", ".RC"}}, + {"Swift", {".swift"}}, +}; + static std::string format(const char *format, tsl::ordered_map variables) { std::string s = format; for (const auto &itr : variables) { @@ -510,6 +539,17 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { auto is_root_project = parent_project == nullptr; parser::Project project(parent_project, path, false); + + for (auto const &lang : project.project_languages) { + if (known_languages.find(lang) == known_languages.end()) { + if (project.project_allow_unknown_languages) { + printf("Unknown language '%s' specified\n", lang.c_str()); + } else { + throw std::runtime_error("Unknown language '" + lang + "' specified"); + } + } + } + Generator gen(project); // Helper lambdas for more convenient CMake generation @@ -837,6 +877,31 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { gen.conditional_cmake(subdir.cmake_after); } + // The implicit default is ["C", "CXX"], so make sure this list isn't + // empty or projects without languages explicitly defined will error. + auto project_languages = project.project_languages; + if (project_languages.empty()) + project_languages = {"C", "CXX"}; + + // All acceptable extensions based off our given languages. + tsl::ordered_set project_extensions; + for (const auto &language : project_languages) { + auto itr = known_languages.find(language); + if (itr != known_languages.end()) { + project_extensions.insert(itr->second.begin(), itr->second.end()); + } + } + + auto contains_language_source = [&project_extensions](const std::vector& sources) { + for (const auto &source : sources) { + auto extension = fs::path(source).extension().string(); + if (project_extensions.count(extension) > 0) { + return true; + } + } + return false; + }; + if (!project.targets.empty()) { auto project_root = project.root(); for (size_t i = 0; i < project.targets.size(); i++) { @@ -946,6 +1011,22 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { auto source_key = condition.empty() ? "sources" : (condition + ".sources"); throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files"); } + + // Make sure there are source files for the languages used by the project + switch (target.type) { + case parser::target_executable: + case parser::target_library: + case parser::target_shared: + case parser::target_static: + case parser::target_object: + if (!contains_language_source(sources)) { + throw std::runtime_error("There were no source files linked within the target " + target.name); + } + break; + default: + break; + } + if (sources_with_set) { // This is a sanity check to make sure the unconditional sources are first if (!condition.empty()) { diff --git a/src/project_parser.cpp b/src/project_parser.cpp index c26b1ef..a333fd7 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -289,6 +289,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p project.optional("version", project_version); project.optional("description", project_description); project.optional("languages", project_languages); + project.optional("allow-unknown-languages", project_allow_unknown_languages); project.optional("cmake-before", cmake_before); project.optional("cmake-after", cmake_after); project.optional("include-before", include_before);