/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ /* clang-format off */ #include "cmGeneratorTarget.h" /* clang-format on */ #include "cmConfigure.h" #include #include #include #include #include #include #include #include #include #include #include #include "cmEvaluatedTargetProperty.h" #include "cmGeneratorExpressionDAGChecker.h" #include "cmGlobalGenerator.h" #include "cmList.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" #include "cmake.h" enum class OptionsParse { None, Shell }; namespace { const auto DL_BEGIN = ""_s; const auto DL_END = ""_s; void processOptions(cmGeneratorTarget const* tgt, EvaluatedTargetPropertyEntries const& entries, std::vector>& options, std::unordered_set& uniqueOptions, bool debugOptions, const char* logName, OptionsParse parse, bool processDeviceOptions = false) { bool splitOption = !processDeviceOptions; for (EvaluatedTargetPropertyEntry const& entry : entries.Entries) { std::string usedOptions; for (std::string const& opt : entry.Values) { if (processDeviceOptions && (opt == DL_BEGIN || opt == DL_END)) { options.emplace_back(opt, entry.Backtrace); splitOption = opt == DL_BEGIN; continue; } if (uniqueOptions.insert(opt).second) { if (parse == OptionsParse::Shell && cmHasLiteralPrefix(opt, "SHELL:")) { if (splitOption) { std::vector tmp; cmSystemTools::ParseUnixCommandLine(opt.c_str() + 6, tmp); for (std::string& o : tmp) { options.emplace_back(std::move(o), entry.Backtrace); } } else { options.emplace_back(std::string(opt.c_str() + 6), entry.Backtrace); } } else { options.emplace_back(opt, entry.Backtrace); } if (debugOptions) { usedOptions += " * " + opt + "\n"; } } } if (!usedOptions.empty()) { tgt->GetLocalGenerator()->GetCMakeInstance()->IssueMessage( MessageType::LOG, std::string("Used ") + logName + std::string(" for target ") + tgt->GetName() + ":\n" + usedOptions, entry.Backtrace); } } } enum class NestedLinkerFlags { PreserveAsSpelled, Normalize, }; std::vector> wrapOptions( std::vector& options, const cmListFileBacktrace& bt, const std::vector& wrapperFlag, const std::string& wrapperSep, bool concatFlagAndArgs, NestedLinkerFlags nestedLinkerFlags) { std::vector> result; if (options.empty()) { return result; } if (wrapperFlag.empty()) { // nothing specified, insert elements as is result.reserve(options.size()); for (std::string& o : options) { result.emplace_back(std::move(o), bt); } return result; } auto insertWrapped = [&](std::vector& opts) { if (!wrapperSep.empty()) { if (concatFlagAndArgs) { // insert flag elements except last one for (auto i = wrapperFlag.begin(); i != wrapperFlag.end() - 1; ++i) { result.emplace_back(*i, bt); } // concatenate last flag element and all list values // in one option result.emplace_back(wrapperFlag.back() + cmJoin(opts, wrapperSep), bt); } else { for (std::string const& i : wrapperFlag) { result.emplace_back(i, bt); } // concatenate all list values in one option result.emplace_back(cmJoin(opts, wrapperSep), bt); } } else { // prefix each element of list with wrapper if (concatFlagAndArgs) { std::transform(opts.begin(), opts.end(), opts.begin(), [&wrapperFlag](std::string const& o) -> std::string { return wrapperFlag.back() + o; }); } for (std::string& o : opts) { for (auto i = wrapperFlag.begin(), e = concatFlagAndArgs ? wrapperFlag.end() - 1 : wrapperFlag.end(); i != e; ++i) { result.emplace_back(*i, bt); } result.emplace_back(std::move(o), bt); } } }; if (nestedLinkerFlags == NestedLinkerFlags::PreserveAsSpelled) { insertWrapped(options); return result; } for (std::vector::size_type index = 0; index < options.size(); index++) { if (cmHasLiteralPrefix(options[index], "LINKER:")) { // LINKER wrapper specified, insert elements as is result.emplace_back(std::move(options[index]), bt); continue; } if (cmHasLiteralPrefix(options[index], "-Wl,")) { // replace option by LINKER wrapper result.emplace_back(options[index].replace(0, 4, "LINKER:"), bt); continue; } if (cmHasLiteralPrefix(options[index], "-Xlinker=")) { // replace option by LINKER wrapper result.emplace_back(options[index].replace(0, 9, "LINKER:"), bt); continue; } if (options[index] == "-Xlinker") { // replace option by LINKER wrapper if (index + 1 < options.size()) { result.emplace_back("LINKER:" + options[++index], bt); } else { result.emplace_back(std::move(options[index]), bt); } continue; } // collect all options which must be transformed std::vector opts; while (index < options.size()) { if (!cmHasLiteralPrefix(options[index], "LINKER:") && !cmHasLiteralPrefix(options[index], "-Wl,") && !cmHasLiteralPrefix(options[index], "-Xlinker")) { opts.emplace_back(std::move(options[index++])); } else { --index; break; } } if (opts.empty()) { continue; } insertWrapped(opts); } return result; } } void cmGeneratorTarget::GetCompileOptions(std::vector& result, const std::string& config, const std::string& language) const { std::vector> tmp = this->GetCompileOptions(config, language); result.reserve(tmp.size()); for (BT& v : tmp) { result.emplace_back(std::move(v.Value)); } } std::vector> cmGeneratorTarget::GetCompileOptions( std::string const& config, std::string const& language) const { ConfigAndLanguage cacheKey(config, language); { auto it = this->CompileOptionsCache.find(cacheKey); if (it != this->CompileOptionsCache.end()) { return it->second; } } std::vector> result; std::unordered_set uniqueOptions; cmGeneratorExpressionDAGChecker dagChecker( this, "COMPILE_OPTIONS", nullptr, nullptr, this->LocalGenerator, config); cmList debugProperties{ this->Makefile->GetDefinition( "CMAKE_DEBUG_TARGET_PROPERTIES") }; bool debugOptions = !this->DebugCompileOptionsDone && cm::contains(debugProperties, "COMPILE_OPTIONS"); if (this->GlobalGenerator->GetConfigureDoneCMP0026()) { this->DebugCompileOptionsDone = true; } EvaluatedTargetPropertyEntries entries = EvaluateTargetPropertyEntries( this, config, language, &dagChecker, this->CompileOptionsEntries); AddInterfaceEntries(this, config, "INTERFACE_COMPILE_OPTIONS", language, &dagChecker, entries, IncludeRuntimeInterface::Yes); processOptions(this, entries, result, uniqueOptions, debugOptions, "compile options", OptionsParse::Shell); CompileOptionsCache.emplace(cacheKey, result); return result; } void cmGeneratorTarget::GetCompileFeatures(std::vector& result, const std::string& config) const { std::vector> tmp = this->GetCompileFeatures(config); result.reserve(tmp.size()); for (BT& v : tmp) { result.emplace_back(std::move(v.Value)); } } std::vector> cmGeneratorTarget::GetCompileFeatures( std::string const& config) const { std::vector> result; std::unordered_set uniqueFeatures; cmGeneratorExpressionDAGChecker dagChecker( this, "COMPILE_FEATURES", nullptr, nullptr, this->LocalGenerator, config); cmList debugProperties{ this->Makefile->GetDefinition( "CMAKE_DEBUG_TARGET_PROPERTIES") }; bool debugFeatures = !this->DebugCompileFeaturesDone && cm::contains(debugProperties, "COMPILE_FEATURES"); if (this->GlobalGenerator->GetConfigureDoneCMP0026()) { this->DebugCompileFeaturesDone = true; } EvaluatedTargetPropertyEntries entries = EvaluateTargetPropertyEntries( this, config, std::string(), &dagChecker, this->CompileFeaturesEntries); AddInterfaceEntries(this, config, "INTERFACE_COMPILE_FEATURES", std::string(), &dagChecker, entries, IncludeRuntimeInterface::Yes); processOptions(this, entries, result, uniqueFeatures, debugFeatures, "compile features", OptionsParse::None); return result; } void cmGeneratorTarget::GetCompileDefinitions( std::vector& result, const std::string& config, const std::string& language) const { std::vector> tmp = this->GetCompileDefinitions(config, language); result.reserve(tmp.size()); for (BT& v : tmp) { result.emplace_back(std::move(v.Value)); } } std::vector> cmGeneratorTarget::GetCompileDefinitions( std::string const& config, std::string const& language) const { ConfigAndLanguage cacheKey(config, language); { auto it = this->CompileDefinitionsCache.find(cacheKey); if (it != this->CompileDefinitionsCache.end()) { return it->second; } } std::vector> list; std::unordered_set uniqueOptions; cmGeneratorExpressionDAGChecker dagChecker(this, "COMPILE_DEFINITIONS", nullptr, nullptr, this->LocalGenerator, config); cmList debugProperties{ this->Makefile->GetDefinition( "CMAKE_DEBUG_TARGET_PROPERTIES") }; bool debugDefines = !this->DebugCompileDefinitionsDone && cm::contains(debugProperties, "COMPILE_DEFINITIONS"); if (this->GlobalGenerator->GetConfigureDoneCMP0026()) { this->DebugCompileDefinitionsDone = true; } EvaluatedTargetPropertyEntries entries = EvaluateTargetPropertyEntries( this, config, language, &dagChecker, this->CompileDefinitionsEntries); AddInterfaceEntries(this, config, "INTERFACE_COMPILE_DEFINITIONS", language, &dagChecker, entries, IncludeRuntimeInterface::Yes); if (!config.empty()) { std::string configPropName = "COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(config); cmValue configProp = this->GetProperty(configPropName); if (configProp) { switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0043)) { case cmPolicies::WARN: { this->LocalGenerator->IssueMessage( MessageType::AUTHOR_WARNING, cmPolicies::GetPolicyWarning(cmPolicies::CMP0043)); CM_FALLTHROUGH; } case cmPolicies::OLD: { std::unique_ptr entry = TargetPropertyEntry::Create( *this->LocalGenerator->GetCMakeInstance(), *configProp); entries.Entries.emplace_back(EvaluateTargetPropertyEntry( this, config, language, &dagChecker, *entry)); } break; case cmPolicies::NEW: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::REQUIRED_IF_USED: break; } } } processOptions(this, entries, list, uniqueOptions, debugDefines, "compile definitions", OptionsParse::None); this->CompileDefinitionsCache.emplace(cacheKey, list); return list; } std::vector> cmGeneratorTarget::GetPrecompileHeaders( const std::string& config, const std::string& language) const { ConfigAndLanguage cacheKey(config, language); { auto it = this->PrecompileHeadersCache.find(cacheKey); if (it != this->PrecompileHeadersCache.end()) { return it->second; } } std::unordered_set uniqueOptions; cmGeneratorExpressionDAGChecker dagChecker(this, "PRECOMPILE_HEADERS", nullptr, nullptr, this->LocalGenerator, config); cmList debugProperties{ this->Makefile->GetDefinition( "CMAKE_DEBUG_TARGET_PROPERTIES") }; bool debugDefines = !this->DebugPrecompileHeadersDone && std::find(debugProperties.begin(), debugProperties.end(), "PRECOMPILE_HEADERS") != debugProperties.end(); if (this->GlobalGenerator->GetConfigureDoneCMP0026()) { this->DebugPrecompileHeadersDone = true; } EvaluatedTargetPropertyEntries entries = EvaluateTargetPropertyEntries( this, config, language, &dagChecker, this->PrecompileHeadersEntries); AddInterfaceEntries(this, config, "INTERFACE_PRECOMPILE_HEADERS", language, &dagChecker, entries, IncludeRuntimeInterface::Yes); std::vector> list; processOptions(this, entries, list, uniqueOptions, debugDefines, "precompile headers", OptionsParse::None); this->PrecompileHeadersCache.emplace(cacheKey, list); return list; } void cmGeneratorTarget::GetLinkOptions(std::vector& result, const std::string& config, const std::string& language) const { if (this->IsDeviceLink() && this->GetPolicyStatusCMP0105() != cmPolicies::NEW) { // link options are not propagated to the device link step return; } std::vector> tmp = this->GetLinkOptions(config, language); result.reserve(tmp.size()); for (BT& v : tmp) { result.emplace_back(std::move(v.Value)); } } std::vector> cmGeneratorTarget::GetLinkOptions( std::string const& config, std::string const& language) const { ConfigAndLanguage cacheKey( config, cmStrCat(language, this->IsDeviceLink() ? "-device" : "")); { auto it = this->LinkOptionsCache.find(cacheKey); if (it != this->LinkOptionsCache.end()) { return it->second; } } std::vector> result; std::unordered_set uniqueOptions; cmGeneratorExpressionDAGChecker dagChecker( this, "LINK_OPTIONS", nullptr, nullptr, this->LocalGenerator, config); cmList debugProperties{ this->Makefile->GetDefinition( "CMAKE_DEBUG_TARGET_PROPERTIES") }; bool debugOptions = !this->DebugLinkOptionsDone && cm::contains(debugProperties, "LINK_OPTIONS"); if (this->GlobalGenerator->GetConfigureDoneCMP0026()) { this->DebugLinkOptionsDone = true; } EvaluatedTargetPropertyEntries entries = EvaluateTargetPropertyEntries( this, config, language, &dagChecker, this->LinkOptionsEntries); AddInterfaceEntries(this, config, "INTERFACE_LINK_OPTIONS", language, &dagChecker, entries, IncludeRuntimeInterface::Yes, this->GetPolicyStatusCMP0099() == cmPolicies::NEW ? UseTo::Link : UseTo::Compile); processOptions(this, entries, result, uniqueOptions, debugOptions, "link options", OptionsParse::Shell, this->IsDeviceLink()); if (this->IsDeviceLink()) { // wrap host link options const std::string wrapper(this->Makefile->GetSafeDefinition( "CMAKE_" + language + "_DEVICE_COMPILER_WRAPPER_FLAG")); cmList wrapperFlag{ wrapper }; const std::string wrapperSep(this->Makefile->GetSafeDefinition( "CMAKE_" + language + "_DEVICE_COMPILER_WRAPPER_FLAG_SEP")); bool concatFlagAndArgs = true; if (!wrapperFlag.empty() && wrapperFlag.back() == " ") { concatFlagAndArgs = false; wrapperFlag.pop_back(); } auto it = result.begin(); while (it != result.end()) { if (it->Value == DL_BEGIN) { // device link options, no treatment it = result.erase(it); it = std::find_if(it, result.end(), [](const BT& item) { return item.Value == DL_END; }); if (it != result.end()) { it = result.erase(it); } } else { // host link options must be wrapped std::vector options; cmSystemTools::ParseUnixCommandLine(it->Value.c_str(), options); auto hostOptions = wrapOptions(options, it->Backtrace, wrapperFlag, wrapperSep, concatFlagAndArgs, NestedLinkerFlags::Normalize); it = result.erase(it); // some compilers (like gcc 4.8 or Intel 19.0 or XLC 16) do not respect // C++11 standard: 'std::vector::insert()' do not returns an iterator, // so need to recompute the iterator after insertion. if (it == result.end()) { cm::append(result, hostOptions); it = result.end(); } else { auto index = it - result.begin(); result.insert(it, hostOptions.begin(), hostOptions.end()); it = result.begin() + index + hostOptions.size(); } } } } // Last step: replace "LINKER:" prefixed elements by // actual linker wrapper result = this->ResolveLinkerWrapper(result, language); this->LinkOptionsCache.emplace(cacheKey, result); return result; } std::vector>& cmGeneratorTarget::ResolveLinkerWrapper( std::vector>& result, const std::string& language, bool joinItems) const { // replace "LINKER:" prefixed elements by actual linker wrapper const std::string wrapper(this->Makefile->GetSafeDefinition( "CMAKE_" + language + (this->IsDeviceLink() ? "_DEVICE_LINKER_WRAPPER_FLAG" : "_LINKER_WRAPPER_FLAG"))); cmList wrapperFlag{ wrapper }; const std::string wrapperSep(this->Makefile->GetSafeDefinition( "CMAKE_" + language + (this->IsDeviceLink() ? "_DEVICE_LINKER_WRAPPER_FLAG_SEP" : "_LINKER_WRAPPER_FLAG_SEP"))); bool concatFlagAndArgs = true; if (!wrapperFlag.empty() && wrapperFlag.back() == " ") { concatFlagAndArgs = false; wrapperFlag.pop_back(); } const std::string LINKER{ "LINKER:" }; const std::string SHELL{ "SHELL:" }; const std::string LINKER_SHELL = LINKER + SHELL; for (auto entry = result.begin(); entry != result.end();) { if (entry->Value.compare(0, LINKER.length(), LINKER) != 0) { ++entry; continue; } std::string value = std::move(entry->Value); cmListFileBacktrace bt = std::move(entry->Backtrace); entry = result.erase(entry); std::vector linkerOptions; if (value.compare(0, LINKER_SHELL.length(), LINKER_SHELL) == 0) { cmSystemTools::ParseUnixCommandLine( value.c_str() + LINKER_SHELL.length(), linkerOptions); } else { linkerOptions = cmTokenize(value.substr(LINKER.length()), ","); } if (linkerOptions.empty() || (linkerOptions.size() == 1 && linkerOptions.front().empty())) { continue; } // for now, raise an error if prefix SHELL: is part of arguments if (std::find_if(linkerOptions.begin(), linkerOptions.end(), [&SHELL](const std::string& item) -> bool { return item.find(SHELL) != std::string::npos; }) != linkerOptions.end()) { this->LocalGenerator->GetCMakeInstance()->IssueMessage( MessageType::FATAL_ERROR, "'SHELL:' prefix is not supported as part of 'LINKER:' arguments.", this->GetBacktrace()); return result; } // Very old versions of the C++ standard library return void for insert, so // can't use it to get the new iterator const auto index = entry - result.begin(); std::vector> options = wrapOptions(linkerOptions, bt, wrapperFlag, wrapperSep, concatFlagAndArgs, NestedLinkerFlags::PreserveAsSpelled); if (joinItems) { result.insert( entry, cmJoin(cmMakeRange(options.begin(), options.end()), " "_s)); entry = std::next(result.begin(), index + 1); } else { result.insert(entry, options.begin(), options.end()); entry = std::next(result.begin(), index + options.size()); } } return result; } void cmGeneratorTarget::GetStaticLibraryLinkOptions( std::vector& result, const std::string& config, const std::string& language) const { std::vector> tmp = this->GetStaticLibraryLinkOptions(config, language); result.reserve(tmp.size()); for (BT& v : tmp) { result.emplace_back(std::move(v.Value)); } } std::vector> cmGeneratorTarget::GetStaticLibraryLinkOptions( std::string const& config, std::string const& language) const { std::vector> result; std::unordered_set uniqueOptions; cmGeneratorExpressionDAGChecker dagChecker(this, "STATIC_LIBRARY_OPTIONS", nullptr, nullptr, this->LocalGenerator, config); EvaluatedTargetPropertyEntries entries; if (cmValue linkOptions = this->GetProperty("STATIC_LIBRARY_OPTIONS")) { std::unique_ptr entry = TargetPropertyEntry::Create( *this->LocalGenerator->GetCMakeInstance(), *linkOptions); entries.Entries.emplace_back(EvaluateTargetPropertyEntry( this, config, language, &dagChecker, *entry)); } processOptions(this, entries, result, uniqueOptions, false, "static library link options", OptionsParse::Shell); return result; } void cmGeneratorTarget::GetLinkDepends(std::vector& result, const std::string& config, const std::string& language) const { std::vector> tmp = this->GetLinkDepends(config, language); result.reserve(tmp.size()); for (BT& v : tmp) { result.emplace_back(std::move(v.Value)); } } std::vector> cmGeneratorTarget::GetLinkDepends( std::string const& config, std::string const& language) const { std::vector> result; std::unordered_set uniqueOptions; cmGeneratorExpressionDAGChecker dagChecker( this, "LINK_DEPENDS", nullptr, nullptr, this->LocalGenerator, config); EvaluatedTargetPropertyEntries entries; if (cmValue linkDepends = this->GetProperty("LINK_DEPENDS")) { cmList depends{ *linkDepends }; for (const auto& depend : depends) { std::unique_ptr entry = TargetPropertyEntry::Create( *this->LocalGenerator->GetCMakeInstance(), depend); entries.Entries.emplace_back(EvaluateTargetPropertyEntry( this, config, language, &dagChecker, *entry)); } } AddInterfaceEntries(this, config, "INTERFACE_LINK_DEPENDS", language, &dagChecker, entries, IncludeRuntimeInterface::Yes, this->GetPolicyStatusCMP0099() == cmPolicies::NEW ? UseTo::Link : UseTo::Compile); processOptions(this, entries, result, uniqueOptions, false, "link depends", OptionsParse::None); return result; }