diff options
Diffstat (limited to 'Source')
83 files changed, 3265 insertions, 870 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index ddcdd7e..d6e0096 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -358,6 +358,8 @@ set(SRCS cmLocalCommonGenerator.h cmLocalGenerator.cxx cmLocalGenerator.h + cmPlaceholderExpander.cxx + cmPlaceholderExpander.h cmRulePlaceholderExpander.cxx cmRulePlaceholderExpander.h cmLocalUnixMakefileGenerator3.cxx diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index 6603a56..8487a85 100644 --- a/Source/CMakeVersion.cmake +++ b/Source/CMakeVersion.cmake @@ -1,8 +1,8 @@ # CMake version number components. set(CMake_VERSION_MAJOR 3) set(CMake_VERSION_MINOR 23) -set(CMake_VERSION_PATCH 0) -set(CMake_VERSION_RC 5) +set(CMake_VERSION_PATCH 20220323) +#set(CMake_VERSION_RC 0) set(CMake_VERSION_IS_DIRTY 0) # Start with the full version number used in tags. It has no dev info. diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx index 6bb8e79..2d8276a 100644 --- a/Source/CTest/cmCTestMemCheckHandler.cxx +++ b/Source/CTest/cmCTestMemCheckHandler.cxx @@ -371,7 +371,8 @@ void cmCTestMemCheckHandler::GenerateCTestXML(cmXMLWriter& xml) } this->CleanTestOutput( memcheckstr, - static_cast<size_t>(this->CustomMaximumFailedTestOutputSize)); + static_cast<size_t>(this->CustomMaximumFailedTestOutputSize), + this->TestOutputTruncation); this->WriteTestResultHeader(xml, result); xml.StartElement("Results"); int memoryErrors = 0; diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 6cd3b09..2a2cb1c 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -277,7 +277,8 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started) static_cast<size_t>( this->TestResult.Status == cmCTestTestHandler::COMPLETED ? this->TestHandler->CustomMaximumPassedTestOutputSize - : this->TestHandler->CustomMaximumFailedTestOutputSize)); + : this->TestHandler->CustomMaximumFailedTestOutputSize), + this->TestHandler->TestOutputTruncation); } this->TestResult.Reason = reason; if (this->TestHandler->LogFile) { @@ -694,6 +695,14 @@ void cmCTestRunTest::ComputeArguments() << " command: " << testCommand << std::endl); // Print any test-specific env vars in verbose mode + if (!this->TestProperties->Directory.empty()) { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + this->Index << ": " + << "Working Directory: " + << this->TestProperties->Directory << std::endl); + } + + // Print any test-specific env vars in verbose mode if (!this->TestProperties->Environment.empty()) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index << ": " diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 5a3a8d0..a794e3d 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -281,6 +281,7 @@ cmCTestTestHandler::cmCTestTestHandler() this->CustomMaximumPassedTestOutputSize = 1 * 1024; this->CustomMaximumFailedTestOutputSize = 300 * 1024; + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail; this->MemCheck = false; @@ -325,6 +326,7 @@ void cmCTestTestHandler::Initialize() this->CustomPostTest.clear(); this->CustomMaximumPassedTestOutputSize = 1 * 1024; this->CustomMaximumFailedTestOutputSize = 300 * 1024; + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail; this->TestsToRun.clear(); @@ -358,6 +360,11 @@ void cmCTestTestHandler::PopulateCustomVectors(cmMakefile* mf) this->CTest->PopulateCustomInteger( mf, "CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE", this->CustomMaximumFailedTestOutputSize); + + cmValue dval = mf->GetDefinition("CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION"); + if (dval) { + this->SetTestOutputTruncation(dval); + } } int cmCTestTestHandler::PreProcessHandler() @@ -2076,6 +2083,20 @@ void cmCTestTestHandler::SetExcludeRegExp(const std::string& arg) this->ExcludeRegExp = arg; } +bool cmCTestTestHandler::SetTestOutputTruncation(const std::string& mode) +{ + if (mode == "tail") { + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail; + } else if (mode == "middle") { + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Middle; + } else if (mode == "head") { + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Head; + } else { + return false; + } + return true; +} + void cmCTestTestHandler::SetTestsToRunInformation(cmValue in) { if (!in) { @@ -2094,41 +2115,58 @@ void cmCTestTestHandler::SetTestsToRunInformation(cmValue in) } } -void cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length) +void cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length, + cmCTestTypes::TruncationMode truncate) { if (!length || length >= output.size() || output.find("CTEST_FULL_OUTPUT") != std::string::npos) { return; } - // Truncate at given length but do not break in the middle of a multi-byte - // UTF-8 encoding. + // Advance n bytes in string delimited by begin/end but do not break in the + // middle of a multi-byte UTF-8 encoding. + auto utf8_advance = [](char const* const begin, char const* const end, + size_t n) -> const char* { + char const* const stop = begin + n; + char const* current = begin; + while (current < stop) { + unsigned int ch; + if (const char* next = cm_utf8_decode_character(current, end, &ch)) { + if (next > stop) { + break; + } + current = next; + } else // Bad byte will be handled by cmXMLWriter. + { + ++current; + } + } + return current; + }; + + // Truncation message. + const std::string msg = + "\n[This part of the test output was removed since it " + "exceeds the threshold of " + + std::to_string(length) + " bytes.]\n"; + char const* const begin = output.c_str(); char const* const end = begin + output.size(); - char const* const truncate = begin + length; - char const* current = begin; - while (current < truncate) { - unsigned int ch; - if (const char* next = cm_utf8_decode_character(current, end, &ch)) { - if (next > truncate) { - break; - } - current = next; - } else // Bad byte will be handled by cmXMLWriter. - { - ++current; - } - } - output.erase(current - begin); - - // Append truncation message. - std::ostringstream msg; - msg << "...\n" - "The rest of the test output was removed since it exceeds the " - "threshold " - "of " - << length << " bytes.\n"; - output += msg.str(); + + // Erase head, middle or tail of output. + if (truncate == cmCTestTypes::TruncationMode::Head) { + char const* current = utf8_advance(begin, end, output.size() - length); + output.erase(0, current - begin); + output.insert(0, msg + "..."); + } else if (truncate == cmCTestTypes::TruncationMode::Middle) { + char const* current = utf8_advance(begin, end, length / 2); + output.erase(current - begin, output.size() - length); + output.insert(current - begin, "..." + msg + "..."); + } else { // default or "tail" + char const* current = utf8_advance(begin, end, length); + output.erase(current - begin); + output += ("..." + msg); + } } bool cmCTestTestHandler::SetTestsProperties( diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 135e764..d0049da 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -19,6 +19,7 @@ #include "cmCTest.h" #include "cmCTestGenericHandler.h" #include "cmCTestResourceSpec.h" +#include "cmCTestTypes.h" #include "cmDuration.h" #include "cmListFileCache.h" #include "cmValue.h" @@ -32,6 +33,7 @@ class cmXMLWriter; */ class cmCTestTestHandler : public cmCTestGenericHandler { + friend class cmCTest; friend class cmCTestRunTest; friend class cmCTestMultiProcessHandler; @@ -80,6 +82,9 @@ public: this->CustomMaximumFailedTestOutputSize = n; } + //! Set test output truncation mode. Return false if unknown mode. + bool SetTestOutputTruncation(const std::string& mode); + //! pass the -I argument down void SetTestsToRunInformation(cmValue); @@ -242,8 +247,9 @@ protected: void AttachFile(cmXMLWriter& xml, std::string const& file, std::string const& name); - //! Clean test output to specified length - void CleanTestOutput(std::string& output, size_t length); + //! Clean test output to specified length and truncation mode + void CleanTestOutput(std::string& output, size_t length, + cmCTestTypes::TruncationMode truncate); cmDuration ElapsedTestingTime; @@ -258,6 +264,7 @@ protected: bool MemCheck; int CustomMaximumPassedTestOutputSize; int CustomMaximumFailedTestOutputSize; + cmCTestTypes::TruncationMode TestOutputTruncation; int MaxIndex; public: diff --git a/Source/CTest/cmCTestTypes.h b/Source/CTest/cmCTestTypes.h new file mode 100644 index 0000000..843d27a --- /dev/null +++ b/Source/CTest/cmCTestTypes.h @@ -0,0 +1,16 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +namespace cmCTestTypes { + +enum class TruncationMode +{ // Test output truncation mode + Tail, + Middle, + Head +}; +} diff --git a/Source/CursesDialog/form/frm_driver.c b/Source/CursesDialog/form/frm_driver.c index 112ab08..9cbb12f 100644 --- a/Source/CursesDialog/form/frm_driver.c +++ b/Source/CursesDialog/form/frm_driver.c @@ -1456,7 +1456,7 @@ static int IFN_Beginning_Of_Field(FORM * form) | | Description : Place the cursor after the last non-pad character in | the field. If the field occupies the last position in -| the buffer, the cursos is positioned on the last +| the buffer, the cursor is positioned on the last | character. | | Return Values : E_OK - success diff --git a/Source/cmAddExecutableCommand.cxx b/Source/cmAddExecutableCommand.cxx index 9dd8a19..16a8965 100644 --- a/Source/cmAddExecutableCommand.cxx +++ b/Source/cmAddExecutableCommand.cxx @@ -54,6 +54,10 @@ bool cmAddExecutableCommand(std::vector<std::string> const& args, } } + if (importTarget && !importGlobal) { + importGlobal = mf.IsImportedTargetGlobalScope(); + } + bool nameOk = cmGeneratorExpression::IsValidTargetName(exename) && !cmGlobalGenerator::IsReservedTarget(exename); diff --git a/Source/cmAddLibraryCommand.cxx b/Source/cmAddLibraryCommand.cxx index a5d1f6a..29fc09b 100644 --- a/Source/cmAddLibraryCommand.cxx +++ b/Source/cmAddLibraryCommand.cxx @@ -131,6 +131,10 @@ bool cmAddLibraryCommand(std::vector<std::string> const& args, } } + if (importTarget && !importGlobal) { + importGlobal = mf.IsImportedTargetGlobalScope(); + } + if (type == cmStateEnums::INTERFACE_LIBRARY) { if (importGlobal && !importTarget) { status.SetError( diff --git a/Source/cmArchiveWrite.cxx b/Source/cmArchiveWrite.cxx index 9e0d80c..cfde37c 100644 --- a/Source/cmArchiveWrite.cxx +++ b/Source/cmArchiveWrite.cxx @@ -95,6 +95,19 @@ cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c, , Verbose(false) , Format(format) { + // Upstream fixed an issue with their integer parsing in 3.4.0 + // which would cause spurious errors to be raised from `strtoull`. + + if (numThreads < 1) { + int upperLimit = (numThreads == 0) ? std::numeric_limits<int>::max() + : std::abs(numThreads); + + numThreads = + cm::clamp<int>(std::thread::hardware_concurrency(), 1, upperLimit); + } + + std::string sNumThreads = std::to_string(numThreads); + switch (c) { case CompressNone: if (archive_write_add_filter_none(this->Archive) != ARCHIVE_OK) { @@ -150,36 +163,23 @@ cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c, return; } - { #if ARCHIVE_VERSION_NUMBER >= 3004000 - // Upstream fixed an issue with their integer parsing in 3.4.0 - // which would cause spurious errors to be raised from `strtoull`. - - if (numThreads < 1) { - int upperLimit = (numThreads == 0) ? std::numeric_limits<int>::max() - : std::abs(numThreads); - - numThreads = - cm::clamp<int>(std::thread::hardware_concurrency(), 1, upperLimit); - } # ifdef _AIX - // FIXME: Using more than 2 threads creates an empty archive. - // Enforce this limit pending further investigation. - numThreads = std::min(numThreads, 2); + // FIXME: Using more than 2 threads creates an empty archive. + // Enforce this limit pending further investigation. + if (numThreads > 2) { + numThreads = 2; + sNumThreads = std::to_string(numThreads); + } # endif - - std::string sNumThreads = std::to_string(numThreads); - - if (archive_write_set_filter_option(this->Archive, "xz", "threads", - sNumThreads.c_str()) != - ARCHIVE_OK) { - this->Error = cmStrCat("archive_compressor_xz_options: ", - cm_archive_error_string(this->Archive)); - return; - } -#endif + if (archive_write_set_filter_option(this->Archive, "xz", "threads", + sNumThreads.c_str()) != ARCHIVE_OK) { + this->Error = cmStrCat("archive_compressor_xz_options: ", + cm_archive_error_string(this->Archive)); + return; } +#endif break; case CompressZstd: @@ -188,6 +188,15 @@ cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c, cm_archive_error_string(this->Archive)); return; } + +#if ARCHIVE_VERSION_NUMBER >= 3006000 + if (archive_write_set_filter_option(this->Archive, "zstd", "threads", + sNumThreads.c_str()) != ARCHIVE_OK) { + this->Error = cmStrCat("archive_compressor_zstd_options: ", + cm_archive_error_string(this->Archive)); + return; + } +#endif break; } diff --git a/Source/cmCMakeLanguageCommand.cxx b/Source/cmCMakeLanguageCommand.cxx index 789c78d..27d8cb8 100644 --- a/Source/cmCMakeLanguageCommand.cxx +++ b/Source/cmCMakeLanguageCommand.cxx @@ -84,7 +84,8 @@ bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args, for (size_t i = startArg; i < args.size(); ++i) { funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line); } - cmListFileFunction func{ callCommand, context.Line, std::move(funcArgs) }; + cmListFileFunction func{ callCommand, context.Line, context.Line, + std::move(funcArgs) }; if (defer) { if (defer->Id.empty()) { diff --git a/Source/cmCMakePresetsGraph.cxx b/Source/cmCMakePresetsGraph.cxx index dc14831..478c175 100644 --- a/Source/cmCMakePresetsGraph.cxx +++ b/Source/cmCMakePresetsGraph.cxx @@ -781,6 +781,8 @@ cmCMakePresetsGraph::TestPreset::VisitPresetInherit( parentOutput.MaxPassedTestOutputSize); InheritOptionalValue(output.MaxFailedTestOutputSize, parentOutput.MaxFailedTestOutputSize); + InheritOptionalValue(output.TestOutputTruncation, + parentOutput.TestOutputTruncation); InheritOptionalValue(output.MaxTestNameWidth, parentOutput.MaxTestNameWidth); } else { @@ -1035,6 +1037,9 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result) "support."; case ReadFileResult::CYCLIC_INCLUDE: return "Cyclic include among preset files"; + case ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED: + return "File version must be 5 or higher for testOutputTruncation " + "preset support."; } return "Unknown error"; diff --git a/Source/cmCMakePresetsGraph.h b/Source/cmCMakePresetsGraph.h index 9d6c61a..f1f8662 100644 --- a/Source/cmCMakePresetsGraph.h +++ b/Source/cmCMakePresetsGraph.h @@ -14,6 +14,8 @@ #include <cm/optional> +#include "CTest/cmCTestTypes.h" + enum class PackageResolveMode; class cmCMakePresetsGraph @@ -47,6 +49,7 @@ public: CONDITION_UNSUPPORTED, TOOLCHAIN_FILE_UNSUPPORTED, CYCLIC_INCLUDE, + TEST_OUTPUT_TRUNCATION_UNSUPPORTED, }; enum class ArchToolsetStrategy @@ -226,6 +229,7 @@ public: cm::optional<bool> SubprojectSummary; cm::optional<int> MaxPassedTestOutputSize; cm::optional<int> MaxFailedTestOutputSize; + cm::optional<cmCTestTypes::TruncationMode> TestOutputTruncation; cm::optional<int> MaxTestNameWidth; }; diff --git a/Source/cmCMakePresetsGraphReadJSON.cxx b/Source/cmCMakePresetsGraphReadJSON.cxx index 85cf5be..e68f0fa 100644 --- a/Source/cmCMakePresetsGraphReadJSON.cxx +++ b/Source/cmCMakePresetsGraphReadJSON.cxx @@ -33,7 +33,7 @@ using TestPreset = cmCMakePresetsGraph::TestPreset; using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy; constexpr int MIN_VERSION = 1; -constexpr int MAX_VERSION = 4; +constexpr int MAX_VERSION = 5; struct CMakeVersion { @@ -568,6 +568,11 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile( return ReadFileResult::CONDITION_UNSUPPORTED; } + // Support for TestOutputTruncation added in version 5. + if (v < 5 && preset.Output) { + return ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED; + } + this->TestPresetOrder.push_back(preset.Name); } diff --git a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx index 4d6474a..43eccfe 100644 --- a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx +++ b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx @@ -16,6 +16,8 @@ #include "cmCMakePresetsGraphInternal.h" #include "cmJSONHelpers.h" +#include "CTest/cmCTestTypes.h" + namespace { using ReadFileResult = cmCMakePresetsGraph::ReadFileResult; using TestPreset = cmCMakePresetsGraph::TestPreset; @@ -55,6 +57,40 @@ auto const TestPresetOptionalOutputVerbosityHelper = ReadFileResult>(ReadFileResult::READ_OK, TestPresetOutputVerbosityHelper); +ReadFileResult TestPresetOutputTruncationHelper( + cmCTestTypes::TruncationMode& out, const Json::Value* value) +{ + if (!value) { + out = cmCTestTypes::TruncationMode::Tail; + return ReadFileResult::READ_OK; + } + + if (!value->isString()) { + return ReadFileResult::INVALID_PRESET; + } + + if (value->asString() == "tail") { + out = cmCTestTypes::TruncationMode::Tail; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "middle") { + out = cmCTestTypes::TruncationMode::Middle; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "head") { + out = cmCTestTypes::TruncationMode::Head; + return ReadFileResult::READ_OK; + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const TestPresetOptionalTruncationHelper = + cmJSONOptionalHelper<cmCTestTypes::TruncationMode, ReadFileResult>( + ReadFileResult::READ_OK, TestPresetOutputTruncationHelper); + auto const TestPresetOptionalOutputHelper = cmJSONOptionalHelper<TestPreset::OutputOptions, ReadFileResult>( ReadFileResult::READ_OK, @@ -83,6 +119,9 @@ auto const TestPresetOptionalOutputHelper = .Bind("maxFailedTestOutputSize"_s, &TestPreset::OutputOptions::MaxFailedTestOutputSize, cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false) + .Bind("testOutputTruncation"_s, + &TestPreset::OutputOptions::TestOutputTruncation, + TestPresetOptionalTruncationHelper, false) .Bind("maxTestNameWidth"_s, &TestPreset::OutputOptions::MaxTestNameWidth, cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)); diff --git a/Source/cmCPluginAPI.cxx b/Source/cmCPluginAPI.cxx index 1b11f20..abec968 100644 --- a/Source/cmCPluginAPI.cxx +++ b/Source/cmCPluginAPI.cxx @@ -432,7 +432,7 @@ static int CCONV cmExecuteCommand(void* arg, const char* name, int numArgs, lffArgs.emplace_back(args[i], cmListFileArgument::Quoted, 0); } - cmListFileFunction lff{ name, 0, std::move(lffArgs) }; + cmListFileFunction lff{ name, 0, 0, std::move(lffArgs) }; cmExecutionStatus status(*mf); return mf->ExecuteCommand(lff, status); } diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index a1e920e..710b4d7 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -2036,6 +2036,13 @@ bool cmCTest::HandleCommandLineArguments(size_t& i, "Invalid value for '--test-output-size-failed': " << args[i] << "\n"); } + } else if (this->CheckArgument(arg, "--test-output-truncation"_s) && + i < args.size() - 1) { + i++; + if (!this->Impl->TestHandler.SetTestOutputTruncation(args[i])) { + errormsg = "Invalid value for '--test-output-truncation': " + args[i]; + return false; + } } else if (this->CheckArgument(arg, "-N"_s, "--show-only")) { this->Impl->ShowOnly = true; } else if (cmHasLiteralPrefix(arg, "--show-only=")) { @@ -2464,6 +2471,11 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName, *expandedPreset->Output->MaxFailedTestOutputSize); } + if (expandedPreset->Output->TestOutputTruncation) { + this->Impl->TestHandler.TestOutputTruncation = + *expandedPreset->Output->TestOutputTruncation; + } + if (expandedPreset->Output->MaxTestNameWidth) { this->Impl->MaxTestNameWidth = *expandedPreset->Output->MaxTestNameWidth; } diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx index 370ddff..a4dc01b 100644 --- a/Source/cmComputeLinkDepends.cxx +++ b/Source/cmComputeLinkDepends.cxx @@ -11,13 +11,18 @@ #include <utility> #include <cm/memory> +#include <cm/string_view> +#include <cmext/string_view> #include "cmComputeComponentGraph.h" +#include "cmGeneratorExpression.h" +#include "cmGeneratorExpressionDAGChecker.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" +#include "cmMessageType.h" #include "cmRange.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" @@ -174,8 +179,62 @@ items that we know the linker will re-use automatically (shared libs). */ +namespace { +// LINK_LIBRARY helpers +const auto LL_BEGIN = "<LINK_LIBRARY:"_s; +const auto LL_END = "</LINK_LIBRARY:"_s; + +inline std::string ExtractFeature(std::string const& item) +{ + return item.substr(LL_BEGIN.length(), + item.find('>', LL_BEGIN.length()) - LL_BEGIN.length()); +} + +bool IsFeatureSupported(cmMakefile* makefile, std::string const& linkLanguage, + std::string const& feature) +{ + auto featureSupported = cmStrCat( + "CMAKE_", linkLanguage, "_LINK_LIBRARY_USING_", feature, "_SUPPORTED"); + if (makefile->GetDefinition(featureSupported).IsOn()) { + return true; + } + + featureSupported = + cmStrCat("CMAKE_LINK_LIBRARY_USING_", feature, "_SUPPORTED"); + return makefile->GetDefinition(featureSupported).IsOn(); +} + +// LINK_GROUP helpers +const auto LG_BEGIN = "<LINK_GROUP:"_s; +const auto LG_END = "</LINK_GROUP:"_s; + +inline std::string ExtractGroupFeature(std::string const& item) +{ + return item.substr(LG_BEGIN.length(), + item.find(':', LG_BEGIN.length()) - LG_BEGIN.length()); +} + +bool IsGroupFeatureSupported(cmMakefile* makefile, + std::string const& linkLanguage, + std::string const& feature) +{ + auto featureSupported = cmStrCat( + "CMAKE_", linkLanguage, "_LINK_GROUP_USING_", feature, "_SUPPORTED"); + if (makefile->GetDefinition(featureSupported).IsOn()) { + return true; + } + + featureSupported = + cmStrCat("CMAKE_LINK_GROUP_USING_", feature, "_SUPPORTED"); + return makefile->GetDefinition(featureSupported).IsOn(); +} +} + +const std::string cmComputeLinkDepends::LinkEntry::DEFAULT = "DEFAULT"; + cmComputeLinkDepends::cmComputeLinkDepends(const cmGeneratorTarget* target, - const std::string& config) + const std::string& config, + const std::string& linkLanguage) { // Store context information. this->Target = target; @@ -183,6 +242,50 @@ cmComputeLinkDepends::cmComputeLinkDepends(const cmGeneratorTarget* target, this->GlobalGenerator = this->Target->GetLocalGenerator()->GetGlobalGenerator(); this->CMakeInstance = this->GlobalGenerator->GetCMakeInstance(); + this->LinkLanguage = linkLanguage; + + // target oriented feature override property takes precedence over + // global override property + cm::string_view lloPrefix = "LINK_LIBRARY_OVERRIDE_"_s; + auto const& keys = this->Target->GetPropertyKeys(); + std::for_each( + keys.cbegin(), keys.cend(), + [this, &lloPrefix, &config, &linkLanguage](std::string const& key) { + if (cmHasPrefix(key, lloPrefix)) { + if (cmValue feature = this->Target->GetProperty(key)) { + if (!feature->empty() && key.length() > lloPrefix.length()) { + auto item = key.substr(lloPrefix.length()); + cmGeneratorExpressionDAGChecker dag{ this->Target->GetBacktrace(), + this->Target, + "LINK_LIBRARY_OVERRIDE", + nullptr, nullptr }; + auto overrideFeature = cmGeneratorExpression::Evaluate( + feature, this->Target->GetLocalGenerator(), config, this->Target, + &dag, this->Target, linkLanguage); + this->LinkLibraryOverride.emplace(item, overrideFeature); + } + } + } + }); + // global override property + if (cmValue linkLibraryOverride = + this->Target->GetProperty("LINK_LIBRARY_OVERRIDE")) { + cmGeneratorExpressionDAGChecker dag{ target->GetBacktrace(), target, + "LINK_LIBRARY_OVERRIDE", nullptr, + nullptr }; + auto overrideValue = cmGeneratorExpression::Evaluate( + linkLibraryOverride, target->GetLocalGenerator(), config, target, &dag, + target, linkLanguage); + + auto overrideList = cmTokenize(overrideValue, ","_s); + if (overrideList.size() >= 2) { + auto const& feature = overrideList.front(); + for_each(overrideList.cbegin() + 1, overrideList.cend(), + [this, &feature](std::string const& item) { + this->LinkLibraryOverride.emplace(item, feature); + }); + } + } // The configuration being linked. this->HasConfig = !config.empty(); @@ -234,6 +337,11 @@ cmComputeLinkDepends::Compute() // Infer dependencies of targets for which they were not known. this->InferDependencies(); + // finalize groups dependencies + // All dependencies which are raw items must be replaced by the group + // it belongs to, if any. + this->UpdateGroupDependencies(); + // Cleanup the constraint graph. this->CleanConstraintGraph(); @@ -248,8 +356,21 @@ cmComputeLinkDepends::Compute() this->DisplayConstraintGraph(); } + // Compute the DAG of strongly connected components. The algorithm + // used by cmComputeComponentGraph should identify the components in + // the same order in which the items were originally discovered in + // the BFS. This should preserve the original order when no + // constraints disallow it. + this->CCG = + cm::make_unique<cmComputeComponentGraph>(this->EntryConstraintGraph); + this->CCG->Compute(); + + if (!this->CheckCircularDependencies()) { + return this->FinalLinkEntries; + } + // Compute the final ordering. - this->OrderLinkEntires(); + this->OrderLinkEntries(); // Compute the final set of link entries. // Iterate in reverse order so we can keep only the last occurrence @@ -273,6 +394,29 @@ cmComputeLinkDepends::Compute() // Reverse the resulting order since we iterated in reverse. std::reverse(this->FinalLinkEntries.begin(), this->FinalLinkEntries.end()); + // Expand group items + if (!this->GroupItems.empty()) { + for (const auto& group : this->GroupItems) { + const LinkEntry& groupEntry = this->EntryList[group.first]; + auto it = this->FinalLinkEntries.begin(); + while (true) { + it = std::find_if(it, this->FinalLinkEntries.end(), + [&groupEntry](const LinkEntry& entry) -> bool { + return groupEntry.Item == entry.Item; + }); + if (it == this->FinalLinkEntries.end()) { + break; + } + it->Item.Value = "</LINK_GROUP>"; + for (auto i = group.second.rbegin(); i != group.second.rend(); ++i) { + it = this->FinalLinkEntries.insert(it, this->EntryList[*i]); + } + it = this->FinalLinkEntries.insert(it, groupEntry); + it->Item.Value = "<LINK_GROUP>"; + } + } + } + // Display the final set. if (this->DebugMode) { this->DisplayFinalEntries(); @@ -281,76 +425,91 @@ cmComputeLinkDepends::Compute() return this->FinalLinkEntries; } -std::map<cmLinkItem, int>::iterator cmComputeLinkDepends::AllocateLinkEntry( - cmLinkItem const& item) +std::string const& cmComputeLinkDepends::GetCurrentFeature( + std::string const& item, std::string const& defaultFeature) const +{ + auto it = this->LinkLibraryOverride.find(item); + return it == this->LinkLibraryOverride.end() ? defaultFeature : it->second; +} + +std::pair<std::map<cmLinkItem, int>::iterator, bool> +cmComputeLinkDepends::AllocateLinkEntry(cmLinkItem const& item) { std::map<cmLinkItem, int>::value_type index_entry( item, static_cast<int>(this->EntryList.size())); - auto lei = this->LinkEntryIndex.insert(index_entry).first; - this->EntryList.emplace_back(); - this->InferredDependSets.emplace_back(); - this->EntryConstraintGraph.emplace_back(); + auto lei = this->LinkEntryIndex.insert(index_entry); + if (lei.second) { + this->EntryList.emplace_back(); + this->InferredDependSets.emplace_back(); + this->EntryConstraintGraph.emplace_back(); + } return lei; } -int cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item) +std::pair<int, bool> cmComputeLinkDepends::AddLinkEntry(cmLinkItem const& item, + int groupIndex) { + // Allocate a spot for the item entry. + auto lei = this->AllocateLinkEntry(item); + // Check if the item entry has already been added. - auto lei = this->LinkEntryIndex.find(item); - if (lei != this->LinkEntryIndex.end()) { + if (!lei.second) { // Yes. We do not need to follow the item's dependencies again. - return lei->second; + return { lei.first->second, false }; } - // Allocate a spot for the item entry. - lei = this->AllocateLinkEntry(item); - // Initialize the item entry. - int index = lei->second; + int index = lei.first->second; LinkEntry& entry = this->EntryList[index]; entry.Item = BT<std::string>(item.AsStr(), item.Backtrace); entry.Target = item.Target; - entry.IsFlag = (!entry.Target && entry.Item.Value[0] == '-' && - entry.Item.Value[1] != 'l' && - entry.Item.Value.substr(0, 10) != "-framework"); + if (!entry.Target && entry.Item.Value[0] == '-' && + entry.Item.Value[1] != 'l' && + entry.Item.Value.substr(0, 10) != "-framework") { + entry.Kind = LinkEntry::Flag; + } else if (cmHasPrefix(entry.Item.Value, LG_BEGIN) && + cmHasSuffix(entry.Item.Value, '>')) { + entry.Kind = LinkEntry::Group; + } - // If the item has dependencies queue it to follow them. - if (entry.Target) { - // Target dependencies are always known. Follow them. - BFSEntry qe = { index, nullptr }; - this->BFSQueue.push(qe); - } else { - // Look for an old-style <item>_LIB_DEPENDS variable. - std::string var = cmStrCat(entry.Item.Value, "_LIB_DEPENDS"); - if (cmValue val = this->Makefile->GetDefinition(var)) { - // The item dependencies are known. Follow them. - BFSEntry qe = { index, val->c_str() }; + if (entry.Kind != LinkEntry::Group) { + // If the item has dependencies queue it to follow them. + if (entry.Target) { + // Target dependencies are always known. Follow them. + BFSEntry qe = { index, groupIndex, nullptr }; this->BFSQueue.push(qe); - } else if (!entry.IsFlag) { - // The item dependencies are not known. We need to infer them. - this->InferredDependSets[index].Initialized = true; + } else { + // Look for an old-style <item>_LIB_DEPENDS variable. + std::string var = cmStrCat(entry.Item.Value, "_LIB_DEPENDS"); + if (cmValue val = this->Makefile->GetDefinition(var)) { + // The item dependencies are known. Follow them. + BFSEntry qe = { index, groupIndex, val->c_str() }; + this->BFSQueue.push(qe); + } else if (entry.Kind != LinkEntry::Flag) { + // The item dependencies are not known. We need to infer them. + this->InferredDependSets[index].Initialized = true; + } } } - return index; + return { index, true }; } void cmComputeLinkDepends::AddLinkObject(cmLinkItem const& item) { + // Allocate a spot for the item entry. + auto lei = this->AllocateLinkEntry(item); + // Check if the item entry has already been added. - auto lei = this->LinkEntryIndex.find(item); - if (lei != this->LinkEntryIndex.end()) { + if (!lei.second) { return; } - // Allocate a spot for the item entry. - lei = this->AllocateLinkEntry(item); - // Initialize the item entry. - int index = lei->second; + int index = lei.first->second; LinkEntry& entry = this->EntryList[index]; entry.Item = BT<std::string>(item.AsStr(), item.Backtrace); - entry.IsObject = true; + entry.Kind = LinkEntry::Object; // Record explicitly linked object files separately. this->ObjectEntries.emplace_back(index); @@ -359,8 +518,8 @@ void cmComputeLinkDepends::AddLinkObject(cmLinkItem const& item) void cmComputeLinkDepends::FollowLinkEntry(BFSEntry qe) { // Get this entry representation. - int depender_index = qe.Index; - LinkEntry const& entry = this->EntryList[depender_index]; + int depender_index = qe.GroupIndex == -1 ? qe.Index : qe.GroupIndex; + LinkEntry const& entry = this->EntryList[qe.Index]; // Follow the item's dependencies. if (entry.Target) { @@ -423,25 +582,24 @@ void cmComputeLinkDepends::QueueSharedDependencies( void cmComputeLinkDepends::HandleSharedDependency(SharedDepEntry const& dep) { - // Check if the target already has an entry. - auto lei = this->LinkEntryIndex.find(dep.Item); - if (lei == this->LinkEntryIndex.end()) { - // Allocate a spot for the item entry. - lei = this->AllocateLinkEntry(dep.Item); + // Allocate a spot for the item entry. + auto lei = this->AllocateLinkEntry(dep.Item); + int index = lei.first->second; + // Check if the target does not already has an entry. + if (lei.second) { // Initialize the item entry. - LinkEntry& entry = this->EntryList[lei->second]; + LinkEntry& entry = this->EntryList[index]; entry.Item = BT<std::string>(dep.Item.AsStr(), dep.Item.Backtrace); entry.Target = dep.Item.Target; // This item was added specifically because it is a dependent // shared library. It may get special treatment // in cmComputeLinkInformation. - entry.IsSharedDep = true; + entry.Kind = LinkEntry::SharedDep; } // Get the link entry for this target. - int index = lei->second; LinkEntry& entry = this->EntryList[index]; // This shared library dependency must follow the item that listed @@ -541,6 +699,11 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, { // Track inferred dependency sets implied by this list. std::map<int, DependSet> dependSets; + std::string feature = LinkEntry::DEFAULT; + + bool inGroup = false; + std::pair<int, bool> groupIndex{ -1, false }; + std::vector<int> groupItems; // Loop over the libraries linked directly by the depender. for (T const& l : libs) { @@ -551,35 +714,233 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index, continue; } + if (cmHasPrefix(item.AsStr(), LL_BEGIN) && + cmHasSuffix(item.AsStr(), '>')) { + feature = ExtractFeature(item.AsStr()); + // emit a warning if an undefined feature is used as part of + // an imported target + if (depender_index >= 0) { + const auto& depender = this->EntryList[depender_index]; + if (depender.Target != nullptr && depender.Target->IsImported() && + !IsFeatureSupported(this->Makefile, this->LinkLanguage, feature)) { + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_ERROR, + cmStrCat("The 'IMPORTED' target '", depender.Target->GetName(), + "' uses the generator-expression '$<LINK_LIBRARY>' with " + "the feature '", + feature, + "', which is undefined or unsupported.\nDid you miss to " + "define it by setting variables \"CMAKE_", + this->LinkLanguage, "_LINK_LIBRARY_USING_", feature, + "\" and \"CMAKE_", this->LinkLanguage, + "_LINK_LIBRARY_USING_", feature, "_SUPPORTED\"?"), + this->Target->GetBacktrace()); + } + } + continue; + } + if (cmHasPrefix(item.AsStr(), LL_END) && cmHasSuffix(item.AsStr(), '>')) { + feature = LinkEntry::DEFAULT; + continue; + } + + if (cmHasPrefix(item.AsStr(), LG_BEGIN) && + cmHasSuffix(item.AsStr(), '>')) { + groupIndex = this->AddLinkEntry(item); + if (groupIndex.second) { + LinkEntry& entry = this->EntryList[groupIndex.first]; + entry.Feature = ExtractGroupFeature(item.AsStr()); + } + inGroup = true; + if (depender_index >= 0) { + this->EntryConstraintGraph[depender_index].emplace_back( + groupIndex.first, false, false, cmListFileBacktrace()); + } else { + // This is a direct dependency of the target being linked. + this->OriginalEntries.push_back(groupIndex.first); + } + continue; + } + + int dependee_index; + + if (cmHasPrefix(item.AsStr(), LG_END) && cmHasSuffix(item.AsStr(), '>')) { + dependee_index = groupIndex.first; + if (groupIndex.second) { + this->GroupItems.emplace(groupIndex.first, groupItems); + } + inGroup = false; + groupIndex = std::make_pair(-1, false); + groupItems.clear(); + continue; + } + + if (depender_index >= 0 && inGroup) { + const auto& depender = this->EntryList[depender_index]; + const auto& groupFeature = this->EntryList[groupIndex.first].Feature; + if (depender.Target != nullptr && depender.Target->IsImported() && + !IsGroupFeatureSupported(this->Makefile, this->LinkLanguage, + groupFeature)) { + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_ERROR, + cmStrCat("The 'IMPORTED' target '", depender.Target->GetName(), + "' uses the generator-expression '$<LINK_GROUP>' with " + "the feature '", + groupFeature, + "', which is undefined or unsupported.\nDid you miss to " + "define it by setting variables \"CMAKE_", + this->LinkLanguage, "_LINK_GROUP_USING_", groupFeature, + "\" and \"CMAKE_", this->LinkLanguage, "_LINK_GROUP_USING_", + groupFeature, "_SUPPORTED\"?"), + this->Target->GetBacktrace()); + } + } + // Add a link entry for this item. - int dependee_index = this->AddLinkEntry(l); + auto ale = this->AddLinkEntry(item, groupIndex.first); + dependee_index = ale.first; + LinkEntry& entry = this->EntryList[dependee_index]; + auto const& itemFeature = + this->GetCurrentFeature(entry.Item.Value, feature); + if (inGroup && ale.second && entry.Target != nullptr && + (entry.Target->GetType() == cmStateEnums::TargetType::OBJECT_LIBRARY || + entry.Target->GetType() == + cmStateEnums::TargetType::INTERFACE_LIBRARY)) { + const auto& groupFeature = this->EntryList[groupIndex.first].Feature; + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat( + "The feature '", groupFeature, + "', specified as part of a generator-expression " + "'$", + LG_BEGIN, groupFeature, ">', will not be applied to the ", + (entry.Target->GetType() == cmStateEnums::TargetType::OBJECT_LIBRARY + ? "OBJECT" + : "INTERFACE"), + " library '", entry.Item.Value, "'."), + this->Target->GetBacktrace()); + } + if (itemFeature != LinkEntry::DEFAULT) { + if (ale.second) { + // current item not yet defined + if (entry.Target != nullptr && + (entry.Target->GetType() == + cmStateEnums::TargetType::OBJECT_LIBRARY || + entry.Target->GetType() == + cmStateEnums::TargetType::INTERFACE_LIBRARY)) { + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat("The feature '", feature, + "', specified as part of a generator-expression " + "'$", + LL_BEGIN, feature, ">', will not be applied to the ", + (entry.Target->GetType() == + cmStateEnums::TargetType::OBJECT_LIBRARY + ? "OBJECT" + : "INTERFACE"), + " library '", entry.Item.Value, "'."), + this->Target->GetBacktrace()); + } else { + entry.Feature = itemFeature; + } + } + } - // The dependee must come after the depender. - if (depender_index >= 0) { - this->EntryConstraintGraph[depender_index].emplace_back( - dependee_index, false, false, cmListFileBacktrace()); - } else { - // This is a direct dependency of the target being linked. - this->OriginalEntries.push_back(dependee_index); - } - - // Update the inferred dependencies for earlier items. - for (auto& dependSet : dependSets) { - // Add this item to the inferred dependencies of other items. - // Target items are never inferred dependees because unknown - // items are outside libraries that should not be depending on - // targets. - if (!this->EntryList[dependee_index].Target && - !this->EntryList[dependee_index].IsFlag && - dependee_index != dependSet.first) { - dependSet.second.insert(dependee_index); + bool supportedItem = entry.Target == nullptr || + (entry.Target->GetType() != cmStateEnums::TargetType::OBJECT_LIBRARY && + entry.Target->GetType() != cmStateEnums::TargetType::INTERFACE_LIBRARY); + + if (supportedItem) { + if (inGroup) { + const auto& currentFeature = this->EntryList[groupIndex.first].Feature; + for (const auto& g : this->GroupItems) { + const auto& groupFeature = this->EntryList[g.first].Feature; + if (groupFeature == currentFeature) { + continue; + } + if (std::find(g.second.cbegin(), g.second.cend(), dependee_index) != + g.second.cend()) { + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Impossible to link target '", this->Target->GetName(), + "' because the link item '", entry.Item.Value, + "', specified with the group feature '", currentFeature, + '\'', ", has already occurred with the feature '", + groupFeature, '\'', ", which is not allowed."), + this->Target->GetBacktrace()); + continue; + } + } + } + if (entry.Feature != itemFeature) { + // incompatibles features occurred + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Impossible to link target '", this->Target->GetName(), + "' because the link item '", entry.Item.Value, + "', specified ", + (itemFeature == LinkEntry::DEFAULT + ? "without any feature or 'DEFAULT' feature" + : cmStrCat("with the feature '", itemFeature, '\'')), + ", has already occurred ", + (entry.Feature == LinkEntry::DEFAULT + ? "without any feature or 'DEFAULT' feature" + : cmStrCat("with the feature '", entry.Feature, '\'')), + ", which is not allowed."), + this->Target->GetBacktrace()); } } - // If this item needs to have dependencies inferred, do so. - if (this->InferredDependSets[dependee_index].Initialized) { - // Make sure an entry exists to hold the set for the item. - dependSets[dependee_index]; + if (inGroup) { + // store item index for dependencies handling + groupItems.push_back(dependee_index); + } else { + std::vector<int> indexes; + bool entryHandled = false; + // search any occurrence of the library in already defined groups + for (const auto& group : this->GroupItems) { + for (auto index : group.second) { + if (entry.Item.Value == this->EntryList[index].Item.Value) { + indexes.push_back(group.first); + entryHandled = true; + break; + } + } + } + if (!entryHandled) { + indexes.push_back(dependee_index); + } + + for (auto index : indexes) { + // The dependee must come after the depender. + if (depender_index >= 0) { + this->EntryConstraintGraph[depender_index].emplace_back( + index, false, false, cmListFileBacktrace()); + } else { + // This is a direct dependency of the target being linked. + this->OriginalEntries.push_back(index); + } + + // Update the inferred dependencies for earlier items. + for (auto& dependSet : dependSets) { + // Add this item to the inferred dependencies of other items. + // Target items are never inferred dependees because unknown + // items are outside libraries that should not be depending on + // targets. + if (!this->EntryList[index].Target && + this->EntryList[index].Kind != LinkEntry::Flag && + this->EntryList[index].Kind != LinkEntry::Group && + dependee_index != dependSet.first) { + dependSet.second.insert(index); + } + } + + // If this item needs to have dependencies inferred, do so. + if (this->InferredDependSets[index].Initialized) { + // Make sure an entry exists to hold the set for the item. + dependSets[index]; + } + } } } @@ -642,6 +1003,36 @@ void cmComputeLinkDepends::InferDependencies() } } +void cmComputeLinkDepends::UpdateGroupDependencies() +{ + if (this->GroupItems.empty()) { + return; + } + + // Walks through all entries of the constraint graph to replace dependencies + // over raw items by the group it belongs to, if any. + for (auto& edgeList : this->EntryConstraintGraph) { + for (auto& edge : edgeList) { + int index = edge; + if (this->EntryList[index].Kind == LinkEntry::Group || + this->EntryList[index].Kind == LinkEntry::Flag || + this->EntryList[index].Kind == LinkEntry::Object) { + continue; + } + // search the item in the defined groups + for (const auto& groupItems : this->GroupItems) { + auto pos = std::find(groupItems.second.cbegin(), + groupItems.second.cend(), index); + if (pos != groupItems.second.cend()) { + // replace lib dependency by the group it belongs to + edge = cmGraphEdge{ groupItems.first, false, false, + cmListFileBacktrace() }; + } + } + } + } +} + void cmComputeLinkDepends::CleanConstraintGraph() { for (cmGraphEdgeList& edgeList : this->EntryConstraintGraph) { @@ -655,6 +1046,76 @@ void cmComputeLinkDepends::CleanConstraintGraph() } } +bool cmComputeLinkDepends::CheckCircularDependencies() const +{ + std::vector<NodeList> const& components = this->CCG->GetComponents(); + int nc = static_cast<int>(components.size()); + for (int c = 0; c < nc; ++c) { + // Get the current component. + NodeList const& nl = components[c]; + + // Skip trivial components. + if (nl.size() < 2) { + continue; + } + + // no group must be evolved + bool cycleDetected = false; + for (int ni : nl) { + if (this->EntryList[ni].Kind == LinkEntry::Group) { + cycleDetected = true; + break; + } + } + if (!cycleDetected) { + continue; + } + + // Construct the error message. + auto formatItem = [](LinkEntry const& entry) -> std::string { + if (entry.Kind == LinkEntry::Group) { + auto items = + entry.Item.Value.substr(entry.Item.Value.find(':', 12) + 1); + items.pop_back(); + std::replace(items.begin(), items.end(), '|', ','); + return cmStrCat("group \"", ExtractGroupFeature(entry.Item.Value), + ":{", items, "}\""); + } + return cmStrCat('"', entry.Item.Value, '"'); + }; + + std::ostringstream e; + e << "The inter-target dependency graph, for the target \"" + << this->Target->GetName() + << "\", contains the following strongly connected component " + "(cycle):\n"; + std::vector<int> const& cmap = this->CCG->GetComponentMap(); + for (int i : nl) { + // Get the depender. + LinkEntry const& depender = this->EntryList[i]; + + // Describe the depender. + e << " " << formatItem(depender) << "\n"; + + // List its dependencies that are inside the component. + EdgeList const& el = this->EntryConstraintGraph[i]; + for (cmGraphEdge const& ni : el) { + int j = ni; + if (cmap[j] == c) { + LinkEntry const& dependee = this->EntryList[j]; + e << " depends on " << formatItem(dependee) << "\n"; + } + } + } + this->CMakeInstance->IssueMessage(MessageType::FATAL_ERROR, e.str(), + this->Target->GetBacktrace()); + + return false; + } + + return true; +} + void cmComputeLinkDepends::DisplayConstraintGraph() { // Display the graph nodes and their edges. @@ -667,17 +1128,8 @@ void cmComputeLinkDepends::DisplayConstraintGraph() fprintf(stderr, "%s\n", e.str().c_str()); } -void cmComputeLinkDepends::OrderLinkEntires() +void cmComputeLinkDepends::OrderLinkEntries() { - // Compute the DAG of strongly connected components. The algorithm - // used by cmComputeComponentGraph should identify the components in - // the same order in which the items were originally discovered in - // the BFS. This should preserve the original order when no - // constraints disallow it. - this->CCG = - cm::make_unique<cmComputeComponentGraph>(this->EntryConstraintGraph); - this->CCG->Compute(); - // The component graph is guaranteed to be acyclic. Start a DFS // from every entry to compute a topological order for the // components. @@ -867,12 +1319,23 @@ int cmComputeLinkDepends::ComputeComponentCount(NodeList const& nl) void cmComputeLinkDepends::DisplayFinalEntries() { fprintf(stderr, "target [%s] links to:\n", this->Target->GetName().c_str()); + char space[] = " "; + int count = 2; for (LinkEntry const& lei : this->FinalLinkEntries) { - if (lei.Target) { - fprintf(stderr, " target [%s]\n", lei.Target->GetName().c_str()); + if (lei.Kind == LinkEntry::Group) { + fprintf(stderr, " %s group", + lei.Item.Value == "<LINK_GROUP>" ? "start" : "end"); + count = lei.Item.Value == "<LINK_GROUP>" ? 4 : 2; + } else if (lei.Target) { + fprintf(stderr, "%*starget [%s]", count, space, + lei.Target->GetName().c_str()); } else { - fprintf(stderr, " item [%s]\n", lei.Item.Value.c_str()); + fprintf(stderr, "%*sitem [%s]", count, space, lei.Item.Value.c_str()); + } + if (lei.Feature != LinkEntry::DEFAULT) { + fprintf(stderr, ", feature [%s]", lei.Feature.c_str()); } + fprintf(stderr, "\n"); } fprintf(stderr, "\n"); } diff --git a/Source/cmComputeLinkDepends.h b/Source/cmComputeLinkDepends.h index 72316f1..8cc916a 100644 --- a/Source/cmComputeLinkDepends.h +++ b/Source/cmComputeLinkDepends.h @@ -9,6 +9,7 @@ #include <queue> #include <set> #include <string> +#include <utility> #include <vector> #include "cmGraphAdjacencyList.h" @@ -29,7 +30,8 @@ class cmComputeLinkDepends { public: cmComputeLinkDepends(cmGeneratorTarget const* target, - const std::string& config); + const std::string& config, + const std::string& linkLanguage); ~cmComputeLinkDepends(); cmComputeLinkDepends(const cmComputeLinkDepends&) = delete; @@ -38,11 +40,32 @@ public: // Basic information about each link item. struct LinkEntry { + LinkEntry() = default; + LinkEntry(BT<std::string> item, cmGeneratorTarget const* target = nullptr) + : Item(std::move(item)) + , Target(target) + { + } + + static const std::string DEFAULT; + + enum EntryKind + { + Library, + Object, + SharedDep, + Flag, + // The following member is for the management of items specified + // through genex $<LINK_GROUP:...> + Group + }; + BT<std::string> Item; cmGeneratorTarget const* Target = nullptr; - bool IsSharedDep = false; - bool IsFlag = false; - bool IsObject = false; + EntryKind Kind = Library; + // The following member is for the management of items specified + // through genex $<LINK_LIBRARY:...> + std::string Feature = std::string(DEFAULT); }; using EntryVector = std::vector<LinkEntry>; @@ -60,12 +83,18 @@ private: cmMakefile* Makefile; cmGlobalGenerator const* GlobalGenerator; cmake* CMakeInstance; + std::string LinkLanguage; std::string Config; EntryVector FinalLinkEntries; + std::map<std::string, std::string> LinkLibraryOverride; + + std::string const& GetCurrentFeature( + std::string const& item, std::string const& defaultFeature) const; - std::map<cmLinkItem, int>::iterator AllocateLinkEntry( + std::pair<std::map<cmLinkItem, int>::iterator, bool> AllocateLinkEntry( cmLinkItem const& item); - int AddLinkEntry(cmLinkItem const& item); + std::pair<int, bool> AddLinkEntry(cmLinkItem const& item, + int groupIndex = -1); void AddLinkObject(cmLinkItem const& item); void AddVarLinkEntries(int depender_index, const char* value); void AddDirectLinkEntries(); @@ -78,10 +107,14 @@ private: std::vector<LinkEntry> EntryList; std::map<cmLinkItem, int> LinkEntryIndex; + // map storing, for each group, the list of items + std::map<int, std::vector<int>> GroupItems; + // BFS of initial dependencies. struct BFSEntry { int Index; + int GroupIndex; const char* LibDepends; }; std::queue<BFSEntry> BFSQueue; @@ -114,16 +147,20 @@ private: std::vector<DependSetList> InferredDependSets; void InferDependencies(); + // To finalize dependencies over groups in place of raw items + void UpdateGroupDependencies(); + // Ordering constraint graph adjacency list. using NodeList = cmGraphNodeList; using EdgeList = cmGraphEdgeList; using Graph = cmGraphAdjacencyList; Graph EntryConstraintGraph; void CleanConstraintGraph(); + bool CheckCircularDependencies() const; void DisplayConstraintGraph(); // Ordering algorithm. - void OrderLinkEntires(); + void OrderLinkEntries(); std::vector<char> ComponentVisited; std::vector<int> ComponentOrder; diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx index 2ff91fe..67214f1 100644 --- a/Source/cmComputeLinkInformation.cxx +++ b/Source/cmComputeLinkInformation.cxx @@ -8,7 +8,9 @@ #include <utility> #include <cm/memory> +#include <cm/optional> #include <cmext/algorithm> +#include <cmext/string_view> #include "cmComputeLinkDepends.h" #include "cmGeneratorTarget.h" @@ -18,7 +20,7 @@ #include "cmMakefile.h" #include "cmMessageType.h" #include "cmOrderDirectories.h" -#include "cmOutputConverter.h" +#include "cmPlaceholderExpander.h" #include "cmPolicies.h" #include "cmState.h" #include "cmStateTypes.h" @@ -344,6 +346,29 @@ cmComputeLinkInformation::cmComputeLinkInformation( this->LinkWithRuntimePath = this->Makefile->IsOn(var); } + // Define some Feature descriptors to handle standard library and object link + if (!this->GetLibLinkFileFlag().empty()) { + this->LibraryFeatureDescriptors.emplace( + "__CMAKE_LINK_LIBRARY", + LibraryFeatureDescriptor{ + "__CMAKE_LINK_LIBRARY", + cmStrCat(this->GetLibLinkFileFlag(), "<LIBRARY>") }); + } + if (!this->GetObjLinkFileFlag().empty()) { + this->LibraryFeatureDescriptors.emplace( + "__CMAKE_LINK_OBJECT", + LibraryFeatureDescriptor{ + "__CMAKE_LINK_OBJECT", + cmStrCat(this->GetObjLinkFileFlag(), "<LIBRARY>") }); + } + if (!this->LoaderFlag->empty()) { + // Define a Feature descriptor for the link of an executable with exports + this->LibraryFeatureDescriptors.emplace( + "__CMAKE_LINK_EXECUTABLE", + LibraryFeatureDescriptor{ "__CMAKE_LINK_EXECUTABLE", + cmStrCat(this->LoaderFlag, "<LIBRARY>") }); + } + // Check the platform policy for missing soname case. this->NoSONameUsesPath = this->Makefile->IsOn("CMAKE_PLATFORM_USES_PATH_WHEN_NO_SONAME"); @@ -409,6 +434,10 @@ cmComputeLinkInformation::cmComputeLinkInformation( cmComputeLinkInformation::~cmComputeLinkInformation() = default; +namespace { +const std::string& DEFAULT = cmComputeLinkDepends::LinkEntry::DEFAULT; +} + void cmComputeLinkInformation::AppendValues( std::string& result, std::vector<BT<std::string>>& values) { @@ -510,17 +539,67 @@ bool cmComputeLinkInformation::Compute() } // Compute the ordered link line items. - cmComputeLinkDepends cld(this->Target, this->Config); + cmComputeLinkDepends cld(this->Target, this->Config, this->LinkLanguage); cld.SetOldLinkDirMode(this->OldLinkDirMode); cmComputeLinkDepends::EntryVector const& linkEntries = cld.Compute(); + FeatureDescriptor const* currentFeature = nullptr; // Add the link line items. for (cmComputeLinkDepends::LinkEntry const& linkEntry : linkEntries) { - if (linkEntry.IsSharedDep) { - this->AddSharedDepItem(linkEntry.Item, linkEntry.Target); + if (linkEntry.Kind == cmComputeLinkDepends::LinkEntry::Group) { + const auto& groupFeature = this->GetGroupFeature(linkEntry.Feature); + if (groupFeature.Supported) { + this->Items.emplace_back( + BT<std::string>{ linkEntry.Item.Value == "<LINK_GROUP>" + ? groupFeature.Prefix + : groupFeature.Suffix, + linkEntry.Item.Backtrace }, + ItemIsPath::No); + } + continue; + } + + if (currentFeature != nullptr && + linkEntry.Feature != currentFeature->Name) { + // emit feature suffix, if any + if (!currentFeature->Suffix.empty()) { + this->Items.emplace_back( + BT<std::string>{ currentFeature->Suffix, + this->Items.back().Value.Backtrace }, + ItemIsPath::No); + } + currentFeature = nullptr; + } + + if (linkEntry.Feature != DEFAULT && + (currentFeature == nullptr || + linkEntry.Feature != currentFeature->Name)) { + if (!this->AddLibraryFeature(linkEntry.Feature)) { + continue; + } + currentFeature = this->FindLibraryFeature(linkEntry.Feature); + // emit feature prefix, if any + if (!currentFeature->Prefix.empty()) { + this->Items.emplace_back( + BT<std::string>{ currentFeature->Prefix, linkEntry.Item.Backtrace }, + ItemIsPath::No); + } + } + + if (linkEntry.Kind == cmComputeLinkDepends::LinkEntry::SharedDep) { + this->AddSharedDepItem(linkEntry); } else { - this->AddItem(linkEntry.Item, linkEntry.Target, - linkEntry.IsObject ? ItemIsObject::Yes : ItemIsObject::No); + this->AddItem(linkEntry); + } + } + + if (currentFeature != nullptr) { + // emit feature suffix, if any + if (!currentFeature->Suffix.empty()) { + this->Items.emplace_back( + BT<std::string>{ currentFeature->Suffix, + this->Items.back().Value.Backtrace }, + ItemIsPath::No); } } @@ -576,6 +655,373 @@ bool cmComputeLinkInformation::Compute() return true; } +namespace { +void FinalizeFeatureFormat(std::string& format, const std::string& activeTag, + const std::string& otherTag) +{ + auto pos = format.find(otherTag); + if (pos != std::string::npos) { + format.erase(pos, format.find('}', pos) - pos + 1); + } + pos = format.find(activeTag); + if (pos != std::string::npos) { + format.erase(pos, activeTag.length()); + pos = format.find('}', pos); + if (pos != std::string::npos) { + format.erase(pos, 1); + } + } +} + +bool IsValidFeatureFormat(const std::string& format) +{ + return format.find("<LIBRARY>") != std::string::npos || + format.find("<LIB_ITEM>") != std::string::npos || + format.find("<LINK_ITEM>") != std::string::npos; +} + +class FeaturePlaceHolderExpander : public cmPlaceholderExpander +{ +public: + FeaturePlaceHolderExpander(const std::string* library, + const std::string* libItem = nullptr, + const std::string* linkItem = nullptr) + : Library(library) + , LibItem(libItem) + , LinkItem(linkItem) + { + } + +private: + std::string ExpandVariable(std::string const& variable) override + { + if (this->Library != nullptr && variable == "LIBRARY") { + return *this->Library; + } + if (this->LibItem != nullptr && variable == "LIB_ITEM") { + return *this->LibItem; + } + if (this->LinkItem != nullptr && variable == "LINK_ITEM") { + return *this->LinkItem; + } + + return variable; + } + + const std::string* Library = nullptr; + const std::string* LibItem = nullptr; + const std::string* LinkItem = nullptr; +}; +} + +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string itemFormat) + : Name(std::move(name)) + , Supported(true) + , ItemPathFormat(std::move(itemFormat)) + , ItemNameFormat(this->ItemPathFormat) +{ +} +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string itemPathFormat, std::string itemNameFormat) + : Name(std::move(name)) + , Supported(true) + , ItemPathFormat(std::move(itemPathFormat)) + , ItemNameFormat(std::move(itemNameFormat)) +{ +} +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string prefix, std::string itemPathFormat, + std::string itemNameFormat, std::string suffix) + : Name(std::move(name)) + , Supported(true) + , Prefix(std::move(prefix)) + , Suffix(std::move(suffix)) + , ItemPathFormat(std::move(itemPathFormat)) + , ItemNameFormat(std::move(itemNameFormat)) +{ +} +cmComputeLinkInformation::FeatureDescriptor::FeatureDescriptor( + std::string name, std::string prefix, std::string suffix, bool) + : Name(std::move(name)) + , Supported(true) + , Prefix(std::move(prefix)) + , Suffix(std::move(suffix)) +{ +} + +std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem( + std::string const& library, ItemIsPath isPath) const +{ + auto format = + isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat; + + // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns with library path + FeaturePlaceHolderExpander expander(&library, &library, &library); + return expander.ExpandVariables(format); +} +std::string cmComputeLinkInformation::FeatureDescriptor::GetDecoratedItem( + std::string const& library, std::string const& libItem, + std::string const& linkItem, ItemIsPath isPath) const +{ + auto format = + isPath == ItemIsPath::Yes ? this->ItemPathFormat : this->ItemNameFormat; + + // replace <LIBRARY>, <LIB_ITEM> and <LINK_ITEM> patterns + FeaturePlaceHolderExpander expander(&library, &libItem, &linkItem); + return expander.ExpandVariables(format); +} + +cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor( + std::string name, std::string itemFormat) + : FeatureDescriptor(std::move(name), std::move(itemFormat)) +{ +} +cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor( + std::string name, std::string itemPathFormat, std::string itemNameFormat) + : FeatureDescriptor(std::move(name), std::move(itemPathFormat), + std::move(itemNameFormat)) +{ +} +cmComputeLinkInformation::LibraryFeatureDescriptor::LibraryFeatureDescriptor( + std::string name, std::string prefix, std::string itemPathFormat, + std::string itemNameFormat, std::string suffix) + : FeatureDescriptor(std::move(name), std::move(prefix), + std::move(itemPathFormat), std::move(itemNameFormat), + std::move(suffix)) +{ +} + +bool cmComputeLinkInformation::AddLibraryFeature(std::string const& feature) +{ + auto it = this->LibraryFeatureDescriptors.find(feature); + if (it != this->LibraryFeatureDescriptors.end()) { + return it->second.Supported; + } + + auto featureName = + cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_LIBRARY_USING_", feature); + cmValue featureSupported = + this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED")); + if (!featureSupported.IsOn()) { + featureName = cmStrCat("CMAKE_LINK_LIBRARY_USING_", feature); + featureSupported = + this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED")); + } + if (!featureSupported.IsOn()) { + this->LibraryFeatureDescriptors.emplace(feature, FeatureDescriptor{}); + + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat( + "Feature '", feature, + "', specified through generator-expression '$<LINK_LIBRARY>' to " + "link target '", + this->Target->GetName(), "', is not supported for the '", + this->LinkLanguage, "' link language."), + this->Target->GetBacktrace()); + + return false; + } + + cmValue langFeature = this->Makefile->GetDefinition(featureName); + if (!langFeature) { + this->LibraryFeatureDescriptors.emplace(feature, FeatureDescriptor{}); + + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat( + "Feature '", feature, + "', specified through generator-expression '$<LINK_LIBRARY>' to " + "link target '", + this->Target->GetName(), "', is not defined for the '", + this->LinkLanguage, "' link language."), + this->Target->GetBacktrace()); + + return false; + } + + auto items = + cmExpandListWithBacktrace(langFeature, this->Target->GetBacktrace(), true); + + if ((items.size() == 1 && !IsValidFeatureFormat(items.front().Value)) || + (items.size() == 3 && !IsValidFeatureFormat(items[1].Value))) { + this->LibraryFeatureDescriptors.emplace(feature, FeatureDescriptor{}); + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, "', specified by variable '", featureName, + "', is malformed (\"<LIBRARY>\", \"<LIB_ITEM>\", or " + "\"<LINK_ITEM>\" patterns " + "are missing) and cannot be used to link target '", + this->Target->GetName(), "'."), + this->Target->GetBacktrace()); + + return false; + } + + // now, handle possible "PATH{}" and "NAME{}" patterns + if (items.size() == 1) { + items.push_back(items.front()); + FinalizeFeatureFormat(items[0].Value, "PATH{", "NAME{"); + FinalizeFeatureFormat(items[1].Value, "NAME{", "PATH{"); + } else if (items.size() == 3) { + items.insert(items.begin() + 1, items[1]); + FinalizeFeatureFormat(items[1].Value, "PATH{", "NAME{"); + FinalizeFeatureFormat(items[2].Value, "NAME{", "PATH{"); + } else { + this->LibraryFeatureDescriptors.emplace(feature, FeatureDescriptor{}); + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, "', specified by variable '", featureName, + "', is malformed (wrong number of elements) and cannot be used " + "to link target '", + this->Target->GetName(), "'."), + this->Target->GetBacktrace()); + + return false; + } + if ((items.size() == 2 && !IsValidFeatureFormat(items[0].Value)) || + (items.size() == 4 && !IsValidFeatureFormat(items[1].Value))) { + // PATH{} has wrong format + this->LibraryFeatureDescriptors.emplace(feature, FeatureDescriptor{}); + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, "', specified by variable '", featureName, + "', is malformed (\"<LIBRARY>\", \"<LIB_ITEM>\", or " + "\"<LINK_ITEM>\" patterns " + "are missing for \"PATH{}\" alternative) and cannot be used to " + "link target '", + this->Target->GetName(), "'."), + this->Target->GetBacktrace()); + + return false; + } + if ((items.size() == 2 && !IsValidFeatureFormat(items[1].Value)) || + (items.size() == 4 && !IsValidFeatureFormat(items[2].Value))) { + // NAME{} has wrong format + this->LibraryFeatureDescriptors.emplace(feature, FeatureDescriptor{}); + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, "', specified by variable '", featureName, + "', is malformed (\"<LIBRARY>\", \"<LIB_ITEM>\", or " + "\"<LINK_ITEM>\" patterns " + "are missing for \"NAME{}\" alternative) and cannot be used to " + "link target '", + this->Target->GetName(), "'."), + this->Target->GetBacktrace()); + + return false; + } + + // replace LINKER: pattern + this->Target->ResolveLinkerWrapper(items, this->LinkLanguage, true); + + if (items.size() == 2) { + this->LibraryFeatureDescriptors.emplace( + feature, + LibraryFeatureDescriptor{ feature, items[0].Value, items[1].Value }); + } else { + this->LibraryFeatureDescriptors.emplace( + feature, + LibraryFeatureDescriptor{ feature, items[0].Value, items[1].Value, + items[2].Value, items[3].Value }); + } + + return true; +} + +cmComputeLinkInformation::FeatureDescriptor const& +cmComputeLinkInformation::GetLibraryFeature(std::string const& feature) const +{ + return this->LibraryFeatureDescriptors.find(feature)->second; +} +cmComputeLinkInformation::FeatureDescriptor const* +cmComputeLinkInformation::FindLibraryFeature(std::string const& feature) const +{ + auto it = this->LibraryFeatureDescriptors.find(feature); + if (it == this->LibraryFeatureDescriptors.end()) { + return nullptr; + } + + return &it->second; +} + +cmComputeLinkInformation::GroupFeatureDescriptor::GroupFeatureDescriptor( + std::string name, std::string prefix, std::string suffix) + : FeatureDescriptor(std::move(name), std::move(prefix), std::move(suffix), + true) +{ +} + +cmComputeLinkInformation::FeatureDescriptor const& +cmComputeLinkInformation::GetGroupFeature(std::string const& feature) +{ + auto it = this->GroupFeatureDescriptors.find(feature); + if (it != this->GroupFeatureDescriptors.end()) { + return it->second; + } + + auto featureName = + cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_GROUP_USING_", feature); + cmValue featureSupported = + this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED")); + if (!featureSupported.IsOn()) { + featureName = cmStrCat("CMAKE_LINK_GROUP_USING_", feature); + featureSupported = + this->Makefile->GetDefinition(cmStrCat(featureName, "_SUPPORTED")); + } + if (!featureSupported.IsOn()) { + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, + "', specified through generator-expression '$<LINK_GROUP>' to " + "link target '", + this->Target->GetName(), "', is not supported for the '", + this->LinkLanguage, "' link language."), + this->Target->GetBacktrace()); + return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{}) + .first->second; + } + + cmValue langFeature = this->Makefile->GetDefinition(featureName); + if (!langFeature) { + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, + "', specified through generator-expression '$<LINK_GROUP>' to " + "link target '", + this->Target->GetName(), "', is not defined for the '", + this->LinkLanguage, "' link language."), + this->Target->GetBacktrace()); + return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{}) + .first->second; + } + + auto items = + cmExpandListWithBacktrace(langFeature, this->Target->GetBacktrace(), true); + + // replace LINKER: pattern + this->Target->ResolveLinkerWrapper(items, this->LinkLanguage, true); + + if (items.size() == 2) { + return this->GroupFeatureDescriptors + .emplace( + feature, + GroupFeatureDescriptor{ feature, items[0].Value, items[1].Value }) + .first->second; + } + + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Feature '", feature, "', specified by variable '", featureName, + "', is malformed (wrong number of elements) and cannot be used " + "to link target '", + this->Target->GetName(), "'."), + this->Target->GetBacktrace()); + return this->GroupFeatureDescriptors.emplace(feature, FeatureDescriptor{}) + .first->second; +} + void cmComputeLinkInformation::AddImplicitLinkInfo() { // The link closure lists all languages whose implicit info is needed. @@ -610,7 +1056,7 @@ void cmComputeLinkInformation::AddRuntimeLinkLibrary(std::string const& lang) std::vector<std::string> libsVec = cmExpandedList(*runtimeLinkOptions); for (std::string const& i : libsVec) { if (!cm::contains(this->ImplicitLinkLibs, i)) { - this->AddItem(i, nullptr); + this->AddItem({ i }); } } } @@ -625,7 +1071,7 @@ void cmComputeLinkInformation::AddImplicitLinkInfo(std::string const& lang) std::vector<std::string> libsVec = cmExpandedList(*libs); for (std::string const& i : libsVec) { if (!cm::contains(this->ImplicitLinkLibs, i)) { - this->AddItem(i, nullptr); + this->AddItem({ i }); } } } @@ -639,10 +1085,11 @@ void cmComputeLinkInformation::AddImplicitLinkInfo(std::string const& lang) } } -void cmComputeLinkInformation::AddItem(BT<std::string> const& item, - cmGeneratorTarget const* tgt, - ItemIsObject isObject) +void cmComputeLinkInformation::AddItem(LinkEntry const& entry) { + cmGeneratorTarget const* tgt = entry.Target; + BT<std::string> const& item = entry.Item; + // Compute the proper name to use to link this library. const std::string& config = this->Config; bool impexe = (tgt && tgt->IsExecutableWithExports()); @@ -657,28 +1104,27 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item, if (impexe && this->LoaderFlag) { // This link item is an executable that may provide symbols // used by this target. A special flag is needed on this - // platform. Add it now. - std::string linkItem = this->LoaderFlag; + // platform. Add it now using a special feature. cmStateEnums::ArtifactType artifact = tgt->HasImportLibrary(config) ? cmStateEnums::ImportLibraryArtifact : cmStateEnums::RuntimeBinaryArtifact; - std::string exe = tgt->GetFullPath(config, artifact, true); - linkItem += exe; - this->Items.emplace_back(BT<std::string>(linkItem, item.Backtrace), - ItemIsPath::Yes, ItemIsObject::No, tgt); + this->Items.emplace_back( + BT<std::string>(exe, item.Backtrace), ItemIsPath::Yes, tgt, + this->FindLibraryFeature(entry.Feature == DEFAULT + ? "__CMAKE_LINK_EXECUTABLE" + : entry.Feature)); this->Depends.push_back(std::move(exe)); } else if (tgt->GetType() == cmStateEnums::INTERFACE_LIBRARY) { // Add the interface library as an item so it can be considered as part // of COMPATIBLE_INTERFACE_ enforcement. The generators will ignore // this for the actual link line. - this->Items.emplace_back(std::string(), ItemIsPath::No, ItemIsObject::No, - tgt); + this->Items.emplace_back(std::string(), ItemIsPath::No, tgt); // Also add the item the interface specifies to be used in its place. std::string const& libName = tgt->GetImportedLibName(config); if (!libName.empty()) { - this->AddItem(BT<std::string>(libName, item.Backtrace), nullptr); + this->AddItem(BT<std::string>(libName, item.Backtrace)); } } else if (tgt->GetType() == cmStateEnums::OBJECT_LIBRARY) { // Ignore object library! @@ -706,7 +1152,9 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item, this->Depends.push_back(lib.Value); } - this->AddTargetItem(lib, tgt); + LinkEntry libEntry{ entry }; + libEntry.Item = lib; + this->AddTargetItem(libEntry); this->AddLibraryRuntimeInfo(lib.Value, tgt); if (tgt && tgt->GetType() == cmStateEnums::SHARED_LIBRARY && this->Target->IsDLLPlatform()) { @@ -715,30 +1163,34 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item, } } else { // This is not a CMake target. Use the name given. - if (cmSystemTools::FileIsFullPath(item.Value)) { - if (cmSystemTools::IsPathToFramework(item.Value) && - this->Makefile->IsOn("APPLE")) { - // This is a framework. - this->AddFrameworkItem(item.Value); - } else if (cmSystemTools::FileIsDirectory(item.Value)) { + if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s) || + (entry.Feature == DEFAULT && + cmSystemTools::IsPathToFramework(item.Value) && + this->Makefile->IsOn("APPLE"))) { + // This is a framework. + this->AddFrameworkItem(entry); + } else if (cmSystemTools::FileIsFullPath(item.Value)) { + if (cmSystemTools::FileIsDirectory(item.Value)) { // This is a directory. this->DropDirectoryItem(item); } else { // Use the full path given to the library file. this->Depends.push_back(item.Value); - this->AddFullItem(item, isObject); + this->AddFullItem(entry); this->AddLibraryRuntimeInfo(item.Value); } } else { // This is a library or option specified by the user. - this->AddUserItem(item, true); + this->AddUserItem(entry, true); } } } -void cmComputeLinkInformation::AddSharedDepItem(BT<std::string> const& item, - const cmGeneratorTarget* tgt) +void cmComputeLinkInformation::AddSharedDepItem(LinkEntry const& entry) { + BT<std::string> const& item = entry.Item; + const cmGeneratorTarget* tgt = entry.Target; + // Record dependencies on DLLs. if (tgt && tgt->GetType() == cmStateEnums::SHARED_LIBRARY && this->Target->IsDLLPlatform() && @@ -776,7 +1228,7 @@ void cmComputeLinkInformation::AddSharedDepItem(BT<std::string> const& item, // If in linking mode, just link to the shared library. if (this->SharedDependencyMode == SharedDepModeLink) { - this->AddItem(item, tgt); + this->AddItem(entry); return; } @@ -1058,8 +1510,7 @@ void cmComputeLinkInformation::SetCurrentLinkType(LinkType lt) } } -void cmComputeLinkInformation::AddTargetItem(BT<std::string> const& item, - cmGeneratorTarget const* target) +void cmComputeLinkInformation::AddTargetItem(LinkEntry const& entry) { // This is called to handle a link item that is a full path to a target. // If the target is not a static library make sure the link type is @@ -1067,6 +1518,9 @@ void cmComputeLinkInformation::AddTargetItem(BT<std::string> const& item, // shared and static libraries but static-mode can handle only // static libraries. If a previous user item changed the link type // to static we need to make sure it is back to shared. + BT<std::string> const& item = entry.Item; + cmGeneratorTarget const* target = entry.Target; + if (target->GetType() != cmStateEnums::STATIC_LIBRARY) { this->SetCurrentLinkType(LinkShared); } @@ -1079,7 +1533,7 @@ void cmComputeLinkInformation::AddTargetItem(BT<std::string> const& item, // Handle case of an imported shared library with no soname. if (this->NoSONameUsesPath && target->IsImportedSharedLibWithoutSOName(this->Config)) { - this->AddSharedLibNoSOName(item.Value); + this->AddSharedLibNoSOName(entry); return; } @@ -1091,20 +1545,54 @@ void cmComputeLinkInformation::AddTargetItem(BT<std::string> const& item, this->OldLinkDirItems.push_back(item.Value); } - // Now add the full path to the library. - this->Items.emplace_back(item, ItemIsPath::Yes, ItemIsObject::No, target); + if (target->IsFrameworkOnApple() && this->GlobalGenerator->IsXcode() && + entry.Feature == DEFAULT) { + // ensure FRAMEWORK feature is loaded + this->AddLibraryFeature("FRAMEWORK"); + } + + if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s) && + target->IsFrameworkOnApple() && !this->GlobalGenerator->IsXcode()) { + // Add the framework directory and the framework item itself + auto fwItems = this->GlobalGenerator->SplitFrameworkPath(item.Value, true); + if (!fwItems) { + this->CMakeInstance->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Could not parse framework path \"", item.Value, + "\" linked by target ", this->Target->GetName(), '.'), + item.Backtrace); + return; + } + if (!fwItems->first.empty()) { + // Add the directory portion to the framework search path. + this->AddFrameworkPath(fwItems->first); + } + this->Items.emplace_back(fwItems->second, ItemIsPath::Yes, target, + this->FindLibraryFeature(entry.Feature)); + } else { + // Now add the full path to the library. + this->Items.emplace_back( + item, ItemIsPath::Yes, target, + this->FindLibraryFeature( + entry.Feature == DEFAULT + ? (target->IsFrameworkOnApple() && this->GlobalGenerator->IsXcode() + ? "FRAMEWORK" + : "__CMAKE_LINK_LIBRARY") + : entry.Feature)); + } } -void cmComputeLinkInformation::AddFullItem(BT<std::string> const& item, - ItemIsObject isObject) +void cmComputeLinkInformation::AddFullItem(LinkEntry const& entry) { + BT<std::string> const& item = entry.Item; + // Check for the implicit link directory special case. - if (this->CheckImplicitDirItem(item.Value)) { + if (this->CheckImplicitDirItem(entry)) { return; } // Check for case of shared library with no builtin soname. - if (this->NoSONameUsesPath && this->CheckSharedLibNoSOName(item.Value)) { + if (this->NoSONameUsesPath && this->CheckSharedLibNoSOName(entry)) { return; } @@ -1116,7 +1604,7 @@ void cmComputeLinkInformation::AddFullItem(BT<std::string> const& item, generator.find("Xcode") != std::string::npos)) { std::string file = cmSystemTools::GetFilenameName(item.Value); if (!this->ExtractAnyLibraryName.find(file)) { - this->HandleBadFullItem(item.Value, file); + this->HandleBadFullItem(entry, file); return; } } @@ -1147,11 +1635,20 @@ void cmComputeLinkInformation::AddFullItem(BT<std::string> const& item, } // Now add the full path to the library. - this->Items.emplace_back(item, ItemIsPath::Yes, isObject); + this->Items.emplace_back( + item, ItemIsPath::Yes, nullptr, + this->FindLibraryFeature( + entry.Feature == DEFAULT + ? (entry.Kind == cmComputeLinkDepends::LinkEntry::Object + ? "__CMAKE_LINK_OBJECT" + : "__CMAKE_LINK_LIBRARY") + : entry.Feature)); } -bool cmComputeLinkInformation::CheckImplicitDirItem(std::string const& item) +bool cmComputeLinkInformation::CheckImplicitDirItem(LinkEntry const& entry) { + BT<std::string> const& item = entry.Item; + // We only switch to a pathless item if the link type may be // enforced. Fortunately only platforms that support link types // seem to have magic per-architecture implicit link directories. @@ -1160,7 +1657,7 @@ bool cmComputeLinkInformation::CheckImplicitDirItem(std::string const& item) } // Check if this item is in an implicit link directory. - std::string dir = cmSystemTools::GetFilenamePath(item); + std::string dir = cmSystemTools::GetFilenamePath(item.Value); if (!cm::contains(this->ImplicitLinkDirs, dir)) { // Only libraries in implicit link directories are converted to // pathless items. @@ -1169,7 +1666,7 @@ bool cmComputeLinkInformation::CheckImplicitDirItem(std::string const& item) // Only apply the policy below if the library file is one that can // be found by the linker. - std::string file = cmSystemTools::GetFilenameName(item); + std::string file = cmSystemTools::GetFilenameName(item.Value); if (!this->ExtractAnyLibraryName.find(file)) { return false; } @@ -1179,10 +1676,10 @@ bool cmComputeLinkInformation::CheckImplicitDirItem(std::string const& item) case cmPolicies::WARN: if (this->CMP0060Warn) { // Print the warning at most once for this item. - std::string const& wid = "CMP0060-WARNING-GIVEN-" + item; + std::string const& wid = "CMP0060-WARNING-GIVEN-" + item.Value; if (!this->CMakeInstance->GetPropertyAsBool(wid)) { this->CMakeInstance->SetProperty(wid, "1"); - this->CMP0060WarnItems.insert(item); + this->CMP0060WarnItems.insert(item.Value); } } CM_FALLTHROUGH; @@ -1200,15 +1697,17 @@ bool cmComputeLinkInformation::CheckImplicitDirItem(std::string const& item) // directory then just report the file name without the directory // portion. This will allow the system linker to locate the proper // library for the architecture at link time. - this->AddUserItem(file, false); + LinkEntry fileEntry{ entry }; + fileEntry.Item = file; + this->AddUserItem(fileEntry, false); // Make sure the link directory ordering will find the library. - this->OrderLinkerSearchPath->AddLinkLibrary(item); + this->OrderLinkerSearchPath->AddLinkLibrary(item.Value); return true; } -void cmComputeLinkInformation::AddUserItem(BT<std::string> const& item, +void cmComputeLinkInformation::AddUserItem(LinkEntry const& entry, bool pathNotKnown) { // This is called to handle a link item that does not match a CMake @@ -1219,8 +1718,10 @@ void cmComputeLinkInformation::AddUserItem(BT<std::string> const& item, // foo ==> -lfoo // libfoo.a ==> -Wl,-Bstatic -lfoo - // Pass flags through untouched. + BT<std::string> const& item = entry.Item; + if (item.Value[0] == '-' || item.Value[0] == '$' || item.Value[0] == '`') { + // Pass flags through untouched. // if this is a -l option then we might need to warn about // CMP0003 so put it in OldUserFlagItems, if it is not a -l // or -Wl,-l (-framework -pthread), then allow it without a @@ -1305,9 +1806,20 @@ void cmComputeLinkInformation::AddUserItem(BT<std::string> const& item, } // Create an option to ask the linker to search for the library. - std::string out = cmStrCat(this->LibLinkFlag, lib, this->LibLinkSuffix); - this->Items.emplace_back(BT<std::string>(out, item.Backtrace), - ItemIsPath::No); + auto out = cmStrCat(this->LibLinkFlag, lib, this->LibLinkSuffix); + + if (entry.Feature != DEFAULT) { + auto const& feature = this->GetLibraryFeature(entry.Feature); + this->Items.emplace_back( + BT<std::string>( + feature.GetDecoratedItem(cmStrCat(lib, this->LibLinkSuffix), + item.Value, out, ItemIsPath::No), + item.Backtrace), + ItemIsPath::No); + } else { + this->Items.emplace_back(BT<std::string>(out, item.Backtrace), + ItemIsPath::No); + } // Here we could try to find the library the linker will find and // add a runtime information entry for it. It would probably not be @@ -1315,10 +1827,14 @@ void cmComputeLinkInformation::AddUserItem(BT<std::string> const& item, // specification. } -void cmComputeLinkInformation::AddFrameworkItem(std::string const& item) +void cmComputeLinkInformation::AddFrameworkItem(LinkEntry const& entry) { + std::string const& item = entry.Item.Value; + // Try to separate the framework name and path. - if (!this->SplitFramework.find(item)) { + auto fwItems = + this->GlobalGenerator->SplitFrameworkPath(item, entry.Feature != DEFAULT); + if (!fwItems) { std::ostringstream e; e << "Could not parse framework path \"" << item << "\" " << "linked by target " << this->Target->GetName() << "."; @@ -1326,26 +1842,36 @@ void cmComputeLinkInformation::AddFrameworkItem(std::string const& item) return; } - std::string fw_path = this->SplitFramework.match(1); - std::string fw = this->SplitFramework.match(2); - std::string full_fw = cmStrCat(fw_path, '/', fw, ".framework/", fw); + std::string fw_path = std::move(fwItems->first); + std::string fw = std::move(fwItems->second); + std::string full_fw = cmStrCat(fw, ".framework/", fw); - // Add the directory portion to the framework search path. - this->AddFrameworkPath(fw_path); + if (!fw_path.empty()) { + full_fw = cmStrCat(fw_path, '/', full_fw); + // Add the directory portion to the framework search path. + this->AddFrameworkPath(fw_path); + } // add runtime information this->AddLibraryRuntimeInfo(full_fw); + if (entry.Feature == DEFAULT) { + // ensure FRAMEWORK feature is loaded + this->AddLibraryFeature("FRAMEWORK"); + } + if (this->GlobalGenerator->IsXcode()) { // Add framework path - it will be handled by Xcode after it's added to // "Link Binary With Libraries" build phase - this->Items.emplace_back(item, ItemIsPath::Yes); + this->Items.emplace_back(item, ItemIsPath::Yes, nullptr, + this->FindLibraryFeature(entry.Feature == DEFAULT + ? "FRAMEWORK" + : entry.Feature)); } else { - // Add the item using the -framework option. - this->Items.emplace_back(std::string("-framework"), ItemIsPath::No); - cmOutputConverter converter(this->Makefile->GetStateSnapshot()); - fw = converter.EscapeForShell(fw); - this->Items.emplace_back(fw, ItemIsPath::No); + this->Items.emplace_back(fw, ItemIsPath::Yes, nullptr, + this->FindLibraryFeature(entry.Feature == DEFAULT + ? "FRAMEWORK" + : entry.Feature)); } } @@ -1355,10 +1881,10 @@ void cmComputeLinkInformation::DropDirectoryItem(BT<std::string> const& item) // user. this->CMakeInstance->IssueMessage( MessageType::WARNING, - cmStrCat( - "Target \"", this->Target->GetName(), - "\" requests linking to directory \"", item.Value, - "\". Targets may link only to libraries. CMake is dropping the item."), + cmStrCat("Target \"", this->Target->GetName(), + "\" requests linking to directory \"", item.Value, + "\". Targets may link only to libraries. CMake is dropping " + "the item."), item.Backtrace); } @@ -1378,9 +1904,6 @@ void cmComputeLinkInformation::ComputeFrameworkInfo() this->FrameworkPathsEmitted.insert(implicitDirVec.begin(), implicitDirVec.end()); - - // Regular expression to extract a framework path and name. - this->SplitFramework.compile("(.*)/(.*)\\.framework$"); } void cmComputeLinkInformation::AddFrameworkPath(std::string const& p) @@ -1390,42 +1913,44 @@ void cmComputeLinkInformation::AddFrameworkPath(std::string const& p) } } -bool cmComputeLinkInformation::CheckSharedLibNoSOName(std::string const& item) +bool cmComputeLinkInformation::CheckSharedLibNoSOName(LinkEntry const& entry) { // This platform will use the path to a library as its soname if the // library is given via path and was not built with an soname. If // this is a shared library that might be the case. - std::string file = cmSystemTools::GetFilenameName(item); + std::string file = cmSystemTools::GetFilenameName(entry.Item.Value); if (this->ExtractSharedLibraryName.find(file)) { // If we can guess the soname fairly reliably then assume the // library has one. Otherwise assume the library has no builtin // soname. std::string soname; - if (!cmSystemTools::GuessLibrarySOName(item, soname)) { - this->AddSharedLibNoSOName(item); + if (!cmSystemTools::GuessLibrarySOName(entry.Item.Value, soname)) { + this->AddSharedLibNoSOName(entry); return true; } } return false; } -void cmComputeLinkInformation::AddSharedLibNoSOName(std::string const& item) +void cmComputeLinkInformation::AddSharedLibNoSOName(LinkEntry const& entry) { // We have a full path to a shared library with no soname. We need // to ask the linker to locate the item because otherwise the path // we give to it will be embedded in the target linked. Then at // runtime the dynamic linker will search for the library using the // path instead of just the name. - std::string file = cmSystemTools::GetFilenameName(item); - this->AddUserItem(file, false); + LinkEntry fileEntry{ entry }; + fileEntry.Item = cmSystemTools::GetFilenameName(entry.Item.Value); + this->AddUserItem(fileEntry, false); // Make sure the link directory ordering will find the library. - this->OrderLinkerSearchPath->AddLinkLibrary(item); + this->OrderLinkerSearchPath->AddLinkLibrary(entry.Item.Value); } -void cmComputeLinkInformation::HandleBadFullItem(std::string const& item, +void cmComputeLinkInformation::HandleBadFullItem(LinkEntry const& entry, std::string const& file) { + std::string const& item = entry.Item.Value; // Do not depend on things that do not exist. auto i = std::find(this->Depends.begin(), this->Depends.end(), item); if (i != this->Depends.end()) { @@ -1435,7 +1960,9 @@ void cmComputeLinkInformation::HandleBadFullItem(std::string const& item, // Tell the linker to search for the item and provide the proper // path for it. Do not contribute to any CMP0003 warning (do not // put in OldLinkDirItems or OldUserFlagItems). - this->AddUserItem(file, false); + LinkEntry fileEntry{ entry }; + fileEntry.Item = file; + this->AddUserItem(fileEntry, false); this->OrderLinkerSearchPath->AddLinkLibrary(item); // Produce any needed message. @@ -1781,8 +2308,8 @@ void cmComputeLinkInformation::GetRPath(std::vector<std::string>& runtimeDirs, // Add directories explicitly specified by user std::string build_rpath; if (this->Target->GetBuildRPATH(this->Config, build_rpath)) { - // This will not resolve entries to use $ORIGIN, the user is expected to - // do that if necessary. + // This will not resolve entries to use $ORIGIN, the user is expected + // to do that if necessary. cmCLI_ExpandListUnique(build_rpath, runtimeDirs, emitted); } } diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h index 0315540..a4ada1f 100644 --- a/Source/cmComputeLinkInformation.h +++ b/Source/cmComputeLinkInformation.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep #include <iosfwd> +#include <map> #include <memory> #include <set> #include <string> @@ -13,6 +14,7 @@ #include "cmsys/RegularExpression.hxx" +#include "cmComputeLinkDepends.h" #include "cmListFileCache.h" #include "cmValue.h" @@ -27,6 +29,9 @@ class cmake; */ class cmComputeLinkInformation { +private: + class FeatureDescriptor; + public: cmComputeLinkInformation(cmGeneratorTarget const* target, const std::string& config); @@ -42,28 +47,38 @@ public: Yes, }; - enum class ItemIsObject - { - No, - Yes, - }; - struct Item { - Item() = default; Item(BT<std::string> v, ItemIsPath isPath, - ItemIsObject isObject = ItemIsObject::No, - cmGeneratorTarget const* target = nullptr) + cmGeneratorTarget const* target = nullptr, + FeatureDescriptor const* feature = nullptr) : Value(std::move(v)) , IsPath(isPath) - , IsObject(isObject) , Target(target) + , Feature(feature) { } BT<std::string> Value; - ItemIsPath IsPath = ItemIsPath::Yes; - ItemIsObject IsObject = ItemIsObject::No; + ItemIsPath IsPath = ItemIsPath::No; cmGeneratorTarget const* Target = nullptr; + + bool HasFeature() const { return this->Feature != nullptr; } + const std::string& GetFeatureName() const + { + return HasFeature() ? this->Feature->Name + : cmComputeLinkDepends::LinkEntry::DEFAULT; + } + + BT<std::string> GetFormattedItem(std::string const& path) const + { + return { (this->Feature != nullptr) + ? this->Feature->GetDecoratedItem(path, this->IsPath) + : path, + Value.Backtrace }; + } + + private: + FeatureDescriptor const* Feature = nullptr; }; using ItemVector = std::vector<Item>; void AppendValues(std::string& result, std::vector<BT<std::string>>& values); @@ -104,10 +119,10 @@ public: const cmGeneratorTarget* GetTarget() { return this->Target; } private: - void AddItem(BT<std::string> const& item, const cmGeneratorTarget* tgt, - ItemIsObject isObject = ItemIsObject::No); - void AddSharedDepItem(BT<std::string> const& item, - cmGeneratorTarget const* tgt); + using LinkEntry = cmComputeLinkDepends::LinkEntry; + + void AddItem(LinkEntry const& entry); + void AddSharedDepItem(LinkEntry const& entry); void AddRuntimeDLL(cmGeneratorTarget const* tgt); // Output information. @@ -181,22 +196,20 @@ private: std::string NoCaseExpression(std::string const& str); // Handling of link items. - void AddTargetItem(BT<std::string> const& item, - const cmGeneratorTarget* target); - void AddFullItem(BT<std::string> const& item, ItemIsObject isObject); - bool CheckImplicitDirItem(std::string const& item); - void AddUserItem(BT<std::string> const& item, bool pathNotKnown); - void AddFrameworkItem(std::string const& item); + void AddTargetItem(LinkEntry const& entry); + void AddFullItem(LinkEntry const& entry); + bool CheckImplicitDirItem(LinkEntry const& entry); + void AddUserItem(LinkEntry const& entry, bool pathNotKnown); + void AddFrameworkItem(LinkEntry const& entry); void DropDirectoryItem(BT<std::string> const& item); - bool CheckSharedLibNoSOName(std::string const& item); - void AddSharedLibNoSOName(std::string const& item); - void HandleBadFullItem(std::string const& item, std::string const& file); + bool CheckSharedLibNoSOName(LinkEntry const& entry); + void AddSharedLibNoSOName(LinkEntry const& entry); + void HandleBadFullItem(LinkEntry const& entry, std::string const& file); // Framework info. void ComputeFrameworkInfo(); void AddFrameworkPath(std::string const& p); std::set<std::string> FrameworkPathsEmitted; - cmsys::RegularExpression SplitFramework; // Linker search path computation. std::unique_ptr<cmOrderDirectories> OrderLinkerSearchPath; @@ -237,4 +250,61 @@ private: void AddLibraryRuntimeInfo(std::string const& fullPath, const cmGeneratorTarget* target); void AddLibraryRuntimeInfo(std::string const& fullPath); + + class FeatureDescriptor + { + public: + FeatureDescriptor() = default; + + const std::string Name; + const bool Supported = false; + const std::string Prefix; + const std::string Suffix; + std::string GetDecoratedItem(std::string const& library, + ItemIsPath isPath) const; + std::string GetDecoratedItem(std::string const& library, + std::string const& linkItem, + std::string const& defaultValue, + ItemIsPath isPath) const; + + protected: + FeatureDescriptor(std::string name, std::string itemFormat); + FeatureDescriptor(std::string name, std::string itemPathFormat, + std::string itemNameFormat); + FeatureDescriptor(std::string name, std::string prefix, + std::string itemPathFormat, std::string itemNameFormat, + std::string suffix); + + FeatureDescriptor(std::string name, std::string prefix, std::string suffix, + bool isGroup); + + private: + std::string ItemPathFormat; + std::string ItemNameFormat; + }; + + class LibraryFeatureDescriptor : public FeatureDescriptor + { + public: + LibraryFeatureDescriptor(std::string name, std::string itemFormat); + LibraryFeatureDescriptor(std::string name, std::string itemPathFormat, + std::string itemNameFormat); + LibraryFeatureDescriptor(std::string name, std::string prefix, + std::string itemPathFormat, + std::string itemNameFormat, std::string suffix); + }; + std::map<std::string, FeatureDescriptor> LibraryFeatureDescriptors; + bool AddLibraryFeature(std::string const& feature); + FeatureDescriptor const& GetLibraryFeature(std::string const& feature) const; + FeatureDescriptor const* FindLibraryFeature( + std::string const& feature) const; + + class GroupFeatureDescriptor : public FeatureDescriptor + { + public: + GroupFeatureDescriptor(std::string name, std::string prefix, + std::string suffix); + }; + std::map<std::string, FeatureDescriptor> GroupFeatureDescriptors; + FeatureDescriptor const& GetGroupFeature(std::string const& feature); }; diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index ed1e4cb..a21e614 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -2,6 +2,7 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmExportFileGenerator.h" +#include <array> #include <cassert> #include <cstring> #include <sstream> @@ -175,18 +176,24 @@ bool cmExportFileGenerator::PopulateInterfaceLinkLibrariesProperty( if (!target->IsLinkable()) { return false; } - cmValue input = target->GetProperty("INTERFACE_LINK_LIBRARIES"); - if (input) { - std::string prepro = - cmGeneratorExpression::Preprocess(*input, preprocessRule); - if (!prepro.empty()) { - this->ResolveTargetsInGeneratorExpressions( - prepro, target, missingTargets, ReplaceFreeTargets); - properties["INTERFACE_LINK_LIBRARIES"] = prepro; - return true; + static const std::array<std::string, 3> linkIfaceProps = { + { "INTERFACE_LINK_LIBRARIES", "INTERFACE_LINK_LIBRARIES_DIRECT", + "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE" } + }; + bool hadINTERFACE_LINK_LIBRARIES = false; + for (std::string const& linkIfaceProp : linkIfaceProps) { + if (cmValue input = target->GetProperty(linkIfaceProp)) { + std::string prepro = + cmGeneratorExpression::Preprocess(*input, preprocessRule); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions( + prepro, target, missingTargets, ReplaceFreeTargets); + properties[linkIfaceProp] = prepro; + hadINTERFACE_LINK_LIBRARIES = true; + } } } - return false; + return hadINTERFACE_LINK_LIBRARIES; } static bool isSubDirectory(std::string const& a, std::string const& b) @@ -927,13 +934,13 @@ void cmExportFileGenerator::GeneratePolicyHeaderCode(std::ostream& os) // Isolate the file policy level. // Support CMake versions as far back as 2.6 but also support using NEW - // policy settings for up to CMake 3.21 (this upper limit may be reviewed + // policy settings for up to CMake 3.22 (this upper limit may be reviewed // and increased from time to time). This reduces the opportunity for CMake // warnings when an older export file is later used with newer CMake // versions. /* clang-format off */ os << "cmake_policy(PUSH)\n" - << "cmake_policy(VERSION 2.6...3.21)\n"; + << "cmake_policy(VERSION 2.6...3.22)\n"; /* clang-format on */ } diff --git a/Source/cmExportTryCompileFileGenerator.cxx b/Source/cmExportTryCompileFileGenerator.cxx index db9b05b..e98aa05 100644 --- a/Source/cmExportTryCompileFileGenerator.cxx +++ b/Source/cmExportTryCompileFileGenerator.cxx @@ -111,6 +111,8 @@ void cmExportTryCompileFileGenerator::PopulateProperties( std::vector<std::string> props = target->GetPropertyKeys(); // Include special properties that might be relevant here. props.emplace_back("INTERFACE_LINK_LIBRARIES"); + props.emplace_back("INTERFACE_LINK_LIBRARIES_DIRECT"); + props.emplace_back("INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE"); for (std::string const& p : props) { cmValue v = target->GetProperty(p); if (!v) { diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index da2f15f..09e5015 100644 --- a/Source/cmFileCommand.cxx +++ b/Source/cmFileCommand.cxx @@ -15,6 +15,7 @@ #include <vector> #include <cm/memory> +#include <cm/optional> #include <cm/string_view> #include <cmext/algorithm> #include <cmext/string_view> @@ -1778,6 +1779,7 @@ bool HandleDownloadCommand(std::vector<std::string> const& args, std::string userpwd; std::vector<std::string> curl_headers; + std::vector<std::pair<std::string, cm::optional<std::string>>> curl_ranges; while (i != args.end()) { if (*i == "TIMEOUT") { @@ -1890,6 +1892,27 @@ bool HandleDownloadCommand(std::vector<std::string> const& args, return false; } curl_headers.push_back(*i); + } else if (*i == "RANGE_START") { + ++i; + if (i == args.end()) { + status.SetError("DOWNLOAD missing value for RANGE_START."); + return false; + } + curl_ranges.emplace_back(*i, cm::nullopt); + } else if (*i == "RANGE_END") { + ++i; + if (curl_ranges.empty()) { + curl_ranges.emplace_back("0", *i); + } else { + auto& last_range = curl_ranges.back(); + if (!last_range.second.has_value()) { + last_range.second = *i; + } else { + status.SetError("Multiple RANGE_END values is provided without " + "the corresponding RANGE_START."); + return false; + } + } } else if (file.empty()) { file = *i; } else { @@ -1899,6 +1922,7 @@ bool HandleDownloadCommand(std::vector<std::string> const& args, } ++i; } + // Can't calculate hash if we don't save the file. // TODO Incrementally calculate hash in the write callback as the file is // being downloaded so this check can be relaxed. @@ -1984,6 +2008,13 @@ bool HandleDownloadCommand(std::vector<std::string> const& args, check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: "); } + for (const auto& range : curl_ranges) { + std::string curl_range = range.first + '-' + + (range.second.has_value() ? range.second.value() : ""); + res = ::curl_easy_setopt(curl, CURLOPT_RANGE, curl_range.c_str()); + check_curl_result(res, "DOWNLOAD cannot set range: "); + } + // check to see if a CAINFO file has been specified // command arg comes first std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo); diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx index f55d838..18457a7 100644 --- a/Source/cmFindPackageCommand.cxx +++ b/Source/cmFindPackageCommand.cxx @@ -262,6 +262,9 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args) } else if (args[i] == "EXACT") { this->VersionExact = true; doing = DoingNone; + } else if (args[i] == "GLOBAL") { + this->GlobalScope = true; + doing = DoingNone; } else if (args[i] == "MODULE") { moduleArgs.insert(i); doing = DoingNone; @@ -364,6 +367,12 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args) } } + if (!this->GlobalScope) { + cmValue value( + this->Makefile->GetDefinition("CMAKE_FIND_PACKAGE_TARGETS_GLOBAL")); + this->GlobalScope = value.IsOn(); + } + std::vector<std::string> doubledComponents; std::set_intersection(requiredComponents.begin(), requiredComponents.end(), optionalComponents.begin(), optionalComponents.end(), @@ -1200,6 +1209,11 @@ bool cmFindPackageCommand::ReadListFile(const std::string& f, PolicyScopeRule psr) { const bool noPolicyScope = !this->PolicyScope || psr == NoPolicyScope; + + using ITScope = cmMakefile::ImportedTargetScope; + ITScope scope = this->GlobalScope ? ITScope::Global : ITScope::Local; + cmMakefile::SetGlobalTargetImportScope globScope(this->Makefile, scope); + if (this->Makefile->ReadDependentFile(f, noPolicyScope)) { return true; } diff --git a/Source/cmFindPackageCommand.h b/Source/cmFindPackageCommand.h index f921bb0..b9f19e4 100644 --- a/Source/cmFindPackageCommand.h +++ b/Source/cmFindPackageCommand.h @@ -199,6 +199,7 @@ private: bool UseLibx32Paths = false; bool UseRealPath = false; bool PolicyScope = true; + bool GlobalScope = false; std::string LibraryArchitecture; std::vector<std::string> Names; std::vector<std::string> Configs; diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx index 187db73..d35d428 100644 --- a/Source/cmGeneratorExpressionDAGChecker.cxx +++ b/Source/cmGeneratorExpressionDAGChecker.cxx @@ -167,7 +167,7 @@ bool cmGeneratorExpressionDAGChecker::EvaluatingLinkExpression() const cm::string_view property(this->Top()->Property); return property == "LINK_DIRECTORIES"_s || property == "LINK_OPTIONS"_s || - property == "LINK_DEPENDS"_s; + property == "LINK_DEPENDS"_s || property == "LINK_LIBRARY_OVERRIDE"_s; } bool cmGeneratorExpressionDAGChecker::EvaluatingLinkOptionsExpression() const @@ -189,6 +189,8 @@ bool cmGeneratorExpressionDAGChecker::EvaluatingLinkLibraries( } return prop == "LINK_LIBRARIES"_s || prop == "INTERFACE_LINK_LIBRARIES"_s || + prop == "INTERFACE_LINK_LIBRARIES_DIRECT"_s || + prop == "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE"_s || prop == "LINK_INTERFACE_LIBRARIES"_s || prop == "IMPORTED_LINK_INTERFACE_LIBRARIES"_s || cmHasLiteralPrefix(prop, "LINK_INTERFACE_LIBRARIES_") || diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index 396e9c9..db043ec 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -1198,6 +1198,152 @@ static const struct LinkLanguageAndIdNode : public cmGeneratorExpressionNode } } linkLanguageAndIdNode; +static const struct LinkLibraryNode : public cmGeneratorExpressionNode +{ + LinkLibraryNode() {} // NOLINT(modernize-use-equals-default) + + int NumExpectedParameters() const override { return OneOrMoreParameters; } + + std::string Evaluate( + const std::vector<std::string>& parameters, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content, + cmGeneratorExpressionDAGChecker* dagChecker) const override + { + if (!context->HeadTarget || !dagChecker || + !dagChecker->EvaluatingLinkLibraries()) { + reportError(context, content->GetOriginalExpression(), + "$<LINK_LIBRARY:...> may only be used with binary targets " + "to specify link libraries."); + return std::string(); + } + + std::vector<std::string> list; + cmExpandLists(parameters.begin(), parameters.end(), list); + if (list.empty()) { + reportError( + context, content->GetOriginalExpression(), + "$<LINK_LIBRARY:...> expects a feature name as first argument."); + return std::string(); + } + if (list.size() == 1) { + // no libraries specified, ignore this genex + return std::string(); + } + + static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$"); + auto const& feature = list.front(); + if (!featureNameValidator.find(feature)) { + reportError(context, content->GetOriginalExpression(), + cmStrCat("The feature name '", feature, + "' contains invalid characters.")); + return std::string(); + } + + const auto LL_BEGIN = cmStrCat("<LINK_LIBRARY:", feature, '>'); + const auto LL_END = cmStrCat("</LINK_LIBRARY:", feature, '>'); + + // filter out $<LINK_LIBRARY:..> tags with same feature + // and raise an error for any different feature + cm::erase_if(list, [&](const std::string& item) -> bool { + return item == LL_BEGIN || item == LL_END; + }); + auto it = + std::find_if(list.cbegin() + 1, list.cend(), + [&feature](const std::string& item) -> bool { + return cmHasPrefix(item, "<LINK_LIBRARY:"_s) && + item.substr(14, item.find('>', 14) - 14) != feature; + }); + if (it != list.cend()) { + reportError( + context, content->GetOriginalExpression(), + "$<LINK_LIBRARY:...> with different features cannot be nested."); + return std::string(); + } + // $<LINK_GROUP:...> must not appear as part of $<LINK_LIBRARY:...> + it = std::find_if(list.cbegin() + 1, list.cend(), + [](const std::string& item) -> bool { + return cmHasPrefix(item, "<LINK_GROUP:"_s); + }); + if (it != list.cend()) { + reportError(context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> cannot be nested inside a " + "$<LINK_LIBRARY:...> expression."); + return std::string(); + } + + list.front() = LL_BEGIN; + list.push_back(LL_END); + + return cmJoin(list, ";"_s); + } +} linkLibraryNode; + +static const struct LinkGroupNode : public cmGeneratorExpressionNode +{ + LinkGroupNode() {} // NOLINT(modernize-use-equals-default) + + int NumExpectedParameters() const override { return OneOrMoreParameters; } + + std::string Evaluate( + const std::vector<std::string>& parameters, + cmGeneratorExpressionContext* context, + const GeneratorExpressionContent* content, + cmGeneratorExpressionDAGChecker* dagChecker) const override + { + if (!context->HeadTarget || !dagChecker || + !dagChecker->EvaluatingLinkLibraries()) { + reportError(context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> may only be used with binary targets " + "to specify group of link libraries."); + return std::string(); + } + + std::vector<std::string> list; + cmExpandLists(parameters.begin(), parameters.end(), list); + if (list.empty()) { + reportError( + context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> expects a feature name as first argument."); + return std::string(); + } + // $<LINK_GROUP:..> cannot be nested + if (std::find_if(list.cbegin(), list.cend(), + [](const std::string& item) -> bool { + return cmHasPrefix(item, "<LINK_GROUP"_s); + }) != list.cend()) { + reportError(context, content->GetOriginalExpression(), + "$<LINK_GROUP:...> cannot be nested."); + return std::string(); + } + if (list.size() == 1) { + // no libraries specified, ignore this genex + return std::string(); + } + + static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$"); + auto const& feature = list.front(); + if (!featureNameValidator.find(feature)) { + reportError(context, content->GetOriginalExpression(), + cmStrCat("The feature name '", feature, + "' contains invalid characters.")); + return std::string(); + } + + const auto LG_BEGIN = cmStrCat( + "<LINK_GROUP:", feature, ':', + cmJoin(cmRange<decltype(list.cbegin())>(list.cbegin() + 1, list.cend()), + "|"_s), + '>'); + const auto LG_END = cmStrCat("</LINK_GROUP:", feature, '>'); + + list.front() = LG_BEGIN; + list.push_back(LG_END); + + return cmJoin(list, ";"_s); + } +} linkGroupNode; + static const struct HostLinkNode : public cmGeneratorExpressionNode { HostLinkNode() {} // NOLINT(modernize-use-equals-default) @@ -2668,6 +2814,8 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode( { "COMPILE_LANGUAGE", &languageNode }, { "LINK_LANG_AND_ID", &linkLanguageAndIdNode }, { "LINK_LANGUAGE", &linkLanguageNode }, + { "LINK_LIBRARY", &linkLibraryNode }, + { "LINK_GROUP", &linkGroupNode }, { "HOST_LINK", &hostLinkNode }, { "DEVICE_LINK", &deviceLinkNode }, { "SHELL_PATH", &shellPathNode } diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index a8bc91c..ab3ed12 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -58,6 +58,10 @@ using LinkInterfaceFor = cmGeneratorTarget::LinkInterfaceFor; const cmsys::RegularExpression FrameworkRegularExpression( "^(.*/)?([^/]*)\\.framework/(.*)$"); const std::string kINTERFACE_LINK_LIBRARIES = "INTERFACE_LINK_LIBRARIES"; +const std::string kINTERFACE_LINK_LIBRARIES_DIRECT = + "INTERFACE_LINK_LIBRARIES_DIRECT"; +const std::string kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE = + "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE"; } template <> @@ -2043,7 +2047,11 @@ void cmGeneratorTarget::ComputeKindedSources(KindedSources& files, } else if (ext == "appxmanifest") { kind = SourceKindAppManifest; } else if (ext == "manifest") { - kind = SourceKindManifest; + if (sf->GetPropertyAsBool("VS_DEPLOYMENT_CONTENT")) { + kind = SourceKindExtra; + } else { + kind = SourceKindManifest; + } } else if (ext == "pfx") { kind = SourceKindCertificate; } else if (ext == "xaml") { @@ -3459,6 +3467,23 @@ void cmGeneratorTarget::AddCUDAArchitectureFlags(std::string& flags) const property = *this->Makefile->GetDefinition("CMAKE_CUDA_ARCHITECTURES_ALL_MAJOR"); } + } else if (property == "native") { + cmValue native = + this->Makefile->GetDefinition("CMAKE_CUDA_ARCHITECTURES_NATIVE"); + if (native.IsEmpty()) { + this->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + "CUDA_ARCHITECTURES is set to \"native\", but no GPU was detected."); + } + if (compiler == "NVIDIA" && + cmSystemTools::VersionCompare( + cmSystemTools::OP_GREATER_EQUAL, + this->Makefile->GetDefinition("CMAKE_CUDA_COMPILER_VERSION"), + "11.6")) { + flags = cmStrCat(flags, " -arch=", property); + return; + } + property = *native; } struct CudaArchitecture @@ -4619,7 +4644,8 @@ std::vector<BT<std::string>> cmGeneratorTarget::GetLinkOptions( } std::vector<BT<std::string>>& cmGeneratorTarget::ResolveLinkerWrapper( - std::vector<BT<std::string>>& result, const std::string& language) const + std::vector<BT<std::string>>& result, const std::string& language, + bool joinItems) const { // replace "LINKER:" prefixed elements by actual linker wrapper const std::string wrapper(this->Makefile->GetSafeDefinition( @@ -4678,7 +4704,14 @@ std::vector<BT<std::string>>& cmGeneratorTarget::ResolveLinkerWrapper( std::vector<BT<std::string>> options = wrapOptions( linkerOptions, bt, wrapperFlag, wrapperSep, concatFlagAndArgs); - result.insert(entry, options.begin(), options.end()); + if (joinItems) { + result.insert(entry, + cmJoin(cmRange<decltype(options.cbegin())>( + options.cbegin(), options.cend()), + " "_s)); + } else { + result.insert(entry, options.begin(), options.end()); + } } return result; } @@ -6325,7 +6358,8 @@ cm::string_view missingTargetPossibleReasons = bool cmGeneratorTarget::VerifyLinkItemColons(LinkItemRole role, cmLinkItem const& item) const { - if (item.Target || item.AsStr().find("::") == std::string::npos) { + if (item.Target || cmHasPrefix(item.AsStr(), "<LINK_GROUP:"_s) || + item.AsStr().find("::") == std::string::npos) { return true; } MessageType messageType = MessageType::FATAL_ERROR; @@ -6371,7 +6405,9 @@ bool cmGeneratorTarget::VerifyLinkItemIsTarget(LinkItemRole role, std::string const& str = item.AsStr(); if (!str.empty() && (str[0] == '-' || str[0] == '$' || str[0] == '`' || - str.find_first_of("/\\") != std::string::npos)) { + str.find_first_of("/\\") != std::string::npos || + cmHasPrefix(str, "<LINK_LIBRARY:"_s) || + cmHasPrefix(str, "<LINK_GROUP:"_s))) { return true; } @@ -6667,12 +6703,10 @@ cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem( return maybeItem; } -void cmGeneratorTarget::ExpandLinkItems(std::string const& prop, - cmBTStringRange entries, - std::string const& config, - cmGeneratorTarget const* headTarget, - LinkInterfaceFor interfaceFor, - cmLinkInterface& iface) const +void cmGeneratorTarget::ExpandLinkItems( + std::string const& prop, cmBTStringRange entries, std::string const& config, + cmGeneratorTarget const* headTarget, LinkInterfaceFor interfaceFor, + LinkInterfaceField field, cmLinkInterface& iface) const { if (entries.empty()) { return; @@ -6696,9 +6730,19 @@ void cmGeneratorTarget::ExpandLinkItems(std::string const& prop, this, headTarget->LinkerLanguage)); for (std::string const& lib : libs) { if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem( - lib, cge->GetBacktrace(), &scope, LookupSelf::No)) { + lib, cge->GetBacktrace(), &scope, + field == LinkInterfaceField::Libraries ? LookupSelf::No + : LookupSelf::Yes)) { cmLinkItem item = std::move(*maybeItem); + if (field == LinkInterfaceField::HeadInclude) { + iface.HeadInclude.emplace_back(std::move(item)); + continue; + } + if (field == LinkInterfaceField::HeadExclude) { + iface.HeadExclude.emplace_back(std::move(item)); + continue; + } if (!item.Target) { // Report explicitly linked object files separately. std::string const& maybeObj = item.AsStr(); @@ -7169,7 +7213,9 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries( this->GetPolicyStatusCMP0022() != cmPolicies::WARN); if (cmp0022NEW) { // CMP0022 NEW behavior is to use INTERFACE_LINK_LIBRARIES. - haveExplicitLibraries = !this->Target->GetLinkInterfaceEntries().empty(); + haveExplicitLibraries = !this->Target->GetLinkInterfaceEntries().empty() || + !this->Target->GetLinkInterfaceDirectEntries().empty() || + !this->Target->GetLinkInterfaceDirectExcludeEntries().empty(); } else { // CMP0022 OLD behavior is to use LINK_INTERFACE_LIBRARIES if set on a // shared lib or executable. @@ -7234,15 +7280,24 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries( if (cmp0022NEW) { // The interface libraries are specified by INTERFACE_LINK_LIBRARIES. // Use its special representation directly to get backtraces. - this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES, - this->Target->GetLinkInterfaceEntries(), config, - headTarget, interfaceFor, iface); + this->ExpandLinkItems( + kINTERFACE_LINK_LIBRARIES, this->Target->GetLinkInterfaceEntries(), + config, headTarget, interfaceFor, LinkInterfaceField::Libraries, iface); + this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT, + this->Target->GetLinkInterfaceDirectEntries(), + config, headTarget, interfaceFor, + LinkInterfaceField::HeadInclude, iface); + this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE, + this->Target->GetLinkInterfaceDirectExcludeEntries(), + config, headTarget, interfaceFor, + LinkInterfaceField::HeadExclude, iface); } else if (explicitLibrariesCMP0022OLD) { // The interface libraries have been explicitly set in pre-CMP0022 style. std::vector<BT<std::string>> entries; entries.emplace_back(*explicitLibrariesCMP0022OLD); this->ExpandLinkItems(linkIfacePropCMP0022OLD, cmMakeRange(entries), - config, headTarget, interfaceFor, iface); + config, headTarget, interfaceFor, + LinkInterfaceField::Libraries, iface); } // If the link interface is explicit, do not fall back to the link impl. @@ -7262,7 +7317,8 @@ void cmGeneratorTarget::ComputeLinkInterfaceLibraries( cmLinkInterface ifaceNew; this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES, this->Target->GetLinkInterfaceEntries(), config, - headTarget, interfaceFor, ifaceNew); + headTarget, interfaceFor, + LinkInterfaceField::Libraries, ifaceNew); if (ifaceNew.Libraries != iface.Libraries) { std::string oldLibraries = cmJoin(impl->Libraries, ";"); std::string newLibraries = cmJoin(ifaceNew.Libraries, ";"); @@ -7402,8 +7458,17 @@ const cmLinkInterface* cmGeneratorTarget::GetImportLinkInterface( iface.LibrariesDone = true; iface.Multiplicity = info->Multiplicity; cmExpandList(info->Languages, iface.Languages); + this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT, + cmMakeRange(info->LibrariesHeadInclude), config, + headTarget, interfaceFor, + LinkInterfaceField::HeadInclude, iface); + this->ExpandLinkItems(kINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE, + cmMakeRange(info->LibrariesHeadExclude), config, + headTarget, interfaceFor, + LinkInterfaceField::HeadExclude, iface); this->ExpandLinkItems(info->LibrariesProp, cmMakeRange(info->Libraries), - config, headTarget, interfaceFor, iface); + config, headTarget, interfaceFor, + LinkInterfaceField::Libraries, iface); std::vector<std::string> deps = cmExpandedList(info->SharedDeps); LookupLinkItemScope scope{ this->LocalGenerator }; for (std::string const& dep : deps) { @@ -7496,6 +7561,14 @@ void cmGeneratorTarget::ComputeImportInfo(std::string const& desired_config, } } } + for (BT<std::string> const& entry : + this->Target->GetLinkInterfaceDirectEntries()) { + info.LibrariesHeadInclude.emplace_back(entry); + } + for (BT<std::string> const& entry : + this->Target->GetLinkInterfaceDirectExcludeEntries()) { + info.LibrariesHeadExclude.emplace_back(entry); + } if (this->GetType() == cmStateEnums::INTERFACE_LIBRARY) { if (loc) { info.LibName = *loc; @@ -7929,6 +8002,112 @@ bool cmGeneratorTarget::IsNullImpliedByLinkLibraries( return cm::contains(this->LinkImplicitNullProperties, p); } +namespace { +class TransitiveLinkImpl +{ + cmGeneratorTarget const* Self; + std::string const& Config; + cmLinkImplementation& Impl; + + std::set<cmLinkItem> Emitted; + std::set<cmLinkItem> Excluded; + std::unordered_set<cmGeneratorTarget const*> Followed; + + void Follow(cmGeneratorTarget const* target); + +public: + TransitiveLinkImpl(cmGeneratorTarget const* self, std::string const& config, + cmLinkImplementation& impl) + : Self(self) + , Config(config) + , Impl(impl) + { + } + + void Compute(); +}; + +void TransitiveLinkImpl::Follow(cmGeneratorTarget const* target) +{ + if (!target || !this->Followed.insert(target).second || + target->GetPolicyStatusCMP0022() == cmPolicies::OLD || + target->GetPolicyStatusCMP0022() == cmPolicies::WARN) { + return; + } + + // Get this target's usage requirements. + cmLinkInterfaceLibraries const* iface = target->GetLinkInterfaceLibraries( + this->Config, this->Self, LinkInterfaceFor::Usage); + if (!iface) { + return; + } + if (iface->HadContextSensitiveCondition) { + this->Impl.HadContextSensitiveCondition = true; + } + + // Process 'INTERFACE_LINK_LIBRARIES_DIRECT' usage requirements. + for (cmLinkItem const& item : iface->HeadInclude) { + // Inject direct dependencies from the item's usage requirements + // before the item itself. + this->Follow(item.Target); + + // Add the item itself, but at most once. + if (this->Emitted.insert(item).second) { + this->Impl.Libraries.emplace_back(item, /* checkCMP0027= */ false); + } + } + + // Follow transitive dependencies. + for (cmLinkItem const& item : iface->Libraries) { + this->Follow(item.Target); + } + + // Record exclusions from 'INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE' + // usage requirements. + for (cmLinkItem const& item : iface->HeadExclude) { + this->Excluded.insert(item); + } +} + +void TransitiveLinkImpl::Compute() +{ + // Save the original items and start with an empty list. + std::vector<cmLinkImplItem> original = std::move(this->Impl.Libraries); + + // Avoid injecting any original items as usage requirements. + // This gives LINK_LIBRARIES final control over the order + // if it explicitly lists everything. + this->Emitted.insert(original.cbegin(), original.cend()); + + // Process each original item. + for (cmLinkImplItem& item : original) { + // Inject direct dependencies listed in 'INTERFACE_LINK_LIBRARIES_DIRECT' + // usage requirements before the item itself. + this->Follow(item.Target); + + // Add the item itself. + this->Impl.Libraries.emplace_back(std::move(item)); + } + + // Remove items listed in 'INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE' + // usage requirements found through any dependency above. + this->Impl.Libraries.erase( + std::remove_if(this->Impl.Libraries.begin(), this->Impl.Libraries.end(), + [this](cmLinkImplItem const& item) { + return this->Excluded.find(item) != this->Excluded.end(); + }), + this->Impl.Libraries.end()); +} + +void ComputeLinkImplTransitive(cmGeneratorTarget const* self, + std::string const& config, + cmLinkImplementation& impl) +{ + TransitiveLinkImpl transitiveLinkImpl(self, config, impl); + transitiveLinkImpl.Compute(); +} +} + void cmGeneratorTarget::ComputeLinkImplementationLibraries( const std::string& config, cmOptionalLinkImplementation& impl, cmGeneratorTarget const* head) const @@ -8035,6 +8214,11 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries( cge->GetMaxLanguageStandard(this, this->MaxLanguageStandards); } + // Update the list of direct link dependencies from usage requirements. + if (head == this) { + ComputeLinkImplTransitive(this, config, impl); + } + // Get the list of configurations considered to be DEBUG. std::vector<std::string> debugConfigs = this->Makefile->GetCMakeInstance()->GetDebugConfigs(); diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index 7cf1720..3e30913 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -513,7 +513,8 @@ public: std::string const& config, std::string const& language) const; std::vector<BT<std::string>>& ResolveLinkerWrapper( - std::vector<BT<std::string>>& result, const std::string& language) const; + std::vector<BT<std::string>>& result, const std::string& language, + bool joinItems = false) const; void GetStaticLibraryLinkOptions(std::vector<std::string>& result, const std::string& config, @@ -1006,6 +1007,8 @@ private: std::string Languages; std::string LibrariesProp; std::vector<BT<std::string>> Libraries; + std::vector<BT<std::string>> LibrariesHeadInclude; + std::vector<BT<std::string>> LibrariesHeadExclude; std::string SharedDeps; }; @@ -1066,10 +1069,16 @@ private: bool IsLinkLookupScope(std::string const& n, cmLocalGenerator const*& lg) const; + enum class LinkInterfaceField + { + Libraries, + HeadExclude, + HeadInclude, + }; void ExpandLinkItems(std::string const& prop, cmBTStringRange entries, std::string const& config, const cmGeneratorTarget* headTarget, - LinkInterfaceFor interfaceFor, + LinkInterfaceFor interfaceFor, LinkInterfaceField field, cmLinkInterface& iface) const; struct LookupLinkItemScope diff --git a/Source/cmGhsMultiTargetGenerator.cxx b/Source/cmGhsMultiTargetGenerator.cxx index 47cefae..a7796c9 100644 --- a/Source/cmGhsMultiTargetGenerator.cxx +++ b/Source/cmGhsMultiTargetGenerator.cxx @@ -36,11 +36,6 @@ cmGhsMultiTargetGenerator::cmGhsMultiTargetGenerator(cmGeneratorTarget* target) static_cast<cmLocalGhsMultiGenerator*>(target->GetLocalGenerator())) , Makefile(target->Target->GetMakefile()) , Name(target->GetName()) -#ifdef _WIN32 - , CmdWindowsShell(true) -#else - , CmdWindowsShell(false) -#endif { // Store the configuration name that is being used if (cmValue config = this->Makefile->GetDefinition("CMAKE_BUILD_TYPE")) { @@ -112,12 +107,6 @@ void cmGhsMultiTargetGenerator::Generate() return; } - // Tell the global generator the name of the project file - this->GeneratorTarget->Target->SetProperty("GENERATOR_FILE_NAME", - this->Name); - this->GeneratorTarget->Target->SetProperty( - "GENERATOR_FILE_NAME_EXT", GhsMultiGpj::GetGpjTag(this->TagType)); - this->GenerateTarget(); } @@ -126,7 +115,14 @@ void cmGhsMultiTargetGenerator::GenerateTarget() // Open the target file in copy-if-different mode. std::string fproj = cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/', - this->Name, cmGlobalGhsMultiGenerator::FILE_EXTENSION); + this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), + '/', this->Name, cmGlobalGhsMultiGenerator::FILE_EXTENSION); + + // Tell the global generator the name of the project file + this->GeneratorTarget->Target->SetProperty("GENERATOR_FILE_NAME", fproj); + this->GeneratorTarget->Target->SetProperty( + "GENERATOR_FILE_NAME_EXT", GhsMultiGpj::GetGpjTag(this->TagType)); + cmGeneratedFileStream fout(fproj); fout.SetCopyIfDifferent(true); @@ -160,10 +156,16 @@ void cmGhsMultiTargetGenerator::WriteTargetSpecifics(std::ostream& fout, { std::string outpath; + /* Determine paths from the target project file to where the output artifacts + * need to be located. + */ if (this->TagType != GhsMultiGpj::SUBPROJECT) { // set target binary file destination - outpath = this->GeneratorTarget->GetDirectory(config); - outpath = this->LocalGenerator->MaybeRelativeToCurBinDir(outpath); + std::string binpath = cmStrCat( + this->LocalGenerator->GetCurrentBinaryDirectory(), '/', + this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget)); + outpath = cmSystemTools::RelativePath( + binpath, this->GeneratorTarget->GetDirectory(config)); /* clang-format off */ fout << " :binDirRelative=\"" << outpath << "\"\n" " -o \"" << this->TargetNameReal << "\"\n"; @@ -171,7 +173,7 @@ void cmGhsMultiTargetGenerator::WriteTargetSpecifics(std::ostream& fout, } // set target object file destination - outpath = this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget); + outpath = "."; fout << " :outputDirRelative=\"" << outpath << "\"\n"; } @@ -187,6 +189,7 @@ void cmGhsMultiTargetGenerator::SetCompilerFlags(std::string const& config, language, config); this->LocalGenerator->AddVisibilityPresetFlags( flags, this->GeneratorTarget, language); + this->LocalGenerator->AddColorDiagnosticsFlags(flags, language); // Append old-style preprocessor definition flags. if (this->Makefile->GetDefineFlags() != " ") { @@ -316,19 +319,37 @@ void cmGhsMultiTargetGenerator::WriteTargetLinkLine(std::ostream& fout, void cmGhsMultiTargetGenerator::WriteBuildEvents(std::ostream& fout) { - this->WriteBuildEventsHelper( - fout, this->GeneratorTarget->GetPreBuildCommands(), - std::string("prebuild"), std::string("preexecShell")); + this->WriteBuildEventsHelper(fout, + this->GeneratorTarget->GetPreBuildCommands(), + std::string("prebuild"), +#ifdef _WIN32 + std::string("preexecShell") +#else + std::string("preexec") +#endif + ); if (this->TagType != GhsMultiGpj::CUSTOM_TARGET) { - this->WriteBuildEventsHelper( - fout, this->GeneratorTarget->GetPreLinkCommands(), - std::string("prelink"), std::string("preexecShell")); + this->WriteBuildEventsHelper(fout, + this->GeneratorTarget->GetPreLinkCommands(), + std::string("prelink"), +#ifdef _WIN32 + std::string("preexecShell") +#else + std::string("preexec") +#endif + ); } - this->WriteBuildEventsHelper( - fout, this->GeneratorTarget->GetPostBuildCommands(), - std::string("postbuild"), std::string("postexecShell")); + this->WriteBuildEventsHelper(fout, + this->GeneratorTarget->GetPostBuildCommands(), + std::string("postbuild"), +#ifdef _WIN32 + std::string("postexecShell") +#else + std::string("postexec") +#endif + ); } void cmGhsMultiTargetGenerator::WriteBuildEventsHelper( @@ -336,6 +357,13 @@ void cmGhsMultiTargetGenerator::WriteBuildEventsHelper( std::string const& name, std::string const& cmd) { int cmdcount = 0; +#ifdef _WIN32 + std::string fext = ".bat"; + std::string shell; +#else + std::string fext = ".sh"; + std::string shell = "/bin/sh "; +#endif for (cmCustomCommand const& cc : ccv) { cmCustomCommandGenerator ccg(cc, this->ConfigName, this->LocalGenerator); @@ -343,14 +371,14 @@ void cmGhsMultiTargetGenerator::WriteBuildEventsHelper( std::string fname = cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/', this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), - '/', this->Name, '_', name, cmdcount++, - this->CmdWindowsShell ? ".bat" : ".sh"); + '/', this->Name, '_', name, cmdcount++, fext); + cmGeneratedFileStream f(fname); f.SetCopyIfDifferent(true); this->WriteCustomCommandsHelper(f, ccg); f.Close(); if (this->TagType != GhsMultiGpj::CUSTOM_TARGET) { - fout << " :" << cmd << "=\"" << fname << "\"\n"; + fout << " :" << cmd << "=\"" << shell << fname << "\"\n"; } else { fout << fname << "\n :outputName=\"" << fname << ".rule\"\n"; } @@ -409,15 +437,15 @@ void cmGhsMultiTargetGenerator::WriteCustomCommandsHelper( // bool useCall = false; - if (this->CmdWindowsShell) { - std::string suffix; - if (cmd.size() > 4) { - suffix = cmSystemTools::LowerCase(cmd.substr(cmd.size() - 4)); - if (suffix == ".bat" || suffix == ".cmd") { - useCall = true; - } +#ifdef _WIN32 + std::string suffix; + if (cmd.size() > 4) { + suffix = cmSystemTools::LowerCase(cmd.substr(cmd.size() - 4)); + if (suffix == ".bat" || suffix == ".cmd") { + useCall = true; } } +#endif cmSystemTools::ReplaceString(cmd, "/./", "/"); // Convert the command to a relative path only if the current @@ -555,11 +583,12 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) // Open the filestream in copy-if-different mode. std::string gname = sg; cmsys::SystemTools::ReplaceString(gname, "\\", "_"); - std::string lpath = cmStrCat( - this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), '/', - gname, cmGlobalGhsMultiGenerator::FILE_EXTENSION); + std::string lpath = + cmStrCat(gname, cmGlobalGhsMultiGenerator::FILE_EXTENSION); std::string fpath = cmStrCat( - this->LocalGenerator->GetCurrentBinaryDirectory(), '/', lpath); + this->LocalGenerator->GetCurrentBinaryDirectory(), '/', + this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), '/', + lpath); cmGeneratedFileStream* f = new cmGeneratedFileStream(fpath); f->SetCopyIfDifferent(true); gfiles.push_back(f); @@ -645,6 +674,11 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) } } int cmdcount = 0; +#ifdef _WIN32 + std::string fext = ".bat"; +#else + std::string fext = ".sh"; +#endif for (auto& sf : customCommands) { const cmCustomCommand* cc = sf->GetCustomCommand(); cmCustomCommandGenerator ccg(*cc, this->ConfigName, @@ -655,8 +689,8 @@ void cmGhsMultiTargetGenerator::WriteSources(std::ostream& fout_proj) this->LocalGenerator->GetCurrentBinaryDirectory(), '/', this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), '/', this->Name, "_cc", cmdcount++, '_', - (sf->GetLocation()).GetName(), - this->CmdWindowsShell ? ".bat" : ".sh"); + (sf->GetLocation()).GetName(), fext); + cmGeneratedFileStream f(fname); f.SetCopyIfDifferent(true); this->WriteCustomCommandsHelper(f, ccg); diff --git a/Source/cmGhsMultiTargetGenerator.h b/Source/cmGhsMultiTargetGenerator.h index e9d7537..9289a72 100644 --- a/Source/cmGhsMultiTargetGenerator.h +++ b/Source/cmGhsMultiTargetGenerator.h @@ -78,6 +78,5 @@ private: std::string TargetNameReal; GhsMultiGpj::Types TagType; std::string const Name; - std::string ConfigName; /* CMAKE_BUILD_TYPE */ - bool const CmdWindowsShell; /* custom commands run in cmd.exe or /bin/sh */ + std::string ConfigName; /* CMAKE_BUILD_TYPE */ }; diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 156ecce..baa54e5 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -19,6 +19,7 @@ #include "cmsys/Directory.hxx" #include "cmsys/FStream.hxx" +#include "cmsys/RegularExpression.hxx" #if defined(_WIN32) && !defined(__CYGWIN__) # include <windows.h> @@ -2522,6 +2523,47 @@ bool cmGlobalGenerator::NameResolvesToFramework( return false; } +// If the file has no extension it's either a raw executable or might +// be a direct reference to a binary within a framework (bad practice!). +// This is where we change the path to point to the framework directory. +// .tbd files also can be located in SDK frameworks (they are +// placeholders for actual libraries shipped with the OS) +cm::optional<std::pair<std::string, std::string>> +cmGlobalGenerator::SplitFrameworkPath(const std::string& path, + bool extendedFormat) const +{ + // Check for framework structure: + // (/path/to/)?FwName.framework + // or (/path/to/)?FwName.framework/FwName(.tbd)? + // or (/path/to/)?FwName.framework/Versions/*/FwName(.tbd)? + static cmsys::RegularExpression frameworkPath( + "((.+)/)?(.+)\\.framework(/Versions/[^/]+)?(/(.+))?$"); + + auto ext = cmSystemTools::GetFilenameLastExtension(path); + if ((ext.empty() || ext == ".tbd" || ext == ".framework") && + frameworkPath.find(path)) { + auto name = frameworkPath.match(3); + auto libname = + cmSystemTools::GetFilenameWithoutExtension(frameworkPath.match(6)); + if (!libname.empty() && name != libname) { + return cm::nullopt; + } + return std::pair<std::string, std::string>{ frameworkPath.match(2), name }; + } + + if (extendedFormat) { + // path format can be more flexible: (/path/to/)?fwName(.framework)? + auto fwDir = cmSystemTools::GetParentDirectory(path); + auto name = cmSystemTools::GetFilenameLastExtension(path) == ".framework" + ? cmSystemTools::GetFilenameWithoutExtension(path) + : cmSystemTools::GetFilenameName(path); + + return std::pair<std::string, std::string>{ fwDir, name }; + } + + return cm::nullopt; +} + bool cmGlobalGenerator::CheckCMP0037(std::string const& targetName, std::string const& reason) const { diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index a43d4a6..a4b2ae3 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -367,6 +367,13 @@ public: /** Determine if a name resolves to a framework on disk or a built target that is a framework. */ bool NameResolvesToFramework(const std::string& libname) const; + /** Split a framework path to the directory and name of the framework + * returns std::nullopt if the path does not match with framework format + * when extendedFormat is true, required format is relaxed (i.e. extension + * `.framework' is optional). Used when FRAMEWORK link feature is + * specified */ + cm::optional<std::pair<std::string, std::string>> SplitFrameworkPath( + const std::string& path, bool extendedFormat = false) const; cmMakefile* FindMakefile(const std::string& start_dir) const; cmLocalGenerator* FindLocalGenerator(cmDirectoryId const& id) const; diff --git a/Source/cmGlobalGhsMultiGenerator.cxx b/Source/cmGlobalGhsMultiGenerator.cxx index 5e51dd2..f405a04 100644 --- a/Source/cmGlobalGhsMultiGenerator.cxx +++ b/Source/cmGlobalGhsMultiGenerator.cxx @@ -2,14 +2,19 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmGlobalGhsMultiGenerator.h" +#include <algorithm> +#include <functional> #include <map> -#include <ostream> +#include <sstream> #include <utility> #include <cm/memory> #include <cm/string> #include <cmext/algorithm> +#include <cmext/memory> +#include "cmCustomCommand.h" +#include "cmCustomCommandLines.h" #include "cmDocumentationEntry.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" @@ -18,10 +23,13 @@ #include "cmLocalGhsMultiGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" +#include "cmPolicies.h" +#include "cmSourceFile.h" #include "cmState.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" +#include "cmTarget.h" #include "cmValue.h" #include "cmVersion.h" #include "cmake.h" @@ -32,6 +40,8 @@ const char* cmGlobalGhsMultiGenerator::DEFAULT_BUILD_PROGRAM = "gbuild"; #elif defined(_WIN32) const char* cmGlobalGhsMultiGenerator::DEFAULT_BUILD_PROGRAM = "gbuild.exe"; #endif +const char* cmGlobalGhsMultiGenerator::CHECK_BUILD_SYSTEM_TARGET = + "RERUN_CMAKE"; cmGlobalGhsMultiGenerator::cmGlobalGhsMultiGenerator(cmake* cm) : cmGlobalGenerator(cm) @@ -279,7 +289,7 @@ void cmGlobalGhsMultiGenerator::WriteTopLevelProject(std::ostream& fout, { this->WriteFileHeader(fout); this->WriteMacros(fout, root); - this->WriteHighLevelDirectives(root, fout); + this->WriteHighLevelDirectives(fout, root); GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fout); fout << "# Top Level Project File\n"; @@ -308,9 +318,13 @@ void cmGlobalGhsMultiGenerator::WriteTopLevelProject(std::ostream& fout, } void cmGlobalGhsMultiGenerator::WriteSubProjects(std::ostream& fout, - std::string& all_target) + bool filterPredefined) { - fout << "CMakeFiles/" << all_target << " [Project]\n"; + std::set<std::string> predefinedTargets; + predefinedTargets.insert(this->GetInstallTargetName()); + predefinedTargets.insert(this->GetAllTargetName()); + predefinedTargets.insert(std::string(CHECK_BUILD_SYSTEM_TARGET)); + // All known targets for (cmGeneratorTarget const* target : this->ProjectTargets) { if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || @@ -320,8 +334,13 @@ void cmGlobalGhsMultiGenerator::WriteSubProjects(std::ostream& fout, target->GetName() != this->GetInstallTargetName())) { continue; } - fout << "CMakeFiles/" << target->GetName() + ".tgt" + FILE_EXTENSION - << " [Project]\n"; + /* Check if the current target is a predefined CMake target */ + bool predefinedTarget = + predefinedTargets.find(target->GetName()) != predefinedTargets.end(); + if ((filterPredefined && predefinedTarget) || + (!filterPredefined && !predefinedTarget)) { + fout << target->GetName() + ".tgt" + FILE_EXTENSION << " [Project]\n"; + } } } @@ -329,36 +348,22 @@ void cmGlobalGhsMultiGenerator::WriteProjectLine( std::ostream& fout, cmGeneratorTarget const* target, std::string& rootBinaryDir) { - cmValue projName = target->GetProperty("GENERATOR_FILE_NAME"); + cmValue projFile = target->GetProperty("GENERATOR_FILE_NAME"); cmValue projType = target->GetProperty("GENERATOR_FILE_NAME_EXT"); - if (projName && projType) { - cmLocalGenerator* lg = target->GetLocalGenerator(); - std::string dir = lg->GetCurrentBinaryDirectory(); - dir = cmSystemTools::ForceToRelativePath(rootBinaryDir, dir); - if (dir == ".") { - dir.clear(); - } else { - if (dir.back() != '/') { - dir += "/"; - } - } + /* If either value is not valid then this particular target is an + * unsupported target type and should be skipped. + */ + if (projFile && projType) { + std::string path = cmSystemTools::RelativePath(rootBinaryDir, projFile); - std::string projFile = dir + *projName + FILE_EXTENSION; - fout << projFile; + fout << path; fout << ' ' << *projType << '\n'; - } else { - /* Should never happen */ - std::string message = - "The project file for target [" + target->GetName() + "] is missing.\n"; - cmSystemTools::Error(message); - fout << "{comment} " << target->GetName() << " [missing project file]\n"; } } void cmGlobalGhsMultiGenerator::WriteTargets(cmLocalGenerator* root) { - std::string rootBinaryDir = - cmStrCat(root->GetCurrentBinaryDirectory(), "/CMakeFiles"); + std::string rootBinaryDir = root->GetCurrentBinaryDirectory(); // All known targets for (cmGeneratorTarget const* target : this->ProjectTargets) { @@ -391,62 +396,6 @@ void cmGlobalGhsMultiGenerator::WriteTargets(cmLocalGenerator* root) } } -void cmGlobalGhsMultiGenerator::WriteAllTarget( - cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators, - std::string& all_target) -{ - this->ProjectTargets.clear(); - - // create target build file - all_target = root->GetProjectName() + "." + this->GetAllTargetName() + - ".tgt" + FILE_EXTENSION; - std::string fname = - root->GetCurrentBinaryDirectory() + "/CMakeFiles/" + all_target; - cmGeneratedFileStream fbld(fname); - fbld.SetCopyIfDifferent(true); - this->WriteFileHeader(fbld); - GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fbld); - - // Collect all targets under this root generator and the transitive - // closure of their dependencies. - TargetDependSet projectTargets; - TargetDependSet originalTargets; - this->GetTargetSets(projectTargets, originalTargets, root, generators); - OrderedTargetDependSet sortedProjectTargets(projectTargets, ""); - std::vector<cmGeneratorTarget const*> defaultTargets; - for (cmGeneratorTarget const* t : sortedProjectTargets) { - /* save list of all targets in sorted order */ - this->ProjectTargets.push_back(t); - } - for (cmGeneratorTarget const* t : sortedProjectTargets) { - if (!t->IsInBuildSystem()) { - continue; - } - if (!this->IsExcluded(t->GetLocalGenerator(), t)) { - defaultTargets.push_back(t); - } - } - std::vector<cmGeneratorTarget const*> build; - if (this->ComputeTargetBuildOrder(defaultTargets, build)) { - std::string message = "The inter-target dependency graph for project [" + - root->GetProjectName() + "] had a cycle.\n"; - cmSystemTools::Error(message); - } else { - // determine the targets for ALL target - std::string rootBinaryDir = - cmStrCat(root->GetCurrentBinaryDirectory(), "/CMakeFiles"); - for (cmGeneratorTarget const* target : build) { - if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || - target->GetType() == cmStateEnums::MODULE_LIBRARY || - target->GetType() == cmStateEnums::SHARED_LIBRARY) { - continue; - } - this->WriteProjectLine(fbld, target, rootBinaryDir); - } - } - fbld.Close(); -} - void cmGlobalGhsMultiGenerator::Generate() { std::string fname; @@ -476,18 +425,36 @@ void cmGlobalGhsMultiGenerator::Generate() this->WriteFileHeader(ftarget); this->WriteCustomTargetBOD(ftarget); ftarget.Close(); + + // create the stamp file when running CMake + if (!this->StampFile.empty()) { + cmGeneratedFileStream fstamp(this->StampFile); + fstamp.SetCopyIfDifferent(false); + fstamp.Close(); + } } void cmGlobalGhsMultiGenerator::OutputTopLevelProject( cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators) { std::string fname; - std::string all_target; if (generators.empty()) { return; } + // Collect all targets under this root generator and the transitive + // closure of their dependencies. + TargetDependSet projectTargets; + TargetDependSet originalTargets; + this->GetTargetSets(projectTargets, originalTargets, root, generators); + OrderedTargetDependSet sortedProjectTargets(projectTargets, ""); + this->ProjectTargets.clear(); + for (cmGeneratorTarget const* t : sortedProjectTargets) { + /* save list of all targets in sorted order */ + this->ProjectTargets.push_back(t); + } + /* Name top-level projects as filename.top.gpj to avoid name clashes * with target projects. This avoid the issue where the project has * the same name as the executable target. @@ -498,11 +465,9 @@ void cmGlobalGhsMultiGenerator::OutputTopLevelProject( cmGeneratedFileStream top(fname); top.SetCopyIfDifferent(true); this->WriteTopLevelProject(top, root); - - this->WriteAllTarget(root, generators, all_target); this->WriteTargets(root); - - this->WriteSubProjects(top, all_target); + this->WriteSubProjects(top, true); + this->WriteSubProjects(top, false); top.Close(); } @@ -510,59 +475,62 @@ std::vector<cmGlobalGenerator::GeneratedMakeCommand> cmGlobalGhsMultiGenerator::GenerateBuildCommand( const std::string& makeProgram, const std::string& projectName, const std::string& projectDir, std::vector<std::string> const& targetNames, - const std::string& /*config*/, int jobs, bool /*verbose*/, + const std::string& /*config*/, int jobs, bool verbose, const cmBuildOptions& /*buildOptions*/, std::vector<std::string> const& makeOptions) { - GeneratedMakeCommand makeCommand = {}; - std::string gbuild; - if (cmValue gbuildCached = - this->CMakeInstance->GetCacheDefinition("CMAKE_MAKE_PROGRAM")) { - gbuild = *gbuildCached; - } - makeCommand.Add(this->SelectMakeProgram(makeProgram, gbuild)); + GeneratedMakeCommand makeCommand; + + makeCommand.Add(this->SelectMakeProgram(makeProgram)); if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) { - makeCommand.Add("-parallel"); - if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) { - makeCommand.Add(std::to_string(jobs)); + if (jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL) { + makeCommand.Add("-parallel"); + } else { + makeCommand.Add(std::string("-parallel=") + std::to_string(jobs)); } } - makeCommand.Add(makeOptions.begin(), makeOptions.end()); - - /* determine which top-project file to use */ + /* determine the top-project file in the project directory */ std::string proj = projectName + ".top" + FILE_EXTENSION; std::vector<std::string> files; cmSystemTools::Glob(projectDir, ".*\\.top\\.gpj", files); if (!files.empty()) { - /* if multiple top-projects are found in build directory - * then prefer projectName top-project. - */ - if (!cm::contains(files, proj)) { - proj = files.at(0); - } + /* use the real top-level project in the directory */ + proj = files.at(0); } - makeCommand.Add("-top", proj); + + /* determine targets to build */ + bool build_all = false; if (!targetNames.empty()) { - if (cm::contains(targetNames, "clean")) { - makeCommand.Add("-clean"); - } else { - for (const auto& tname : targetNames) { - if (!tname.empty()) { + for (const auto& tname : targetNames) { + if (!tname.empty()) { + if (tname == "clean") { + makeCommand.Add("-clean"); + } else { makeCommand.Add(tname + ".tgt.gpj"); } + } else { + build_all = true; } } } else { + build_all = true; + } + + if (build_all) { /* transform name to default build */; - std::string all = proj; - all.replace(all.end() - 7, all.end(), - std::string(this->GetAllTargetName()) + ".tgt.gpj"); + std::string all = std::string(this->GetAllTargetName()) + ".tgt.gpj"; makeCommand.Add(all); } - return { makeCommand }; + + if (verbose) { + makeCommand.Add("-commands"); + } + makeCommand.Add(makeOptions.begin(), makeOptions.end()); + + return { std::move(makeCommand) }; } void cmGlobalGhsMultiGenerator::WriteMacros(std::ostream& fout, @@ -579,7 +547,7 @@ void cmGlobalGhsMultiGenerator::WriteMacros(std::ostream& fout, } void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives( - cmLocalGenerator* root, std::ostream& fout) + std::ostream& fout, cmLocalGenerator* root) { /* put primary target and customization files into project file */ cmValue const tgt = root->GetMakefile()->GetDefinition("GHS_PRIMARY_TARGET"); @@ -681,3 +649,147 @@ bool cmGlobalGhsMultiGenerator::VisitTarget( /* already complete */ return false; } + +bool cmGlobalGhsMultiGenerator::AddCheckTarget() +{ + // Skip the target if no regeneration is to be done. + if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) { + return false; + } + + // Get the generators. + std::vector<std::unique_ptr<cmLocalGenerator>> const& generators = + this->LocalGenerators; + auto& lg = + cm::static_reference_cast<cmLocalGhsMultiGenerator>(generators[0]); + + // The name of the output file for the custom command. + this->StampFile = lg.GetBinaryDirectory() + std::string("/CMakeFiles/") + + CHECK_BUILD_SYSTEM_TARGET; + + // Add a custom rule to re-run CMake if any input files changed. + { + // Collect the input files used to generate all targets in this + // project. + std::vector<std::string> listFiles; + for (const auto& gen : generators) { + cm::append(listFiles, gen->GetMakefile()->GetListFiles()); + } + + // Add the cache file. + listFiles.push_back(cmStrCat( + this->GetCMakeInstance()->GetHomeOutputDirectory(), "/CMakeCache.txt")); + + // Print not implemented warning. + if (this->GetCMakeInstance()->DoWriteGlobVerifyTarget()) { + std::ostringstream msg; + msg << "Any pre-check scripts, such as those generated for file(GLOB " + "CONFIGURE_DEPENDS), will not be run by gbuild."; + this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING, + msg.str()); + } + + // Sort the list of input files and remove duplicates. + std::sort(listFiles.begin(), listFiles.end(), std::less<std::string>()); + auto newEnd = std::unique(listFiles.begin(), listFiles.end()); + listFiles.erase(newEnd, listFiles.end()); + + // Create a rule to re-run CMake and create output file. + std::string argS = cmStrCat("-S", lg.GetSourceDirectory()); + std::string argB = cmStrCat("-B", lg.GetBinaryDirectory()); + cmCustomCommandLines commandLines = cmMakeSingleCommandLine( + { cmSystemTools::GetCMakeCommand(), argS, argB }); + + /* Create the target(Exclude from ALL_BUILD). + * + * The build tool, currently, does not support rereading the project files + * if they get updated. So do not run this target as part of ALL_BUILD. + */ + auto cc = cm::make_unique<cmCustomCommand>(); + cmTarget* tgt = + lg.AddUtilityCommand(CHECK_BUILD_SYSTEM_TARGET, true, std::move(cc)); + auto ptr = cm::make_unique<cmGeneratorTarget>(tgt, &lg); + auto* gt = ptr.get(); + lg.AddGeneratorTarget(std::move(ptr)); + + // Add the rule. + cc = cm::make_unique<cmCustomCommand>(); + cc->SetOutputs(this->StampFile); + cc->SetDepends(listFiles); + cc->SetCommandLines(commandLines); + cc->SetComment("Checking Build System"); + cc->SetCMP0116Status(cmPolicies::NEW); + cc->SetEscapeOldStyle(false); + cc->SetStdPipesUTF8(true); + + if (cmSourceFile* file = + lg.AddCustomCommandToOutput(std::move(cc), true)) { + gt->AddSource(file->ResolveFullPath()); + } else { + cmSystemTools::Error("Error adding rule for " + this->StampFile); + } + // Organize in the "predefined targets" folder: + if (this->UseFolderProperty()) { + tgt->SetProperty("FOLDER", this->GetPredefinedTargetsFolder()); + } + } + + return true; +} + +void cmGlobalGhsMultiGenerator::AddAllTarget() +{ + // Add a special target that depends on ALL projects for easy build + // of one configuration only. + for (auto const& it : this->ProjectMap) { + std::vector<cmLocalGenerator*> const& gen = it.second; + // add the ALL_BUILD to the first local generator of each project + if (!gen.empty()) { + // Use no actual command lines so that the target itself is not + // considered always out of date. + auto cc = cm::make_unique<cmCustomCommand>(); + cc->SetCMP0116Status(cmPolicies::NEW); + cc->SetEscapeOldStyle(false); + cc->SetComment("Build all projects"); + cmTarget* allBuild = gen[0]->AddUtilityCommand(this->GetAllTargetName(), + true, std::move(cc)); + + gen[0]->AddGeneratorTarget( + cm::make_unique<cmGeneratorTarget>(allBuild, gen[0])); + + // Organize in the "predefined targets" folder: + if (this->UseFolderProperty()) { + allBuild->SetProperty("FOLDER", this->GetPredefinedTargetsFolder()); + } + + // Now make all targets depend on the ALL_BUILD target + for (cmLocalGenerator const* i : gen) { + for (const auto& tgt : i->GetGeneratorTargets()) { + // Skip global or imported targets + if (tgt->GetType() == cmStateEnums::GLOBAL_TARGET || + tgt->IsImported()) { + continue; + } + // Skip Exclude From All Targets + if (!this->IsExcluded(gen[0], tgt.get())) { + allBuild->AddUtility(tgt->GetName(), false); + } + } + } + } + } +} + +void cmGlobalGhsMultiGenerator::AddExtraIDETargets() +{ + // Add a special target that depends on ALL projects. + this->AddAllTarget(); + + /* Add Custom Target to check if CMake needs to be rerun. + * + * The build tool, currently, does not support rereading the project files + * if they get updated. So do not make the other targets dependent on this + * check. + */ + this->AddCheckTarget(); +} diff --git a/Source/cmGlobalGhsMultiGenerator.h b/Source/cmGlobalGhsMultiGenerator.h index 26ea3c7..aa68d3b 100644 --- a/Source/cmGlobalGhsMultiGenerator.h +++ b/Source/cmGlobalGhsMultiGenerator.h @@ -81,8 +81,6 @@ public: // Write the common disclaimer text at the top of each build file. void WriteFileHeader(std::ostream& fout); - const char* GetInstallTargetName() const override { return "install"; } - protected: void Generate() override; std::vector<GeneratedMakeCommand> GenerateBuildCommand( @@ -92,6 +90,7 @@ protected: const cmBuildOptions& buildOptions = cmBuildOptions(), std::vector<std::string> const& makeOptions = std::vector<std::string>()) override; + void AddExtraIDETargets() override; private: void GetToolset(cmMakefile* mf, std::string& tsd, const std::string& ts); @@ -101,20 +100,21 @@ private: std::vector<cmLocalGenerator*>& generators); void WriteTopLevelProject(std::ostream& fout, cmLocalGenerator* root); void WriteMacros(std::ostream& fout, cmLocalGenerator* root); - void WriteHighLevelDirectives(cmLocalGenerator* root, std::ostream& fout); - void WriteSubProjects(std::ostream& fout, std::string& all_target); + void WriteHighLevelDirectives(std::ostream& fout, cmLocalGenerator* root); + void WriteSubProjects(std::ostream& fout, bool filterPredefined); void WriteTargets(cmLocalGenerator* root); void WriteProjectLine(std::ostream& fout, cmGeneratorTarget const* target, std::string& rootBinaryDir); void WriteCustomRuleBOD(std::ostream& fout); void WriteCustomTargetBOD(std::ostream& fout); - void WriteAllTarget(cmLocalGenerator* root, - std::vector<cmLocalGenerator*>& generators, - std::string& all_target); + bool AddCheckTarget(); + void AddAllTarget(); + std::string StampFile; static std::string TrimQuotes(std::string str); static const char* DEFAULT_BUILD_PROGRAM; + static const char* CHECK_BUILD_SYSTEM_TARGET; bool ComputeTargetBuildOrder(cmGeneratorTarget const* tgt, std::vector<cmGeneratorTarget const*>& build); diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 4245037..bbc9c54 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -2495,8 +2495,7 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( snapshot.GetDirectory().SetCurrentBinary(dir_cur_bld); auto mfd = cm::make_unique<cmMakefile>(this, snapshot); auto lgd = this->CreateLocalGenerator(mfd.get()); - lgd->SetRelativePathTopSource(dir_top_src); - lgd->SetRelativePathTopBinary(dir_top_bld); + lgd->SetRelativePathTop(dir_top_src, dir_top_bld); this->Makefiles.push_back(std::move(mfd)); this->LocalGenerators.push_back(std::move(lgd)); } diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx index db54b86..35448ee 100644 --- a/Source/cmGlobalVisualStudio10Generator.cxx +++ b/Source/cmGlobalVisualStudio10Generator.cxx @@ -1286,6 +1286,14 @@ cmGlobalVisualStudio10Generator::GenerateBuildCommand( return makeCommands; } +bool cmGlobalVisualStudio10Generator::IsInSolution( + const cmGeneratorTarget* gt) const +{ + return gt->IsInBuildSystem() && + !(this->Version >= cmGlobalVisualStudioGenerator::VSVersion::VS16 && + gt->GetName() == CMAKE_CHECK_BUILD_SYSTEM_TARGET); +} + bool cmGlobalVisualStudio10Generator::Find64BitTools(cmMakefile* mf) { if (this->DefaultPlatformToolset == "v100") { diff --git a/Source/cmGlobalVisualStudio10Generator.h b/Source/cmGlobalVisualStudio10Generator.h index 4977a84..2203f71 100644 --- a/Source/cmGlobalVisualStudio10Generator.h +++ b/Source/cmGlobalVisualStudio10Generator.h @@ -118,6 +118,8 @@ public: return this->WindowsTargetPlatformVersion; } + bool IsInSolution(const cmGeneratorTarget* gt) const override; + /** Return true if building for WindowsCE */ bool TargetsWindowsCE() const override { return this->SystemIsWindowsCE; } diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index 134937e..1c10fb3 100644 --- a/Source/cmGlobalVisualStudio7Generator.cxx +++ b/Source/cmGlobalVisualStudio7Generator.cxx @@ -359,7 +359,7 @@ void cmGlobalVisualStudio7Generator::WriteTargetConfigurations( // loop over again and write out configurations for each target // in the solution for (cmGeneratorTarget const* target : projectTargets) { - if (!target->IsInBuildSystem()) { + if (!this->IsInSolution(target)) { continue; } cmValue expath = target->GetProperty("EXTERNAL_MSPROJECT"); @@ -396,7 +396,7 @@ void cmGlobalVisualStudio7Generator::WriteTargetsToSolution( VisualStudioFolders.clear(); for (cmGeneratorTarget const* target : projectTargets) { - if (!target->IsInBuildSystem()) { + if (!this->IsInSolution(target)) { continue; } bool written = false; @@ -458,22 +458,6 @@ void cmGlobalVisualStudio7Generator::WriteTargetsToSolution( } } -void cmGlobalVisualStudio7Generator::WriteTargetDepends( - std::ostream& fout, OrderedTargetDependSet const& projectTargets) -{ - for (cmGeneratorTarget const* target : projectTargets) { - if (!target->IsInBuildSystem()) { - continue; - } - cmValue vcprojName = target->GetProperty("GENERATOR_FILE_NAME"); - if (vcprojName) { - std::string dir = - target->GetLocalGenerator()->GetCurrentSourceDirectory(); - this->WriteProjectDepends(fout, *vcprojName, dir, target); - } - } -} - void cmGlobalVisualStudio7Generator::WriteFolders(std::ostream& fout) { cm::string_view const prefix = "CMAKE_FOLDER_GUID_"; diff --git a/Source/cmGlobalVisualStudio7Generator.h b/Source/cmGlobalVisualStudio7Generator.h index 33f1063..a55cf45 100644 --- a/Source/cmGlobalVisualStudio7Generator.h +++ b/Source/cmGlobalVisualStudio7Generator.h @@ -148,8 +148,6 @@ protected: virtual void WriteTargetsToSolution( std::ostream& fout, cmLocalGenerator* root, OrderedTargetDependSet const& projectTargets); - virtual void WriteTargetDepends( - std::ostream& fout, OrderedTargetDependSet const& projectTargets); virtual void WriteTargetConfigurations( std::ostream& fout, std::vector<std::string> const& configs, OrderedTargetDependSet const& projectTargets); diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx index dbd2de9..323ee67 100644 --- a/Source/cmGlobalVisualStudio8Generator.cxx +++ b/Source/cmGlobalVisualStudio8Generator.cxx @@ -392,7 +392,7 @@ void cmGlobalVisualStudio8Generator::WriteProjectDepends( TargetDependSet const& unordered = this->GetTargetDirectDepends(gt); OrderedTargetDependSet depends(unordered, std::string()); for (cmTargetDepend const& i : depends) { - if (!i->IsInBuildSystem()) { + if (!this->IsInSolution(i)) { continue; } std::string guid = this->GetGUID(i->GetName()); diff --git a/Source/cmGlobalVisualStudioGenerator.cxx b/Source/cmGlobalVisualStudioGenerator.cxx index 141b5eb..cddaaa4 100644 --- a/Source/cmGlobalVisualStudioGenerator.cxx +++ b/Source/cmGlobalVisualStudioGenerator.cxx @@ -837,6 +837,12 @@ bool cmGlobalVisualStudioGenerator::TargetIsFortranOnly( return languages.size() == 1 && *languages.begin() == "Fortran"; } +bool cmGlobalVisualStudioGenerator::IsInSolution( + const cmGeneratorTarget* gt) const +{ + return gt->IsInBuildSystem(); +} + bool cmGlobalVisualStudioGenerator::TargetCompare::operator()( cmGeneratorTarget const* l, cmGeneratorTarget const* r) const { diff --git a/Source/cmGlobalVisualStudioGenerator.h b/Source/cmGlobalVisualStudioGenerator.h index cb1b14b..4f5f100 100644 --- a/Source/cmGlobalVisualStudioGenerator.h +++ b/Source/cmGlobalVisualStudioGenerator.h @@ -98,6 +98,9 @@ public: // return true if target is fortran only bool TargetIsFortranOnly(const cmGeneratorTarget* gt); + // return true if target should be included in solution. + virtual bool IsInSolution(const cmGeneratorTarget* gt) const; + /** Get the top-level registry key for this VS version. */ std::string GetRegistryBase(); diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 203addd..1e6624e 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -1154,47 +1154,25 @@ std::string GetSourcecodeValueFromFileExtension( return sourcecode; } -// If the file has no extension it's either a raw executable or might -// be a direct reference to a binary within a framework (bad practice!). -// This is where we change the path to point to the framework directory. -// .tbd files also can be located in SDK frameworks (they are -// placeholders for actual libraries shipped with the OS) -std::string GetLibraryOrFrameworkPath(const std::string& path) +} // anonymous + +// Extracts the framework directory, if path matches the framework syntax +// otherwise returns the path untouched +std::string cmGlobalXCodeGenerator::GetLibraryOrFrameworkPath( + const std::string& path) const { - auto ext = cmSystemTools::GetFilenameLastExtension(path); - if (ext.empty() || ext == ".tbd") { - auto name = cmSystemTools::GetFilenameWithoutExtension(path); - // Check for iOS framework structure: - // FwName.framework/FwName (and also on macOS where FwName lib is a - // symlink) - auto parentDir = cmSystemTools::GetParentDirectory(path); - auto parentName = cmSystemTools::GetFilenameWithoutExtension(parentDir); - ext = cmSystemTools::GetFilenameLastExtension(parentDir); - if (ext == ".framework" && name == parentName) { - return parentDir; - } - // Check for macOS framework structure: - // FwName.framework/Versions/*/FwName - std::vector<std::string> components; - cmSystemTools::SplitPath(path, components); - if (components.size() > 3 && - components[components.size() - 3] == "Versions") { - ext = cmSystemTools::GetFilenameLastExtension( - components[components.size() - 4]); - parentName = cmSystemTools::GetFilenameWithoutExtension( - components[components.size() - 4]); - if (ext == ".framework" && name == parentName) { - components.erase(components.begin() + components.size() - 3, - components.end()); - return cmSystemTools::JoinPath(components); - } + auto fwItems = this->SplitFrameworkPath(path); + if (fwItems) { + if (fwItems->first.empty()) { + return cmStrCat(fwItems->second, ".framework"); + } else { + return cmStrCat(fwItems->first, '/', fwItems->second, ".framework"); } } + return path; } -} // anonymous - cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath( const std::string& fullpath, cmGeneratorTarget* target, const std::string& lang, cmSourceFile* sf) @@ -1217,7 +1195,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath( ext = ext.substr(1); } if (fileType.empty()) { - path = GetLibraryOrFrameworkPath(path); + path = this->GetLibraryOrFrameworkPath(path); ext = cmSystemTools::GetFilenameLastExtension(path); if (!ext.empty()) { ext = ext.substr(1); @@ -3541,13 +3519,14 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) } else { linkDir = libItem->Value.Value; } - linkDir = GetLibraryOrFrameworkPath(linkDir); - bool isFramework = cmSystemTools::IsPathToFramework(linkDir); - linkDir = cmSystemTools::GetParentDirectory(linkDir); - if (isFramework) { - if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(), - linkDir) == frameworkSearchPaths.end()) { - frameworkSearchPaths.push_back(linkDir); + if (cmHasSuffix(libItem->GetFeatureName(), "FRAMEWORK"_s)) { + auto fwItems = this->SplitFrameworkPath(linkDir, true); + if (fwItems && !fwItems->first.empty()) { + linkDir = std::move(fwItems->first); + if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(), + linkDir) == frameworkSearchPaths.end()) { + frameworkSearchPaths.push_back(linkDir); + } } } else { if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) == @@ -3555,7 +3534,7 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) linkSearchPaths.push_back(linkDir); } } - // Add target dependency + if (libItem->Target && !libItem->Target->IsImported()) { for (auto const& configName : this->CurrentConfigurationTypes) { target->AddDependTarget(configName, libItem->Target->GetName()); @@ -3729,22 +3708,27 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) if (cmSystemTools::FileIsFullPath(cleanPath)) { cleanPath = cmSystemTools::CollapseFullPath(cleanPath); } - const auto libPath = GetLibraryOrFrameworkPath(cleanPath); - if (cmSystemTools::StringEndsWith(libPath.c_str(), ".framework")) { - const auto fwName = - cmSystemTools::GetFilenameWithoutExtension(libPath); - const auto fwDir = cmSystemTools::GetParentDirectory(libPath); - if (emitted.insert(fwDir).second) { - // This is a search path we had not added before and it isn't an - // implicit search path, so we need it - libPaths.Add("-F " + this->XCodeEscapePath(fwDir)); + bool isFramework = + cmHasSuffix(libName.GetFeatureName(), "FRAMEWORK"_s); + if (isFramework) { + const auto fwItems = + this->SplitFrameworkPath(cleanPath, isFramework); + if (!fwItems->first.empty() && + emitted.insert(fwItems->first).second) { + // This is a search path we had not added before and it isn't + // an implicit search path, so we need it + libPaths.Add("-F " + this->XCodeEscapePath(fwItems->first)); } - libPaths.Add("-framework " + this->XCodeEscapePath(fwName)); + libPaths.Add( + libName.GetFormattedItem(this->XCodeEscapePath(fwItems->second)) + .Value); } else { - libPaths.Add(this->XCodeEscapePath(cleanPath)); + libPaths.Add( + libName.GetFormattedItem(this->XCodeEscapePath(cleanPath)) + .Value); } if ((!libName.Target || libName.Target->IsImported()) && - IsLinkPhaseLibraryExtension(libPath)) { + (isFramework || IsLinkPhaseLibraryExtension(cleanPath))) { // Create file reference for embedding auto it = this->ExternalLibRefs.find(cleanPath); if (it == this->ExternalLibRefs.end()) { @@ -3912,8 +3896,8 @@ void cmGlobalXCodeGenerator::AddEmbeddedFrameworks(cmXCodeObject* target) { static const auto dstSubfolderSpec = "10"; - // Despite the name, by default Xcode uses "Embed Frameworks" build phase for - // both frameworks and dynamic libraries + // Despite the name, by default Xcode uses "Embed Frameworks" build phase + // for both frameworks and dynamic libraries this->AddEmbeddedObjects(target, "Embed Frameworks", "XCODE_EMBED_FRAMEWORKS", dstSubfolderSpec, NoActionOnCopyByDefault); @@ -4711,10 +4695,12 @@ std::string cmGlobalXCodeGenerator::ConvertToRelativeForMake( std::string cmGlobalXCodeGenerator::RelativeToSource(const std::string& p) { - // We force conversion because Xcode breakpoints do not work unless - // they are in a file named relative to the source tree. - return cmSystemTools::ForceToRelativePath( - this->CurrentRootGenerator->GetCurrentSourceDirectory(), p); + std::string const& rootSrc = + this->CurrentRootGenerator->GetCurrentSourceDirectory(); + if (cmSystemTools::IsSubDirectory(p, rootSrc)) { + return cmSystemTools::ForceToRelativePath(rootSrc, p); + } + return p; } std::string cmGlobalXCodeGenerator::RelativeToBinary(const std::string& p) diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index ff6ffe8..98cebef 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -330,6 +330,8 @@ private: { } + std::string GetLibraryOrFrameworkPath(const std::string& path) const; + std::string GetObjectsDirectory(const std::string& projName, const std::string& configName, const cmGeneratorTarget* t, diff --git a/Source/cmLinkItem.h b/Source/cmLinkItem.h index e715659..262728b 100644 --- a/Source/cmLinkItem.h +++ b/Source/cmLinkItem.h @@ -70,6 +70,12 @@ struct cmLinkInterfaceLibraries // Object files listed in the interface. std::vector<cmLinkItem> Objects; + // Items to be included as if directly linked by the head target. + std::vector<cmLinkItem> HeadInclude; + + // Items to be excluded from direct linking by the head target. + std::vector<cmLinkItem> HeadExclude; + // Whether the list depends on a genex referencing the head target. bool HadHeadSensitiveCondition = false; diff --git a/Source/cmLinkLibrariesCommand.cxx b/Source/cmLinkLibrariesCommand.cxx index 2b8f836..ed89e91 100644 --- a/Source/cmLinkLibrariesCommand.cxx +++ b/Source/cmLinkLibrariesCommand.cxx @@ -35,5 +35,7 @@ bool cmLinkLibrariesCommand(std::vector<std::string> const& args, mf.AppendProperty("LINK_LIBRARIES", *i); } + mf.CheckProperty("LINK_LIBRARIES"); + return true; } diff --git a/Source/cmLinkLineComputer.cxx b/Source/cmLinkLineComputer.cxx index 5646368..290642b 100644 --- a/Source/cmLinkLineComputer.cxx +++ b/Source/cmLinkLineComputer.cxx @@ -75,14 +75,8 @@ void cmLinkLineComputer::ComputeLinkLibs( BT<std::string> linkLib; if (item.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) { - if (item.IsObject == cmComputeLinkInformation::ItemIsObject::Yes) { - linkLib.Value += cli.GetObjLinkFileFlag(); - } else { - linkLib.Value += cli.GetLibLinkFileFlag(); - } - linkLib.Value += this->ConvertToOutputFormat( - this->ConvertToLinkReference(item.Value.Value)); - linkLib.Backtrace = item.Value.Backtrace; + linkLib = item.GetFormattedItem(this->ConvertToOutputFormat( + this->ConvertToLinkReference(item.Value.Value))); } else { linkLib = item.Value; } diff --git a/Source/cmLinkLineDeviceComputer.cxx b/Source/cmLinkLineDeviceComputer.cxx index 43f161b..71f9f80 100644 --- a/Source/cmLinkLineDeviceComputer.cxx +++ b/Source/cmLinkLineDeviceComputer.cxx @@ -118,8 +118,10 @@ void cmLinkLineDeviceComputer::ComputeLinkLibraries( // can tolerate '.so' or '.dylib' it cannot tolerate '.so.1'. if (cmHasLiteralSuffix(item.Value.Value, ".a") || cmHasLiteralSuffix(item.Value.Value, ".lib")) { - linkLib.Value += this->ConvertToOutputFormat( - this->ConvertToLinkReference(item.Value.Value)); + linkLib.Value = item + .GetFormattedItem(this->ConvertToOutputFormat( + this->ConvertToLinkReference(item.Value.Value))) + .Value; } } else if (item.Value == "-framework") { // This is the first part of '-framework Name' where the framework @@ -127,7 +129,7 @@ void cmLinkLineDeviceComputer::ComputeLinkLibraries( skipItemAfterFramework = true; continue; } else if (cmLinkItemValidForDevice(item.Value.Value)) { - linkLib.Value += item.Value.Value; + linkLib.Value = item.Value.Value; } if (emitted.insert(linkLib.Value).second) { diff --git a/Source/cmListFileCache.cxx b/Source/cmListFileCache.cxx index 3da266d..b90af08 100644 --- a/Source/cmListFileCache.cxx +++ b/Source/cmListFileCache.cxx @@ -40,6 +40,7 @@ struct cmListFileParser cmListFileLexer* Lexer; std::string FunctionName; long FunctionLine; + long FunctionLineEnd; std::vector<cmListFileArgument> FunctionArguments; enum { @@ -146,7 +147,7 @@ bool cmListFileParser::Parse() if (this->ParseFunction(token->text, token->line)) { this->ListFile->Functions.emplace_back( std::move(this->FunctionName), this->FunctionLine, - std::move(this->FunctionArguments)); + this->FunctionLineEnd, std::move(this->FunctionArguments)); } else { return false; } @@ -259,6 +260,7 @@ bool cmListFileParser::ParseFunction(const char* name, long line) } } else if (token->type == cmListFileLexer_Token_ParenRight) { if (parenDepth == 0) { + this->FunctionLineEnd = token->line; return true; } parenDepth--; @@ -539,11 +541,11 @@ std::ostream& operator<<(std::ostream& os, BT<std::string> const& s) return os << s.Value; } -std::vector<BT<std::string>> ExpandListWithBacktrace( - std::string const& list, cmListFileBacktrace const& bt) +std::vector<BT<std::string>> cmExpandListWithBacktrace( + std::string const& list, cmListFileBacktrace const& bt, bool emptyArgs) { std::vector<BT<std::string>> result; - std::vector<std::string> tmp = cmExpandedList(list); + std::vector<std::string> tmp = cmExpandedList(list, emptyArgs); result.reserve(tmp.size()); for (std::string& i : tmp) { result.emplace_back(std::move(i), bt); diff --git a/Source/cmListFileCache.h b/Source/cmListFileCache.h index 5d45027..c3da81b 100644 --- a/Source/cmListFileCache.h +++ b/Source/cmListFileCache.h @@ -51,9 +51,9 @@ struct cmListFileArgument class cmListFileFunction { public: - cmListFileFunction(std::string name, long line, + cmListFileFunction(std::string name, long line, long lineEnd, std::vector<cmListFileArgument> args) - : Impl{ std::make_shared<Implementation>(std::move(name), line, + : Impl{ std::make_shared<Implementation>(std::move(name), line, lineEnd, std::move(args)) } { } @@ -69,6 +69,7 @@ public: } long Line() const noexcept { return this->Impl->Line; } + long LineEnd() const noexcept { return this->Impl->LineEnd; } std::vector<cmListFileArgument> const& Arguments() const noexcept { @@ -78,11 +79,12 @@ public: private: struct Implementation { - Implementation(std::string name, long line, + Implementation(std::string name, long line, long lineEnd, std::vector<cmListFileArgument> args) : OriginalName{ std::move(name) } , LowerCaseName{ cmSystemTools::LowerCase(this->OriginalName) } , Line{ line } + , LineEnd{ lineEnd } , Arguments{ std::move(args) } { } @@ -90,6 +92,7 @@ private: std::string OriginalName; std::string LowerCaseName; long Line = 0; + long LineEnd = 0; std::vector<cmListFileArgument> Arguments; }; @@ -230,9 +233,10 @@ public: friend bool operator==(T const& l, BTs<T> const& r) { return l == r.Value; } }; -std::vector<BT<std::string>> ExpandListWithBacktrace( +std::vector<BT<std::string>> cmExpandListWithBacktrace( std::string const& list, - cmListFileBacktrace const& bt = cmListFileBacktrace()); + cmListFileBacktrace const& bt = cmListFileBacktrace(), + bool emptyArgs = false); struct cmListFile { diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 2adb232..4fa7d4f 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -1609,6 +1609,7 @@ std::vector<BT<std::string>> cmLocalGenerator::GetTargetCompileFlags( this->AddCMP0018Flags(compileFlags, target, lang, config); this->AddVisibilityPresetFlags(compileFlags, target, lang); + this->AddColorDiagnosticsFlags(compileFlags, lang); this->AppendFlags(compileFlags, mf->GetDefineFlags()); this->AppendFlags(compileFlags, this->GetFrameworkFlags(lang, config, target)); @@ -2354,6 +2355,29 @@ void cmLocalGenerator::AddPositionIndependentFlags(std::string& flags, } } +void cmLocalGenerator::AddColorDiagnosticsFlags(std::string& flags, + const std::string& lang) +{ + cmValue diag = this->Makefile->GetDefinition("CMAKE_COLOR_DIAGNOSTICS"); + if (diag.IsSet()) { + std::string colorFlagName; + if (diag.IsOn()) { + colorFlagName = + cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_COLOR_DIAGNOSTICS"); + } else { + colorFlagName = + cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_COLOR_DIAGNOSTICS_OFF"); + } + + std::vector<std::string> options; + this->Makefile->GetDefExpandList(colorFlagName, options); + + for (std::string const& option : options) { + this->AppendFlagEscape(flags, option); + } + } +} + void cmLocalGenerator::AddConfigVariableFlags(std::string& flags, const std::string& var, const std::string& config) @@ -3199,7 +3223,7 @@ void cmLocalGenerator::AppendDefines(std::set<std::string>& defines, std::string const& defines_list) const { std::set<BT<std::string>> tmp; - this->AppendDefines(tmp, ExpandListWithBacktrace(defines_list)); + this->AppendDefines(tmp, cmExpandListWithBacktrace(defines_list)); for (BT<std::string> const& i : tmp) { defines.emplace(i.Value); } @@ -3214,7 +3238,7 @@ void cmLocalGenerator::AppendDefines(std::set<BT<std::string>>& defines, } // Expand the list of definitions. - this->AppendDefines(defines, ExpandListWithBacktrace(defines_list)); + this->AppendDefines(defines, cmExpandListWithBacktrace(defines_list)); } void cmLocalGenerator::AppendDefines( diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index 115a54a..436a29e 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -159,6 +159,7 @@ public: cmGeneratorTarget const* target, const std::string& lang, const std::string& config); + void AddColorDiagnosticsFlags(std::string& flags, const std::string& lang); //! Append flags to a string. virtual void AppendFlags(std::string& flags, const std::string& newFlags) const; diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx index 0f8cdca..97a275a 100644 --- a/Source/cmLocalUnixMakefileGenerator3.cxx +++ b/Source/cmLocalUnixMakefileGenerator3.cxx @@ -133,7 +133,11 @@ void cmLocalUnixMakefileGenerator3::Generate() // Record whether some options are enabled to avoid checking many // times later. if (!this->GetGlobalGenerator()->GetCMakeInstance()->GetIsInTryCompile()) { - this->ColorMakefile = this->Makefile->IsOn("CMAKE_COLOR_MAKEFILE"); + if (this->Makefile->IsSet("CMAKE_COLOR_MAKEFILE")) { + this->ColorMakefile = this->Makefile->IsOn("CMAKE_COLOR_MAKEFILE"); + } else { + this->ColorMakefile = this->Makefile->IsOn("CMAKE_COLOR_DIAGNOSTICS"); + } } this->SkipPreprocessedSourceRules = this->Makefile->IsOn("CMAKE_SKIP_PREPROCESSED_SOURCE_RULES"); @@ -1508,13 +1512,12 @@ bool cmLocalUnixMakefileGenerator3::ScanDependencies( } // Setup relative path top directories. - if (cmValue relativePathTopSource = - mf->GetDefinition("CMAKE_RELATIVE_PATH_TOP_SOURCE")) { - this->SetRelativePathTopSource(*relativePathTopSource); - } - if (cmValue relativePathTopBinary = - mf->GetDefinition("CMAKE_RELATIVE_PATH_TOP_BINARY")) { - this->SetRelativePathTopBinary(*relativePathTopBinary); + cmValue relativePathTopSource = + mf->GetDefinition("CMAKE_RELATIVE_PATH_TOP_SOURCE"); + cmValue relativePathTopBinary = + mf->GetDefinition("CMAKE_RELATIVE_PATH_TOP_BINARY"); + if (relativePathTopSource && relativePathTopBinary) { + this->SetRelativePathTop(*relativePathTopSource, *relativePathTopBinary); } } else { cmSystemTools::Error("Directory Information file not found"); @@ -1849,7 +1852,7 @@ void cmLocalUnixMakefileGenerator3::ClearDependencies(cmMakefile* mf, cmSystemTools::Touch(DepTimestamp.GenericString(), true); // clear the dependencies files generated by the compiler - std::vector<std::string> dependencies = cmExpandedList(depsFiles); + std::vector<std::string> dependencies = cmExpandedList(depsFiles, true); cmDependsCompiler depsManager; depsManager.SetVerbose(verbose); depsManager.ClearDependencies(dependencies); diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx index ed7e888..f65add1 100644 --- a/Source/cmLocalVisualStudio7Generator.cxx +++ b/Source/cmLocalVisualStudio7Generator.cxx @@ -1294,7 +1294,9 @@ void cmLocalVisualStudio7GeneratorInternals::OutputLibraries( for (auto const& lib : libs) { if (lib.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) { std::string rel = lg->MaybeRelativeToCurBinDir(lib.Value.Value); - fout << lg->ConvertToXMLOutputPath(rel) << " "; + rel = lg->ConvertToXMLOutputPath(rel); + fout << (lib.HasFeature() ? lib.GetFormattedItem(rel).Value : rel) + << " "; } else if (!lib.Target || lib.Target->GetType() != cmStateEnums::INTERFACE_LIBRARY) { fout << lib.Value.Value << " "; diff --git a/Source/cmLocalVisualStudioGenerator.cxx b/Source/cmLocalVisualStudioGenerator.cxx index 93f01ed..2703c7b 100644 --- a/Source/cmLocalVisualStudioGenerator.cxx +++ b/Source/cmLocalVisualStudioGenerator.cxx @@ -252,7 +252,7 @@ std::string cmLocalVisualStudioGenerator::FinishConstructScript( // Store the script in a string. std::string script; - if (useLocal && projectType == VsProjectType::csproj) { + if (useLocal && projectType != VsProjectType::vcxproj) { // This label is not provided by MSBuild for C# projects. script += newline; script += this->GetReportErrorLabel(); diff --git a/Source/cmMacroCommand.cxx b/Source/cmMacroCommand.cxx index 154df63..ef12487 100644 --- a/Source/cmMacroCommand.cxx +++ b/Source/cmMacroCommand.cxx @@ -116,7 +116,7 @@ bool cmMacroHelperCommand::operator()( newLFFArgs.push_back(std::move(arg)); } cmListFileFunction newLFF{ func.OriginalName(), func.Line(), - std::move(newLFFArgs) }; + func.LineEnd(), std::move(newLFFArgs) }; cmExecutionStatus status(makefile); if (!makefile.ExecuteCommand(newLFF, status) || status.GetNestedError()) { // The error message should have already included the call stack diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 94d3be6..f0a96a8 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -291,6 +291,9 @@ void cmMakefile::PrintCommandTrace( builder["indentation"] = ""; val["file"] = full_path; val["line"] = static_cast<Json::Value::Int64>(lff.Line()); + if (lff.Line() != lff.LineEnd()) { + val["line_end"] = static_cast<Json::Value::Int64>(lff.LineEnd()); + } if (deferId) { val["defer"] = *deferId; } @@ -302,6 +305,8 @@ void cmMakefile::PrintCommandTrace( val["time"] = cmSystemTools::GetTime(); val["frame"] = static_cast<Json::Value::UInt64>(this->ExecutionStatusStack.size()); + val["global_frame"] = + static_cast<Json::Value::UInt64>(this->RecursionDepth); msg << Json::writeString(builder, val); #endif break; @@ -453,6 +458,11 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff, return result; } +bool cmMakefile::IsImportedTargetGlobalScope() const +{ + return this->CurrentImportedTargetScope == ImportedTargetScope::Global; +} + class cmMakefile::IncludeScope { public: @@ -1663,6 +1673,7 @@ void cmMakefile::Configure() this->Backtrace); cmListFileFunction project{ "project", 0, + 0, { { "Project", cmListFileArgument::Unquoted, 0 }, { "__CMAKE_INJECTED_PROJECT_COMMAND__", @@ -3976,6 +3987,31 @@ std::vector<std::string> cmMakefile::GetPropertyKeys() const return this->StateSnapshot.GetDirectory().GetPropertyKeys(); } +void cmMakefile::CheckProperty(const std::string& prop) const +{ + // Certain properties need checking. + if (prop == "LINK_LIBRARIES") { + if (cmValue value = this->GetProperty(prop)) { + // Look for <LINK_LIBRARY:> internal pattern + static cmsys::RegularExpression linkPattern( + "(^|;)(</?LINK_(LIBRARY|GROUP):[^;>]*>)(;|$)"); + if (!linkPattern.find(value)) { + return; + } + + // Report an error. + this->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Property ", prop, " contains the invalid item \"", + linkPattern.match(2), "\". The ", prop, + " property may contain the generator-expression \"$<LINK_", + linkPattern.match(3), + ":...>\" which may be used to specify how the libraries are " + "linked.")); + } + } +} + cmTarget* cmMakefile::FindLocalNonAliasTarget(const std::string& name) const { auto i = this->Targets.find(name); @@ -4395,12 +4431,14 @@ bool cmMakefile::SetPolicy(cmPolicies::PolicyID id, } // Deprecate old policies. - if (status == cmPolicies::OLD && id <= cmPolicies::CMP0094 && + if (status == cmPolicies::OLD && id <= cmPolicies::CMP0097 && !(this->GetCMakeInstance()->GetIsInTryCompile() && ( // Policies set by cmCoreTryCompile::TryCompileCode. id == cmPolicies::CMP0065 || id == cmPolicies::CMP0083 || - id == cmPolicies::CMP0091))) { + id == cmPolicies::CMP0091)) && + (!this->IsSet("CMAKE_WARN_DEPRECATED") || + this->IsOn("CMAKE_WARN_DEPRECATED"))) { this->IssueMessage(MessageType::DEPRECATION_WARNING, cmPolicies::GetPolicyDeprecatedWarning(id)); } diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index f425697..6d44e79 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -787,6 +787,7 @@ public: cmValue GetProperty(const std::string& prop, bool chain) const; bool GetPropertyAsBool(const std::string& prop) const; std::vector<std::string> GetPropertyKeys() const; + void CheckProperty(const std::string& prop) const; //! Initialize a makefile from its parent void InitializeFromParent(cmMakefile* parent); @@ -859,6 +860,44 @@ public: void PushLoopBlockBarrier(); void PopLoopBlockBarrier(); + bool IsImportedTargetGlobalScope() const; + + enum class ImportedTargetScope + { + Local, + Global, + }; + + /** Helper class to manage whether imported packages + * should be globally scoped based off the find package command + */ + class SetGlobalTargetImportScope + { + public: + SetGlobalTargetImportScope(cmMakefile* mk, ImportedTargetScope const scope) + : Makefile(mk) + { + if (scope == ImportedTargetScope::Global && + !this->Makefile->IsImportedTargetGlobalScope()) { + this->Makefile->CurrentImportedTargetScope = scope; + this->Set = true; + } else { + this->Set = false; + } + } + ~SetGlobalTargetImportScope() + { + if (this->Set) { + this->Makefile->CurrentImportedTargetScope = + ImportedTargetScope::Local; + } + } + + private: + cmMakefile* Makefile; + bool Set; + }; + /** Helper class to push and pop scopes automatically. */ class ScopePushPop { @@ -1123,4 +1162,5 @@ private: std::set<std::string> WarnedCMP0074; bool IsSourceFileTryCompile; mutable bool SuppressSideEffects; + ImportedTargetScope CurrentImportedTargetScope = ImportedTargetScope::Local; }; diff --git a/Source/cmOutputConverter.cxx b/Source/cmOutputConverter.cxx index b143170..e62e0cd 100644 --- a/Source/cmOutputConverter.cxx +++ b/Source/cmOutputConverter.cxx @@ -34,6 +34,7 @@ cmOutputConverter::cmOutputConverter(cmStateSnapshot const& snapshot) assert(this->StateSnapshot.IsValid()); this->ComputeRelativePathTopSource(); this->ComputeRelativePathTopBinary(); + this->ComputeRelativePathTopRelation(); } void cmOutputConverter::ComputeRelativePathTopSource() @@ -69,6 +70,22 @@ void cmOutputConverter::ComputeRelativePathTopBinary() this->RelativePathTopBinary = snapshot.GetDirectory().GetCurrentBinary(); } +void cmOutputConverter::ComputeRelativePathTopRelation() +{ + if (cmSystemTools::ComparePath(this->RelativePathTopSource, + this->RelativePathTopBinary)) { + this->RelativePathTopRelation = TopRelation::InSource; + } else if (cmSystemTools::IsSubDirectory(this->RelativePathTopBinary, + this->RelativePathTopSource)) { + this->RelativePathTopRelation = TopRelation::BinInSrc; + } else if (cmSystemTools::IsSubDirectory(this->RelativePathTopSource, + this->RelativePathTopBinary)) { + this->RelativePathTopRelation = TopRelation::SrcInBin; + } else { + this->RelativePathTopRelation = TopRelation::Separate; + } +} + std::string const& cmOutputConverter::GetRelativePathTopSource() const { return this->RelativePathTopSource; @@ -79,27 +96,45 @@ std::string const& cmOutputConverter::GetRelativePathTopBinary() const return this->RelativePathTopBinary; } -void cmOutputConverter::SetRelativePathTopSource(std::string const& top) -{ - this->RelativePathTopSource = top; -} - -void cmOutputConverter::SetRelativePathTopBinary(std::string const& top) +void cmOutputConverter::SetRelativePathTop(std::string const& topSource, + std::string const& topBinary) { - this->RelativePathTopBinary = top; + this->RelativePathTopSource = topSource; + this->RelativePathTopBinary = topBinary; + this->ComputeRelativePathTopRelation(); } std::string cmOutputConverter::MaybeRelativeTo( std::string const& local_path, std::string const& remote_path) const { - bool bothInBinary = - PathEqOrSubDir(local_path, this->RelativePathTopBinary) && + bool localInBinary = PathEqOrSubDir(local_path, this->RelativePathTopBinary); + bool remoteInBinary = PathEqOrSubDir(remote_path, this->RelativePathTopBinary); - bool bothInSource = - PathEqOrSubDir(local_path, this->RelativePathTopSource) && + bool localInSource = PathEqOrSubDir(local_path, this->RelativePathTopSource); + bool remoteInSource = PathEqOrSubDir(remote_path, this->RelativePathTopSource); + switch (this->RelativePathTopRelation) { + case TopRelation::Separate: + // Checks are independent. + break; + case TopRelation::BinInSrc: + localInSource = localInSource && !localInBinary; + remoteInSource = remoteInSource && !remoteInBinary; + break; + case TopRelation::SrcInBin: + localInBinary = localInBinary && !localInSource; + remoteInBinary = remoteInBinary && !remoteInSource; + break; + case TopRelation::InSource: + // Checks are identical. + break; + }; + + bool const bothInBinary = localInBinary && remoteInBinary; + bool const bothInSource = localInSource && remoteInSource; + if (bothInBinary || bothInSource) { return cmSystemTools::ForceToRelativePath(local_path, remote_path); } diff --git a/Source/cmOutputConverter.h b/Source/cmOutputConverter.h index 335442d..d19bccc 100644 --- a/Source/cmOutputConverter.h +++ b/Source/cmOutputConverter.h @@ -29,8 +29,8 @@ public: std::string const& GetRelativePathTopSource() const; std::string const& GetRelativePathTopBinary() const; - void SetRelativePathTopSource(std::string const& top); - void SetRelativePathTopBinary(std::string const& top); + void SetRelativePathTop(std::string const& topSource, + std::string const& topBinary); enum OutputFormat { @@ -147,8 +147,17 @@ private: // safely by the build tools. std::string RelativePathTopSource; std::string RelativePathTopBinary; + enum class TopRelation + { + Separate, + BinInSrc, + SrcInBin, + InSource, + }; + TopRelation RelativePathTopRelation = TopRelation::Separate; void ComputeRelativePathTopSource(); void ComputeRelativePathTopBinary(); + void ComputeRelativePathTopRelation(); std::string MaybeRelativeTo(std::string const& local_path, std::string const& remote_path) const; }; diff --git a/Source/cmPlaceholderExpander.cxx b/Source/cmPlaceholderExpander.cxx new file mode 100644 index 0000000..118017e --- /dev/null +++ b/Source/cmPlaceholderExpander.cxx @@ -0,0 +1,54 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmPlaceholderExpander.h" + +#include <cctype> + +std::string& cmPlaceholderExpander::ExpandVariables(std::string& s) +{ + std::string::size_type start = s.find('<'); + // no variables to expand + if (start == std::string::npos) { + return s; + } + std::string::size_type pos = 0; + std::string expandedInput; + while (start != std::string::npos && start < s.size() - 2) { + std::string::size_type end = s.find('>', start); + // if we find a < with no > we are done + if (end == std::string::npos) { + s = expandedInput; + return s; + } + char c = s[start + 1]; + // if the next char after the < is not A-Za-z then + // skip it and try to find the next < in the string + if (!isalpha(c)) { + start = s.find('<', start + 1); + } else { + // extract the var + std::string var = s.substr(start + 1, end - start - 1); + std::string replace = this->ExpandVariable(var); + expandedInput += s.substr(pos, start - pos); + + // Prevent consecutive whitespace in the output if the rule variable + // expands to an empty string. + bool consecutive = replace.empty() && start > 0 && s[start - 1] == ' ' && + end + 1 < s.size() && s[end + 1] == ' '; + if (consecutive) { + expandedInput.pop_back(); + } + + expandedInput += replace; + + // move to next one + start = s.find('<', start + var.size() + 2); + pos = end + 1; + } + } + // add the rest of the input + expandedInput += s.substr(pos, s.size() - pos); + s = expandedInput; + + return s; +} diff --git a/Source/cmPlaceholderExpander.h b/Source/cmPlaceholderExpander.h new file mode 100644 index 0000000..24225cc --- /dev/null +++ b/Source/cmPlaceholderExpander.h @@ -0,0 +1,19 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <string> + +class cmPlaceholderExpander +{ +public: + virtual ~cmPlaceholderExpander() = default; + + std::string& ExpandVariables(std::string& string); + +protected: + virtual std::string ExpandVariable(std::string const& variable) = 0; +}; diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index 99e2eb6..7d4012d 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -388,7 +388,9 @@ class cmMakefile; 22, 0, cmPolicies::WARN) \ SELECT(POLICY, CMP0129, \ "Compiler id for MCST LCC compilers is now LCC, not GNU.", 3, 23, 0, \ - cmPolicies::WARN) + cmPolicies::WARN) \ + SELECT(POLICY, CMP0130, "while() diagnoses condition evaluation errors.", \ + 3, 24, 0, cmPolicies::WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) #define CM_FOR_EACH_POLICY_ID(POLICY) \ diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx index 4cee09d..b63d11c 100644 --- a/Source/cmRulePlaceholderExpander.cxx +++ b/Source/cmRulePlaceholderExpander.cxx @@ -2,7 +2,6 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmRulePlaceholderExpander.h" -#include <cctype> #include <utility> #include "cmOutputConverter.h" @@ -20,195 +19,194 @@ cmRulePlaceholderExpander::cmRulePlaceholderExpander( { } -std::string cmRulePlaceholderExpander::ExpandRuleVariable( - cmOutputConverter* outputConverter, std::string const& variable, - const RuleVariables& replaceValues) +std::string cmRulePlaceholderExpander::ExpandVariable( + std::string const& variable) { - if (replaceValues.LinkFlags) { + if (this->ReplaceValues->LinkFlags) { if (variable == "LINK_FLAGS") { - return replaceValues.LinkFlags; + return this->ReplaceValues->LinkFlags; } } - if (replaceValues.Manifests) { + if (this->ReplaceValues->Manifests) { if (variable == "MANIFESTS") { - return replaceValues.Manifests; + return this->ReplaceValues->Manifests; } } - if (replaceValues.Flags) { + if (this->ReplaceValues->Flags) { if (variable == "FLAGS") { - return replaceValues.Flags; + return this->ReplaceValues->Flags; } } - if (replaceValues.Source) { + if (this->ReplaceValues->Source) { if (variable == "SOURCE") { - return replaceValues.Source; + return this->ReplaceValues->Source; } } - if (replaceValues.DynDepFile) { + if (this->ReplaceValues->DynDepFile) { if (variable == "DYNDEP_FILE") { - return replaceValues.DynDepFile; + return this->ReplaceValues->DynDepFile; } } - if (replaceValues.PreprocessedSource) { + if (this->ReplaceValues->PreprocessedSource) { if (variable == "PREPROCESSED_SOURCE") { - return replaceValues.PreprocessedSource; + return this->ReplaceValues->PreprocessedSource; } } - if (replaceValues.AssemblySource) { + if (this->ReplaceValues->AssemblySource) { if (variable == "ASSEMBLY_SOURCE") { - return replaceValues.AssemblySource; + return this->ReplaceValues->AssemblySource; } } - if (replaceValues.Object) { + if (this->ReplaceValues->Object) { if (variable == "OBJECT") { - return replaceValues.Object; + return this->ReplaceValues->Object; } } - if (replaceValues.ObjectDir) { + if (this->ReplaceValues->ObjectDir) { if (variable == "OBJECT_DIR") { - return replaceValues.ObjectDir; + return this->ReplaceValues->ObjectDir; } } - if (replaceValues.ObjectFileDir) { + if (this->ReplaceValues->ObjectFileDir) { if (variable == "OBJECT_FILE_DIR") { - return replaceValues.ObjectFileDir; + return this->ReplaceValues->ObjectFileDir; } } - if (replaceValues.Objects) { + if (this->ReplaceValues->Objects) { if (variable == "OBJECTS") { - return replaceValues.Objects; + return this->ReplaceValues->Objects; } } - if (replaceValues.ObjectsQuoted) { + if (this->ReplaceValues->ObjectsQuoted) { if (variable == "OBJECTS_QUOTED") { - return replaceValues.ObjectsQuoted; + return this->ReplaceValues->ObjectsQuoted; } } - if (replaceValues.CudaCompileMode) { + if (this->ReplaceValues->CudaCompileMode) { if (variable == "CUDA_COMPILE_MODE") { - return replaceValues.CudaCompileMode; + return this->ReplaceValues->CudaCompileMode; } } - if (replaceValues.AIXExports) { + if (this->ReplaceValues->AIXExports) { if (variable == "AIX_EXPORTS") { - return replaceValues.AIXExports; + return this->ReplaceValues->AIXExports; } } - if (replaceValues.ISPCHeader) { + if (this->ReplaceValues->ISPCHeader) { if (variable == "ISPC_HEADER") { - return replaceValues.ISPCHeader; + return this->ReplaceValues->ISPCHeader; } } - if (replaceValues.Defines && variable == "DEFINES") { - return replaceValues.Defines; + if (this->ReplaceValues->Defines && variable == "DEFINES") { + return this->ReplaceValues->Defines; } - if (replaceValues.Includes && variable == "INCLUDES") { - return replaceValues.Includes; + if (this->ReplaceValues->Includes && variable == "INCLUDES") { + return this->ReplaceValues->Includes; } - if (replaceValues.SwiftLibraryName) { + if (this->ReplaceValues->SwiftLibraryName) { if (variable == "SWIFT_LIBRARY_NAME") { - return replaceValues.SwiftLibraryName; + return this->ReplaceValues->SwiftLibraryName; } } - if (replaceValues.SwiftModule) { + if (this->ReplaceValues->SwiftModule) { if (variable == "SWIFT_MODULE") { - return replaceValues.SwiftModule; + return this->ReplaceValues->SwiftModule; } } - if (replaceValues.SwiftModuleName) { + if (this->ReplaceValues->SwiftModuleName) { if (variable == "SWIFT_MODULE_NAME") { - return replaceValues.SwiftModuleName; + return this->ReplaceValues->SwiftModuleName; } } - if (replaceValues.SwiftOutputFileMap) { + if (this->ReplaceValues->SwiftOutputFileMap) { if (variable == "SWIFT_OUTPUT_FILE_MAP") { - return replaceValues.SwiftOutputFileMap; + return this->ReplaceValues->SwiftOutputFileMap; } } - if (replaceValues.SwiftSources) { + if (this->ReplaceValues->SwiftSources) { if (variable == "SWIFT_SOURCES") { - return replaceValues.SwiftSources; + return this->ReplaceValues->SwiftSources; } } - if (replaceValues.TargetPDB) { + if (this->ReplaceValues->TargetPDB) { if (variable == "TARGET_PDB") { - return replaceValues.TargetPDB; + return this->ReplaceValues->TargetPDB; } } - if (replaceValues.TargetCompilePDB) { + if (this->ReplaceValues->TargetCompilePDB) { if (variable == "TARGET_COMPILE_PDB") { - return replaceValues.TargetCompilePDB; + return this->ReplaceValues->TargetCompilePDB; } } - if (replaceValues.DependencyFile) { + if (this->ReplaceValues->DependencyFile) { if (variable == "DEP_FILE") { - return replaceValues.DependencyFile; + return this->ReplaceValues->DependencyFile; } } - if (replaceValues.DependencyTarget) { + if (this->ReplaceValues->DependencyTarget) { if (variable == "DEP_TARGET") { - return replaceValues.DependencyTarget; + return this->ReplaceValues->DependencyTarget; } } - if (replaceValues.Fatbinary) { + if (this->ReplaceValues->Fatbinary) { if (variable == "FATBINARY") { - return replaceValues.Fatbinary; + return this->ReplaceValues->Fatbinary; } } - if (replaceValues.RegisterFile) { + if (this->ReplaceValues->RegisterFile) { if (variable == "REGISTER_FILE") { - return replaceValues.RegisterFile; + return this->ReplaceValues->RegisterFile; } } - if (replaceValues.Target) { + if (this->ReplaceValues->Target) { if (variable == "TARGET_QUOTED") { - std::string targetQuoted = replaceValues.Target; + std::string targetQuoted = this->ReplaceValues->Target; if (!targetQuoted.empty() && targetQuoted.front() != '\"') { targetQuoted = '\"'; - targetQuoted += replaceValues.Target; + targetQuoted += this->ReplaceValues->Target; targetQuoted += '\"'; } return targetQuoted; } if (variable == "TARGET_UNQUOTED") { - std::string unquoted = replaceValues.Target; + std::string unquoted = this->ReplaceValues->Target; std::string::size_type sz = unquoted.size(); if (sz > 2 && unquoted.front() == '\"' && unquoted.back() == '\"') { unquoted = unquoted.substr(1, sz - 2); } return unquoted; } - if (replaceValues.LanguageCompileFlags) { + if (this->ReplaceValues->LanguageCompileFlags) { if (variable == "LANGUAGE_COMPILE_FLAGS") { - return replaceValues.LanguageCompileFlags; + return this->ReplaceValues->LanguageCompileFlags; } } - if (replaceValues.Target) { + if (this->ReplaceValues->Target) { if (variable == "TARGET") { - return replaceValues.Target; + return this->ReplaceValues->Target; } } if (variable == "TARGET_IMPLIB") { return this->TargetImpLib; } if (variable == "TARGET_VERSION_MAJOR") { - if (replaceValues.TargetVersionMajor) { - return replaceValues.TargetVersionMajor; + if (this->ReplaceValues->TargetVersionMajor) { + return this->ReplaceValues->TargetVersionMajor; } return "0"; } if (variable == "TARGET_VERSION_MINOR") { - if (replaceValues.TargetVersionMinor) { - return replaceValues.TargetVersionMinor; + if (this->ReplaceValues->TargetVersionMinor) { + return this->ReplaceValues->TargetVersionMinor; } return "0"; } - if (replaceValues.Target) { + if (this->ReplaceValues->Target) { if (variable == "TARGET_BASE") { // Strip the last extension off the target name. - std::string targetBase = replaceValues.Target; + std::string targetBase = this->ReplaceValues->Target; std::string::size_type pos = targetBase.rfind('.'); if (pos != std::string::npos) { return targetBase.substr(0, pos); @@ -220,54 +218,54 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable( if (variable == "TARGET_SONAME" || variable == "SONAME_FLAG" || variable == "TARGET_INSTALLNAME_DIR") { // All these variables depend on TargetSOName - if (replaceValues.TargetSOName) { + if (this->ReplaceValues->TargetSOName) { if (variable == "TARGET_SONAME") { - return replaceValues.TargetSOName; + return this->ReplaceValues->TargetSOName; } - if (variable == "SONAME_FLAG" && replaceValues.SONameFlag) { - return replaceValues.SONameFlag; + if (variable == "SONAME_FLAG" && this->ReplaceValues->SONameFlag) { + return this->ReplaceValues->SONameFlag; } - if (replaceValues.TargetInstallNameDir && + if (this->ReplaceValues->TargetInstallNameDir && variable == "TARGET_INSTALLNAME_DIR") { - return replaceValues.TargetInstallNameDir; + return this->ReplaceValues->TargetInstallNameDir; } } return ""; } - if (replaceValues.LinkLibraries) { + if (this->ReplaceValues->LinkLibraries) { if (variable == "LINK_LIBRARIES") { - return replaceValues.LinkLibraries; + return this->ReplaceValues->LinkLibraries; } } - if (replaceValues.Language) { + if (this->ReplaceValues->Language) { if (variable == "LANGUAGE") { - return replaceValues.Language; + return this->ReplaceValues->Language; } } - if (replaceValues.CMTargetName) { + if (this->ReplaceValues->CMTargetName) { if (variable == "TARGET_NAME") { - return replaceValues.CMTargetName; + return this->ReplaceValues->CMTargetName; } } - if (replaceValues.CMTargetType) { + if (this->ReplaceValues->CMTargetType) { if (variable == "TARGET_TYPE") { - return replaceValues.CMTargetType; + return this->ReplaceValues->CMTargetType; } } - if (replaceValues.Output) { + if (this->ReplaceValues->Output) { if (variable == "OUTPUT") { - return replaceValues.Output; + return this->ReplaceValues->Output; } } if (variable == "CMAKE_COMMAND") { - return outputConverter->ConvertToOutputFormat( + return this->OutputConverter->ConvertToOutputFormat( cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL); } auto compIt = this->Compilers.find(variable); if (compIt != this->Compilers.end()) { - std::string ret = outputConverter->ConvertToOutputForExisting( + std::string ret = this->OutputConverter->ConvertToOutputForExisting( this->VariableMappings["CMAKE_" + compIt->second + "_COMPILER"]); std::string const& compilerArg1 = this->VariableMappings["CMAKE_" + compIt->second + "_COMPILER_ARG1"]; @@ -286,11 +284,12 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable( this->VariableMappings["CMAKE_" + compIt->second + "_COMPILE_OPTIONS_SYSROOT"]; - if (compIt->second == replaceValues.Language && replaceValues.Launcher) { + if (compIt->second == this->ReplaceValues->Language && + this->ReplaceValues->Launcher) { // Add launcher as part of expansion so that it always appears // immediately before the command itself, regardless of whether the // overall rule template contains other content at the front. - ret = cmStrCat(replaceValues.Launcher, " ", ret); + ret = cmStrCat(this->ReplaceValues->Launcher, " ", ret); } // if there are required arguments to the compiler add it @@ -308,13 +307,14 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable( !compilerOptionExternalToolchain.empty()) { ret += " "; ret += compilerOptionExternalToolchain; - ret += outputConverter->EscapeForShell(compilerExternalToolchain, true); + ret += + this->OutputConverter->EscapeForShell(compilerExternalToolchain, true); } std::string sysroot; // Some platforms may use separate sysroots for compiling and linking. // If we detect link flags, then we pass the link sysroot instead. // FIXME: Use a more robust way to detect link line expansion. - if (replaceValues.LinkFlags) { + if (this->ReplaceValues->LinkFlags) { sysroot = this->LinkerSysroot; } else { sysroot = this->CompilerSysroot; @@ -322,7 +322,7 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable( if (!sysroot.empty() && !compilerOptionSysroot.empty()) { ret += " "; ret += compilerOptionSysroot; - ret += outputConverter->EscapeForShell(sysroot, true); + ret += this->OutputConverter->EscapeForShell(sysroot, true); } return ret; } @@ -331,13 +331,13 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable( if (mapIt != this->VariableMappings.end()) { if (variable.find("_FLAG") == std::string::npos) { std::string ret = - outputConverter->ConvertToOutputForExisting(mapIt->second); + this->OutputConverter->ConvertToOutputForExisting(mapIt->second); - if (replaceValues.Launcher && variable == "CMAKE_LINKER") { + if (this->ReplaceValues->Launcher && variable == "CMAKE_LINKER") { // Add launcher as part of expansion so that it always appears // immediately before the command itself, regardless of whether the // overall rule template contains other content at the front. - ret = cmStrCat(replaceValues.Launcher, " ", ret); + ret = cmStrCat(this->ReplaceValues->Launcher, " ", ret); } return ret; @@ -351,47 +351,8 @@ void cmRulePlaceholderExpander::ExpandRuleVariables( cmOutputConverter* outputConverter, std::string& s, const RuleVariables& replaceValues) { - std::string::size_type start = s.find('<'); - // no variables to expand - if (start == std::string::npos) { - return; - } - std::string::size_type pos = 0; - std::string expandedInput; - while (start != std::string::npos && start < s.size() - 2) { - std::string::size_type end = s.find('>', start); - // if we find a < with no > we are done - if (end == std::string::npos) { - return; - } - char c = s[start + 1]; - // if the next char after the < is not A-Za-z then - // skip it and try to find the next < in the string - if (!isalpha(c)) { - start = s.find('<', start + 1); - } else { - // extract the var - std::string var = s.substr(start + 1, end - start - 1); - std::string replace = - this->ExpandRuleVariable(outputConverter, var, replaceValues); - expandedInput += s.substr(pos, start - pos); - - // Prevent consecutive whitespace in the output if the rule variable - // expands to an empty string. - bool consecutive = replace.empty() && start > 0 && s[start - 1] == ' ' && - end + 1 < s.size() && s[end + 1] == ' '; - if (consecutive) { - expandedInput.pop_back(); - } + this->OutputConverter = outputConverter; + this->ReplaceValues = &replaceValues; - expandedInput += replace; - - // move to next one - start = s.find('<', start + var.size() + 2); - pos = end + 1; - } - } - // add the rest of the input - expandedInput += s.substr(pos, s.size() - pos); - s = expandedInput; + this->ExpandVariables(s); } diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h index 852954f..23ec405 100644 --- a/Source/cmRulePlaceholderExpander.h +++ b/Source/cmRulePlaceholderExpander.h @@ -8,9 +8,11 @@ #include <map> #include <string> +#include "cmPlaceholderExpander.h" + class cmOutputConverter; -class cmRulePlaceholderExpander +class cmRulePlaceholderExpander : public cmPlaceholderExpander { public: cmRulePlaceholderExpander( @@ -76,16 +78,16 @@ public: std::string& string, const RuleVariables& replaceValues); - // Expand rule variables in a single string - std::string ExpandRuleVariable(cmOutputConverter* outputConverter, - std::string const& variable, - const RuleVariables& replaceValues); - private: + std::string ExpandVariable(std::string const& variable) override; + std::string TargetImpLib; std::map<std::string, std::string> Compilers; std::map<std::string, std::string> VariableMappings; std::string CompilerSysroot; std::string LinkerSysroot; + + cmOutputConverter* OutputConverter = nullptr; + RuleVariables const* ReplaceValues = nullptr; }; diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index effb837..a5dfa4c 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -3314,12 +3314,22 @@ cmsys::Status cmSystemTools::CreateSymlink(std::string const& origName, uv_fs_t req; int flags = 0; #if defined(_WIN32) - if (cmsys::SystemTools::FileIsDirectory(origName)) { - flags |= UV_FS_SYMLINK_DIR; + bool const isDir = cmsys::SystemTools::FileIsDirectory(origName); + if (isDir) { + flags |= UV_FS_SYMLINK_JUNCTION; } #endif int err = uv_fs_symlink(nullptr, &req, origName.c_str(), newName.c_str(), flags, nullptr); +#if defined(_WIN32) + if (err && uv_fs_get_system_error(&req) == ERROR_NOT_SUPPORTED && isDir) { + // Try fallback to symlink for network (requires additional permissions). + flags ^= UV_FS_SYMLINK_JUNCTION | UV_FS_SYMLINK_DIR; + err = uv_fs_symlink(nullptr, &req, origName.c_str(), newName.c_str(), + flags, nullptr); + } +#endif + cmsys::Status status; if (err) { #if defined(_WIN32) diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index e1a9667..4ca1b9b 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -203,6 +203,8 @@ public: std::vector<BT<std::string>> LinkDirectoriesEntries; std::vector<BT<std::string>> LinkImplementationPropertyEntries; std::vector<BT<std::string>> LinkInterfacePropertyEntries; + std::vector<BT<std::string>> LinkInterfaceDirectPropertyEntries; + std::vector<BT<std::string>> LinkInterfaceDirectExcludePropertyEntries; std::vector<BT<std::string>> HeaderSetsEntries; std::vector<BT<std::string>> InterfaceHeaderSetsEntries; std::vector<std::pair<cmTarget::TLLSignature, cmListFileContext>> @@ -1142,6 +1144,16 @@ cmBTStringRange cmTarget::GetLinkInterfaceEntries() const return cmMakeRange(this->impl->LinkInterfacePropertyEntries); } +cmBTStringRange cmTarget::GetLinkInterfaceDirectEntries() const +{ + return cmMakeRange(this->impl->LinkInterfaceDirectPropertyEntries); +} + +cmBTStringRange cmTarget::GetLinkInterfaceDirectExcludeEntries() const +{ + return cmMakeRange(this->impl->LinkInterfaceDirectExcludePropertyEntries); +} + cmBTStringRange cmTarget::GetHeaderSetsEntries() const { return cmMakeRange(this->impl->HeaderSetsEntries); @@ -1186,6 +1198,8 @@ MAKE_PROP(HEADER_SET); MAKE_PROP(HEADER_SETS); MAKE_PROP(INTERFACE_HEADER_SETS); MAKE_PROP(INTERFACE_LINK_LIBRARIES); +MAKE_PROP(INTERFACE_LINK_LIBRARIES_DIRECT); +MAKE_PROP(INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE); #undef MAKE_PROP } @@ -1317,6 +1331,19 @@ void cmTarget::StoreProperty(const std::string& prop, ValueType value) cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace(); this->impl->LinkInterfacePropertyEntries.emplace_back(value, lfbt); } + } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT) { + this->impl->LinkInterfaceDirectPropertyEntries.clear(); + if (value) { + cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace(); + this->impl->LinkInterfaceDirectPropertyEntries.emplace_back(value, lfbt); + } + } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE) { + this->impl->LinkInterfaceDirectExcludePropertyEntries.clear(); + if (value) { + cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace(); + this->impl->LinkInterfaceDirectExcludePropertyEntries.emplace_back(value, + lfbt); + } } else if (prop == propSOURCES) { this->impl->SourceEntries.clear(); if (value) { @@ -1575,6 +1602,17 @@ void cmTarget::AppendProperty(const std::string& prop, cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace(); this->impl->LinkInterfacePropertyEntries.emplace_back(value, lfbt); } + } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT) { + if (!value.empty()) { + cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace(); + this->impl->LinkInterfaceDirectPropertyEntries.emplace_back(value, lfbt); + } + } else if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE) { + if (!value.empty()) { + cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace(); + this->impl->LinkInterfaceDirectExcludePropertyEntries.emplace_back(value, + lfbt); + } } else if (prop == "SOURCES") { cmListFileBacktrace lfbt = this->impl->Makefile->GetBacktrace(); this->impl->SourceEntries.emplace_back(value, lfbt); @@ -1745,69 +1783,94 @@ void cmTarget::InsertPrecompileHeader(BT<std::string> const& entry) this->impl->PrecompileHeadersEntries.push_back(entry); } -static void cmTargetCheckLINK_INTERFACE_LIBRARIES(const std::string& prop, - const std::string& value, - cmMakefile* context, - bool imported) +namespace { +void CheckLinkLibraryPattern(const std::string& property, + const std::string& value, cmMakefile* context) { - // Look for link-type keywords in the value. - static cmsys::RegularExpression keys("(^|;)(debug|optimized|general)(;|$)"); - if (!keys.find(value)) { + // Look for <LINK_LIBRARY:> and </LINK_LIBRARY:> internal tags + static cmsys::RegularExpression linkPattern( + "(^|;)(</?LINK_(LIBRARY|GROUP):[^;>]*>)(;|$)"); + if (!linkPattern.find(value)) { return; } + // Report an error. + context->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat( + "Property ", property, " contains the invalid item \"", + linkPattern.match(2), "\". The ", property, + " property may contain the generator-expression \"$<LINK_", + linkPattern.match(3), + ":...>\" which may be used to specify how the libraries are linked.")); +} + +void CheckLINK_INTERFACE_LIBRARIES(const std::string& prop, + const std::string& value, + cmMakefile* context, bool imported) +{ // Support imported and non-imported versions of the property. const char* base = (imported ? "IMPORTED_LINK_INTERFACE_LIBRARIES" : "LINK_INTERFACE_LIBRARIES"); - // Report an error. - std::ostringstream e; - e << "Property " << prop << " may not contain link-type keyword \"" - << keys.match(2) << "\". " - << "The " << base << " property has a per-configuration " - << "version called " << base << "_<CONFIG> which may be " - << "used to specify per-configuration rules."; - if (!imported) { - e << " " - << "Alternatively, an IMPORTED library may be created, configured " - << "with a per-configuration location, and then named in the " - << "property value. " - << "See the add_library command's IMPORTED mode for details." - << "\n" - << "If you have a list of libraries that already contains the " - << "keyword, use the target_link_libraries command with its " - << "LINK_INTERFACE_LIBRARIES mode to set the property. " - << "The command automatically recognizes link-type keywords and sets " - << "the LINK_INTERFACE_LIBRARIES and LINK_INTERFACE_LIBRARIES_DEBUG " - << "properties accordingly."; - } - context->IssueMessage(MessageType::FATAL_ERROR, e.str()); -} - -static void cmTargetCheckINTERFACE_LINK_LIBRARIES(const std::string& value, - cmMakefile* context) -{ // Look for link-type keywords in the value. static cmsys::RegularExpression keys("(^|;)(debug|optimized|general)(;|$)"); - if (!keys.find(value)) { - return; + if (keys.find(value)) { + // Report an error. + std::ostringstream e; + e << "Property " << prop << " may not contain link-type keyword \"" + << keys.match(2) << "\". " + << "The " << base << " property has a per-configuration " + << "version called " << base << "_<CONFIG> which may be " + << "used to specify per-configuration rules."; + if (!imported) { + e << " " + << "Alternatively, an IMPORTED library may be created, configured " + << "with a per-configuration location, and then named in the " + << "property value. " + << "See the add_library command's IMPORTED mode for details." + << "\n" + << "If you have a list of libraries that already contains the " + << "keyword, use the target_link_libraries command with its " + << "LINK_INTERFACE_LIBRARIES mode to set the property. " + << "The command automatically recognizes link-type keywords and sets " + << "the LINK_INTERFACE_LIBRARIES and LINK_INTERFACE_LIBRARIES_DEBUG " + << "properties accordingly."; + } + context->IssueMessage(MessageType::FATAL_ERROR, e.str()); } - // Report an error. - std::ostringstream e; + CheckLinkLibraryPattern(base, value, context); +} + +void CheckLINK_LIBRARIES(const std::string& value, cmMakefile* context) +{ + CheckLinkLibraryPattern("LINK_LIBRARIES", value, context); +} + +void CheckINTERFACE_LINK_LIBRARIES(const std::string& value, + cmMakefile* context) +{ + // Look for link-type keywords in the value. + static cmsys::RegularExpression keys("(^|;)(debug|optimized|general)(;|$)"); + if (keys.find(value)) { + // Report an error. + std::ostringstream e; + + e << "Property INTERFACE_LINK_LIBRARIES may not contain link-type " + "keyword \"" + << keys.match(2) + << "\". The INTERFACE_LINK_LIBRARIES " + "property may contain configuration-sensitive generator-expressions " + "which may be used to specify per-configuration rules."; - e << "Property INTERFACE_LINK_LIBRARIES may not contain link-type " - "keyword \"" - << keys.match(2) - << "\". The INTERFACE_LINK_LIBRARIES " - "property may contain configuration-sensitive generator-expressions " - "which may be used to specify per-configuration rules."; + context->IssueMessage(MessageType::FATAL_ERROR, e.str()); + } - context->IssueMessage(MessageType::FATAL_ERROR, e.str()); + CheckLinkLibraryPattern("INTERFACE_LINK_LIBRARIES", value, context); } -static void cmTargetCheckIMPORTED_GLOBAL(const cmTarget* target, - cmMakefile* context) +void CheckIMPORTED_GLOBAL(const cmTarget* target, cmMakefile* context) { const auto& targets = context->GetOwnedImportedTargets(); auto it = @@ -1823,6 +1886,7 @@ static void cmTargetCheckIMPORTED_GLOBAL(const cmTarget* target, context->IssueMessage(MessageType::FATAL_ERROR, e.str()); } } +} void cmTarget::CheckProperty(const std::string& prop, cmMakefile* context) const @@ -1830,22 +1894,23 @@ void cmTarget::CheckProperty(const std::string& prop, // Certain properties need checking. if (cmHasLiteralPrefix(prop, "LINK_INTERFACE_LIBRARIES")) { if (cmValue value = this->GetProperty(prop)) { - cmTargetCheckLINK_INTERFACE_LIBRARIES(prop, *value, context, false); + CheckLINK_INTERFACE_LIBRARIES(prop, *value, context, false); } - } - if (cmHasLiteralPrefix(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES")) { + } else if (cmHasLiteralPrefix(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES")) { if (cmValue value = this->GetProperty(prop)) { - cmTargetCheckLINK_INTERFACE_LIBRARIES(prop, *value, context, true); + CheckLINK_INTERFACE_LIBRARIES(prop, *value, context, true); } - } - if (prop == "INTERFACE_LINK_LIBRARIES") { + } else if (prop == "LINK_LIBRARIES") { if (cmValue value = this->GetProperty(prop)) { - cmTargetCheckINTERFACE_LINK_LIBRARIES(*value, context); + CheckLINK_LIBRARIES(*value, context); } - } - if (prop == "IMPORTED_GLOBAL") { + } else if (prop == "INTERFACE_LINK_LIBRARIES") { + if (cmValue value = this->GetProperty(prop)) { + CheckINTERFACE_LINK_LIBRARIES(*value, context); + } + } else if (prop == "IMPORTED_GLOBAL") { if (this->IsImported()) { - cmTargetCheckIMPORTED_GLOBAL(this, context); + CheckIMPORTED_GLOBAL(this, context); } } } @@ -1885,6 +1950,8 @@ cmValue cmTarget::GetProperty(const std::string& prop) const propHEADER_SETS, propINTERFACE_HEADER_SETS, propINTERFACE_LINK_LIBRARIES, + propINTERFACE_LINK_LIBRARIES_DIRECT, + propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE, }; if (specialProps.count(prop)) { if (prop == propC_STANDARD || prop == propCXX_STANDARD || @@ -1914,6 +1981,25 @@ cmValue cmTarget::GetProperty(const std::string& prop) const output = cmJoin(this->impl->LinkInterfacePropertyEntries, ";"); return cmValue(output); } + if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT) { + if (this->impl->LinkInterfaceDirectPropertyEntries.empty()) { + return nullptr; + } + + static std::string output; + output = cmJoin(this->impl->LinkInterfaceDirectPropertyEntries, ";"); + return cmValue(output); + } + if (prop == propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE) { + if (this->impl->LinkInterfaceDirectExcludePropertyEntries.empty()) { + return nullptr; + } + + static std::string output; + output = + cmJoin(this->impl->LinkInterfaceDirectExcludePropertyEntries, ";"); + return cmValue(output); + } // the type property returns what type the target is if (prop == propTYPE) { return cmValue(cmState::GetTargetTypeName(this->GetType())); diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 1bbd0b0..0cdd2fc 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -269,6 +269,8 @@ public: cmBTStringRange GetLinkImplementationEntries() const; cmBTStringRange GetLinkInterfaceEntries() const; + cmBTStringRange GetLinkInterfaceDirectEntries() const; + cmBTStringRange GetLinkInterfaceDirectExcludeEntries() const; cmBTStringRange GetHeaderSetsEntries() const; diff --git a/Source/cmTargetLinkLibrariesCommand.cxx b/Source/cmTargetLinkLibrariesCommand.cxx index e10d0b5..e1a6c78 100644 --- a/Source/cmTargetLinkLibrariesCommand.cxx +++ b/Source/cmTargetLinkLibrariesCommand.cxx @@ -379,6 +379,9 @@ bool cmTargetLinkLibrariesCommand(std::vector<std::string> const& args, target->SetProperty("LINK_INTERFACE_LIBRARIES", ""); } + target->CheckProperty("LINK_LIBRARIES", &mf); + target->CheckProperty("INTERFACE_LINK_LIBRARIES", &mf); + return true; } diff --git a/Source/cmVariableWatchCommand.cxx b/Source/cmVariableWatchCommand.cxx index fd5402c..24394d9 100644 --- a/Source/cmVariableWatchCommand.cxx +++ b/Source/cmVariableWatchCommand.cxx @@ -58,7 +58,7 @@ void cmVariableWatchCommandVariableAccessed(const std::string& variable, { stack, cmListFileArgument::Quoted, fakeLineNo } }; - cmListFileFunction newLFF{ data->Command, fakeLineNo, + cmListFileFunction newLFF{ data->Command, fakeLineNo, fakeLineNo, std::move(newLFFArgs) }; cmExecutionStatus status(*makefile); if (!makefile->ExecuteCommand(newLFF, status)) { diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx index 8ffb664..276eccf 100644 --- a/Source/cmVisualStudio10TargetGenerator.cxx +++ b/Source/cmVisualStudio10TargetGenerator.cxx @@ -50,6 +50,24 @@ #include "cmValue.h" #include "cmVisualStudioGeneratorOptions.h" +namespace { +std::string getProjectFileExtension(VsProjectType projectType) +{ + switch (projectType) { + case VsProjectType::csproj: + return ".csproj"; + case VsProjectType::proj: + return ".proj"; + case VsProjectType::vcxproj: + return ".vcxproj"; + // Valid inputs shouldn't reach here. This default is needed so that all + // paths return value (C4715). + default: + return ""; + } +} +} + struct cmIDEFlagTable; static void ConvertToWindowsSlash(std::string& s); @@ -235,29 +253,6 @@ static bool cmVS10IsTargetsFile(std::string const& path) return cmSystemTools::Strucmp(ext.c_str(), ".targets") == 0; } -static VsProjectType computeProjectType(cmGeneratorTarget const* t) -{ - if (t->IsCSharpOnly()) { - return VsProjectType::csproj; - } - return VsProjectType::vcxproj; -} - -static std::string computeProjectFileExtension(VsProjectType projectType) -{ - switch (projectType) { - case VsProjectType::csproj: - return ".csproj"; - default: - return ".vcxproj"; - } -} - -static std::string computeProjectFileExtension(cmGeneratorTarget const* t) -{ - return computeProjectFileExtension(computeProjectType(t)); -} - cmVisualStudio10TargetGenerator::cmVisualStudio10TargetGenerator( cmGeneratorTarget* target, cmGlobalVisualStudio10Generator* gg) : GeneratorTarget(target) @@ -352,10 +347,10 @@ std::ostream& cmVisualStudio10TargetGenerator::Elem::WriteString( void cmVisualStudio10TargetGenerator::Generate() { - this->ProjectType = computeProjectType(this->GeneratorTarget); + this->ProjectType = this->ComputeProjectType(this->GeneratorTarget); this->Managed = this->ProjectType == VsProjectType::csproj; const std::string ProjectFileExtension = - computeProjectFileExtension(this->ProjectType); + getProjectFileExtension(this->ProjectType); if (this->ProjectType == VsProjectType::csproj && this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY) { @@ -416,10 +411,12 @@ void cmVisualStudio10TargetGenerator::Generate() char magic[] = { char(0xEF), char(0xBB), char(0xBF) }; BuildFileStream.write(magic, 3); - if (this->ProjectType == VsProjectType::csproj && - this->GeneratorTarget->IsDotNetSdkTarget() && - this->GlobalGenerator->GetVersion() >= - cmGlobalVisualStudioGenerator::VSVersion::VS16) { + if (this->ProjectType == VsProjectType::proj) { + this->WriteZeroCheckProj(BuildFileStream); + } else if (this->ProjectType == VsProjectType::csproj && + this->GeneratorTarget->IsDotNetSdkTarget() && + this->GlobalGenerator->GetVersion() >= + cmGlobalVisualStudioGenerator::VSVersion::VS16) { this->WriteSdkStyleProjectFile(BuildFileStream); } else { this->WriteClassicMsBuildProjectFile(BuildFileStream); @@ -681,6 +678,8 @@ void cmVisualStudio10TargetGenerator::WriteClassicMsBuildProjectFile( .Attribute("Project", VS10_CSharp_DEFAULT_PROPS) .Attribute("Condition", "Exists('" VS10_CSharp_DEFAULT_PROPS "')"); break; + default: + break; } this->WriteProjectConfigurationValues(e0); @@ -737,6 +736,8 @@ void cmVisualStudio10TargetGenerator::WriteClassicMsBuildProjectFile( case VsProjectType::csproj: props = VS10_CSharp_USER_PROPS; break; + default: + break; } if (cmValue p = this->GeneratorTarget->GetProperty("VS_USER_PROPS")) { props = *p; @@ -779,6 +780,8 @@ void cmVisualStudio10TargetGenerator::WriteClassicMsBuildProjectFile( Elem(e0, "Import").Attribute("Project", VS10_CSharp_TARGETS); } break; + default: + break; } this->WriteTargetSpecificReferences(e0); @@ -944,6 +947,45 @@ void cmVisualStudio10TargetGenerator::WriteSdkStyleProjectFile( this->WriteProjectReferences(e0); } +void cmVisualStudio10TargetGenerator::WriteZeroCheckProj( + cmGeneratedFileStream& BuildFileStream) +{ + // ZERO_CHECK.proj is an XML file without any imports or targets. This is a + // ProjectReference for other targets and therefore, it needs to follow the + // ProjectReference protocol as documented here: + // https://github.com/dotnet/msbuild/blob/main/documentation/ProjectReference-Protocol.md + // + // We implement MSBuild target Build from WriteCustomCommand which calls + // WriteZeroCheckBuildTarget after setting up the command generator. MSBuild + // target Clean is a no-op as we do all the work for ZERO_CHECK on Build. + // MSBuild target GetTargetPath is needed and is no-op. + // MSBuild targets GetNativeManifest and GetCopyToOutputDirectoryItems are + // needed for MSBuild versions below 15.7 and are no-op. MSBuild target + // BeforeBuild is needed for supporting GLOBs. + BuildFileStream << "<?xml version=\"1.0\" encoding=\"" + << this->GlobalGenerator->Encoding() << "\"?>"; + { + Elem e0(BuildFileStream, "Project"); + e0.Attribute("DefaultTargets", "Build"); + e0.Attribute("ToolsVersion", this->GlobalGenerator->GetToolsVersion()); + e0.Attribute("xmlns", + "http://schemas.microsoft.com/developer/msbuild/2003"); + + this->WriteCustomCommands(e0); + + for (const char* targetName : + { "Clean", "GetTargetPath", "GetNativeManifest", + "GetCopyToOutputDirectoryItems" }) { + { + Elem e1(e0, "Target"); + e1.Attribute("Name", targetName); + } + } + + this->WriteZeroCheckBeforeBuildTarget(e0); + } +} + void cmVisualStudio10TargetGenerator::WriteCommonPropertyGroupGlobals(Elem& e1) { e1.Attribute("Label", "Globals"); @@ -1659,11 +1701,16 @@ void cmVisualStudio10TargetGenerator::WriteCustomRule( } } } + if (this->ProjectType == VsProjectType::proj) { + this->WriteZeroCheckBuildTarget(e0, command, source); + return; + } + cmLocalVisualStudio7Generator* lg = this->LocalGenerator; std::unique_ptr<Elem> spe1; std::unique_ptr<Elem> spe2; - if (this->ProjectType != VsProjectType::csproj) { + if (this->ProjectType == VsProjectType::vcxproj) { spe1 = cm::make_unique<Elem>(e0, "ItemGroup"); spe2 = cm::make_unique<Elem>(*spe1, "CustomBuild"); this->WriteSource(*spe2, source); @@ -1861,7 +1908,7 @@ void cmVisualStudio10TargetGenerator::WriteGroups() // Write out group file std::string path = cmStrCat( this->LocalGenerator->GetCurrentBinaryDirectory(), '/', this->Name, - computeProjectFileExtension(this->GeneratorTarget), ".filters"); + this->ComputeProjectFileExtension(this->GeneratorTarget), ".filters"); cmGeneratedFileStream fout(path); fout.SetCopyIfDifferent(true); char magic[] = { char(0xEF), char(0xBB), char(0xBF) }; @@ -3016,6 +3063,134 @@ void cmVisualStudio10TargetGenerator::OutputLinkIncremental( } } +void cmVisualStudio10TargetGenerator::WriteZeroCheckBuildTarget( + cmVisualStudio10TargetGenerator::Elem& e0, const cmCustomCommand& command, + const cmSourceFile* source) +{ + cmLocalVisualStudio7Generator* lg = this->LocalGenerator; + + Elem e1(e0, "Target"); + e1.Attribute("Name", "Build"); + + std::string noConfig{}; + cmCustomCommandGenerator ccg{ command, noConfig, lg, true }; + std::string comment = lg->ConstructComment(ccg); + comment = cmVS10EscapeComment(comment); + std::string script = lg->ConstructScript(ccg); + bool symbolic = false; + // input files for custom command + std::stringstream additional_inputs; + { + const char* sep = ""; + if (this->ProjectType == VsProjectType::proj) { + // List explicitly the path to primary input. + std::string sourceFullPath = source->GetFullPath(); + ConvertToWindowsSlash(sourceFullPath); + additional_inputs << sourceFullPath; + sep = ";"; + } + + // Avoid listing an input more than once. + std::set<std::string> unique_inputs; + // The source is either implicitly an input or has been added above. + unique_inputs.insert(source->GetFullPath()); + + for (std::string const& d : ccg.GetDepends()) { + std::string dep; + if (lg->GetRealDependency(d, noConfig, dep)) { + if (!unique_inputs.insert(dep).second) { + // already listed + continue; + } + ConvertToWindowsSlash(dep); + additional_inputs << sep << dep; + sep = ";"; + if (!symbolic) { + if (cmSourceFile* sf = this->Makefile->GetSource( + dep, cmSourceFileLocationKind::Known)) { + symbolic = sf->GetPropertyAsBool("SYMBOLIC"); + } + } + } + } + } + // output files for custom command + std::stringstream outputs; + { + const char* sep = ""; + for (std::string const& o : ccg.GetOutputs()) { + std::string out = o; + ConvertToWindowsSlash(out); + outputs << sep << out; + sep = ";"; + if (!symbolic) { + if (cmSourceFile* sf = + this->Makefile->GetSource(o, cmSourceFileLocationKind::Known)) { + symbolic = sf->GetPropertyAsBool("SYMBOLIC"); + } + } + } + } + script += lg->FinishConstructScript(this->ProjectType); + + e1.Attribute("Inputs", cmVS10EscapeAttr(additional_inputs.str())); + e1.Attribute("Outputs", cmVS10EscapeAttr(outputs.str())); + + e1.SetHasElements(); + + if (!comment.empty()) { + Elem(e1, "Message").Attribute("Text", comment); + } + Elem(e1, "Exec").Attribute("Command", script); +} + +void cmVisualStudio10TargetGenerator::WriteZeroCheckBeforeBuildTarget( + cmVisualStudio10TargetGenerator::Elem& e0) +{ + const auto& commands = this->GeneratorTarget->GetPreBuildCommands(); + if (commands.empty()) { + return; + } + + { + Elem e1(e0, "Target"); + e1.Attribute("Name", "BeforeBuild"); + e1.Attribute("BeforeTargets", "Build"); + + cmLocalVisualStudio7Generator* lg = this->LocalGenerator; + std::string script; + const char* pre = ""; + std::string comment; + for (cmCustomCommand const& cc : commands) { + cmCustomCommandGenerator ccg(cc, std::string{}, lg); + if (!ccg.HasOnlyEmptyCommandLines()) { + comment += pre; + comment += lg->ConstructComment(ccg); + script += pre; + pre = "\n"; + script += lg->ConstructScript(ccg); + } + } + + if (script.empty()) { + return; + } + + script += lg->FinishConstructScript(this->ProjectType); + comment = cmVS10EscapeComment(comment); + std::string strippedComment = comment; + strippedComment.erase( + std::remove(strippedComment.begin(), strippedComment.end(), '\t'), + strippedComment.end()); + + e1.SetHasElements(); + if (!comment.empty() && !strippedComment.empty()) { + Elem(e1, "Message").Attribute("Text", comment); + } + Elem(e1, "Exec").Attribute("Command", script); + } +} + std::vector<std::string> cmVisualStudio10TargetGenerator::GetIncludes( std::string const& config, std::string const& lang) const { @@ -3057,6 +3232,8 @@ bool cmVisualStudio10TargetGenerator::ComputeClOptions( cm::make_unique<Options>(this->LocalGenerator, Options::CSharpCompiler, gg->GetCSharpFlagTable()); break; + default: + break; } Options& clOptions = *pOptions; @@ -3182,6 +3359,8 @@ bool cmVisualStudio10TargetGenerator::ComputeClOptions( return def.find('=') != std::string::npos; }); break; + default: + break; } clOptions.AddDefines(targetDefines); @@ -3309,7 +3488,9 @@ void cmVisualStudio10TargetGenerator::WriteClOptions( } else if (this->MSTools) { cmsys::RegularExpression clangToolset("v[0-9]+_clang_.*"); const char* toolset = this->GlobalGenerator->GetPlatformToolset(); - if (toolset && clangToolset.find(toolset)) { + cmValue noCompileBatching = + this->GeneratorTarget->GetProperty("VS_NO_COMPILE_BATCHING"); + if (noCompileBatching.IsOn() || (toolset && clangToolset.find(toolset))) { e2.Element("ObjectFileName", "$(IntDir)%(filename).obj"); } else { e2.Element("ObjectFileName", "$(IntDir)"); @@ -3688,7 +3869,8 @@ bool cmVisualStudio10TargetGenerator::ComputeCudaLinkOptions( this->LocalGenerator->MaybeRelativeToCurBinDir(l.Value.Value); ConvertToWindowsSlash(path); if (!cmVS10IsTargetsFile(l.Value.Value)) { - libVec.push_back(path); + libVec.push_back(l.HasFeature() ? l.GetFormattedItem(path).Value + : path); } } else { libVec.push_back(l.Value.Value); @@ -4305,6 +4487,9 @@ void cmVisualStudio10TargetGenerator::AddLibraries( this->AdditionalUsingDirectories[config].insert( cmSystemTools::GetFilenamePath(location)); break; + default: + // In .proj files, we wouldn't be referencing libraries. + break; } } } @@ -4326,7 +4511,8 @@ void cmVisualStudio10TargetGenerator::AddLibraries( if (cmVS10IsTargetsFile(l.Value.Value)) { vsTargetVec.push_back(path); } else { - libVec.push_back(path); + libVec.push_back(l.HasFeature() ? l.GetFormattedItem(path).Value + : path); } } else if (!l.Target || l.Target->GetType() != cmStateEnums::INTERFACE_LIBRARY) { @@ -4533,7 +4719,7 @@ void cmVisualStudio10TargetGenerator::WriteProjectReferences(Elem& e0) path = *p; } else { path = cmStrCat(lg->GetCurrentBinaryDirectory(), '/', dt->GetName(), - computeProjectFileExtension(dt)); + this->ComputeProjectFileExtension(dt)); } ConvertToWindowsSlash(path); Elem e2(e1, "ProjectReference"); @@ -5387,6 +5573,26 @@ std::string cmVisualStudio10TargetGenerator::GetCMakeFilePath( return path; } +std::string cmVisualStudio10TargetGenerator::ComputeProjectFileExtension( + cmGeneratorTarget const* t) const +{ + return getProjectFileExtension(this->ComputeProjectType(t)); +} + +VsProjectType cmVisualStudio10TargetGenerator::ComputeProjectType( + cmGeneratorTarget const* t) const +{ + if (this->GlobalGenerator->GetVersion() >= + cmGlobalVisualStudioGenerator::VSVersion::VS16 && + t->GetName() == CMAKE_CHECK_BUILD_SYSTEM_TARGET) { + return VsProjectType::proj; + } + if (t->IsCSharpOnly()) { + return VsProjectType::csproj; + } + return VsProjectType::vcxproj; +} + void cmVisualStudio10TargetGenerator::WriteStdOutEncodingUtf8(Elem& e1) { if (this->GlobalGenerator->IsUtf8EncodingSupported()) { diff --git a/Source/cmVisualStudio10TargetGenerator.h b/Source/cmVisualStudio10TargetGenerator.h index 8d777a3..7a0b8da 100644 --- a/Source/cmVisualStudio10TargetGenerator.h +++ b/Source/cmVisualStudio10TargetGenerator.h @@ -264,6 +264,13 @@ private: void WriteClassicMsBuildProjectFile(cmGeneratedFileStream& BuildFileStream); void WriteSdkStyleProjectFile(cmGeneratedFileStream& BuildFileStream); + void WriteZeroCheckProj(cmGeneratedFileStream& BuildFileStream); + void WriteZeroCheckBuildTarget(cmVisualStudio10TargetGenerator::Elem& e0, + const cmCustomCommand& command, + const cmSourceFile* source); + void WriteZeroCheckBeforeBuildTarget( + cmVisualStudio10TargetGenerator::Elem& e0); + void WriteCommonPropertyGroupGlobals( cmVisualStudio10TargetGenerator::Elem& e1); @@ -275,4 +282,7 @@ private: void ParseSettingsProperty(const std::string& settingsPropertyValue, ConfigToSettings& toolSettings); std::string GetCMakeFilePath(const char* name) const; + + std::string ComputeProjectFileExtension(cmGeneratorTarget const* t) const; + VsProjectType ComputeProjectType(cmGeneratorTarget const* t) const; }; diff --git a/Source/cmVisualStudioGeneratorOptions.cxx b/Source/cmVisualStudioGeneratorOptions.cxx index 9045a4d..d5d08b8 100644 --- a/Source/cmVisualStudioGeneratorOptions.cxx +++ b/Source/cmVisualStudioGeneratorOptions.cxx @@ -182,7 +182,8 @@ void cmVisualStudioGeneratorOptions::FixCudaCodeGeneration() // First entries for the -arch=<arch> [-code=<code>,...] pair. if (!arch.empty()) { std::string arch_name = arch[0]; - if (arch_name == "all" || arch_name == "all-major") { + if (arch_name == "all" || arch_name == "all-major" || + arch_name == "native") { AppendFlagString("AdditionalOptions", "-arch=" + arch_name); return; } diff --git a/Source/cmVsProjectType.h b/Source/cmVsProjectType.h index 8899267..04053a0 100644 --- a/Source/cmVsProjectType.h +++ b/Source/cmVsProjectType.h @@ -7,5 +7,6 @@ enum class VsProjectType { vcxproj, - csproj + csproj, + proj, }; diff --git a/Source/cmWhileCommand.cxx b/Source/cmWhileCommand.cxx index a93a81f..68d5a9a 100644 --- a/Source/cmWhileCommand.cxx +++ b/Source/cmWhileCommand.cxx @@ -17,6 +17,8 @@ #include "cmMakefile.h" #include "cmMessageType.h" #include "cmOutputConverter.h" +#include "cmPolicies.h" +#include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmake.h" @@ -79,9 +81,8 @@ bool cmWhileFunctionBlocker::Replay(std::vector<cmListFileFunction> functions, return out; }; - // FIXME(#23296): For compatibility with older versions of CMake, we - // tolerate condition errors that evaluate to false. We should add - // a policy to enforce such errors. + // For compatibility with projects that do not set CMP0130 to NEW, + // we tolerate condition errors that evaluate to false. bool enforceError = true; std::string errorString; MessageType messageType; @@ -110,14 +111,38 @@ bool cmWhileFunctionBlocker::Replay(std::vector<cmListFileFunction> functions, } } + if (!errorString.empty() && !enforceError) { + // This error should only be enforced if CMP0130 is NEW. + switch (mf.GetPolicyStatus(cmPolicies::CMP0130)) { + case cmPolicies::WARN: + // Convert the error to a warning and enforce it. + messageType = MessageType::AUTHOR_WARNING; + enforceError = true; + break; + case cmPolicies::OLD: + // OLD behavior is to silently ignore the error. + break; + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::NEW: + // NEW behavior is to enforce the error. + enforceError = true; + break; + } + } + if (!errorString.empty() && enforceError) { - std::string err = "had incorrect arguments:\n "; + std::string err = "while() given incorrect arguments:\n "; for (auto const& i : expandedArguments) { err += " "; err += cmOutputConverter::EscapeForCMake(i.GetValue()); } err += "\n"; err += errorString; + if (mf.GetPolicyStatus(cmPolicies::CMP0130) == cmPolicies::WARN) { + err = + cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0130), '\n', err); + } mf.GetCMakeInstance()->IssueMessage(messageType, err, whileBT); if (messageType == MessageType::FATAL_ERROR) { cmSystemTools::SetFatalErrorOccured(); diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 7de488b..a602458 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -1158,6 +1158,12 @@ void cmake::SetArgs(const std::vector<std::string>& args) // iterate each argument std::string const& arg = args[i]; + if (scriptMode && arg == "--") { + // Stop processing CMake args and avoid possible errors + // when arbitrary args are given to CMake script. + break; + } + // Generator flag has special handling for when to print help // so it becomes the exception if (generatorCommand.matches(arg)) { @@ -1455,7 +1461,7 @@ void cmake::PrintTraceFormatVersion() Json::StreamWriterBuilder builder; builder["indentation"] = ""; version["major"] = 1; - version["minor"] = 1; + version["minor"] = 2; val["version"] = version; msg = Json::writeString(builder, val); #endif diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 32c01e5..f1c1bdc 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -97,7 +97,8 @@ void CMakeCommandUsage(std::string const& program) << "Available commands: \n" << " capabilities - Report capabilities built into cmake " "in JSON format\n" - << " cat <files>... - concat the files and print them to the standard output\n" + << " cat [--] <files>... - concat the files and print them to the " + "standard output\n" << " chdir dir cmd [args...] - run command in a given directory\n" << " compare_files [--ignore-eol] file1 file2\n" << " - check if file1 is same as file2\n" @@ -110,7 +111,7 @@ void CMakeCommandUsage(std::string const& program) << " echo [<string>...] - displays arguments as text\n" << " echo_append [<string>...] - displays arguments as text but no new " "line\n" - << " env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...\n" + << " env [--unset=NAME ...] [NAME=VALUE ...] [--] <command> [<arg>...]\n" << " - run command in a modified environment\n" << " environment - display the current environment\n" << " make_directory <dir>... - create parent and <dir> directories\n" @@ -125,8 +126,9 @@ void CMakeCommandUsage(std::string const& program) << " remove_directory <dir>... - remove directories and their contents (deprecated: use rm instead)\n" << " rename oldname newname - rename a file or directory " "(on one volume)\n" - << " rm [-rRf] <file/dir>... - remove files or directories, use -f to " - "force it, r or R to remove directories and their contents recursively\n" + << " rm [-rRf] [--] <file/dir>... - remove files or directories, use -f " + "to force it, r or R to remove directories and their contents " + "recursively\n" << " sleep <number>... - sleep for given number of seconds\n" << " tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]\n" << " - create or extract a tar or zip archive\n" @@ -793,6 +795,12 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, auto ae = args.cend(); for (; ai != ae; ++ai) { std::string const& a = *ai; + if (a == "--") { + // Stop parsing options/environment variables; the next argument + // should be the command. + ++ai; + break; + } if (cmHasLiteralPrefix(a, "--unset=")) { // Unset environment variable. cmSystemTools::UnPutEnv(a.substr(8)); @@ -1051,9 +1059,12 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, // Command to concat files into one if (args[1] == "cat" && args.size() >= 3) { int return_value = 0; + bool doing_options = true; for (auto const& arg : cmMakeRange(args).advance(2)) { - if (cmHasLiteralPrefix(arg, "-")) { - if (arg != "--") { + if (doing_options && cmHasLiteralPrefix(arg, "-")) { + if (arg == "--") { + doing_options = false; + } else { cmSystemTools::Error(arg + ": option not handled"); return_value = 1; } @@ -1275,8 +1286,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, // FIXME: With advanced add_subdirectory usage, these are // not necessarily the same as the generator originally used. // We should pass all these directories through an info file. - lgd->SetRelativePathTopSource(homeDir); - lgd->SetRelativePathTopBinary(homeOutDir); + lgd->SetRelativePathTop(homeDir, homeOutDir); // Actually scan dependencies. return lgd->UpdateDependencies(depInfo, verbose, color) ? 0 : 2; @@ -1558,8 +1568,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, // FIXME: With advanced add_subdirectory usage, these are // not necessarily the same as the generator originally used. // We should pass all these directories through an info file. - lgd->SetRelativePathTopSource(homeDir); - lgd->SetRelativePathTopBinary(homeOutDir); + lgd->SetRelativePathTop(homeDir, homeOutDir); return cmTransformDepfile(format, *lgd, args[8], args[9]) ? 0 : 2; } diff --git a/Source/ctest.cxx b/Source/ctest.cxx index cad27fa..363f473 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -44,6 +44,9 @@ static const char* cmDocumentationOptions[][2] = { { "--test-output-size-failed <size>", "Limit the output for failed tests " "to <size> bytes" }, + { "--test-output-truncation <mode>", + "Truncate 'tail' (default), 'middle' or 'head' of test output once " + "maximum output size is reached" }, { "-F", "Enable failover." }, { "-j <jobs>, --parallel <jobs>", "Run the tests in parallel using the " diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c index 19bf982..22cbf06 100644 --- a/Source/kwsys/ProcessUNIX.c +++ b/Source/kwsys/ProcessUNIX.c @@ -1906,7 +1906,7 @@ static void kwsysProcessDestroy(kwsysProcess* cp) (errno == EINTR)) { } if (result > 0) { - /* This child has termianted. */ + /* This child has terminated. */ cp->ForkPIDs[i] = 0; if (--cp->CommandsLeft == 0) { /* All children have terminated. Close the signal pipe diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c index e97973e..17e1507 100644 --- a/Source/kwsys/ProcessWin32.c +++ b/Source/kwsys/ProcessWin32.c @@ -2119,7 +2119,7 @@ static void kwsysProcessSetExitExceptionByIndex(kwsysProcess* cp, int code, KWSYSPE_CASE(Fault, "In-page error"); break; case STATUS_INVALID_HANDLE: - KWSYSPE_CASE(Fault, "Invalid hanlde"); + KWSYSPE_CASE(Fault, "Invalid handle"); break; case STATUS_NONCONTINUABLE_EXCEPTION: KWSYSPE_CASE(Fault, "Noncontinuable exception"); |