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"
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"
25 #include "cmMakefile.h"
26 #include "cmMessageType.h"
27 #include "cmOutputConverter.h"
28 #include "cmPolicies.h"
31 #include "cmStringAlgorithms.h"
32 #include "cmSystemTools.h"
35 #include "cmVersion.h"
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", "" }
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
);
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",
120 using Arguments
= cmCoreTryCompile::Arguments
;
122 ArgumentParser::Continue
TryCompileLangProp(Arguments
& args
,
126 args
.LangProps
[std::string(key
)] = std::string(val
);
127 return ArgumentParser::Continue::No
;
130 ArgumentParser::Continue
TryCompileCompileDefs(Arguments
& args
,
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
)
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
;
241 } else if (sourceType
== "CXX_MODULE"_s
) {
242 this->SourceTypeContext
= SourceType::CxxModule
;
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
);
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).
283 this->ParseArgs(args
, TryCompileProjectArgParser
, unparsedArguments
);
284 if (!arguments
.BinaryDirectory
) {
285 arguments
.BinaryDirectory
= unique_binary_directory
;
290 if (cmHasLiteralPrefix(second
, "SOURCE")) {
291 // New SOURCES signature.
292 auto arguments
= this->ParseArgs(
293 args
, isTryRun
? TryRunSourcesArgParser
: TryCompileSourcesArgParser
,
295 arguments
.BinaryDirectory
= unique_binary_directory
;
300 auto arguments
= this->ParseArgs(
301 args
, isTryRun
? TryRunOldArgParser
: TryCompileOldArgParser
,
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
;
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
;
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.");
351 sourceDirectory
= *arguments
.SourceDirectoryOrFile
;
352 projectName
= *arguments
.ProjectName
;
353 if (arguments
.TargetName
) {
354 targetName
= *arguments
.TargetName
;
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.");
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;
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
, '\''));
384 this->BinaryDirectory
= *arguments
.BinaryDirectory
;
385 // compute the binary dir when TRY_COMPILE is called with a src file
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
:
402 case cmStateEnums::EXECUTABLE
:
403 if (tgt
->IsExecutableWithExports()) {
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()), '.'));
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");
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");
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");
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");
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");
458 if (arguments
.SourceFromVar
&& arguments
.SourceFromVar
->size() % 2) {
459 this->Makefile
->IssueMessage(
460 MessageType::FATAL_ERROR
,
461 "SOURCE_FROM_VAR requires exactly two arguments");
464 if (arguments
.SourceFromFile
&& arguments
.SourceFromFile
->size() % 2) {
465 this->Makefile
->IssueMessage(
466 MessageType::FATAL_ERROR
,
467 "SOURCE_FROM_FILE requires exactly two arguments");
470 if (!arguments
.SourceTypeError
.empty()) {
471 this->Makefile
->IssueMessage(MessageType::FATAL_ERROR
,
472 arguments
.SourceTypeError
);
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"));
484 if (!arguments
.CompileDefs
.empty()) {
485 this->Makefile
->IssueMessage(
486 MessageType::FATAL_ERROR
,
487 "COMPILE_DEFINITIONS allowed only in source file signature");
490 if (arguments
.CopyFileTo
) {
491 this->Makefile
->IssueMessage(
492 MessageType::FATAL_ERROR
,
493 "COPY_FILE allowed only in source file signature");
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
);
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());
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
);
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");
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");
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()) {
569 cmStrCat("SOURCE_FROM_FILE given invalid filename \"", dst
, '"');
570 this->Makefile
->IssueMessage(MessageType::FATAL_ERROR
, msg
);
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
);
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());
597 testLangs
.insert(lang
);
599 std::ostringstream err
;
600 err
<< "Unknown extension \"" << ext
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());
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");
632 this->Makefile
->IssueMessage(
633 MessageType::FATAL_ERROR
,
634 cmStrCat("Failed to open\n"
636 outFileName
, '\n', cmSystemTools::GetLastSystemError()));
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());
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
) !=
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
) !=
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
) !=
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.
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",
733 fprintf(fout
, "set(CMAKE_%s_USE_RESPONSE_FILE_FOR_LIBRARIES OFF)\n",
735 fprintf(fout
, "set(CMAKE_%s_USE_RESPONSE_FILE_FOR_OBJECTS OFF)\n",
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());
747 "set(CMAKE_%s_FLAGS \"${CMAKE_%s_FLAGS}"
748 " ${COMPILE_DEFINITIONS}\")\n",
749 li
.c_str(), li
.c_str());
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());
769 case cmPolicies::OLD
:
770 // OLD behavior is to do nothing.
772 case cmPolicies::REQUIRED_IF_USED
:
773 case cmPolicies::REQUIRED_ALWAYS
:
774 this->Makefile
->IssueMessage(
775 MessageType::FATAL_ERROR
,
776 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0066
));
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());
790 cmakeVariables
.emplace(langFlagsCfg
, *flagsCfg
);
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());
810 case cmPolicies::OLD
:
811 // OLD behavior is to do nothing.
813 case cmPolicies::REQUIRED_IF_USED
:
814 case cmPolicies::REQUIRED_ALWAYS
:
815 this->Makefile
->IssueMessage(
816 MessageType::FATAL_ERROR
,
817 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0056
));
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());
827 cmakeVariables
.emplace("CMAKE_EXE_LINKER_FLAGS", *exeLinkFlags
);
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
,
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.");
858 fprintf(fout
, "\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/%s\")\n",
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());
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());
884 /* Set the appropriate policy information for ENABLE_EXPORTS */
885 fprintf(fout
, "cmake_policy(SET CMP0065 %s)\n",
886 this->Makefile
->GetPolicyStatus(cmPolicies::CMP0065
) ==
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
) ==
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
) ==
905 /* Set the appropriate policy information for Swift compilation mode */
907 fout
, "cmake_policy(SET CMP0157 %s)\n",
908 this->Makefile
->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT")
913 // Workaround for -Wl,-headerpad_max_install_names issue until we can avoid
914 // adding that flag in the platform and compiler language files
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
: {
941 fprintf(fout
, " PRIVATE\n");
945 case Arguments::SourceType::CxxModule
: {
947 file_set_name
+= 'a';
949 " PRIVATE FILE_SET %s TYPE CXX_MODULES BASE_DIRS \"%s\" "
951 file_set_name
.c_str(),
952 this->Makefile
->GetCurrentSourceDirectory().c_str());
956 case Arguments::SourceType::Directory
:
957 /* Handled elsewhere. */
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>>";
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");
989 case cmPolicies::OLD
:
990 // OLD behavior is to not honor the language standard variables.
991 honorStandard
= false;
993 case cmPolicies::REQUIRED_IF_USED
:
994 case cmPolicies::REQUIRED_ALWAYS
:
995 this->Makefile
->IssueMessage(
996 MessageType::FATAL_ERROR
,
997 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0067
));
999 case cmPolicies::NEW
:
1000 // NEW behavior is to honor the language standard variables.
1001 // We already initialized honorStandard to true.
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()) {
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()) {
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()) {
1054 fprintf(fout
, "set_property(TARGET %s PROPERTY %s %s)\n",
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
) {
1069 "set_property(TARGET %s PROPERTY STATIC_LIBRARY_OPTIONS %s)\n",
1070 targetName
.c_str(), cmJoin(options
, " ").c_str());
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());
1098 fprintf(fout
, "target_link_libraries(%s ${LINK_LIBRARIES})\n",
1099 targetName
.c_str());
1104 // Forward a set of variables to the inner project cache.
1105 if ((this->SrcFileSignature
||
1106 this->Makefile
->GetPolicyStatus(cmPolicies::CMP0137
) ==
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
)) {
1176 if (this->Makefile
->GetPolicyStatus(cmPolicies::CMP0083
) ==
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
);
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
) {
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()) {
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();
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
);
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"));
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
);
1307 std::string err
= status
.GetString();
1308 switch (status
.Path
) {
1309 case cmsys::SystemTools::CopyStatus::SourcePath
:
1310 err
= cmStrCat(err
, " (input)");
1312 case cmsys::SystemTools::CopyStatus::DestPath
:
1313 err
= cmStrCat(err
, " (output)");
1318 /* clang-format off */
1320 "Cannot copy output executable\n"
1321 " '", this->OutputFile
, "'\n"
1322 "to destination specified by COPY_FILE:\n"
1323 " '", copyFile
, "'\n"
1326 this->FindErrorMessage
);
1327 /* clang-format on */
1328 if (!arguments
.CopyFileError
) {
1329 this->Makefile
->IssueMessage(MessageType::FATAL_ERROR
, err
);
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()) {
1368 if (!IsTemporary(binDir
)) {
1369 cmSystemTools::Error(cmStrCat(
1370 "TRY_COMPILE attempt to remove -rf directory that does not contain "
1371 "CMakeTmp or CMakeScratch: \"",
1376 cmsys::Directory dir
;
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
);
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)
1404 cmsys::Status status
= cmSystemTools::RemoveFile(fullPath
);
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();
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();
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()) {
1469 cmStrCat(command
, " given invalid filename \"", filename
, '"');
1470 this->Makefile
->IssueMessage(MessageType::FATAL_ERROR
, msg
);
1474 auto filepath
= cmStrCat(this->BinaryDirectory
, '/', filename
);
1475 cmsys::ofstream file
{ filepath
.c_str(), std::ios::out
};
1478 cmStrCat(command
, " failed to open \"", filename
, "\" for writing");
1479 this->Makefile
->IssueMessage(MessageType::FATAL_ERROR
, msg
);
1485 const auto& msg
= cmStrCat(command
, " failed to write \"", filename
, '"');
1486 this->Makefile
->IssueMessage(MessageType::FATAL_ERROR
, msg
);
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
);
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
);