|
14 | 14 | namespace cmkr { |
15 | 15 | namespace gen { |
16 | 16 |
|
| 17 | +using LanguageExtensions = std::vector<std::string>; |
| 18 | +static tsl::ordered_map<std::string, LanguageExtensions> known_languages = { |
| 19 | + {"C", {".c", ".m"}}, |
| 20 | + {"CXX", {".C", ".M", ".c++", ".cc", ".cpp", ".cxx", ".mm", ".CPP"}}, |
| 21 | + {"CSharp", {".cs"}}, |
| 22 | +}; |
| 23 | + |
17 | 24 | static std::string format(const char *format, tsl::ordered_map<std::string, std::string> variables) { |
18 | 25 | std::string s = format; |
19 | 26 | for (const auto &itr : variables) { |
@@ -510,6 +517,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { |
510 | 517 | auto is_root_project = parent_project == nullptr; |
511 | 518 |
|
512 | 519 | parser::Project project(parent_project, path, false); |
| 520 | + |
| 521 | + for (auto const &lang : project.project_languages) { |
| 522 | + if (known_languages.find(lang) == known_languages.end()) { |
| 523 | + throw std::runtime_error("Unknown language specified: " + lang); |
| 524 | + } |
| 525 | + } |
| 526 | + |
513 | 527 | Generator gen(project); |
514 | 528 |
|
515 | 529 | // Helper lambdas for more convenient CMake generation |
@@ -946,6 +960,60 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { |
946 | 960 | auto source_key = condition.empty() ? "sources" : (condition + ".sources"); |
947 | 961 | throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files"); |
948 | 962 | } |
| 963 | + |
| 964 | + // For non-interface targets, ensure there is a source file linked for the project's languages. |
| 965 | + if (target.type != parser::target_interface) { |
| 966 | + // The implicit default is ["C", "CXX"], so make sure this list isn't |
| 967 | + // empty or projects without languages explicitly defined will error. |
| 968 | + auto proj_languages = project.project_languages; |
| 969 | + if (proj_languages.empty()) |
| 970 | + proj_languages = {"C", "CXX"}; |
| 971 | + |
| 972 | + // All acceptable extensions based off our given languages. |
| 973 | + tsl::ordered_set<std::string> proj_extensions = [&]() { |
| 974 | + tsl::ordered_set<std::string> exts; |
| 975 | + for (auto it = known_languages.begin(); it != known_languages.end(); ++it) { |
| 976 | + if (std::find(proj_languages.begin(), proj_languages.end(), it->first) == proj_languages.end()) { |
| 977 | + continue; |
| 978 | + } |
| 979 | + // Add all extensions of this language into the list |
| 980 | + for (auto const &ext : it->second) { |
| 981 | + exts.insert(ext); |
| 982 | + } |
| 983 | + } |
| 984 | + return exts; |
| 985 | + }(); |
| 986 | + |
| 987 | + bool has_hit_def = false; |
| 988 | + for (auto &source : sources) { |
| 989 | + fs::path path = source; |
| 990 | + if (!path.has_extension()) { |
| 991 | + continue; |
| 992 | + } |
| 993 | + |
| 994 | + auto ext = path.extension().string(); |
| 995 | + |
| 996 | + // Only test lower-case variant of the extension |
| 997 | + static auto asciitolower = [](char in) -> char { |
| 998 | + if (in <= 'Z' && in >= 'A') |
| 999 | + return in - ('Z' - 'z'); |
| 1000 | + return in; |
| 1001 | + }; |
| 1002 | + |
| 1003 | + std::transform(ext.begin(), ext.end(), ext.begin(), asciitolower); |
| 1004 | + |
| 1005 | + // Check if we've hit an acceptable project extensions |
| 1006 | + if (std::find(proj_extensions.begin(), proj_extensions.end(), ext) != proj_extensions.end()) { |
| 1007 | + has_hit_def = true; |
| 1008 | + break; |
| 1009 | + } |
| 1010 | + } |
| 1011 | + |
| 1012 | + if (!has_hit_def) { |
| 1013 | + throw std::runtime_error("There were no source files linked within the target " + target.name); |
| 1014 | + } |
| 1015 | + } |
| 1016 | + |
949 | 1017 | if (sources_with_set) { |
950 | 1018 | // This is a sanity check to make sure the unconditional sources are first |
951 | 1019 | if (!condition.empty()) { |
|
0 commit comments