/src/CMake/Source/cmPackageInfoReader.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | #include "cmPackageInfoReader.h" |
4 | | |
5 | | #include <algorithm> |
6 | | #include <initializer_list> |
7 | | #include <limits> |
8 | | #include <unordered_map> |
9 | | #include <utility> |
10 | | |
11 | | #include <cmext/algorithm> |
12 | | #include <cmext/string_view> |
13 | | |
14 | | #include <cm3p/json/reader.h> |
15 | | #include <cm3p/json/value.h> |
16 | | #include <cm3p/json/version.h> |
17 | | |
18 | | #include "cmsys/FStream.hxx" |
19 | | #include "cmsys/RegularExpression.hxx" |
20 | | |
21 | | #include "cmCxxModuleMetadata.h" |
22 | | #include "cmExecutionStatus.h" |
23 | | #include "cmList.h" |
24 | | #include "cmListFileCache.h" |
25 | | #include "cmMakefile.h" |
26 | | #include "cmMessageType.h" |
27 | | #include "cmStringAlgorithms.h" |
28 | | #include "cmSystemTools.h" |
29 | | #include "cmTarget.h" |
30 | | #include "cmValue.h" |
31 | | |
32 | | namespace { |
33 | | |
34 | | // Map of CPS language names to CMake language name. Case insensitivity is |
35 | | // achieved by converting the CPS value to lower case, so keys in this map must |
36 | | // be lower case. |
37 | | std::unordered_map<std::string, std::string> Languages = { |
38 | | // clang-format off |
39 | | { "c", "C" }, |
40 | | { "c++", "CXX" }, |
41 | | { "cpp", "CXX" }, |
42 | | { "cxx", "CXX" }, |
43 | | { "objc", "OBJC" }, |
44 | | { "objc++", "OBJCXX" }, |
45 | | { "objcpp", "OBJCXX" }, |
46 | | { "objcxx", "OBJCXX" }, |
47 | | { "swift", "swift" }, |
48 | | { "hip", "HIP" }, |
49 | | { "cuda", "CUDA" }, |
50 | | { "ispc", "ISPC" }, |
51 | | { "c#", "CSharp" }, |
52 | | { "csharp", "CSharp" }, |
53 | | { "fortran", "Fortran" }, |
54 | | // clang-format on |
55 | | }; |
56 | | |
57 | | enum LanguageGlobOption |
58 | | { |
59 | | DisallowGlob, |
60 | | AllowGlob, |
61 | | }; |
62 | | |
63 | | cm::string_view MapLanguage(cm::string_view lang, |
64 | | LanguageGlobOption glob = AllowGlob) |
65 | 0 | { |
66 | 0 | if (glob == AllowGlob && lang == "*"_s) { |
67 | 0 | return "*"_s; |
68 | 0 | } |
69 | 0 | auto const li = Languages.find(cmSystemTools::LowerCase(lang)); |
70 | 0 | if (li != Languages.end()) { |
71 | 0 | return li->second; |
72 | 0 | } |
73 | 0 | return {}; |
74 | 0 | } |
75 | | |
76 | | std::string GetRealPath(std::string const& path) |
77 | 0 | { |
78 | 0 | return cmSystemTools::GetRealPath(path); |
79 | 0 | } |
80 | | |
81 | | std::string GetRealDir(std::string const& path) |
82 | 0 | { |
83 | 0 | return cmSystemTools::GetFilenamePath(cmSystemTools::GetRealPath(path)); |
84 | 0 | } |
85 | | |
86 | | Json::Value ReadJson(std::string const& fileName) |
87 | 0 | { |
88 | | // Open the specified file. |
89 | 0 | cmsys::ifstream file(fileName.c_str(), std::ios::in | std::ios::binary); |
90 | 0 | if (!file) { |
91 | | #if JSONCPP_VERSION_HEXA < 0x01070300 |
92 | | return Json::Value::null; |
93 | | #else |
94 | 0 | return Json::Value::nullSingleton(); |
95 | 0 | #endif |
96 | 0 | } |
97 | | |
98 | | // Read file content and translate JSON. |
99 | 0 | Json::Value data; |
100 | 0 | Json::CharReaderBuilder builder; |
101 | 0 | builder["collectComments"] = false; |
102 | 0 | if (!Json::parseFromStream(builder, file, &data, nullptr)) { |
103 | | #if JSONCPP_VERSION_HEXA < 0x01070300 |
104 | | return Json::Value::null; |
105 | | #else |
106 | 0 | return Json::Value::nullSingleton(); |
107 | 0 | #endif |
108 | 0 | } |
109 | | |
110 | 0 | return data; |
111 | 0 | } |
112 | | |
113 | | std::string ToString(Json::Value const& value) |
114 | 0 | { |
115 | 0 | if (value.isString()) { |
116 | 0 | return value.asString(); |
117 | 0 | } |
118 | 0 | return {}; |
119 | 0 | } |
120 | | |
121 | | bool CheckSchemaVersion(Json::Value const& data) |
122 | 0 | { |
123 | 0 | std::string const& version = ToString(data["cps_version"]); |
124 | | |
125 | | // Check that a valid version is specified. |
126 | 0 | if (version.empty()) { |
127 | 0 | return false; |
128 | 0 | } |
129 | | |
130 | | // Check that we understand this version. |
131 | 0 | return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL, |
132 | 0 | version, "0.13") && |
133 | 0 | cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, version, "0.15"); |
134 | | |
135 | | // TODO Eventually this probably needs to return the version tuple, and |
136 | | // should share code with cmPackageInfoReader::ParseVersion. |
137 | 0 | } |
138 | | |
139 | | bool ComparePathSuffix(std::string const& path, std::string const& suffix) |
140 | 0 | { |
141 | 0 | std::string::size_type const ps = path.size(); |
142 | 0 | std::string::size_type const ss = suffix.size(); |
143 | |
|
144 | 0 | if (ss > ps) { |
145 | 0 | return false; |
146 | 0 | } |
147 | | |
148 | 0 | return cmSystemTools::ComparePath(path.substr(ps - ss), suffix); |
149 | 0 | } |
150 | | |
151 | | std::string DeterminePrefix(std::string const& filepath, |
152 | | Json::Value const& data) |
153 | 0 | { |
154 | | // First check if an absolute prefix was supplied. |
155 | 0 | std::string prefix = ToString(data["prefix"]); |
156 | 0 | if (!prefix.empty()) { |
157 | | // Ensure that the specified prefix is valid. |
158 | 0 | if (cmsys::SystemTools::FileIsFullPath(prefix) && |
159 | 0 | cmsys::SystemTools::FileIsDirectory(prefix)) { |
160 | 0 | cmSystemTools::ConvertToUnixSlashes(prefix); |
161 | 0 | return prefix; |
162 | 0 | } |
163 | | // The specified absolute prefix is not valid. |
164 | 0 | return {}; |
165 | 0 | } |
166 | | |
167 | | // Get and validate prefix-relative path. |
168 | 0 | std::string const& absPath = cmSystemTools::GetFilenamePath(filepath); |
169 | 0 | std::string relPath = ToString(data["cps_path"]); |
170 | 0 | cmSystemTools::ConvertToUnixSlashes(relPath); |
171 | 0 | if (relPath.empty() || !cmHasLiteralPrefix(relPath, "@prefix@")) { |
172 | | // The relative prefix is not valid. |
173 | 0 | return {}; |
174 | 0 | } |
175 | 0 | if (relPath.size() == 8) { |
176 | | // The relative path is exactly "@prefix@". |
177 | 0 | return absPath; |
178 | 0 | } |
179 | 0 | if (relPath[8] != '/') { |
180 | | // The relative prefix is not valid. |
181 | 0 | return {}; |
182 | 0 | } |
183 | 0 | relPath = relPath.substr(8); |
184 | | |
185 | | // Get directory portion of the absolute path. |
186 | 0 | if (ComparePathSuffix(absPath, relPath)) { |
187 | 0 | return absPath.substr(0, absPath.size() - relPath.size()); |
188 | 0 | } |
189 | | |
190 | 0 | for (auto* const f : { GetRealPath, GetRealDir }) { |
191 | 0 | std::string const& tmpPath = (*f)(absPath); |
192 | 0 | if (!cmSystemTools::ComparePath(tmpPath, absPath) && |
193 | 0 | ComparePathSuffix(tmpPath, relPath)) { |
194 | 0 | return tmpPath.substr(0, tmpPath.size() - relPath.size()); |
195 | 0 | } |
196 | 0 | } |
197 | | |
198 | 0 | return {}; |
199 | 0 | } |
200 | | |
201 | | // Extract key name from value iterator as string_view. |
202 | | cm::string_view IterKey(Json::Value::const_iterator iter) |
203 | 0 | { |
204 | 0 | char const* end; |
205 | 0 | char const* const start = iter.memberName(&end); |
206 | 0 | return { start, static_cast<std::string::size_type>(end - start) }; |
207 | 0 | } |
208 | | |
209 | | // Get list-of-strings value from object. |
210 | | std::vector<std::string> ReadList(Json::Value const& arr) |
211 | 0 | { |
212 | 0 | std::vector<std::string> result; |
213 | |
|
214 | 0 | if (arr.isArray()) { |
215 | 0 | for (Json::Value const& val : arr) { |
216 | 0 | if (val.isString()) { |
217 | 0 | result.push_back(val.asString()); |
218 | 0 | } |
219 | 0 | } |
220 | 0 | } |
221 | |
|
222 | 0 | return result; |
223 | 0 | } |
224 | | |
225 | | std::vector<std::string> ReadList(Json::Value const& data, char const* key) |
226 | 0 | { |
227 | 0 | return ReadList(data[key]); |
228 | 0 | } |
229 | | |
230 | | std::string NormalizeTargetName(std::string const& name, |
231 | | std::string const& context) |
232 | 0 | { |
233 | 0 | if (cmHasPrefix(name, ':')) { |
234 | 0 | return cmStrCat(context, ':', name); |
235 | 0 | } |
236 | | |
237 | 0 | std::string::size_type const n = name.find_first_of(':'); |
238 | 0 | if (n != std::string::npos) { |
239 | 0 | cm::string_view v{ name }; |
240 | 0 | return cmStrCat(v.substr(0, n), ':', v.substr(n)); |
241 | 0 | } |
242 | 0 | return name; |
243 | 0 | } |
244 | | |
245 | | void AppendProperty(cmMakefile* makefile, cmTarget* target, |
246 | | cm::string_view property, cm::string_view configuration, |
247 | | std::string const& value) |
248 | 0 | { |
249 | 0 | std::string const fullprop = cmStrCat("INTERFACE_", property); |
250 | 0 | if (!configuration.empty()) { |
251 | 0 | std::string const genexValue = |
252 | 0 | cmStrCat("$<$<CONFIG:", configuration, ">:", value, '>'); |
253 | 0 | target->AppendProperty(fullprop, genexValue, makefile->GetBacktrace()); |
254 | 0 | } else { |
255 | 0 | target->AppendProperty(fullprop, value, makefile->GetBacktrace()); |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | | void AppendImportProperty(cmMakefile* makefile, cmTarget* target, |
260 | | cm::string_view property, |
261 | | cm::string_view configuration, |
262 | | std::string const& value) |
263 | 0 | { |
264 | 0 | if (!configuration.empty()) { |
265 | 0 | std::string const fullprop = cmStrCat( |
266 | 0 | "IMPORTED_", property, '_', cmSystemTools::UpperCase(configuration)); |
267 | 0 | target->AppendProperty(fullprop, value, makefile->GetBacktrace()); |
268 | 0 | } else { |
269 | 0 | std::string const fullprop = cmStrCat("IMPORTED_", property); |
270 | 0 | target->AppendProperty(fullprop, value, makefile->GetBacktrace()); |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | | template <typename Transform> |
275 | | void AppendLanguageProperties(cmMakefile* makefile, cmTarget* target, |
276 | | cm::string_view property, |
277 | | cm::string_view configuration, |
278 | | Json::Value const& data, char const* key, |
279 | | Transform transform) |
280 | 0 | { |
281 | 0 | Json::Value const& value = data[key]; |
282 | 0 | if (value.isArray()) { |
283 | 0 | for (std::string v : ReadList(value)) { |
284 | 0 | AppendProperty(makefile, target, property, configuration, |
285 | 0 | transform(std::move(v))); |
286 | 0 | } |
287 | 0 | } else if (value.isObject()) { |
288 | 0 | for (auto vi = value.begin(), ve = value.end(); vi != ve; ++vi) { |
289 | 0 | cm::string_view const originalLang = IterKey(vi); |
290 | 0 | cm::string_view const lang = MapLanguage(originalLang); |
291 | 0 | if (lang.empty()) { |
292 | 0 | makefile->IssueMessage(MessageType::WARNING, |
293 | 0 | cmStrCat(R"(ignoring unknown language ")"_s, |
294 | 0 | originalLang, R"(" in )"_s, key, |
295 | 0 | " for "_s, target->GetName())); |
296 | 0 | continue; |
297 | 0 | } |
298 | | |
299 | 0 | if (lang == "*"_s) { |
300 | 0 | for (std::string v : ReadList(*vi)) { |
301 | 0 | AppendProperty(makefile, target, property, configuration, |
302 | 0 | transform(std::move(v))); |
303 | 0 | } |
304 | 0 | } else { |
305 | 0 | for (std::string v : ReadList(*vi)) { |
306 | 0 | v = cmStrCat("$<$<COMPILE_LANGUAGE:"_s, lang, ">:"_s, |
307 | 0 | transform(std::move(v)), '>'); |
308 | 0 | AppendProperty(makefile, target, property, configuration, v); |
309 | 0 | } |
310 | 0 | } |
311 | 0 | } |
312 | 0 | } |
313 | 0 | } |
314 | | |
315 | | void AddCompileFeature(cmMakefile* makefile, cmTarget* target, |
316 | | cm::string_view configuration, std::string const& value) |
317 | 0 | { |
318 | 0 | auto reLanguageLevel = []() -> cmsys::RegularExpression { |
319 | 0 | static cmsys::RegularExpression re{ "^[Cc]([+][+])?([0-9][0-9])$" }; |
320 | 0 | return re; |
321 | 0 | }(); |
322 | |
|
323 | 0 | if (reLanguageLevel.find(value)) { |
324 | 0 | std::string::size_type const n = reLanguageLevel.end() - 2; |
325 | 0 | cm::string_view const featurePrefix = (n == 3 ? "cxx_std_"_s : "c_std_"_s); |
326 | 0 | if (configuration.empty()) { |
327 | 0 | AppendProperty(makefile, target, "COMPILE_FEATURES"_s, {}, |
328 | 0 | cmStrCat(featurePrefix, value.substr(n))); |
329 | 0 | } else { |
330 | 0 | std::string const& feature = |
331 | 0 | cmStrCat("$<$<CONFIG:"_s, configuration, ">:"_s, featurePrefix, |
332 | 0 | value.substr(n), '>'); |
333 | 0 | AppendProperty(makefile, target, "COMPILE_FEATURES"_s, {}, feature); |
334 | 0 | } |
335 | 0 | } else if (cmStrCaseEq(value, "gnu"_s)) { |
336 | | // Not implemented in CMake at this time |
337 | 0 | } else if (cmStrCaseEq(value, "threads"_s)) { |
338 | 0 | AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, |
339 | 0 | "Threads::Threads"); |
340 | 0 | } |
341 | 0 | } |
342 | | |
343 | | void AddLinkFeature(cmMakefile* makefile, cmTarget* target, |
344 | | cm::string_view configuration, std::string const& value) |
345 | 0 | { |
346 | 0 | if (cmStrCaseEq(value, "thread"_s)) { |
347 | 0 | AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, |
348 | 0 | "Threads::Threads"); |
349 | 0 | } |
350 | 0 | } |
351 | | |
352 | | std::string BuildDefinition(std::string const& name, Json::Value const& value) |
353 | 0 | { |
354 | 0 | if (!value.isNull() && value.isConvertibleTo(Json::stringValue)) { |
355 | 0 | return cmStrCat(name, '=', value.asString()); |
356 | 0 | } |
357 | 0 | return name; |
358 | 0 | } |
359 | | |
360 | | void AddDefinition(cmMakefile* makefile, cmTarget* target, |
361 | | cm::string_view configuration, |
362 | | std::string const& definition) |
363 | 0 | { |
364 | 0 | AppendProperty(makefile, target, "COMPILE_DEFINITIONS"_s, configuration, |
365 | 0 | definition); |
366 | 0 | } |
367 | | |
368 | | using DefinitionLanguageMap = std::map<cm::string_view, Json::Value>; |
369 | | using DefinitionsMap = std::map<std::string, DefinitionLanguageMap>; |
370 | | |
371 | | void AddDefinitions(cmMakefile* makefile, cmTarget* target, |
372 | | cm::string_view configuration, |
373 | | DefinitionsMap const& definitions) |
374 | 0 | { |
375 | 0 | for (auto const& di : definitions) { |
376 | 0 | auto const& g = di.second.find("*"_s); |
377 | 0 | if (g != di.second.end()) { |
378 | 0 | std::string const& def = BuildDefinition(di.first, g->second); |
379 | 0 | if (di.second.size() == 1) { |
380 | | // Only the non-language-specific definition exists. |
381 | 0 | AddDefinition(makefile, target, configuration, def); |
382 | 0 | continue; |
383 | 0 | } |
384 | | |
385 | | // Create a genex to apply this definition to all languages except |
386 | | // those that override it. |
387 | 0 | std::vector<cm::string_view> excludedLanguages; |
388 | 0 | for (auto const& li : di.second) { |
389 | 0 | if (li.first != "*"_s) { |
390 | 0 | excludedLanguages.emplace_back(li.first); |
391 | 0 | } |
392 | 0 | } |
393 | 0 | AddDefinition(makefile, target, configuration, |
394 | 0 | cmStrCat("$<$<NOT:$<COMPILE_LANGUAGE:"_s, |
395 | 0 | cmJoin(excludedLanguages, ","_s), ">>:"_s, def, |
396 | 0 | '>')); |
397 | 0 | } |
398 | | |
399 | | // Add language-specific definitions. |
400 | 0 | for (auto const& li : di.second) { |
401 | 0 | if (li.first != "*"_s) { |
402 | 0 | AddDefinition(makefile, target, configuration, |
403 | 0 | cmStrCat("$<$<COMPILE_LANGUAGE:"_s, li.first, ">:"_s, |
404 | 0 | BuildDefinition(di.first, li.second), '>')); |
405 | 0 | } |
406 | 0 | } |
407 | 0 | } |
408 | 0 | } |
409 | | |
410 | | cm::optional<cmPackageInfoReader::Pep440Version> ParseSimpleVersion( |
411 | | std::string const& version) |
412 | 0 | { |
413 | 0 | if (version.empty()) { |
414 | 0 | return cm::nullopt; |
415 | 0 | } |
416 | | |
417 | 0 | cmPackageInfoReader::Pep440Version result; |
418 | 0 | result.Simple = true; |
419 | |
|
420 | 0 | cm::string_view remnant{ version }; |
421 | 0 | for (;;) { |
422 | | // Find the next part separator. |
423 | 0 | std::string::size_type const n = remnant.find_first_of(".+-"_s); |
424 | 0 | if (n == 0) { |
425 | | // The part is an empty string. |
426 | 0 | return cm::nullopt; |
427 | 0 | } |
428 | | |
429 | | // Extract the part as a number. |
430 | 0 | cm::string_view const part = remnant.substr(0, n); |
431 | 0 | std::string::size_type const l = part.size(); |
432 | 0 | std::string::size_type p; |
433 | 0 | unsigned long const value = std::stoul(std::string{ part }, &p); |
434 | 0 | if (p != l || value > std::numeric_limits<unsigned>::max()) { |
435 | | // The part was not a valid number or is too big. |
436 | 0 | return cm::nullopt; |
437 | 0 | } |
438 | 0 | result.ReleaseComponents.push_back(static_cast<unsigned>(value)); |
439 | | |
440 | | // Have we consumed the entire input? |
441 | 0 | if (n == std::string::npos) { |
442 | 0 | return { std::move(result) }; |
443 | 0 | } |
444 | | |
445 | | // Lop off the current part. |
446 | 0 | char const sep = remnant[n]; |
447 | 0 | remnant = remnant.substr(n + 1); |
448 | 0 | if (sep == '+' || sep == '-') { |
449 | | // If we hit the local label, we're done. |
450 | 0 | result.LocalLabel = remnant; |
451 | 0 | return { std::move(result) }; |
452 | 0 | } |
453 | | |
454 | | // We just consumed a '.'; check that there's more. |
455 | 0 | if (remnant.empty()) { |
456 | | // A trailing part separator is not allowed. |
457 | 0 | return cm::nullopt; |
458 | 0 | } |
459 | | |
460 | | // Continue with the remaining input. |
461 | 0 | } |
462 | | |
463 | | // Unreachable. |
464 | 0 | } |
465 | | |
466 | | } // namespace |
467 | | |
468 | | std::unique_ptr<cmPackageInfoReader> cmPackageInfoReader::Read( |
469 | | cmMakefile* makefile, std::string const& path, |
470 | | cmPackageInfoReader const* parent) |
471 | 0 | { |
472 | | // Read file and perform some basic validation: |
473 | | // - the input is valid JSON |
474 | | // - the input is a JSON object |
475 | | // - the input has a "cps_version" that we (in theory) know how to parse |
476 | 0 | Json::Value data = ReadJson(path); |
477 | 0 | if (!data.isObject() || (!parent && !CheckSchemaVersion(data))) { |
478 | 0 | return nullptr; |
479 | 0 | } |
480 | | |
481 | | // - the input has a "name" attribute that is a non-empty string |
482 | 0 | Json::Value const& name = data["name"]; |
483 | 0 | if (!name.isString() || name.empty()) { |
484 | 0 | return nullptr; |
485 | 0 | } |
486 | | |
487 | | // - the input has a "components" attribute that is a JSON object |
488 | 0 | if (!data["components"].isObject()) { |
489 | 0 | return nullptr; |
490 | 0 | } |
491 | | |
492 | 0 | std::string prefix = (parent ? parent->Prefix : DeterminePrefix(path, data)); |
493 | 0 | if (prefix.empty()) { |
494 | 0 | return nullptr; |
495 | 0 | } |
496 | | |
497 | | // Seems sane enough to hand back to the caller. |
498 | 0 | std::unique_ptr<cmPackageInfoReader> reader{ new cmPackageInfoReader }; |
499 | 0 | reader->Data = std::move(data); |
500 | 0 | reader->Prefix = std::move(prefix); |
501 | 0 | reader->Path = path; |
502 | | |
503 | | // Determine other information we need to know immediately, or (if this is |
504 | | // a supplemental reader) copy from the parent. |
505 | 0 | if (parent) { |
506 | 0 | reader->ComponentTargets = parent->ComponentTargets; |
507 | 0 | reader->DefaultConfigurations = parent->DefaultConfigurations; |
508 | 0 | } else { |
509 | 0 | for (std::string const& config : |
510 | 0 | ReadList(reader->Data, "configurations")) { |
511 | 0 | reader->DefaultConfigurations.emplace_back( |
512 | 0 | cmSystemTools::UpperCase(config)); |
513 | 0 | } |
514 | 0 | } |
515 | | |
516 | | // Check for a default license. |
517 | 0 | Json::Value const& defaultLicense = reader->Data["default_license"]; |
518 | 0 | if (!defaultLicense.isNull()) { |
519 | 0 | if (defaultLicense.isString()) { |
520 | 0 | reader->DefaultLicense = defaultLicense.asString(); |
521 | 0 | } else { |
522 | 0 | makefile->IssueMessage( |
523 | 0 | MessageType::WARNING, |
524 | 0 | "Package attribute \"default_license\" is not a string."); |
525 | 0 | } |
526 | 0 | } else if (parent) { |
527 | 0 | reader->DefaultLicense = parent->DefaultLicense; |
528 | 0 | } else { |
529 | | // If there is no 'default_license', check for 'license'. Note that we |
530 | | // intentionally allow `default_license` on an appendix to override the |
531 | | // parent, but we do not consider `license` on an appendix. This is |
532 | | // consistent with not allowing LICENSE and APPENDIX to be used together. |
533 | 0 | Json::Value const& packageLicense = reader->Data["license"]; |
534 | 0 | if (!packageLicense.isNull()) { |
535 | 0 | if (packageLicense.isString()) { |
536 | 0 | reader->DefaultLicense = packageLicense.asString(); |
537 | 0 | } else { |
538 | 0 | makefile->IssueMessage( |
539 | 0 | MessageType::WARNING, |
540 | 0 | "Package attribute \"license\" is not a string."); |
541 | 0 | } |
542 | 0 | } |
543 | 0 | } |
544 | |
|
545 | 0 | return reader; |
546 | 0 | } |
547 | | |
548 | | std::string cmPackageInfoReader::GetName() const |
549 | 0 | { |
550 | 0 | return ToString(this->Data["name"]); |
551 | 0 | } |
552 | | |
553 | | cm::optional<std::string> cmPackageInfoReader::GetVersion() const |
554 | 0 | { |
555 | 0 | Json::Value const& version = this->Data["version"]; |
556 | 0 | if (version.isString()) { |
557 | 0 | return version.asString(); |
558 | 0 | } |
559 | 0 | return cm::nullopt; |
560 | 0 | } |
561 | | |
562 | | cm::optional<std::string> cmPackageInfoReader::GetCompatVersion() const |
563 | 0 | { |
564 | 0 | Json::Value const& version = this->Data["compat_version"]; |
565 | 0 | if (version.isString()) { |
566 | 0 | return version.asString(); |
567 | 0 | } |
568 | 0 | return cm::nullopt; |
569 | 0 | } |
570 | | |
571 | | cm::optional<cmPackageInfoReader::Pep440Version> |
572 | | cmPackageInfoReader::ParseVersion( |
573 | | cm::optional<std::string> const& version) const |
574 | 0 | { |
575 | | // Check that we have a version. |
576 | 0 | if (!version) { |
577 | 0 | return cm::nullopt; |
578 | 0 | } |
579 | | |
580 | | // Check if we know how to parse the version. |
581 | 0 | Json::Value const& schema = this->Data["version_schema"]; |
582 | 0 | if (schema.isNull() || cmStrCaseEq(ToString(schema), "simple"_s)) { |
583 | 0 | return ParseSimpleVersion(*version); |
584 | 0 | } |
585 | | |
586 | 0 | return cm::nullopt; |
587 | 0 | } |
588 | | |
589 | | std::vector<cmPackageRequirement> cmPackageInfoReader::GetRequirements() const |
590 | 0 | { |
591 | 0 | std::vector<cmPackageRequirement> requirements; |
592 | |
|
593 | 0 | auto const& requirementObjects = this->Data["requires"]; |
594 | |
|
595 | 0 | for (auto ri = requirementObjects.begin(), re = requirementObjects.end(); |
596 | 0 | ri != re; ++ri) { |
597 | 0 | cmPackageRequirement r{ ri.name(), ToString((*ri)["version"]), |
598 | 0 | ReadList(*ri, "components"), |
599 | 0 | ReadList(*ri, "hints") }; |
600 | 0 | requirements.emplace_back(std::move(r)); |
601 | 0 | } |
602 | |
|
603 | 0 | return requirements; |
604 | 0 | } |
605 | | |
606 | | std::vector<std::string> cmPackageInfoReader::GetComponentNames() const |
607 | 0 | { |
608 | 0 | std::vector<std::string> componentNames; |
609 | |
|
610 | 0 | Json::Value const& components = this->Data["components"]; |
611 | 0 | for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) { |
612 | 0 | componentNames.emplace_back(ci.name()); |
613 | 0 | } |
614 | |
|
615 | 0 | return componentNames; |
616 | 0 | } |
617 | | |
618 | | std::string cmPackageInfoReader::ResolvePath(std::string path) const |
619 | 0 | { |
620 | 0 | cmSystemTools::ConvertToUnixSlashes(path); |
621 | 0 | if (cmHasPrefix(path, "@prefix@"_s)) { |
622 | 0 | return cmStrCat(this->Prefix, path.substr(8)); |
623 | 0 | } |
624 | 0 | if (!cmSystemTools::FileIsFullPath(path)) { |
625 | 0 | return cmStrCat(cmSystemTools::GetFilenamePath(this->Path), '/', path); |
626 | 0 | } |
627 | 0 | return path; |
628 | 0 | } |
629 | | |
630 | | void cmPackageInfoReader::AddTargetConfiguration( |
631 | | cmTarget* target, cm::string_view configuration) const |
632 | 0 | { |
633 | 0 | static std::string const icProp = "IMPORTED_CONFIGURATIONS"; |
634 | |
|
635 | 0 | std::string const& configUpper = cmSystemTools::UpperCase(configuration); |
636 | | |
637 | | // Get existing list of imported configurations. |
638 | 0 | cmList configs; |
639 | 0 | if (cmValue v = target->GetProperty(icProp)) { |
640 | 0 | configs.assign(cmSystemTools::UpperCase(*v)); |
641 | 0 | } else { |
642 | | // If the existing list is empty, just add the new one and return. |
643 | 0 | target->SetProperty(icProp, configUpper); |
644 | 0 | return; |
645 | 0 | } |
646 | | |
647 | 0 | if (cm::contains(configs, configUpper)) { |
648 | | // If the configuration is already listed, we don't need to do anything. |
649 | 0 | return; |
650 | 0 | } |
651 | | |
652 | | // Add the new configuration. |
653 | 0 | configs.append(configUpper); |
654 | | |
655 | | // Rebuild the configuration list by extracting any configuration in the |
656 | | // default configurations and reinserting it at the beginning of the list |
657 | | // according to the order of the default configurations. |
658 | 0 | std::vector<std::string> newConfigs; |
659 | 0 | for (std::string const& c : this->DefaultConfigurations) { |
660 | 0 | auto ci = std::find(configs.begin(), configs.end(), c); |
661 | 0 | if (ci != configs.end()) { |
662 | 0 | newConfigs.emplace_back(std::move(*ci)); |
663 | 0 | configs.erase(ci); |
664 | 0 | } |
665 | 0 | } |
666 | 0 | for (std::string& c : configs) { |
667 | 0 | newConfigs.emplace_back(std::move(c)); |
668 | 0 | } |
669 | |
|
670 | 0 | target->SetProperty("IMPORTED_CONFIGURATIONS", cmJoin(newConfigs, ";"_s)); |
671 | 0 | } |
672 | | |
673 | | void cmPackageInfoReader::SetImportProperty(cmMakefile* makefile, |
674 | | cmTarget* target, |
675 | | cm::string_view property, |
676 | | cm::string_view configuration, |
677 | | Json::Value const& object, |
678 | | std::string const& attribute) const |
679 | 0 | { |
680 | 0 | Json::Value const& value = object[attribute]; |
681 | 0 | if (!value.isNull()) { |
682 | 0 | std::string fullprop; |
683 | 0 | if (configuration.empty()) { |
684 | 0 | fullprop = cmStrCat("IMPORTED_"_s, property); |
685 | 0 | } else { |
686 | 0 | fullprop = cmStrCat("IMPORTED_"_s, property, '_', |
687 | 0 | cmSystemTools::UpperCase(configuration)); |
688 | 0 | } |
689 | |
|
690 | 0 | if (value.isString()) { |
691 | 0 | target->SetProperty(fullprop, this->ResolvePath(value.asString())); |
692 | 0 | } else { |
693 | 0 | makefile->IssueMessage(MessageType::WARNING, |
694 | 0 | cmStrCat("Failed to set property \""_s, property, |
695 | 0 | "\" on target \""_s, target->GetName(), |
696 | 0 | "\": attribute \"", attribute, |
697 | 0 | "\" is not a string."_s)); |
698 | 0 | } |
699 | 0 | } |
700 | 0 | } |
701 | | |
702 | | void cmPackageInfoReader::SetMetaProperty( |
703 | | cmMakefile* makefile, cmTarget* target, std::string const& property, |
704 | | Json::Value const& object, std::string const& attribute, |
705 | | std::string const& defaultValue) const |
706 | 0 | { |
707 | 0 | Json::Value const& value = object[attribute]; |
708 | 0 | if (!value.isNull()) { |
709 | 0 | if (value.isString()) { |
710 | 0 | target->SetProperty(property, value.asString()); |
711 | 0 | } else { |
712 | 0 | makefile->IssueMessage(MessageType::WARNING, |
713 | 0 | cmStrCat("Failed to set property \""_s, property, |
714 | 0 | "\" on target \""_s, target->GetName(), |
715 | 0 | "\": attribute \"", attribute, |
716 | 0 | "\" is not a string."_s)); |
717 | 0 | } |
718 | 0 | } else if (!defaultValue.empty()) { |
719 | 0 | target->SetProperty(property, defaultValue); |
720 | 0 | } |
721 | 0 | } |
722 | | |
723 | | void cmPackageInfoReader::SetTargetProperties( |
724 | | cmMakefile* makefile, cmTarget* target, Json::Value const& data, |
725 | | std::string const& package, cm::string_view configuration) const |
726 | 0 | { |
727 | | // Add configuration (if applicable). |
728 | 0 | if (!configuration.empty()) { |
729 | 0 | this->AddTargetConfiguration(target, configuration); |
730 | 0 | } |
731 | | |
732 | | // Add compile and link features. |
733 | 0 | for (std::string const& def : ReadList(data, "compile_features")) { |
734 | 0 | AddCompileFeature(makefile, target, configuration, def); |
735 | 0 | } |
736 | |
|
737 | 0 | for (std::string const& def : ReadList(data, "link_features")) { |
738 | 0 | AddLinkFeature(makefile, target, configuration, def); |
739 | 0 | } |
740 | | |
741 | | // Add compile definitions. |
742 | 0 | Json::Value const& defs = data["definitions"]; |
743 | 0 | DefinitionsMap definitionsMap; |
744 | 0 | for (auto ldi = defs.begin(), lde = defs.end(); ldi != lde; ++ldi) { |
745 | 0 | cm::string_view const originalLang = IterKey(ldi); |
746 | 0 | cm::string_view const lang = MapLanguage(originalLang); |
747 | 0 | if (lang.empty()) { |
748 | 0 | makefile->IssueMessage( |
749 | 0 | MessageType::WARNING, |
750 | 0 | cmStrCat(R"(ignoring unknown language ")"_s, originalLang, |
751 | 0 | R"(" in definitions for )"_s, target->GetName())); |
752 | 0 | continue; |
753 | 0 | } |
754 | | |
755 | 0 | for (auto di = ldi->begin(), de = ldi->end(); di != de; ++di) { |
756 | 0 | definitionsMap[di.name()].emplace(lang, *di); |
757 | 0 | } |
758 | 0 | } |
759 | 0 | AddDefinitions(makefile, target, configuration, definitionsMap); |
760 | | |
761 | | // Add include directories. |
762 | 0 | AppendLanguageProperties(makefile, target, "INCLUDE_DIRECTORIES"_s, |
763 | 0 | configuration, data, "includes", |
764 | 0 | [this](std::string p) -> std::string { |
765 | 0 | return this->ResolvePath(std::move(p)); |
766 | 0 | }); |
767 | | |
768 | | // Add link name/location(s). |
769 | 0 | this->SetImportProperty(makefile, target, "LOCATION"_s, // br |
770 | 0 | configuration, data, "location"); |
771 | |
|
772 | 0 | this->SetImportProperty(makefile, target, "IMPLIB"_s, // br |
773 | 0 | configuration, data, "link_location"); |
774 | |
|
775 | 0 | this->SetImportProperty(makefile, target, "SONAME"_s, // br |
776 | 0 | configuration, data, "link_name"); |
777 | | |
778 | | // Add link languages. |
779 | 0 | for (std::string const& originalLang : ReadList(data, "link_languages")) { |
780 | 0 | cm::string_view const lang = MapLanguage(originalLang, DisallowGlob); |
781 | 0 | if (!lang.empty()) { |
782 | 0 | AppendProperty(makefile, target, "LINK_LANGUAGES"_s, configuration, |
783 | 0 | std::string{ lang }); |
784 | 0 | } |
785 | 0 | } |
786 | | |
787 | | // Add transitive dependencies. |
788 | 0 | for (std::string const& dep : ReadList(data, "requires")) { |
789 | 0 | AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, |
790 | 0 | NormalizeTargetName(dep, package)); |
791 | 0 | } |
792 | |
|
793 | 0 | for (std::string const& dep : ReadList(data, "compile_requires")) { |
794 | 0 | std::string const& lib = |
795 | 0 | cmStrCat("$<COMPILE_ONLY:"_s, NormalizeTargetName(dep, package), '>'); |
796 | 0 | AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib); |
797 | 0 | } |
798 | |
|
799 | 0 | for (std::string const& dep : ReadList(data, "link_requires")) { |
800 | 0 | std::string const& lib = |
801 | 0 | cmStrCat("$<LINK_ONLY:"_s, NormalizeTargetName(dep, package), '>'); |
802 | 0 | AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib); |
803 | 0 | } |
804 | |
|
805 | 0 | for (std::string const& dep : ReadList(data, "dyld_requires")) { |
806 | 0 | AppendImportProperty(makefile, target, "LINK_DEPENDENT_LIBRARIES"_s, |
807 | 0 | configuration, NormalizeTargetName(dep, package)); |
808 | 0 | } |
809 | |
|
810 | 0 | for (std::string const& lib : ReadList(data, "link_libraries")) { |
811 | 0 | AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib); |
812 | 0 | } |
813 | | |
814 | | // TODO: Handle non-configuration modules |
815 | | // once IMPORTED_CXX_MODULES supports it |
816 | 0 | if (!configuration.empty()) { |
817 | 0 | this->ReadCxxModulesMetadata(makefile, target, configuration, data); |
818 | 0 | } |
819 | | |
820 | | // Add other information. |
821 | 0 | if (configuration.empty()) { |
822 | 0 | this->SetMetaProperty(makefile, target, "SPDX_LICENSE", data, "license", |
823 | 0 | this->DefaultLicense); |
824 | 0 | } |
825 | 0 | } |
826 | | |
827 | | void cmPackageInfoReader::ReadCxxModulesMetadata( |
828 | | cmMakefile* makefile, cmTarget* target, cm::string_view configuration, |
829 | | Json::Value const& object) const |
830 | 0 | { |
831 | 0 | #ifndef CMAKE_BOOTSTRAP |
832 | 0 | Json::Value const& path = object["cpp_module_metadata"]; |
833 | |
|
834 | 0 | if (!path.isString()) { |
835 | 0 | return; |
836 | 0 | } |
837 | | |
838 | 0 | cmCxxModuleMetadata::ParseResult result = |
839 | 0 | cmCxxModuleMetadata::LoadFromFile(this->ResolvePath(path.asString())); |
840 | |
|
841 | 0 | if (!result) { |
842 | 0 | makefile->IssueMessage( |
843 | 0 | MessageType::WARNING, |
844 | 0 | cmStrCat("Error parsing module manifest:\n"_s, result.Error)); |
845 | 0 | return; |
846 | 0 | } |
847 | | |
848 | 0 | cmCxxModuleMetadata::PopulateTarget(*target, *result.Meta, configuration); |
849 | 0 | #endif |
850 | 0 | } |
851 | | |
852 | | cmTarget* cmPackageInfoReader::AddLibraryComponent( |
853 | | cmMakefile* makefile, cmStateEnums::TargetType type, std::string const& name, |
854 | | Json::Value const& data, std::string const& package, bool global) const |
855 | 0 | { |
856 | | // Create the imported target. |
857 | 0 | cmTarget* const target = makefile->AddImportedTarget(name, type, global); |
858 | 0 | target->SetOrigin(cmTarget::Origin::Cps); |
859 | | |
860 | | // Set target properties. |
861 | 0 | this->SetTargetProperties(makefile, target, data, package, {}); |
862 | 0 | auto const& cfgData = data["configurations"]; |
863 | 0 | for (auto ci = cfgData.begin(), ce = cfgData.end(); ci != ce; ++ci) { |
864 | 0 | this->SetTargetProperties(makefile, target, *ci, package, IterKey(ci)); |
865 | 0 | } |
866 | |
|
867 | 0 | return target; |
868 | 0 | } |
869 | | |
870 | | bool cmPackageInfoReader::ImportTargets(cmMakefile* makefile, |
871 | | cmExecutionStatus& status, bool global) |
872 | 0 | { |
873 | 0 | std::string const& package = this->GetName(); |
874 | | |
875 | | // Read components. |
876 | 0 | Json::Value const& components = this->Data["components"]; |
877 | |
|
878 | 0 | for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) { |
879 | 0 | cm::string_view const name = IterKey(ci); |
880 | 0 | std::string const& type = |
881 | 0 | cmSystemTools::LowerCase(ToString((*ci)["type"])); |
882 | | |
883 | | // Get and validate full target name. |
884 | 0 | std::string const& fullName = cmStrCat(package, "::"_s, name); |
885 | 0 | { |
886 | 0 | std::string msg; |
887 | 0 | if (!makefile->EnforceUniqueName(fullName, msg)) { |
888 | 0 | status.SetError(msg); |
889 | 0 | return false; |
890 | 0 | } |
891 | 0 | } |
892 | | |
893 | 0 | auto createTarget = [&](cmStateEnums::TargetType typeEnum) { |
894 | 0 | return this->AddLibraryComponent(makefile, typeEnum, fullName, *ci, |
895 | 0 | package, global); |
896 | 0 | }; |
897 | |
|
898 | 0 | cmTarget* target = nullptr; |
899 | 0 | if (type == "symbolic"_s) { |
900 | 0 | target = createTarget(cmStateEnums::INTERFACE_LIBRARY); |
901 | 0 | target->SetSymbolic(true); |
902 | 0 | } else if (type == "dylib"_s) { |
903 | 0 | target = createTarget(cmStateEnums::SHARED_LIBRARY); |
904 | 0 | } else if (type == "module"_s) { |
905 | 0 | target = createTarget(cmStateEnums::MODULE_LIBRARY); |
906 | 0 | } else if (type == "archive"_s) { |
907 | 0 | target = createTarget(cmStateEnums::STATIC_LIBRARY); |
908 | 0 | } else if (type == "interface"_s) { |
909 | 0 | target = createTarget(cmStateEnums::INTERFACE_LIBRARY); |
910 | 0 | } else { |
911 | 0 | makefile->IssueMessage(MessageType::WARNING, |
912 | 0 | cmStrCat(R"(component ")"_s, fullName, |
913 | 0 | R"(" has unknown type ")"_s, type, |
914 | 0 | R"(" and was not imported)"_s)); |
915 | 0 | } |
916 | |
|
917 | 0 | if (target) { |
918 | 0 | this->ComponentTargets.emplace(std::string{ name }, target); |
919 | 0 | } |
920 | 0 | } |
921 | | |
922 | | // Read default components. |
923 | 0 | std::vector<std::string> const& defaultComponents = |
924 | 0 | ReadList(this->Data, "default_components"); |
925 | 0 | if (!defaultComponents.empty()) { |
926 | 0 | std::string msg; |
927 | 0 | if (!makefile->EnforceUniqueName(package, msg)) { |
928 | 0 | status.SetError(msg); |
929 | 0 | return false; |
930 | 0 | } |
931 | | |
932 | 0 | cmTarget* const target = makefile->AddImportedTarget( |
933 | 0 | package, cmStateEnums::INTERFACE_LIBRARY, global); |
934 | 0 | for (std::string const& name : defaultComponents) { |
935 | 0 | std::string const& fullName = cmStrCat(package, "::"_s, name); |
936 | 0 | AppendProperty(makefile, target, "LINK_LIBRARIES"_s, {}, fullName); |
937 | 0 | } |
938 | 0 | } |
939 | | |
940 | 0 | return true; |
941 | 0 | } |
942 | | |
943 | | bool cmPackageInfoReader::ImportTargetConfigurations( |
944 | | cmMakefile* makefile, cmExecutionStatus& status) const |
945 | 0 | { |
946 | 0 | std::string const& configuration = ToString(this->Data["configuration"]); |
947 | |
|
948 | 0 | if (configuration.empty()) { |
949 | 0 | makefile->IssueMessage(MessageType::WARNING, |
950 | 0 | cmStrCat("supplemental file "_s, this->Path, |
951 | 0 | " does not specify a configuration"_s)); |
952 | 0 | return true; |
953 | 0 | } |
954 | | |
955 | 0 | std::string const& package = this->GetName(); |
956 | 0 | Json::Value const& components = this->Data["components"]; |
957 | |
|
958 | 0 | for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) { |
959 | | // Get component name and look up target. |
960 | 0 | cm::string_view const name = IterKey(ci); |
961 | 0 | auto const& ti = this->ComponentTargets.find(std::string{ name }); |
962 | 0 | if (ti == this->ComponentTargets.end()) { |
963 | 0 | status.SetError(cmStrCat("component "_s, name, " was not found"_s)); |
964 | 0 | return false; |
965 | 0 | } |
966 | | |
967 | | // Read supplemental data for component. |
968 | 0 | this->SetTargetProperties(makefile, ti->second, *ci, package, |
969 | 0 | configuration); |
970 | 0 | } |
971 | | |
972 | 0 | return true; |
973 | 0 | } |