diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp index 216e0e1..8f4f8a2 100644 --- a/src/cmake_generator.cpp +++ b/src/cmake_generator.cpp @@ -14,6 +14,13 @@ namespace cmkr { namespace gen { +using LanguageExtensions = std::vector; +static tsl::ordered_map known_languages = { + {"C", {".c", ".m"}}, + {"CXX", {".C", ".M", ".c++", ".cc", ".cpp", ".cxx", ".mm", ".CPP"}}, + {"CSharp", {".cs"}}, +}; + static std::string format(const char *format, tsl::ordered_map variables) { std::string s = format; for (const auto &itr : variables) { @@ -510,6 +517,13 @@ 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()) { + throw std::runtime_error("Unknown language specified: " + lang); + } + } + Generator gen(project); // Helper lambdas for more convenient CMake generation @@ -946,6 +960,60 @@ 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"); } + + // For non-interface targets, ensure there is a source file linked for the project's languages. + if (target.type != parser::target_interface) { + // The implicit default is ["C", "CXX"], so make sure this list isn't + // empty or projects without languages explicitly defined will error. + auto proj_languages = project.project_languages; + if (proj_languages.empty()) + proj_languages = {"C", "CXX"}; + + // All acceptable extensions based off our given languages. + tsl::ordered_set proj_extensions = [&]() { + tsl::ordered_set exts; + for (auto it = known_languages.begin(); it != known_languages.end(); ++it) { + if (std::find(proj_languages.begin(), proj_languages.end(), it->first) == proj_languages.end()) { + continue; + } + // Add all extensions of this language into the list + for (auto const &ext : it->second) { + exts.insert(ext); + } + } + return exts; + }(); + + bool has_hit_def = false; + for (auto &source : sources) { + fs::path path = source; + if (!path.has_extension()) { + continue; + } + + auto ext = path.extension().string(); + + // Only test lower-case variant of the extension + static auto asciitolower = [](char in) -> char { + if (in <= 'Z' && in >= 'A') + return in - ('Z' - 'z'); + return in; + }; + + std::transform(ext.begin(), ext.end(), ext.begin(), asciitolower); + + // Check if we've hit an acceptable project extensions + if (std::find(proj_extensions.begin(), proj_extensions.end(), ext) != proj_extensions.end()) { + has_hit_def = true; + break; + } + } + + if (!has_hit_def) { + throw std::runtime_error("There were no source files linked within the target " + target.name); + } + } + if (sources_with_set) { // This is a sanity check to make sure the unconditional sources are first if (!condition.empty()) {