CMake Nightly Date Stamp
[kiteware-cmake.git] / Source / cmCoreTryCompile.cxx
blob4d739fff757a5473ccc63292ff8d98775609c8f0
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCoreTryCompile.h"
5 #include <array>
6 #include <cstdio>
7 #include <cstring>
8 #include <set>
9 #include <sstream>
10 #include <utility>
12 #include <cm/string_view>
13 #include <cmext/string_view>
15 #include "cmsys/Directory.hxx"
16 #include "cmsys/FStream.hxx"
17 #include "cmsys/RegularExpression.hxx"
19 #include "cmArgumentParser.h"
20 #include "cmConfigureLog.h"
21 #include "cmExperimental.h"
22 #include "cmExportTryCompileFileGenerator.h"
23 #include "cmGlobalGenerator.h"
24 #include "cmList.h"
25 #include "cmMakefile.h"
26 #include "cmMessageType.h"
27 #include "cmOutputConverter.h"
28 #include "cmPolicies.h"
29 #include "cmRange.h"
30 #include "cmState.h"
31 #include "cmStringAlgorithms.h"
32 #include "cmSystemTools.h"
33 #include "cmTarget.h"
34 #include "cmValue.h"
35 #include "cmVersion.h"
36 #include "cmake.h"
38 namespace {
39 constexpr const char* unique_binary_directory = "CMAKE_BINARY_DIR_USE_MKDTEMP";
40 constexpr size_t lang_property_start = 0;
41 constexpr size_t lang_property_size = 4;
42 constexpr size_t pie_property_start = 4;
43 constexpr size_t pie_property_size = 2;
44 /* clang-format off */
45 #define SETUP_LANGUAGE(name, lang) \
46 static const std::string name[lang_property_size + pie_property_size + 1] = \
47 { "CMAKE_" #lang "_COMPILER_EXTERNAL_TOOLCHAIN", \
48 "CMAKE_" #lang "_COMPILER_TARGET", \
49 "CMAKE_" #lang "_LINK_NO_PIE_SUPPORTED", \
50 "CMAKE_" #lang "_PIE_SUPPORTED", "" }
51 /* clang-format on */
53 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
54 SETUP_LANGUAGE(c_properties, C);
55 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
56 SETUP_LANGUAGE(cxx_properties, CXX);
58 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
59 SETUP_LANGUAGE(cuda_properties, CUDA);
60 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
61 SETUP_LANGUAGE(fortran_properties, Fortran);
62 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
63 SETUP_LANGUAGE(hip_properties, HIP);
64 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
65 SETUP_LANGUAGE(objc_properties, OBJC);
66 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
67 SETUP_LANGUAGE(objcxx_properties, OBJCXX);
68 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
69 SETUP_LANGUAGE(ispc_properties, ISPC);
70 // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
71 SETUP_LANGUAGE(swift_properties, Swift);
72 #undef SETUP_LANGUAGE
74 std::string const kCMAKE_CUDA_ARCHITECTURES = "CMAKE_CUDA_ARCHITECTURES";
75 std::string const kCMAKE_CUDA_RUNTIME_LIBRARY = "CMAKE_CUDA_RUNTIME_LIBRARY";
76 std::string const kCMAKE_CXX_SCAN_FOR_MODULES = "CMAKE_CXX_SCAN_FOR_MODULES";
77 std::string const kCMAKE_ENABLE_EXPORTS = "CMAKE_ENABLE_EXPORTS";
78 std::string const kCMAKE_EXECUTABLE_ENABLE_EXPORTS =
79 "CMAKE_EXECUTABLE_ENABLE_EXPORTS";
80 std::string const kCMAKE_SHARED_LIBRARY_ENABLE_EXPORTS =
81 "CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS";
82 std::string const kCMAKE_HIP_ARCHITECTURES = "CMAKE_HIP_ARCHITECTURES";
83 std::string const kCMAKE_HIP_PLATFORM = "CMAKE_HIP_PLATFORM";
84 std::string const kCMAKE_HIP_RUNTIME_LIBRARY = "CMAKE_HIP_RUNTIME_LIBRARY";
85 std::string const kCMAKE_ISPC_INSTRUCTION_SETS = "CMAKE_ISPC_INSTRUCTION_SETS";
86 std::string const kCMAKE_ISPC_HEADER_SUFFIX = "CMAKE_ISPC_HEADER_SUFFIX";
87 std::string const kCMAKE_LINKER_TYPE = "CMAKE_LINKER_TYPE";
88 std::string const kCMAKE_LINK_SEARCH_END_STATIC =
89 "CMAKE_LINK_SEARCH_END_STATIC";
90 std::string const kCMAKE_LINK_SEARCH_START_STATIC =
91 "CMAKE_LINK_SEARCH_START_STATIC";
92 std::string const kCMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT =
93 "CMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT";
94 std::string const kCMAKE_OSX_ARCHITECTURES = "CMAKE_OSX_ARCHITECTURES";
95 std::string const kCMAKE_OSX_DEPLOYMENT_TARGET = "CMAKE_OSX_DEPLOYMENT_TARGET";
96 std::string const kCMAKE_OSX_SYSROOT = "CMAKE_OSX_SYSROOT";
97 std::string const kCMAKE_APPLE_ARCH_SYSROOTS = "CMAKE_APPLE_ARCH_SYSROOTS";
98 std::string const kCMAKE_POSITION_INDEPENDENT_CODE =
99 "CMAKE_POSITION_INDEPENDENT_CODE";
100 std::string const kCMAKE_SYSROOT = "CMAKE_SYSROOT";
101 std::string const kCMAKE_SYSROOT_COMPILE = "CMAKE_SYSROOT_COMPILE";
102 std::string const kCMAKE_SYSROOT_LINK = "CMAKE_SYSROOT_LINK";
103 std::string const kCMAKE_ARMClang_CMP0123 = "CMAKE_ARMClang_CMP0123";
104 std::string const kCMAKE_TRY_COMPILE_OSX_ARCHITECTURES =
105 "CMAKE_TRY_COMPILE_OSX_ARCHITECTURES";
106 std::string const kCMAKE_TRY_COMPILE_PLATFORM_VARIABLES =
107 "CMAKE_TRY_COMPILE_PLATFORM_VARIABLES";
108 std::string const kCMAKE_WARN_DEPRECATED = "CMAKE_WARN_DEPRECATED";
109 std::string const kCMAKE_WATCOM_RUNTIME_LIBRARY_DEFAULT =
110 "CMAKE_WATCOM_RUNTIME_LIBRARY_DEFAULT";
111 std::string const kCMAKE_MSVC_DEBUG_INFORMATION_FORMAT_DEFAULT =
112 "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT_DEFAULT";
114 /* GHS Multi platform variables */
115 std::set<std::string> const ghs_platform_vars{
116 "GHS_TARGET_PLATFORM", "GHS_PRIMARY_TARGET", "GHS_TOOLSET_ROOT",
117 "GHS_OS_ROOT", "GHS_OS_DIR", "GHS_BSP_NAME",
118 "GHS_OS_DIR_OPTION"
120 using Arguments = cmCoreTryCompile::Arguments;
122 ArgumentParser::Continue TryCompileLangProp(Arguments& args,
123 cm::string_view key,
124 cm::string_view val)
126 args.LangProps[std::string(key)] = std::string(val);
127 return ArgumentParser::Continue::No;
130 ArgumentParser::Continue TryCompileCompileDefs(Arguments& args,
131 cm::string_view val)
133 args.CompileDefs.append(val);
134 return ArgumentParser::Continue::Yes;
137 cmArgumentParser<Arguments> makeTryCompileParser(
138 const cmArgumentParser<Arguments>& base)
140 return cmArgumentParser<Arguments>{ base }.Bind("OUTPUT_VARIABLE"_s,
141 &Arguments::OutputVariable);
144 cmArgumentParser<Arguments> makeTryRunParser(
145 const cmArgumentParser<Arguments>& base)
147 return cmArgumentParser<Arguments>{ base }
148 .Bind("COMPILE_OUTPUT_VARIABLE"_s, &Arguments::CompileOutputVariable)
149 .Bind("RUN_OUTPUT_VARIABLE"_s, &Arguments::RunOutputVariable)
150 .Bind("RUN_OUTPUT_STDOUT_VARIABLE"_s, &Arguments::RunOutputStdOutVariable)
151 .Bind("RUN_OUTPUT_STDERR_VARIABLE"_s, &Arguments::RunOutputStdErrVariable)
152 .Bind("WORKING_DIRECTORY"_s, &Arguments::RunWorkingDirectory)
153 .Bind("ARGS"_s, &Arguments::RunArgs)
154 /* keep semicolon on own line */;
157 #define BIND_LANG_PROPS(lang) \
158 Bind(#lang "_STANDARD"_s, TryCompileLangProp) \
159 .Bind(#lang "_STANDARD_REQUIRED"_s, TryCompileLangProp) \
160 .Bind(#lang "_EXTENSIONS"_s, TryCompileLangProp)
162 auto const TryCompileBaseArgParser =
163 cmArgumentParser<Arguments>{}
164 .Bind(0, &Arguments::CompileResultVariable)
165 .Bind("LOG_DESCRIPTION"_s, &Arguments::LogDescription)
166 .Bind("NO_CACHE"_s, &Arguments::NoCache)
167 .Bind("NO_LOG"_s, &Arguments::NoLog)
168 .Bind("CMAKE_FLAGS"_s, &Arguments::CMakeFlags)
169 .Bind("__CMAKE_INTERNAL"_s, &Arguments::CMakeInternal)
170 /* keep semicolon on own line */;
172 auto const TryCompileBaseSourcesArgParser =
173 cmArgumentParser<Arguments>{ TryCompileBaseArgParser }
174 .Bind("SOURCES_TYPE"_s, &Arguments::SetSourceType)
175 .BindWithContext("SOURCES"_s, &Arguments::Sources,
176 &Arguments::SourceTypeContext)
177 .Bind("COMPILE_DEFINITIONS"_s, TryCompileCompileDefs,
178 ArgumentParser::ExpectAtLeast{ 0 })
179 .Bind("LINK_LIBRARIES"_s, &Arguments::LinkLibraries)
180 .Bind("LINK_OPTIONS"_s, &Arguments::LinkOptions)
181 .Bind("LINKER_LANGUAGE"_s, &Arguments::LinkerLanguage)
182 .Bind("COPY_FILE"_s, &Arguments::CopyFileTo)
183 .Bind("COPY_FILE_ERROR"_s, &Arguments::CopyFileError)
184 .BIND_LANG_PROPS(C)
185 .BIND_LANG_PROPS(CUDA)
186 .BIND_LANG_PROPS(CXX)
187 .BIND_LANG_PROPS(HIP)
188 .BIND_LANG_PROPS(OBJC)
189 .BIND_LANG_PROPS(OBJCXX)
190 /* keep semicolon on own line */;
192 auto const TryCompileBaseNewSourcesArgParser =
193 cmArgumentParser<Arguments>{ TryCompileBaseSourcesArgParser }
194 .BindWithContext("SOURCE_FROM_CONTENT"_s, &Arguments::SourceFromContent,
195 &Arguments::SourceTypeContext)
196 .BindWithContext("SOURCE_FROM_VAR"_s, &Arguments::SourceFromVar,
197 &Arguments::SourceTypeContext)
198 .BindWithContext("SOURCE_FROM_FILE"_s, &Arguments::SourceFromFile,
199 &Arguments::SourceTypeContext)
200 /* keep semicolon on own line */;
202 auto const TryCompileBaseProjectArgParser =
203 cmArgumentParser<Arguments>{ TryCompileBaseArgParser }
204 .Bind("PROJECT"_s, &Arguments::ProjectName)
205 .Bind("SOURCE_DIR"_s, &Arguments::SourceDirectoryOrFile)
206 .Bind("BINARY_DIR"_s, &Arguments::BinaryDirectory)
207 .Bind("TARGET"_s, &Arguments::TargetName)
208 /* keep semicolon on own line */;
210 auto const TryCompileProjectArgParser =
211 makeTryCompileParser(TryCompileBaseProjectArgParser);
213 auto const TryCompileSourcesArgParser =
214 makeTryCompileParser(TryCompileBaseNewSourcesArgParser);
216 auto const TryCompileOldArgParser =
217 makeTryCompileParser(TryCompileBaseSourcesArgParser)
218 .Bind(1, &Arguments::BinaryDirectory)
219 .Bind(2, &Arguments::SourceDirectoryOrFile)
220 .Bind(3, &Arguments::ProjectName)
221 .Bind(4, &Arguments::TargetName)
222 /* keep semicolon on own line */;
224 auto const TryRunSourcesArgParser =
225 makeTryRunParser(TryCompileBaseNewSourcesArgParser);
227 auto const TryRunOldArgParser = makeTryRunParser(TryCompileOldArgParser);
229 #undef BIND_LANG_PROPS
231 std::string const TryCompileDefaultConfig = "DEBUG";
234 ArgumentParser::Continue cmCoreTryCompile::Arguments::SetSourceType(
235 cm::string_view sourceType)
237 bool matched = false;
238 if (sourceType == "NORMAL"_s) {
239 this->SourceTypeContext = SourceType::Normal;
240 matched = true;
241 } else if (sourceType == "CXX_MODULE"_s) {
242 this->SourceTypeContext = SourceType::CxxModule;
243 matched = true;
246 if (!matched && this->SourceTypeError.empty()) {
247 // Only remember one error at a time; all other errors related to argument
248 // parsing are "indicate one error and return" anyways.
249 this->SourceTypeError =
250 cmStrCat("Invalid 'SOURCE_TYPE' '", sourceType,
251 "'; must be one of 'SOURCE' or 'CXX_MODULE'");
253 return ArgumentParser::Continue::Yes;
256 Arguments cmCoreTryCompile::ParseArgs(
257 const cmRange<std::vector<std::string>::const_iterator>& args,
258 const cmArgumentParser<Arguments>& parser,
259 std::vector<std::string>& unparsedArguments)
261 Arguments arguments{ this->Makefile };
262 parser.Parse(arguments, args, &unparsedArguments, 0);
263 if (!arguments.MaybeReportError(*(this->Makefile)) &&
264 !unparsedArguments.empty()) {
265 std::string m = "Unknown arguments:";
266 for (const auto& i : unparsedArguments) {
267 m = cmStrCat(m, "\n \"", i, '"');
269 this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, m);
271 return arguments;
274 Arguments cmCoreTryCompile::ParseArgs(
275 cmRange<std::vector<std::string>::const_iterator> args, bool isTryRun)
277 std::vector<std::string> unparsedArguments;
278 const auto& second = *(++args.begin());
280 if (!isTryRun && second == "PROJECT") {
281 // New PROJECT signature (try_compile only).
282 auto arguments =
283 this->ParseArgs(args, TryCompileProjectArgParser, unparsedArguments);
284 if (!arguments.BinaryDirectory) {
285 arguments.BinaryDirectory = unique_binary_directory;
287 return arguments;
290 if (cmHasLiteralPrefix(second, "SOURCE")) {
291 // New SOURCES signature.
292 auto arguments = this->ParseArgs(
293 args, isTryRun ? TryRunSourcesArgParser : TryCompileSourcesArgParser,
294 unparsedArguments);
295 arguments.BinaryDirectory = unique_binary_directory;
296 return arguments;
299 // Old signature.
300 auto arguments = this->ParseArgs(
301 args, isTryRun ? TryRunOldArgParser : TryCompileOldArgParser,
302 unparsedArguments);
303 // For historical reasons, treat some empty-valued keyword
304 // arguments as if they were not specified at all.
305 if (arguments.OutputVariable && arguments.OutputVariable->empty()) {
306 arguments.OutputVariable = cm::nullopt;
308 if (isTryRun) {
309 if (arguments.CompileOutputVariable &&
310 arguments.CompileOutputVariable->empty()) {
311 arguments.CompileOutputVariable = cm::nullopt;
313 if (arguments.RunOutputVariable && arguments.RunOutputVariable->empty()) {
314 arguments.RunOutputVariable = cm::nullopt;
316 if (arguments.RunOutputStdOutVariable &&
317 arguments.RunOutputStdOutVariable->empty()) {
318 arguments.RunOutputStdOutVariable = cm::nullopt;
320 if (arguments.RunOutputStdErrVariable &&
321 arguments.RunOutputStdErrVariable->empty()) {
322 arguments.RunOutputStdErrVariable = cm::nullopt;
324 if (arguments.RunWorkingDirectory &&
325 arguments.RunWorkingDirectory->empty()) {
326 arguments.RunWorkingDirectory = cm::nullopt;
329 return arguments;
332 cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode(
333 Arguments& arguments, cmStateEnums::TargetType targetType)
335 this->OutputFile.clear();
336 // which signature were we called with ?
337 this->SrcFileSignature = true;
339 bool useUniqueBinaryDirectory = false;
340 std::string sourceDirectory;
341 std::string projectName;
342 std::string targetName;
343 if (arguments.ProjectName) {
344 this->SrcFileSignature = false;
345 if (!arguments.SourceDirectoryOrFile ||
346 arguments.SourceDirectoryOrFile->empty()) {
347 this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
348 "No <srcdir> specified.");
349 return cm::nullopt;
351 sourceDirectory = *arguments.SourceDirectoryOrFile;
352 projectName = *arguments.ProjectName;
353 if (arguments.TargetName) {
354 targetName = *arguments.TargetName;
356 } else {
357 projectName = "CMAKE_TRY_COMPILE";
358 /* Use a random file name to avoid rapid creation and deletion
359 of the same executable name (some filesystems fail on that). */
360 char targetNameBuf[64];
361 snprintf(targetNameBuf, sizeof(targetNameBuf), "cmTC_%05x",
362 cmSystemTools::RandomSeed() & 0xFFFFF);
363 targetName = targetNameBuf;
366 if (!arguments.BinaryDirectory || arguments.BinaryDirectory->empty()) {
367 this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
368 "No <bindir> specified.");
369 return cm::nullopt;
371 if (*arguments.BinaryDirectory == unique_binary_directory) {
372 // leave empty until we're ready to create it, so we don't try to remove
373 // a non-existing directory if we abort due to e.g. bad arguments
374 this->BinaryDirectory.clear();
375 useUniqueBinaryDirectory = true;
376 } else {
377 if (!cmSystemTools::FileIsFullPath(*arguments.BinaryDirectory)) {
378 this->Makefile->IssueMessage(
379 MessageType::FATAL_ERROR,
380 cmStrCat("<bindir> is not an absolute path:\n '",
381 *arguments.BinaryDirectory, '\''));
382 return cm::nullopt;
384 this->BinaryDirectory = *arguments.BinaryDirectory;
385 // compute the binary dir when TRY_COMPILE is called with a src file
386 // signature
387 if (this->SrcFileSignature) {
388 this->BinaryDirectory += "/CMakeFiles/CMakeTmp";
392 std::vector<std::string> targets;
393 if (arguments.LinkLibraries) {
394 for (std::string const& i : *arguments.LinkLibraries) {
395 if (cmTarget* tgt = this->Makefile->FindTargetToUse(i)) {
396 switch (tgt->GetType()) {
397 case cmStateEnums::SHARED_LIBRARY:
398 case cmStateEnums::STATIC_LIBRARY:
399 case cmStateEnums::INTERFACE_LIBRARY:
400 case cmStateEnums::UNKNOWN_LIBRARY:
401 break;
402 case cmStateEnums::EXECUTABLE:
403 if (tgt->IsExecutableWithExports()) {
404 break;
406 CM_FALLTHROUGH;
407 default:
408 this->Makefile->IssueMessage(
409 MessageType::FATAL_ERROR,
410 cmStrCat("Only libraries may be used as try_compile or try_run "
411 "IMPORTED LINK_LIBRARIES. Got ",
412 tgt->GetName(), " of type ",
413 cmState::GetTargetTypeName(tgt->GetType()), '.'));
414 return cm::nullopt;
416 if (tgt->IsImported()) {
417 targets.emplace_back(i);
423 if (arguments.CopyFileTo && arguments.CopyFileTo->empty()) {
424 this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
425 "COPY_FILE must be followed by a file path");
426 return cm::nullopt;
429 if (arguments.CopyFileError && arguments.CopyFileError->empty()) {
430 this->Makefile->IssueMessage(
431 MessageType::FATAL_ERROR,
432 "COPY_FILE_ERROR must be followed by a variable name");
433 return cm::nullopt;
436 if (arguments.CopyFileError && !arguments.CopyFileTo) {
437 this->Makefile->IssueMessage(
438 MessageType::FATAL_ERROR,
439 "COPY_FILE_ERROR may be used only with COPY_FILE");
440 return cm::nullopt;
443 if (arguments.Sources && arguments.Sources->empty()) {
444 this->Makefile->IssueMessage(
445 MessageType::FATAL_ERROR,
446 "SOURCES must be followed by at least one source file");
447 return cm::nullopt;
450 if (this->SrcFileSignature) {
451 if (arguments.SourceFromContent &&
452 arguments.SourceFromContent->size() % 2) {
453 this->Makefile->IssueMessage(
454 MessageType::FATAL_ERROR,
455 "SOURCE_FROM_CONTENT requires exactly two arguments");
456 return cm::nullopt;
458 if (arguments.SourceFromVar && arguments.SourceFromVar->size() % 2) {
459 this->Makefile->IssueMessage(
460 MessageType::FATAL_ERROR,
461 "SOURCE_FROM_VAR requires exactly two arguments");
462 return cm::nullopt;
464 if (arguments.SourceFromFile && arguments.SourceFromFile->size() % 2) {
465 this->Makefile->IssueMessage(
466 MessageType::FATAL_ERROR,
467 "SOURCE_FROM_FILE requires exactly two arguments");
468 return cm::nullopt;
470 if (!arguments.SourceTypeError.empty()) {
471 this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
472 arguments.SourceTypeError);
473 return cm::nullopt;
475 } else {
476 // only valid for srcfile signatures
477 if (!arguments.LangProps.empty()) {
478 this->Makefile->IssueMessage(
479 MessageType::FATAL_ERROR,
480 cmStrCat(arguments.LangProps.begin()->first,
481 " allowed only in source file signature"));
482 return cm::nullopt;
484 if (!arguments.CompileDefs.empty()) {
485 this->Makefile->IssueMessage(
486 MessageType::FATAL_ERROR,
487 "COMPILE_DEFINITIONS allowed only in source file signature");
488 return cm::nullopt;
490 if (arguments.CopyFileTo) {
491 this->Makefile->IssueMessage(
492 MessageType::FATAL_ERROR,
493 "COPY_FILE allowed only in source file signature");
494 return cm::nullopt;
498 // make sure the binary directory exists
499 if (useUniqueBinaryDirectory) {
500 this->BinaryDirectory =
501 cmStrCat(this->Makefile->GetHomeOutputDirectory(),
502 "/CMakeFiles/CMakeScratch/TryCompile-XXXXXX");
503 cmSystemTools::MakeTempDirectory(this->BinaryDirectory);
504 } else {
505 cmSystemTools::MakeDirectory(this->BinaryDirectory);
508 // do not allow recursive try Compiles
509 if (this->BinaryDirectory == this->Makefile->GetHomeOutputDirectory()) {
510 std::ostringstream e;
511 e << "Attempt at a recursive or nested TRY_COMPILE in directory\n"
512 << " " << this->BinaryDirectory << "\n";
513 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
514 return cm::nullopt;
517 std::map<std::string, std::string> cmakeVariables;
519 std::string outFileName = cmStrCat(this->BinaryDirectory, "/CMakeLists.txt");
520 // which signature are we using? If we are using var srcfile bindir
521 if (this->SrcFileSignature) {
522 // remove any CMakeCache.txt files so we will have a clean test
523 std::string ccFile = cmStrCat(this->BinaryDirectory, "/CMakeCache.txt");
524 cmSystemTools::RemoveFile(ccFile);
526 // Choose sources.
527 std::vector<std::pair<std::string, Arguments::SourceType>> sources;
528 if (arguments.Sources) {
529 sources = std::move(*arguments.Sources);
530 } else if (arguments.SourceDirectoryOrFile) {
531 sources.emplace_back(*arguments.SourceDirectoryOrFile,
532 Arguments::SourceType::Directory);
534 if (arguments.SourceFromContent) {
535 auto const k = arguments.SourceFromContent->size();
536 for (auto i = decltype(k){ 0 }; i < k; i += 2) {
537 const auto& name = (*arguments.SourceFromContent)[i + 0].first;
538 const auto& content = (*arguments.SourceFromContent)[i + 1].first;
539 auto out = this->WriteSource(name, content, "SOURCE_FROM_CONTENT");
540 if (out.empty()) {
541 return cm::nullopt;
543 sources.emplace_back(std::move(out),
544 (*arguments.SourceFromContent)[i + 0].second);
547 if (arguments.SourceFromVar) {
548 auto const k = arguments.SourceFromVar->size();
549 for (auto i = decltype(k){ 0 }; i < k; i += 2) {
550 const auto& name = (*arguments.SourceFromVar)[i + 0].first;
551 const auto& var = (*arguments.SourceFromVar)[i + 1].first;
552 const auto& content = this->Makefile->GetDefinition(var);
553 auto out = this->WriteSource(name, content, "SOURCE_FROM_VAR");
554 if (out.empty()) {
555 return cm::nullopt;
557 sources.emplace_back(std::move(out),
558 (*arguments.SourceFromVar)[i + 0].second);
561 if (arguments.SourceFromFile) {
562 auto const k = arguments.SourceFromFile->size();
563 for (auto i = decltype(k){ 0 }; i < k; i += 2) {
564 const auto& dst = (*arguments.SourceFromFile)[i + 0].first;
565 const auto& src = (*arguments.SourceFromFile)[i + 1].first;
567 if (!cmSystemTools::GetFilenamePath(dst).empty()) {
568 const auto& msg =
569 cmStrCat("SOURCE_FROM_FILE given invalid filename \"", dst, '"');
570 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
571 return cm::nullopt;
574 auto dstPath = cmStrCat(this->BinaryDirectory, '/', dst);
575 auto const result = cmSystemTools::CopyFileAlways(src, dstPath);
576 if (!result.IsSuccess()) {
577 const auto& msg = cmStrCat("SOURCE_FROM_FILE failed to copy \"", src,
578 "\": ", result.GetString());
579 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
580 return cm::nullopt;
583 sources.emplace_back(std::move(dstPath),
584 (*arguments.SourceFromFile)[i + 0].second);
587 // TODO: ensure sources is not empty
589 // Detect languages to enable.
590 cmGlobalGenerator* gg = this->Makefile->GetGlobalGenerator();
591 std::set<std::string> testLangs;
592 for (auto const& source : sources) {
593 auto const& si = source.first;
594 std::string ext = cmSystemTools::GetFilenameLastExtension(si);
595 std::string lang = gg->GetLanguageFromExtension(ext.c_str());
596 if (!lang.empty()) {
597 testLangs.insert(lang);
598 } else {
599 std::ostringstream err;
600 err << "Unknown extension \"" << ext
601 << "\" for file\n"
603 << si
604 << "\n"
605 "try_compile() works only for enabled languages. "
606 "Currently these are:\n ";
607 std::vector<std::string> langs;
608 gg->GetEnabledLanguages(langs);
609 err << cmJoin(langs, " ");
610 err << "\nSee project() command to enable other languages.";
611 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, err.str());
612 return cm::nullopt;
616 // when the only language is ISPC we know that the output
617 // type must by a static library
618 if (testLangs.size() == 1 && testLangs.count("ISPC") == 1) {
619 targetType = cmStateEnums::STATIC_LIBRARY;
622 std::string const tcConfig =
623 this->Makefile->GetSafeDefinition("CMAKE_TRY_COMPILE_CONFIGURATION");
625 // we need to create a directory and CMakeLists file etc...
626 // first create the directories
627 sourceDirectory = this->BinaryDirectory;
629 // now create a CMakeLists.txt file in that directory
630 FILE* fout = cmsys::SystemTools::Fopen(outFileName, "w");
631 if (!fout) {
632 this->Makefile->IssueMessage(
633 MessageType::FATAL_ERROR,
634 cmStrCat("Failed to open\n"
635 " ",
636 outFileName, '\n', cmSystemTools::GetLastSystemError()));
637 return cm::nullopt;
640 cmValue def = this->Makefile->GetDefinition("CMAKE_MODULE_PATH");
641 fprintf(fout, "cmake_minimum_required(VERSION %u.%u.%u.%u)\n",
642 cmVersion::GetMajorVersion(), cmVersion::GetMinorVersion(),
643 cmVersion::GetPatchVersion(), cmVersion::GetTweakVersion());
644 if (def) {
645 fprintf(fout, "set(CMAKE_MODULE_PATH \"%s\")\n", def->c_str());
646 cmakeVariables.emplace("CMAKE_MODULE_PATH", *def);
649 /* Set MSVC runtime library policy to match our selection. */
650 if (cmValue msvcRuntimeLibraryDefault =
651 this->Makefile->GetDefinition(kCMAKE_MSVC_RUNTIME_LIBRARY_DEFAULT)) {
652 fprintf(fout, "cmake_policy(SET CMP0091 %s)\n",
653 !msvcRuntimeLibraryDefault->empty() ? "NEW" : "OLD");
656 /* Set Watcom runtime library policy to match our selection. */
657 if (cmValue watcomRuntimeLibraryDefault = this->Makefile->GetDefinition(
658 kCMAKE_WATCOM_RUNTIME_LIBRARY_DEFAULT)) {
659 fprintf(fout, "cmake_policy(SET CMP0136 %s)\n",
660 !watcomRuntimeLibraryDefault->empty() ? "NEW" : "OLD");
663 /* Set CUDA architectures policy to match outer project. */
664 if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0104) !=
665 cmPolicies::NEW &&
666 testLangs.find("CUDA") != testLangs.end() &&
667 this->Makefile->GetSafeDefinition(kCMAKE_CUDA_ARCHITECTURES).empty()) {
668 fprintf(fout, "cmake_policy(SET CMP0104 OLD)\n");
671 /* Set ARMClang cpu/arch policy to match outer project. */
672 if (cmValue cmp0123 =
673 this->Makefile->GetDefinition(kCMAKE_ARMClang_CMP0123)) {
674 fprintf(fout, "cmake_policy(SET CMP0123 %s)\n",
675 *cmp0123 == "NEW"_s ? "NEW" : "OLD");
678 /* Set MSVC debug information format policy to match our selection. */
679 if (cmValue msvcDebugInformationFormatDefault =
680 this->Makefile->GetDefinition(
681 kCMAKE_MSVC_DEBUG_INFORMATION_FORMAT_DEFAULT)) {
682 fprintf(fout, "cmake_policy(SET CMP0141 %s)\n",
683 !msvcDebugInformationFormatDefault->empty() ? "NEW" : "OLD");
686 /* Set cache/normal variable policy to match outer project.
687 It may affect toolchain files. */
688 if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) !=
689 cmPolicies::NEW) {
690 fprintf(fout, "cmake_policy(SET CMP0126 OLD)\n");
693 /* Set language extensions policy to match outer project. */
694 if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0128) !=
695 cmPolicies::NEW) {
696 fprintf(fout, "cmake_policy(SET CMP0128 OLD)\n");
699 std::string projectLangs;
700 for (std::string const& li : testLangs) {
701 projectLangs += cmStrCat(' ', li);
702 std::string rulesOverrideBase = "CMAKE_USER_MAKE_RULES_OVERRIDE";
703 std::string rulesOverrideLang = cmStrCat(rulesOverrideBase, '_', li);
704 if (cmValue rulesOverridePath =
705 this->Makefile->GetDefinition(rulesOverrideLang)) {
706 fprintf(fout, "set(%s \"%s\")\n", rulesOverrideLang.c_str(),
707 rulesOverridePath->c_str());
708 cmakeVariables.emplace(rulesOverrideLang, *rulesOverridePath);
709 } else if (cmValue rulesOverridePath2 =
710 this->Makefile->GetDefinition(rulesOverrideBase)) {
711 fprintf(fout, "set(%s \"%s\")\n", rulesOverrideBase.c_str(),
712 rulesOverridePath2->c_str());
713 cmakeVariables.emplace(rulesOverrideBase, *rulesOverridePath2);
716 fprintf(fout, "project(CMAKE_TRY_COMPILE%s)\n", projectLangs.c_str());
717 if (arguments.CMakeInternal == "ABI") {
718 // This is the ABI detection step, also used for implicit includes.
719 // Erase any include_directories() calls from the toolchain file so
720 // that we do not see them as implicit. Our ABI detection source
721 // does not include any system headers anyway.
722 fprintf(fout,
723 "set_property(DIRECTORY PROPERTY INCLUDE_DIRECTORIES \"\")\n");
725 // The link and compile lines for ABI detection step need to not use
726 // response files so we can extract implicit includes given to
727 // the underlying host compiler
728 static std::array<std::string, 2> const noRSP{ { "CUDA", "HIP" } };
729 for (std::string const& lang : noRSP) {
730 if (testLangs.find(lang) != testLangs.end()) {
731 fprintf(fout, "set(CMAKE_%s_USE_RESPONSE_FILE_FOR_INCLUDES OFF)\n",
732 lang.c_str());
733 fprintf(fout, "set(CMAKE_%s_USE_RESPONSE_FILE_FOR_LIBRARIES OFF)\n",
734 lang.c_str());
735 fprintf(fout, "set(CMAKE_%s_USE_RESPONSE_FILE_FOR_OBJECTS OFF)\n",
736 lang.c_str());
740 fprintf(fout, "set(CMAKE_VERBOSE_MAKEFILE 1)\n");
741 for (std::string const& li : testLangs) {
742 std::string langFlags = cmStrCat("CMAKE_", li, "_FLAGS");
743 cmValue flags = this->Makefile->GetDefinition(langFlags);
744 fprintf(fout, "set(CMAKE_%s_FLAGS %s)\n", li.c_str(),
745 cmOutputConverter::EscapeForCMake(*flags).c_str());
746 fprintf(fout,
747 "set(CMAKE_%s_FLAGS \"${CMAKE_%s_FLAGS}"
748 " ${COMPILE_DEFINITIONS}\")\n",
749 li.c_str(), li.c_str());
750 if (flags) {
751 cmakeVariables.emplace(langFlags, *flags);
754 switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0066)) {
755 case cmPolicies::WARN:
756 if (this->Makefile->PolicyOptionalWarningEnabled(
757 "CMAKE_POLICY_WARNING_CMP0066")) {
758 std::ostringstream w;
759 /* clang-format off */
760 w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0066) << "\n"
761 "For compatibility with older versions of CMake, try_compile "
762 "is not honoring caller config-specific compiler flags "
763 "(e.g. CMAKE_C_FLAGS_DEBUG) in the test project."
765 /* clang-format on */
766 this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
768 CM_FALLTHROUGH;
769 case cmPolicies::OLD:
770 // OLD behavior is to do nothing.
771 break;
772 case cmPolicies::REQUIRED_IF_USED:
773 case cmPolicies::REQUIRED_ALWAYS:
774 this->Makefile->IssueMessage(
775 MessageType::FATAL_ERROR,
776 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0066));
777 CM_FALLTHROUGH;
778 case cmPolicies::NEW: {
779 // NEW behavior is to pass config-specific compiler flags.
780 std::string const cfg = !tcConfig.empty()
781 ? cmSystemTools::UpperCase(tcConfig)
782 : TryCompileDefaultConfig;
783 for (std::string const& li : testLangs) {
784 std::string const langFlagsCfg =
785 cmStrCat("CMAKE_", li, "_FLAGS_", cfg);
786 cmValue flagsCfg = this->Makefile->GetDefinition(langFlagsCfg);
787 fprintf(fout, "set(%s %s)\n", langFlagsCfg.c_str(),
788 cmOutputConverter::EscapeForCMake(*flagsCfg).c_str());
789 if (flagsCfg) {
790 cmakeVariables.emplace(langFlagsCfg, *flagsCfg);
793 } break;
795 switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0056)) {
796 case cmPolicies::WARN:
797 if (this->Makefile->PolicyOptionalWarningEnabled(
798 "CMAKE_POLICY_WARNING_CMP0056")) {
799 std::ostringstream w;
800 /* clang-format off */
801 w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0056) << "\n"
802 "For compatibility with older versions of CMake, try_compile "
803 "is not honoring caller link flags (e.g. CMAKE_EXE_LINKER_FLAGS) "
804 "in the test project."
806 /* clang-format on */
807 this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
809 CM_FALLTHROUGH;
810 case cmPolicies::OLD:
811 // OLD behavior is to do nothing.
812 break;
813 case cmPolicies::REQUIRED_IF_USED:
814 case cmPolicies::REQUIRED_ALWAYS:
815 this->Makefile->IssueMessage(
816 MessageType::FATAL_ERROR,
817 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0056));
818 CM_FALLTHROUGH;
819 case cmPolicies::NEW:
820 // NEW behavior is to pass linker flags.
822 cmValue exeLinkFlags =
823 this->Makefile->GetDefinition("CMAKE_EXE_LINKER_FLAGS");
824 fprintf(fout, "set(CMAKE_EXE_LINKER_FLAGS %s)\n",
825 cmOutputConverter::EscapeForCMake(*exeLinkFlags).c_str());
826 if (exeLinkFlags) {
827 cmakeVariables.emplace("CMAKE_EXE_LINKER_FLAGS", *exeLinkFlags);
830 break;
832 fprintf(fout,
833 "set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS}"
834 " ${EXE_LINKER_FLAGS}\")\n");
835 fprintf(fout, "include_directories(${INCLUDE_DIRECTORIES})\n");
836 fprintf(fout, "set(CMAKE_SUPPRESS_REGENERATION 1)\n");
837 fprintf(fout, "link_directories(${LINK_DIRECTORIES})\n");
838 // handle any compile flags we need to pass on
839 if (!arguments.CompileDefs.empty()) {
840 // Pass using bracket arguments to preserve content.
841 fprintf(fout, "add_definitions([==[%s]==])\n",
842 arguments.CompileDefs.join("]==] [==[").c_str());
845 if (!targets.empty()) {
846 std::string fname = cmStrCat('/', targetName, "Targets.cmake");
847 cmExportTryCompileFileGenerator tcfg(gg, targets, this->Makefile,
848 testLangs);
849 tcfg.SetExportFile(cmStrCat(this->BinaryDirectory, fname).c_str());
850 tcfg.SetConfig(tcConfig);
852 if (!tcfg.GenerateImportFile()) {
853 this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
854 "could not write export file.");
855 fclose(fout);
856 return cm::nullopt;
858 fprintf(fout, "\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/%s\")\n",
859 fname.c_str());
860 // Create all relevant alias targets
861 if (arguments.LinkLibraries) {
862 const auto& aliasTargets = this->Makefile->GetAliasTargets();
863 for (std::string const& i : *arguments.LinkLibraries) {
864 auto alias = aliasTargets.find(i);
865 if (alias != aliasTargets.end()) {
866 const auto& aliasTarget =
867 this->Makefile->FindTargetToUse(alias->second);
868 // Create equivalent library/executable alias
869 if (aliasTarget->GetType() == cmStateEnums::EXECUTABLE) {
870 fprintf(fout, "add_executable(\"%s\" ALIAS \"%s\")\n", i.c_str(),
871 alias->second.c_str());
872 } else {
873 // Other cases like UTILITY and GLOBAL_TARGET are excluded when
874 // arguments.LinkLibraries is initially parsed in this function.
875 fprintf(fout, "add_library(\"%s\" ALIAS \"%s\")\n", i.c_str(),
876 alias->second.c_str());
881 fprintf(fout, "\n");
884 /* Set the appropriate policy information for ENABLE_EXPORTS */
885 fprintf(fout, "cmake_policy(SET CMP0065 %s)\n",
886 this->Makefile->GetPolicyStatus(cmPolicies::CMP0065) ==
887 cmPolicies::NEW
888 ? "NEW"
889 : "OLD");
891 /* Set the appropriate policy information for PIE link flags */
892 fprintf(fout, "cmake_policy(SET CMP0083 %s)\n",
893 this->Makefile->GetPolicyStatus(cmPolicies::CMP0083) ==
894 cmPolicies::NEW
895 ? "NEW"
896 : "OLD");
898 /* Set the appropriate policy information for C++ module support */
899 fprintf(fout, "cmake_policy(SET CMP0155 %s)\n",
900 this->Makefile->GetPolicyStatus(cmPolicies::CMP0155) ==
901 cmPolicies::NEW
902 ? "NEW"
903 : "OLD");
905 /* Set the appropriate policy information for Swift compilation mode */
906 fprintf(
907 fout, "cmake_policy(SET CMP0157 %s)\n",
908 this->Makefile->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT")
909 .IsEmpty()
910 ? "OLD"
911 : "NEW");
913 // Workaround for -Wl,-headerpad_max_install_names issue until we can avoid
914 // adding that flag in the platform and compiler language files
915 fprintf(fout,
916 "include(\"${CMAKE_ROOT}/Modules/Internal/"
917 "HeaderpadWorkaround.cmake\")\n");
919 if (targetType == cmStateEnums::EXECUTABLE) {
920 /* Put the executable at a known location (for COPY_FILE). */
921 fprintf(fout, "set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"%s\")\n",
922 this->BinaryDirectory.c_str());
923 /* Create the actual executable. */
924 fprintf(fout, "add_executable(%s)\n", targetName.c_str());
925 } else // if (targetType == cmStateEnums::STATIC_LIBRARY)
927 /* Put the static library at a known location (for COPY_FILE). */
928 fprintf(fout, "set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \"%s\")\n",
929 this->BinaryDirectory.c_str());
930 /* Create the actual static library. */
931 fprintf(fout, "add_library(%s STATIC)\n", targetName.c_str());
933 fprintf(fout, "target_sources(%s PRIVATE\n", targetName.c_str());
934 std::string file_set_name;
935 bool in_file_set = false;
936 for (auto const& source : sources) {
937 auto const& si = source.first;
938 switch (source.second) {
939 case Arguments::SourceType::Normal: {
940 if (in_file_set) {
941 fprintf(fout, " PRIVATE\n");
942 in_file_set = false;
944 } break;
945 case Arguments::SourceType::CxxModule: {
946 if (!in_file_set) {
947 file_set_name += 'a';
948 fprintf(fout,
949 " PRIVATE FILE_SET %s TYPE CXX_MODULES BASE_DIRS \"%s\" "
950 "FILES\n",
951 file_set_name.c_str(),
952 this->Makefile->GetCurrentSourceDirectory().c_str());
953 in_file_set = true;
955 } break;
956 case Arguments::SourceType::Directory:
957 /* Handled elsewhere. */
958 break;
960 fprintf(fout, " \"%s\"\n", si.c_str());
962 // Add dependencies on any non-temporary sources.
963 if (!IsTemporary(si)) {
964 this->Makefile->AddCMakeDependFile(si);
967 fprintf(fout, ")\n");
969 /* Write out the output location of the target we are building */
970 std::string perConfigGenex;
971 if (this->Makefile->GetGlobalGenerator()->IsMultiConfig()) {
972 perConfigGenex = "_$<UPPER_CASE:$<CONFIG>>";
974 fprintf(fout,
975 "file(GENERATE OUTPUT "
976 "\"${CMAKE_BINARY_DIR}/%s%s_loc\"\n",
977 targetName.c_str(), perConfigGenex.c_str());
978 fprintf(fout, " CONTENT $<TARGET_FILE:%s>)\n", targetName.c_str());
980 bool warnCMP0067 = false;
981 bool honorStandard = true;
983 if (arguments.LangProps.empty()) {
984 switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0067)) {
985 case cmPolicies::WARN:
986 warnCMP0067 = this->Makefile->PolicyOptionalWarningEnabled(
987 "CMAKE_POLICY_WARNING_CMP0067");
988 CM_FALLTHROUGH;
989 case cmPolicies::OLD:
990 // OLD behavior is to not honor the language standard variables.
991 honorStandard = false;
992 break;
993 case cmPolicies::REQUIRED_IF_USED:
994 case cmPolicies::REQUIRED_ALWAYS:
995 this->Makefile->IssueMessage(
996 MessageType::FATAL_ERROR,
997 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0067));
998 break;
999 case cmPolicies::NEW:
1000 // NEW behavior is to honor the language standard variables.
1001 // We already initialized honorStandard to true.
1002 break;
1006 std::vector<std::string> warnCMP0067Variables;
1008 if (honorStandard || warnCMP0067) {
1009 static std::array<std::string, 6> const possibleLangs{
1010 { "C", "CXX", "CUDA", "HIP", "OBJC", "OBJCXX" }
1012 static std::array<cm::string_view, 3> const langPropSuffixes{
1013 { "_STANDARD"_s, "_STANDARD_REQUIRED"_s, "_EXTENSIONS"_s }
1015 for (std::string const& lang : possibleLangs) {
1016 if (testLangs.find(lang) == testLangs.end()) {
1017 continue;
1019 for (cm::string_view propSuffix : langPropSuffixes) {
1020 std::string langProp = cmStrCat(lang, propSuffix);
1021 if (!arguments.LangProps.count(langProp)) {
1022 std::string langPropVar = cmStrCat("CMAKE_"_s, langProp);
1023 std::string value = this->Makefile->GetSafeDefinition(langPropVar);
1024 if (warnCMP0067 && !value.empty()) {
1025 value.clear();
1026 warnCMP0067Variables.emplace_back(langPropVar);
1028 if (!value.empty()) {
1029 arguments.LangProps[langProp] = value;
1036 if (!warnCMP0067Variables.empty()) {
1037 std::ostringstream w;
1038 /* clang-format off */
1039 w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0067) << "\n"
1040 "For compatibility with older versions of CMake, try_compile "
1041 "is not honoring language standard variables in the test project:\n"
1043 /* clang-format on */
1044 for (std::string const& vi : warnCMP0067Variables) {
1045 w << " " << vi << "\n";
1047 this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
1050 for (auto const& p : arguments.LangProps) {
1051 if (p.second.empty()) {
1052 continue;
1054 fprintf(fout, "set_property(TARGET %s PROPERTY %s %s)\n",
1055 targetName.c_str(),
1056 cmOutputConverter::EscapeForCMake(p.first).c_str(),
1057 cmOutputConverter::EscapeForCMake(p.second).c_str());
1060 if (!arguments.LinkOptions.empty()) {
1061 std::vector<std::string> options;
1062 options.reserve(arguments.LinkOptions.size());
1063 for (const auto& option : arguments.LinkOptions) {
1064 options.emplace_back(cmOutputConverter::EscapeForCMake(option));
1067 if (targetType == cmStateEnums::STATIC_LIBRARY) {
1068 fprintf(fout,
1069 "set_property(TARGET %s PROPERTY STATIC_LIBRARY_OPTIONS %s)\n",
1070 targetName.c_str(), cmJoin(options, " ").c_str());
1071 } else {
1072 fprintf(fout, "target_link_options(%s PRIVATE %s)\n",
1073 targetName.c_str(), cmJoin(options, " ").c_str());
1077 if (arguments.LinkerLanguage) {
1078 std::string LinkerLanguage = *arguments.LinkerLanguage;
1079 if (testLangs.find(LinkerLanguage) == testLangs.end()) {
1080 this->Makefile->IssueMessage(
1081 MessageType::FATAL_ERROR,
1082 "Linker language '" + LinkerLanguage +
1083 "' must be enabled in project(LANGUAGES).");
1086 fprintf(fout, "set_property(TARGET %s PROPERTY LINKER_LANGUAGE %s)\n",
1087 targetName.c_str(), LinkerLanguage.c_str());
1090 if (arguments.LinkLibraries) {
1091 std::string libsToLink = " ";
1092 for (std::string const& i : *arguments.LinkLibraries) {
1093 libsToLink += cmStrCat('"', cmTrimWhitespace(i), "\" ");
1095 fprintf(fout, "target_link_libraries(%s %s)\n", targetName.c_str(),
1096 libsToLink.c_str());
1097 } else {
1098 fprintf(fout, "target_link_libraries(%s ${LINK_LIBRARIES})\n",
1099 targetName.c_str());
1101 fclose(fout);
1104 // Forward a set of variables to the inner project cache.
1105 if ((this->SrcFileSignature ||
1106 this->Makefile->GetPolicyStatus(cmPolicies::CMP0137) ==
1107 cmPolicies::NEW) &&
1108 !this->Makefile->IsOn("CMAKE_TRY_COMPILE_NO_PLATFORM_VARIABLES")) {
1109 std::set<std::string> vars;
1110 vars.insert(&c_properties[lang_property_start],
1111 &c_properties[lang_property_start + lang_property_size]);
1112 vars.insert(&cxx_properties[lang_property_start],
1113 &cxx_properties[lang_property_start + lang_property_size]);
1114 vars.insert(&cuda_properties[lang_property_start],
1115 &cuda_properties[lang_property_start + lang_property_size]);
1116 vars.insert(&fortran_properties[lang_property_start],
1117 &fortran_properties[lang_property_start + lang_property_size]);
1118 vars.insert(&hip_properties[lang_property_start],
1119 &hip_properties[lang_property_start + lang_property_size]);
1120 vars.insert(&objc_properties[lang_property_start],
1121 &objc_properties[lang_property_start + lang_property_size]);
1122 vars.insert(&objcxx_properties[lang_property_start],
1123 &objcxx_properties[lang_property_start + lang_property_size]);
1124 vars.insert(&ispc_properties[lang_property_start],
1125 &ispc_properties[lang_property_start + lang_property_size]);
1126 vars.insert(&swift_properties[lang_property_start],
1127 &swift_properties[lang_property_start + lang_property_size]);
1128 vars.insert(kCMAKE_CUDA_ARCHITECTURES);
1129 vars.insert(kCMAKE_CUDA_RUNTIME_LIBRARY);
1130 vars.insert(kCMAKE_CXX_SCAN_FOR_MODULES);
1131 vars.insert(kCMAKE_ENABLE_EXPORTS);
1132 vars.insert(kCMAKE_EXECUTABLE_ENABLE_EXPORTS);
1133 vars.insert(kCMAKE_SHARED_LIBRARY_ENABLE_EXPORTS);
1134 vars.insert(kCMAKE_HIP_ARCHITECTURES);
1135 vars.insert(kCMAKE_HIP_PLATFORM);
1136 vars.insert(kCMAKE_HIP_RUNTIME_LIBRARY);
1137 vars.insert(kCMAKE_ISPC_INSTRUCTION_SETS);
1138 vars.insert(kCMAKE_ISPC_HEADER_SUFFIX);
1139 vars.insert(kCMAKE_LINK_SEARCH_END_STATIC);
1140 vars.insert(kCMAKE_LINK_SEARCH_START_STATIC);
1141 vars.insert(kCMAKE_OSX_ARCHITECTURES);
1142 vars.insert(kCMAKE_OSX_DEPLOYMENT_TARGET);
1143 vars.insert(kCMAKE_OSX_SYSROOT);
1144 vars.insert(kCMAKE_APPLE_ARCH_SYSROOTS);
1145 vars.insert(kCMAKE_POSITION_INDEPENDENT_CODE);
1146 vars.insert(kCMAKE_SYSROOT);
1147 vars.insert(kCMAKE_SYSROOT_COMPILE);
1148 vars.insert(kCMAKE_SYSROOT_LINK);
1149 vars.insert(kCMAKE_WARN_DEPRECATED);
1150 vars.emplace("CMAKE_MSVC_RUNTIME_LIBRARY"_s);
1151 vars.emplace("CMAKE_WATCOM_RUNTIME_LIBRARY"_s);
1152 vars.emplace("CMAKE_MSVC_DEBUG_INFORMATION_FORMAT"_s);
1153 vars.emplace("CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS"_s);
1154 vars.emplace("CMAKE_VS_USE_DEBUG_LIBRARIES"_s);
1156 if (cmValue varListStr = this->Makefile->GetDefinition(
1157 kCMAKE_TRY_COMPILE_PLATFORM_VARIABLES)) {
1158 cmList varList{ *varListStr };
1159 vars.insert(varList.begin(), varList.end());
1162 if (this->Makefile->GetDefinition(kCMAKE_LINKER_TYPE)) {
1163 // propagate various variables to support linker selection
1164 vars.insert(kCMAKE_LINKER_TYPE);
1165 auto defs = this->Makefile->GetDefinitions();
1166 cmsys::RegularExpression linkerTypeDef{
1167 "^CMAKE_[A-Za-z_-]+_USING_LINKER_"
1169 for (auto const& def : defs) {
1170 if (linkerTypeDef.find(def)) {
1171 vars.insert(def);
1176 if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0083) ==
1177 cmPolicies::NEW) {
1178 // To ensure full support of PIE, propagate cache variables
1179 // driving the link options
1180 vars.insert(&c_properties[pie_property_start],
1181 &c_properties[pie_property_start + pie_property_size]);
1182 vars.insert(&cxx_properties[pie_property_start],
1183 &cxx_properties[pie_property_start + pie_property_size]);
1184 vars.insert(&cuda_properties[pie_property_start],
1185 &cuda_properties[pie_property_start + pie_property_size]);
1186 vars.insert(&fortran_properties[pie_property_start],
1187 &fortran_properties[pie_property_start + pie_property_size]);
1188 vars.insert(&hip_properties[pie_property_start],
1189 &hip_properties[pie_property_start + pie_property_size]);
1190 vars.insert(&objc_properties[pie_property_start],
1191 &objc_properties[pie_property_start + pie_property_size]);
1192 vars.insert(&objcxx_properties[pie_property_start],
1193 &objcxx_properties[pie_property_start + pie_property_size]);
1194 vars.insert(&ispc_properties[pie_property_start],
1195 &ispc_properties[pie_property_start + pie_property_size]);
1196 vars.insert(&swift_properties[pie_property_start],
1197 &swift_properties[pie_property_start + pie_property_size]);
1200 /* for the TRY_COMPILEs we want to be able to specify the architecture.
1201 So the user can set CMAKE_OSX_ARCHITECTURES to i386;ppc and then set
1202 CMAKE_TRY_COMPILE_OSX_ARCHITECTURES first to i386 and then to ppc to
1203 have the tests run for each specific architecture. Since
1204 cmLocalGenerator doesn't allow building for "the other"
1205 architecture only via CMAKE_OSX_ARCHITECTURES.
1207 if (cmValue tcArchs = this->Makefile->GetDefinition(
1208 kCMAKE_TRY_COMPILE_OSX_ARCHITECTURES)) {
1209 vars.erase(kCMAKE_OSX_ARCHITECTURES);
1210 std::string flag = cmStrCat("-DCMAKE_OSX_ARCHITECTURES=", *tcArchs);
1211 arguments.CMakeFlags.emplace_back(std::move(flag));
1212 cmakeVariables.emplace("CMAKE_OSX_ARCHITECTURES", *tcArchs);
1215 // Pass down CMAKE_EXPERIMENTAL_* feature flags
1216 for (std::size_t i = 0;
1217 i < static_cast<std::size_t>(cmExperimental::Feature::Sentinel);
1218 i++) {
1219 auto const& data = cmExperimental::DataForFeature(
1220 static_cast<cmExperimental::Feature>(i));
1221 if (data.ForwardThroughTryCompile ==
1222 cmExperimental::TryCompileCondition::Always ||
1223 (data.ForwardThroughTryCompile ==
1224 cmExperimental::TryCompileCondition::SkipCompilerChecks &&
1225 arguments.CMakeInternal != "ABI"_s &&
1226 arguments.CMakeInternal != "FEATURE_TESTING"_s)) {
1227 vars.insert(data.Variable);
1228 for (auto const& var : data.TryCompileVariables) {
1229 vars.insert(var);
1234 for (std::string const& var : vars) {
1235 if (cmValue val = this->Makefile->GetDefinition(var)) {
1236 std::string flag = cmStrCat("-D", var, '=', *val);
1237 arguments.CMakeFlags.emplace_back(std::move(flag));
1238 cmakeVariables.emplace(var, *val);
1243 if (!this->SrcFileSignature &&
1244 this->Makefile->GetState()->GetGlobalPropertyAsBool(
1245 "PROPAGATE_TOP_LEVEL_INCLUDES_TO_TRY_COMPILE")) {
1246 const std::string var = "CMAKE_PROJECT_TOP_LEVEL_INCLUDES";
1247 if (cmValue val = this->Makefile->GetDefinition(var)) {
1248 std::string flag = cmStrCat("-D", var, "=\'", *val, '\'');
1249 arguments.CMakeFlags.emplace_back(std::move(flag));
1250 cmakeVariables.emplace(var, *val);
1254 if (this->Makefile->GetState()->UseGhsMultiIDE()) {
1255 // Forward the GHS variables to the inner project cache.
1256 for (std::string const& var : ghs_platform_vars) {
1257 if (cmValue val = this->Makefile->GetDefinition(var)) {
1258 std::string flag = cmStrCat("-D", var, "=\'", *val, '\'');
1259 arguments.CMakeFlags.emplace_back(std::move(flag));
1260 cmakeVariables.emplace(var, *val);
1265 if (this->Makefile->GetCMakeInstance()->GetDebugTryCompile()) {
1266 auto msg =
1267 cmStrCat("Executing try_compile (", *arguments.CompileResultVariable,
1268 ") in:\n ", this->BinaryDirectory);
1269 this->Makefile->IssueMessage(MessageType::LOG, msg);
1272 bool erroroc = cmSystemTools::GetErrorOccurredFlag();
1273 cmSystemTools::ResetErrorOccurredFlag();
1274 std::string output;
1275 // actually do the try compile now that everything is setup
1276 int res = this->Makefile->TryCompile(
1277 sourceDirectory, this->BinaryDirectory, projectName, targetName,
1278 this->SrcFileSignature, cmake::NO_BUILD_PARALLEL_LEVEL,
1279 &arguments.CMakeFlags, output);
1280 if (erroroc) {
1281 cmSystemTools::SetErrorOccurred();
1284 // set the result var to the return value to indicate success or failure
1285 if (arguments.NoCache) {
1286 this->Makefile->AddDefinition(*arguments.CompileResultVariable,
1287 (res == 0 ? "TRUE" : "FALSE"));
1288 } else {
1289 this->Makefile->AddCacheDefinition(
1290 *arguments.CompileResultVariable, (res == 0 ? "TRUE" : "FALSE"),
1291 "Result of TRY_COMPILE", cmStateEnums::INTERNAL);
1294 if (arguments.OutputVariable) {
1295 this->Makefile->AddDefinition(*arguments.OutputVariable, output);
1298 if (this->SrcFileSignature) {
1299 std::string copyFileErrorMessage;
1300 this->FindOutputFile(targetName);
1302 if ((res == 0) && arguments.CopyFileTo) {
1303 std::string const& copyFile = *arguments.CopyFileTo;
1304 cmsys::SystemTools::CopyStatus status =
1305 cmSystemTools::CopyFileAlways(this->OutputFile, copyFile);
1306 if (!status) {
1307 std::string err = status.GetString();
1308 switch (status.Path) {
1309 case cmsys::SystemTools::CopyStatus::SourcePath:
1310 err = cmStrCat(err, " (input)");
1311 break;
1312 case cmsys::SystemTools::CopyStatus::DestPath:
1313 err = cmStrCat(err, " (output)");
1314 break;
1315 default:
1316 break;
1318 /* clang-format off */
1319 err = cmStrCat(
1320 "Cannot copy output executable\n"
1321 " '", this->OutputFile, "'\n"
1322 "to destination specified by COPY_FILE:\n"
1323 " '", copyFile, "'\n"
1324 "because:\n"
1325 " ", err, "\n",
1326 this->FindErrorMessage);
1327 /* clang-format on */
1328 if (!arguments.CopyFileError) {
1329 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, err);
1330 return cm::nullopt;
1332 copyFileErrorMessage = std::move(err);
1336 if (arguments.CopyFileError) {
1337 std::string const& copyFileError = *arguments.CopyFileError;
1338 this->Makefile->AddDefinition(copyFileError, copyFileErrorMessage);
1342 cmTryCompileResult result;
1343 if (arguments.LogDescription) {
1344 result.LogDescription = *arguments.LogDescription;
1346 result.CMakeVariables = std::move(cmakeVariables);
1347 result.SourceDirectory = sourceDirectory;
1348 result.BinaryDirectory = this->BinaryDirectory;
1349 result.Variable = *arguments.CompileResultVariable;
1350 result.VariableCached = !arguments.NoCache;
1351 result.Output = std::move(output);
1352 result.ExitCode = res;
1353 return cm::optional<cmTryCompileResult>(std::move(result));
1356 bool cmCoreTryCompile::IsTemporary(std::string const& path)
1358 return ((path.find("CMakeTmp") != std::string::npos) ||
1359 (path.find("CMakeScratch") != std::string::npos));
1362 void cmCoreTryCompile::CleanupFiles(std::string const& binDir)
1364 if (binDir.empty()) {
1365 return;
1368 if (!IsTemporary(binDir)) {
1369 cmSystemTools::Error(cmStrCat(
1370 "TRY_COMPILE attempt to remove -rf directory that does not contain "
1371 "CMakeTmp or CMakeScratch: \"",
1372 binDir, '"'));
1373 return;
1376 cmsys::Directory dir;
1377 dir.Load(binDir);
1378 std::set<std::string> deletedFiles;
1379 for (unsigned long i = 0; i < dir.GetNumberOfFiles(); ++i) {
1380 const char* fileName = dir.GetFile(i);
1381 if (strcmp(fileName, ".") != 0 && strcmp(fileName, "..") != 0 &&
1382 // Do not delete NFS temporary files.
1383 !cmHasPrefix(fileName, ".nfs")) {
1384 if (deletedFiles.insert(fileName).second) {
1385 std::string const fullPath = cmStrCat(binDir, '/', fileName);
1386 if (cmSystemTools::FileIsSymlink(fullPath)) {
1387 cmSystemTools::RemoveFile(fullPath);
1388 } else if (cmSystemTools::FileIsDirectory(fullPath)) {
1389 this->CleanupFiles(fullPath);
1390 cmSystemTools::RemoveADirectory(fullPath);
1391 } else {
1392 #ifdef _WIN32
1393 // Sometimes anti-virus software hangs on to new files so we
1394 // cannot delete them immediately. Try a few times.
1395 cmSystemTools::WindowsFileRetry retry =
1396 cmSystemTools::GetWindowsFileRetry();
1397 cmsys::Status status;
1398 while (!((status = cmSystemTools::RemoveFile(fullPath))) &&
1399 --retry.Count && cmSystemTools::FileExists(fullPath)) {
1400 cmSystemTools::Delay(retry.Delay);
1402 if (retry.Count == 0)
1403 #else
1404 cmsys::Status status = cmSystemTools::RemoveFile(fullPath);
1405 if (!status)
1406 #endif
1408 this->Makefile->IssueMessage(
1409 MessageType::FATAL_ERROR,
1410 cmStrCat("The file:\n ", fullPath,
1411 "\ncould not be removed:\n ", status.GetString()));
1418 if (binDir.find("CMakeScratch") != std::string::npos) {
1419 cmSystemTools::RemoveADirectory(binDir);
1423 void cmCoreTryCompile::FindOutputFile(const std::string& targetName)
1425 this->FindErrorMessage.clear();
1426 this->OutputFile.clear();
1427 std::string tmpOutputFile = "/";
1428 tmpOutputFile += targetName;
1430 if (this->Makefile->GetGlobalGenerator()->IsMultiConfig()) {
1431 std::string const tcConfig =
1432 this->Makefile->GetSafeDefinition("CMAKE_TRY_COMPILE_CONFIGURATION");
1433 std::string const cfg = !tcConfig.empty()
1434 ? cmSystemTools::UpperCase(tcConfig)
1435 : TryCompileDefaultConfig;
1436 tmpOutputFile = cmStrCat(tmpOutputFile, '_', cfg);
1438 tmpOutputFile += "_loc";
1440 std::string command = cmStrCat(this->BinaryDirectory, tmpOutputFile);
1441 if (!cmSystemTools::FileExists(command)) {
1442 std::ostringstream emsg;
1443 emsg << "Unable to find the recorded try_compile output location:\n";
1444 emsg << cmStrCat(" ", command, "\n");
1445 this->FindErrorMessage = emsg.str();
1446 return;
1449 std::string outputFileLocation;
1450 cmsys::ifstream ifs(command.c_str());
1451 cmSystemTools::GetLineFromStream(ifs, outputFileLocation);
1452 if (!cmSystemTools::FileExists(outputFileLocation)) {
1453 std::ostringstream emsg;
1454 emsg << "Recorded try_compile output location doesn't exist:\n";
1455 emsg << cmStrCat(" ", outputFileLocation, "\n");
1456 this->FindErrorMessage = emsg.str();
1457 return;
1460 this->OutputFile = cmSystemTools::CollapseFullPath(outputFileLocation);
1463 std::string cmCoreTryCompile::WriteSource(std::string const& filename,
1464 std::string const& content,
1465 char const* command) const
1467 if (!cmSystemTools::GetFilenamePath(filename).empty()) {
1468 const auto& msg =
1469 cmStrCat(command, " given invalid filename \"", filename, '"');
1470 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
1471 return {};
1474 auto filepath = cmStrCat(this->BinaryDirectory, '/', filename);
1475 cmsys::ofstream file{ filepath.c_str(), std::ios::out };
1476 if (!file) {
1477 const auto& msg =
1478 cmStrCat(command, " failed to open \"", filename, "\" for writing");
1479 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
1480 return {};
1483 file << content;
1484 if (!file) {
1485 const auto& msg = cmStrCat(command, " failed to write \"", filename, '"');
1486 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
1487 return {};
1490 file.close();
1491 return filepath;
1494 void cmCoreTryCompile::WriteTryCompileEventFields(
1495 cmConfigureLog& log, cmTryCompileResult const& compileResult)
1497 #ifndef CMAKE_BOOTSTRAP
1498 if (compileResult.LogDescription) {
1499 log.WriteValue("description"_s, *compileResult.LogDescription);
1501 log.BeginObject("directories"_s);
1502 log.WriteValue("source"_s, compileResult.SourceDirectory);
1503 log.WriteValue("binary"_s, compileResult.BinaryDirectory);
1504 log.EndObject();
1505 if (!compileResult.CMakeVariables.empty()) {
1506 log.WriteValue("cmakeVariables"_s, compileResult.CMakeVariables);
1508 log.BeginObject("buildResult"_s);
1509 log.WriteValue("variable"_s, compileResult.Variable);
1510 log.WriteValue("cached"_s, compileResult.VariableCached);
1511 log.WriteLiteralTextBlock("stdout"_s, compileResult.Output);
1512 log.WriteValue("exitCode"_s, compileResult.ExitCode);
1513 log.EndObject();
1514 #endif