/src/CMake/Source/cmCxxModuleMetadata.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 | | |
4 | | #include "cmCxxModuleMetadata.h" |
5 | | |
6 | | #include <algorithm> |
7 | | #include <set> |
8 | | #include <string> |
9 | | #include <utility> |
10 | | |
11 | | #include <cmext/string_view> |
12 | | |
13 | | #include <cm3p/json/value.h> |
14 | | #include <cm3p/json/writer.h> |
15 | | |
16 | | #include "cmsys/FStream.hxx" |
17 | | |
18 | | #include "cmFileSet.h" |
19 | | #include "cmGeneratedFileStream.h" |
20 | | #include "cmJSONState.h" |
21 | | #include "cmListFileCache.h" |
22 | | #include "cmStringAlgorithms.h" |
23 | | #include "cmSystemTools.h" |
24 | | #include "cmTarget.h" |
25 | | |
26 | | namespace { |
27 | | |
28 | | bool JsonIsStringArray(Json::Value const& v) |
29 | 0 | { |
30 | 0 | return v.isArray() && |
31 | 0 | std::all_of(v.begin(), v.end(), |
32 | 0 | [](Json::Value const& it) { return it.isString(); }); |
33 | 0 | } |
34 | | |
35 | | bool ParsePreprocessorDefine(Json::Value& dval, |
36 | | cmCxxModuleMetadata::PreprocessorDefineData& out, |
37 | | cmJSONState* state) |
38 | 0 | { |
39 | 0 | if (!dval.isObject()) { |
40 | 0 | state->AddErrorAtValue("each entry in 'definitions' must be an object", |
41 | 0 | &dval); |
42 | 0 | return false; |
43 | 0 | } |
44 | | |
45 | 0 | if (!dval.isMember("name") || !dval["name"].isString() || |
46 | 0 | dval["name"].asString().empty()) { |
47 | 0 | state->AddErrorAtValue( |
48 | 0 | "preprocessor definition requires a non-empty 'name'", &dval["name"]); |
49 | 0 | return false; |
50 | 0 | } |
51 | 0 | out.Name = dval["name"].asString(); |
52 | |
|
53 | 0 | if (dval.isMember("value")) { |
54 | 0 | if (dval["value"].isString()) { |
55 | 0 | out.Value = dval["value"].asString(); |
56 | 0 | } else if (!dval["value"].isNull()) { |
57 | 0 | state->AddErrorAtValue( |
58 | 0 | "'value' in preprocessor definition must be string or null", |
59 | 0 | &dval["value"]); |
60 | 0 | return false; |
61 | 0 | } |
62 | 0 | } |
63 | | |
64 | 0 | if (dval.isMember("undef")) { |
65 | 0 | if (!dval["undef"].isBool()) { |
66 | 0 | state->AddErrorAtValue( |
67 | 0 | "'undef' in preprocessor definition must be boolean", &dval["undef"]); |
68 | 0 | return false; |
69 | 0 | } |
70 | 0 | out.Undef = dval["undef"].asBool(); |
71 | 0 | } |
72 | | |
73 | 0 | return true; |
74 | 0 | } |
75 | | |
76 | | bool ParseCMakeLocalArgumentsVendor( |
77 | | Json::Value& cmlav, cmCxxModuleMetadata::LocalArgumentsData& out, |
78 | | cmJSONState* state) |
79 | 0 | { |
80 | |
|
81 | 0 | if (!cmlav.isObject()) { |
82 | 0 | state->AddErrorAtValue("'vendor' must be an object", &cmlav); |
83 | 0 | return false; |
84 | 0 | } |
85 | | |
86 | 0 | if (cmlav.isMember("compile-options")) { |
87 | 0 | if (!JsonIsStringArray(cmlav["compile-options"])) { |
88 | 0 | state->AddErrorAtValue("'compile-options' must be an array of strings", |
89 | 0 | &cmlav["compile-options"]); |
90 | 0 | return false; |
91 | 0 | } |
92 | 0 | for (auto const& s : cmlav["compile-options"]) { |
93 | 0 | out.CompileOptions.push_back(s.asString()); |
94 | 0 | } |
95 | 0 | } |
96 | | |
97 | 0 | if (cmlav.isMember("compile-features")) { |
98 | 0 | if (!JsonIsStringArray(cmlav["compile-features"])) { |
99 | 0 | state->AddErrorAtValue("'compile-features' must be an array of strings", |
100 | 0 | &cmlav["compile-features"]); |
101 | 0 | return false; |
102 | 0 | } |
103 | 0 | for (auto const& s : cmlav["compile-features"]) { |
104 | 0 | out.CompileFeatures.push_back(s.asString()); |
105 | 0 | } |
106 | 0 | } |
107 | | |
108 | 0 | return true; |
109 | 0 | } |
110 | | |
111 | | bool ParseLocalArguments(Json::Value& lav, |
112 | | cmCxxModuleMetadata::LocalArgumentsData& out, |
113 | | cmJSONState* state) |
114 | 0 | { |
115 | 0 | if (!lav.isObject()) { |
116 | 0 | state->AddErrorAtValue("'local-arguments' must be an object", &lav); |
117 | 0 | return false; |
118 | 0 | } |
119 | | |
120 | 0 | if (lav.isMember("include-directories")) { |
121 | 0 | if (!JsonIsStringArray(lav["include-directories"])) { |
122 | 0 | state->AddErrorAtValue( |
123 | 0 | "'include-directories' must be an array of strings", |
124 | 0 | &lav["include-directories"]); |
125 | 0 | return false; |
126 | 0 | } |
127 | 0 | for (auto const& s : lav["include-directories"]) { |
128 | 0 | out.IncludeDirectories.push_back(s.asString()); |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | 0 | if (lav.isMember("system-include-directories")) { |
133 | 0 | if (!JsonIsStringArray(lav["system-include-directories"])) { |
134 | 0 | state->AddErrorAtValue( |
135 | 0 | "'system-include-directories' must be an array of strings", |
136 | 0 | &lav["system-include-directories"]); |
137 | 0 | return false; |
138 | 0 | } |
139 | 0 | for (auto const& s : lav["system-include-directories"]) { |
140 | 0 | out.SystemIncludeDirectories.push_back(s.asString()); |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | 0 | if (lav.isMember("definitions")) { |
145 | 0 | if (!lav["definitions"].isArray()) { |
146 | 0 | state->AddErrorAtValue("'definitions' must be an array", |
147 | 0 | &lav["definitions"]); |
148 | 0 | return false; |
149 | 0 | } |
150 | 0 | for (Json::Value& dval : lav["definitions"]) { |
151 | 0 | out.Definitions.emplace_back(); |
152 | 0 | if (!ParsePreprocessorDefine(dval, out.Definitions.back(), state)) { |
153 | 0 | return false; |
154 | 0 | } |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | 0 | if (lav.isMember("vendor")) { |
159 | 0 | if (!ParseCMakeLocalArgumentsVendor(lav["vendor"], out, state)) { |
160 | 0 | return false; |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | 0 | return true; |
165 | 0 | } |
166 | | |
167 | | bool ParseModule(Json::Value& mval, cmCxxModuleMetadata::ModuleData& mod, |
168 | | cmJSONState* state) |
169 | 0 | { |
170 | 0 | if (!mval.isObject()) { |
171 | 0 | state->AddErrorAtValue("each entry in 'modules' must be an object", &mval); |
172 | 0 | return false; |
173 | 0 | } |
174 | | |
175 | 0 | if (!mval.isMember("logical-name") || !mval["logical-name"].isString() || |
176 | 0 | mval["logical-name"].asString().empty()) { |
177 | 0 | state->AddErrorAtValue( |
178 | 0 | "module entries require a non-empty 'logical-name' string", |
179 | 0 | &mval["logical-name"]); |
180 | 0 | return false; |
181 | 0 | } |
182 | 0 | mod.LogicalName = mval["logical-name"].asString(); |
183 | |
|
184 | 0 | if (!mval.isMember("source-path") || !mval["source-path"].isString() || |
185 | 0 | mval["source-path"].asString().empty()) { |
186 | 0 | state->AddErrorAtValue( |
187 | 0 | "module entries require a non-empty 'source-path' string", |
188 | 0 | &mval["source-path"]); |
189 | 0 | return false; |
190 | 0 | } |
191 | 0 | mod.SourcePath = mval["source-path"].asString(); |
192 | |
|
193 | 0 | if (mval.isMember("is-interface")) { |
194 | 0 | if (!mval["is-interface"].isBool()) { |
195 | 0 | state->AddErrorAtValue("'is-interface' must be boolean", |
196 | 0 | &mval["is-interface"]); |
197 | 0 | return false; |
198 | 0 | } |
199 | 0 | mod.IsInterface = mval["is-interface"].asBool(); |
200 | 0 | } else { |
201 | 0 | mod.IsInterface = true; |
202 | 0 | } |
203 | | |
204 | 0 | if (mval.isMember("is-std-library")) { |
205 | 0 | if (!mval["is-std-library"].isBool()) { |
206 | 0 | state->AddErrorAtValue("'is-std-library' must be boolean", |
207 | 0 | &mval["is-std-library"]); |
208 | 0 | return false; |
209 | 0 | } |
210 | 0 | mod.IsStdLibrary = mval["is-std-library"].asBool(); |
211 | 0 | } else { |
212 | 0 | mod.IsStdLibrary = false; |
213 | 0 | } |
214 | | |
215 | 0 | if (mval.isMember("local-arguments")) { |
216 | 0 | mod.LocalArguments.emplace(); |
217 | 0 | if (!ParseLocalArguments(mval["local-arguments"], *mod.LocalArguments, |
218 | 0 | state)) { |
219 | 0 | return false; |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | 0 | return true; |
224 | 0 | } |
225 | | |
226 | | bool ParseRoot(Json::Value& root, cmCxxModuleMetadata& meta, |
227 | | cmJSONState* state) |
228 | 0 | { |
229 | 0 | if (!root.isMember("version") || !root["version"].isInt()) { |
230 | 0 | state->AddErrorAtValue( |
231 | 0 | "Top-level member 'version' is required and must be an integer", &root); |
232 | 0 | return false; |
233 | 0 | } |
234 | 0 | meta.Version = root["version"].asInt(); |
235 | |
|
236 | 0 | if (root.isMember("revision")) { |
237 | 0 | if (!root["revision"].isInt()) { |
238 | 0 | state->AddErrorAtValue("'revision' must be an integer", |
239 | 0 | &root["revision"]); |
240 | 0 | return false; |
241 | 0 | } |
242 | 0 | meta.Revision = root["revision"].asInt(); |
243 | 0 | } |
244 | | |
245 | 0 | if (meta.Version != 1) { |
246 | 0 | state->AddErrorAtValue(cmStrCat("Module manifest version number, '", |
247 | 0 | meta.Version, '.', meta.Revision, |
248 | 0 | "' is newer than max supported (1.1)"), |
249 | 0 | &root); |
250 | 0 | return false; |
251 | 0 | } |
252 | | |
253 | 0 | if (root.isMember("modules")) { |
254 | 0 | if (!root["modules"].isArray()) { |
255 | 0 | state->AddErrorAtValue("'modules' must be an array", &root["modules"]); |
256 | 0 | return false; |
257 | 0 | } |
258 | 0 | for (Json::Value& mval : root["modules"]) { |
259 | 0 | meta.Modules.emplace_back(); |
260 | 0 | if (!ParseModule(mval, meta.Modules.back(), state)) { |
261 | 0 | return false; |
262 | 0 | } |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | 0 | return true; |
267 | 0 | } |
268 | | |
269 | | } // namespace |
270 | | |
271 | | cmCxxModuleMetadata::ParseResult cmCxxModuleMetadata::LoadFromFile( |
272 | | std::string const& path) |
273 | 0 | { |
274 | 0 | ParseResult res; |
275 | |
|
276 | 0 | Json::Value root; |
277 | 0 | cmJSONState parseState(path, &root); |
278 | 0 | if (!parseState.errors.empty()) { |
279 | 0 | res.Error = parseState.GetErrorMessage(); |
280 | 0 | return res; |
281 | 0 | } |
282 | | |
283 | 0 | cmCxxModuleMetadata meta; |
284 | 0 | if (!ParseRoot(root, meta, &parseState)) { |
285 | 0 | res.Error = parseState.GetErrorMessage(); |
286 | 0 | return res; |
287 | 0 | } |
288 | | |
289 | 0 | meta.MetadataFilePath = path; |
290 | 0 | res.Meta = std::move(meta); |
291 | 0 | return res; |
292 | 0 | } |
293 | | |
294 | | namespace { |
295 | | |
296 | | Json::Value SerializePreprocessorDefine( |
297 | | cmCxxModuleMetadata::PreprocessorDefineData const& d) |
298 | 0 | { |
299 | 0 | Json::Value dv(Json::objectValue); |
300 | 0 | dv["name"] = d.Name; |
301 | 0 | if (d.Value) { |
302 | 0 | dv["value"] = *d.Value; |
303 | 0 | } |
304 | 0 | if (d.Undef) { |
305 | 0 | dv["undef"] = d.Undef; |
306 | 0 | } |
307 | 0 | return dv; |
308 | 0 | } |
309 | | |
310 | | Json::Value SerializeCMakeLocalArgumentsVendor( |
311 | | cmCxxModuleMetadata::LocalArgumentsData const& la) |
312 | 0 | { |
313 | 0 | Json::Value vend(Json::objectValue); |
314 | |
|
315 | 0 | if (!la.CompileOptions.empty()) { |
316 | 0 | Json::Value& opts = vend["compile-options"] = Json::arrayValue; |
317 | 0 | for (auto const& s : la.CompileOptions) { |
318 | 0 | opts.append(s); |
319 | 0 | } |
320 | 0 | } |
321 | |
|
322 | 0 | if (!la.CompileFeatures.empty()) { |
323 | 0 | Json::Value& feats = vend["compile-features"] = Json::arrayValue; |
324 | 0 | for (auto const& s : la.CompileFeatures) { |
325 | 0 | feats.append(s); |
326 | 0 | } |
327 | 0 | } |
328 | |
|
329 | 0 | return vend; |
330 | 0 | } |
331 | | |
332 | | Json::Value SerializeLocalArguments( |
333 | | cmCxxModuleMetadata::LocalArgumentsData const& la) |
334 | 0 | { |
335 | 0 | Json::Value lav(Json::objectValue); |
336 | |
|
337 | 0 | if (!la.IncludeDirectories.empty()) { |
338 | 0 | Json::Value& inc = lav["include-directories"] = Json::arrayValue; |
339 | 0 | for (auto const& s : la.IncludeDirectories) { |
340 | 0 | inc.append(s); |
341 | 0 | } |
342 | 0 | } |
343 | |
|
344 | 0 | if (!la.SystemIncludeDirectories.empty()) { |
345 | 0 | Json::Value& sinc = lav["system-include-directories"] = Json::arrayValue; |
346 | 0 | for (auto const& s : la.SystemIncludeDirectories) { |
347 | 0 | sinc.append(s); |
348 | 0 | } |
349 | 0 | } |
350 | |
|
351 | 0 | if (!la.Definitions.empty()) { |
352 | 0 | Json::Value& defs = lav["definitions"] = Json::arrayValue; |
353 | 0 | for (auto const& d : la.Definitions) { |
354 | 0 | defs.append(SerializePreprocessorDefine(d)); |
355 | 0 | } |
356 | 0 | } |
357 | |
|
358 | 0 | Json::Value vend = SerializeCMakeLocalArgumentsVendor(la); |
359 | 0 | if (!vend.empty()) { |
360 | 0 | Json::Value& cmvend = lav["vendor"] = Json::objectValue; |
361 | 0 | cmvend["cmake"] = std::move(vend); |
362 | 0 | } |
363 | |
|
364 | 0 | return lav; |
365 | 0 | } |
366 | | |
367 | | Json::Value SerializeModule(std::string& manifestRoot, |
368 | | cmCxxModuleMetadata::ModuleData const& m) |
369 | 0 | { |
370 | 0 | Json::Value mv(Json::objectValue); |
371 | 0 | mv["logical-name"] = m.LogicalName; |
372 | 0 | if (cmSystemTools::FileIsFullPath(m.SourcePath)) { |
373 | 0 | mv["source-path"] = m.SourcePath; |
374 | 0 | } else { |
375 | 0 | mv["source-path"] = cmSystemTools::ForceToRelativePath( |
376 | 0 | manifestRoot, cmStrCat('/', m.SourcePath)); |
377 | 0 | } |
378 | 0 | mv["is-interface"] = m.IsInterface; |
379 | 0 | mv["is-std-library"] = m.IsStdLibrary; |
380 | |
|
381 | 0 | if (m.LocalArguments) { |
382 | 0 | mv["local-arguments"] = SerializeLocalArguments(*m.LocalArguments); |
383 | 0 | } |
384 | |
|
385 | 0 | return mv; |
386 | 0 | } |
387 | | |
388 | | } // namespace |
389 | | |
390 | | Json::Value cmCxxModuleMetadata::ToJsonValue(cmCxxModuleMetadata const& meta) |
391 | 0 | { |
392 | 0 | Json::Value root(Json::objectValue); |
393 | |
|
394 | 0 | root["version"] = meta.Version; |
395 | 0 | root["revision"] = meta.Revision; |
396 | |
|
397 | 0 | Json::Value& modules = root["modules"] = Json::arrayValue; |
398 | 0 | std::string manifestRoot = |
399 | 0 | cmSystemTools::GetFilenamePath(meta.MetadataFilePath); |
400 | |
|
401 | 0 | if (!cmSystemTools::FileIsFullPath(meta.MetadataFilePath)) { |
402 | 0 | manifestRoot = cmStrCat('/', manifestRoot); |
403 | 0 | } |
404 | |
|
405 | 0 | for (auto const& m : meta.Modules) { |
406 | 0 | modules.append(SerializeModule(manifestRoot, m)); |
407 | 0 | } |
408 | |
|
409 | 0 | return root; |
410 | 0 | } |
411 | | |
412 | | cmCxxModuleMetadata::SaveResult cmCxxModuleMetadata::SaveToFile( |
413 | | std::string const& path, cmCxxModuleMetadata const& meta) |
414 | 0 | { |
415 | 0 | SaveResult st; |
416 | |
|
417 | 0 | cmGeneratedFileStream ofs(path); |
418 | 0 | if (!ofs.is_open()) { |
419 | 0 | st.Error = "Unable to open temp file for writing"; |
420 | 0 | return st; |
421 | 0 | } |
422 | | |
423 | 0 | Json::StreamWriterBuilder wbuilder; |
424 | 0 | wbuilder["indentation"] = " "; |
425 | 0 | ofs << Json::writeString(wbuilder, ToJsonValue(meta)); |
426 | |
|
427 | 0 | ofs.Close(); |
428 | |
|
429 | 0 | if (!ofs.good()) { |
430 | 0 | st.Error = cmStrCat("Write failed for file: "_s, path); |
431 | 0 | return st; |
432 | 0 | } |
433 | | |
434 | 0 | st.Ok = true; |
435 | 0 | return st; |
436 | 0 | } |
437 | | |
438 | | void cmCxxModuleMetadata::PopulateTarget( |
439 | | cmTarget& target, cmCxxModuleMetadata const& meta, |
440 | | std::vector<std::string> const& configs) |
441 | 0 | { |
442 | 0 | std::set<cm::string_view> allIncludeDirectories; |
443 | 0 | std::set<cm::string_view> allCompileOptions; |
444 | 0 | std::set<cm::string_view> allCompileFeatures; |
445 | 0 | std::set<std::string> allCompileDefinitions; |
446 | 0 | std::set<std::string> baseDirs; |
447 | |
|
448 | 0 | std::string metadataDir = |
449 | 0 | cmSystemTools::GetFilenamePath(meta.MetadataFilePath); |
450 | |
|
451 | 0 | auto fileSet = target.GetOrCreateFileSet("CXX_MODULES", "CXX_MODULES", |
452 | 0 | cmFileSetVisibility::Interface); |
453 | |
|
454 | 0 | for (auto const& module : meta.Modules) { |
455 | 0 | std::string sourcePath = module.SourcePath; |
456 | 0 | if (!cmSystemTools::FileIsFullPath(sourcePath)) { |
457 | 0 | sourcePath = cmStrCat(metadataDir, '/', sourcePath); |
458 | 0 | } |
459 | |
|
460 | 0 | sourcePath = cmSystemTools::ToNormalizedPathOnDisk(std::move(sourcePath)); |
461 | | |
462 | | // Module metadata files can reference files in different roots, |
463 | | // just use the immediate parent directory as a base directory |
464 | 0 | baseDirs.insert(cmSystemTools::GetFilenamePath(sourcePath)); |
465 | |
|
466 | 0 | fileSet.first->AddFileEntry(sourcePath); |
467 | |
|
468 | 0 | if (module.LocalArguments) { |
469 | 0 | for (auto const& incDir : module.LocalArguments->IncludeDirectories) { |
470 | 0 | allIncludeDirectories.emplace(incDir); |
471 | 0 | } |
472 | 0 | for (auto const& sysIncDir : |
473 | 0 | module.LocalArguments->SystemIncludeDirectories) { |
474 | 0 | allIncludeDirectories.emplace(sysIncDir); |
475 | 0 | } |
476 | 0 | for (auto const& opt : module.LocalArguments->CompileOptions) { |
477 | 0 | allCompileOptions.emplace(opt); |
478 | 0 | } |
479 | 0 | for (auto const& opt : module.LocalArguments->CompileFeatures) { |
480 | 0 | allCompileFeatures.emplace(opt); |
481 | 0 | } |
482 | |
|
483 | 0 | for (auto const& def : module.LocalArguments->Definitions) { |
484 | 0 | if (!def.Undef) { |
485 | 0 | if (def.Value) { |
486 | 0 | allCompileDefinitions.emplace( |
487 | 0 | cmStrCat(def.Name, "="_s, *def.Value)); |
488 | 0 | } else { |
489 | 0 | allCompileDefinitions.emplace(def.Name); |
490 | 0 | } |
491 | 0 | } |
492 | 0 | } |
493 | 0 | } |
494 | 0 | } |
495 | |
|
496 | 0 | for (auto const& baseDir : baseDirs) { |
497 | 0 | fileSet.first->AddDirectoryEntry(baseDir); |
498 | 0 | } |
499 | |
|
500 | 0 | if (!allIncludeDirectories.empty()) { |
501 | 0 | target.SetProperty("IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES", |
502 | 0 | cmJoin(allIncludeDirectories, ";")); |
503 | 0 | } |
504 | |
|
505 | 0 | if (!allCompileDefinitions.empty()) { |
506 | 0 | target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS", |
507 | 0 | cmJoin(allCompileDefinitions, ";")); |
508 | 0 | } |
509 | |
|
510 | 0 | if (!allCompileOptions.empty()) { |
511 | 0 | target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_OPTIONS", |
512 | 0 | cmJoin(allCompileOptions, ";")); |
513 | 0 | } |
514 | |
|
515 | 0 | if (!allCompileFeatures.empty()) { |
516 | 0 | target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_FEATURES", |
517 | 0 | cmJoin(allCompileFeatures, ";")); |
518 | 0 | } |
519 | |
|
520 | 0 | for (auto const& config : configs) { |
521 | 0 | std::vector<std::string> moduleList; |
522 | 0 | for (auto const& module : meta.Modules) { |
523 | 0 | if (module.IsInterface) { |
524 | 0 | std::string sourcePath = module.SourcePath; |
525 | 0 | if (!cmSystemTools::FileIsFullPath(sourcePath)) { |
526 | 0 | sourcePath = cmStrCat(metadataDir, '/', sourcePath); |
527 | 0 | } |
528 | 0 | sourcePath = |
529 | 0 | cmSystemTools::ToNormalizedPathOnDisk(std::move(sourcePath)); |
530 | 0 | moduleList.push_back(cmStrCat(module.LogicalName, "="_s, sourcePath)); |
531 | 0 | } |
532 | 0 | } |
533 | |
|
534 | 0 | if (!moduleList.empty()) { |
535 | 0 | std::string upperConfig = cmSystemTools::UpperCase(config); |
536 | 0 | std::string propertyName = |
537 | 0 | cmStrCat("IMPORTED_CXX_MODULES_"_s, upperConfig); |
538 | 0 | target.SetProperty(propertyName, cmJoin(moduleList, ";")); |
539 | 0 | } |
540 | 0 | } |
541 | 0 | } |