diff options
Diffstat (limited to 'Source')
118 files changed, 3413 insertions, 855 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 1bc855e..8c57762 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -450,7 +450,6 @@ add_library( cmUVProcessChain.h cmUVStream.h cmUVStreambuf.h - cmUVSignalHackRAII.h cmVariableWatch.cxx cmVariableWatch.h cmVersion.cxx @@ -558,6 +557,8 @@ add_library( cmFindLibraryCommand.h cmFindPackageCommand.cxx cmFindPackageCommand.h + cmFindPackageStack.cxx + cmFindPackageStack.h cmFindPathCommand.cxx cmFindPathCommand.h cmFindProgramCommand.cxx @@ -1090,6 +1091,9 @@ add_library( CTest/cmCTestP4.cxx CTest/cmCTestP4.h + CTest/cmUVJobServerClient.cxx + CTest/cmUVJobServerClient.h + LexerParser/cmCTestResourceGroupsLexer.cxx LexerParser/cmCTestResourceGroupsLexer.h LexerParser/cmCTestResourceGroupsLexer.in.l diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index a51ba2e..eda95c1 100644 --- a/Source/CMakeVersion.cmake +++ b/Source/CMakeVersion.cmake @@ -1,7 +1,7 @@ # CMake version number components. set(CMake_VERSION_MAJOR 3) set(CMake_VERSION_MINOR 28) -set(CMake_VERSION_PATCH 1) +set(CMake_VERSION_PATCH 20240104) #set(CMake_VERSION_RC 0) set(CMake_VERSION_IS_DIRTY 0) diff --git a/Source/CPack/WiX/cmCPackWIXGenerator.cxx b/Source/CPack/WiX/cmCPackWIXGenerator.cxx index 5077596..6918b5e 100644 --- a/Source/CPack/WiX/cmCPackWIXGenerator.cxx +++ b/Source/CPack/WiX/cmCPackWIXGenerator.cxx @@ -359,6 +359,7 @@ void cmCPackWIXGenerator::CreateWiXVariablesIncludeFile() GetOption("CPACK_PACKAGE_NAME")); CopyDefinition(includeFile, "CPACK_WIX_PROGRAM_MENU_FOLDER"); CopyDefinition(includeFile, "CPACK_WIX_UI_REF"); + CopyDefinition(includeFile, "CPACK_WIX_INSTALL_SCOPE"); } void cmCPackWIXGenerator::CreateWiXPropertiesIncludeFile() diff --git a/Source/CPack/cmCPackArchiveGenerator.cxx b/Source/CPack/cmCPackArchiveGenerator.cxx index c9c069c..b7b6785 100644 --- a/Source/CPack/cmCPackArchiveGenerator.cxx +++ b/Source/CPack/cmCPackArchiveGenerator.cxx @@ -5,6 +5,8 @@ #include <cstring> #include <map> #include <ostream> +#include <unordered_map> +#include <unordered_set> #include <utility> #include <vector> @@ -17,6 +19,121 @@ #include "cmValue.h" #include "cmWorkingDirectory.h" +enum class DeduplicateStatus +{ + Skip, + Add, + Error +}; + +/** + * @class cmCPackArchiveGenerator::Deduplicator + * @brief A utility class for deduplicating files, folders, and symlinks. + * + * This class is responsible for identifying duplicate files, folders, and + * symlinks when generating an archive. It keeps track of the paths that have + * been processed and helps in deciding whether a new path should be added, + * skipped, or flagged as an error. + */ +class cmCPackArchiveGenerator::Deduplicator +{ +private: + /** + * @brief Compares a file with already processed files. + * + * @param path The path of the file to compare. + * @param localTopLevel The top-level directory for the file. + * @return DeduplicateStatus indicating whether to add, skip, or flag an + * error for the file. + */ + DeduplicateStatus CompareFile(const std::string& path, + const std::string& localTopLevel) + { + auto fileItr = this->Files.find(path); + if (fileItr != this->Files.end()) { + return cmSystemTools::FilesDiffer(path, fileItr->second) + ? DeduplicateStatus::Error + : DeduplicateStatus::Skip; + } + + this->Files[path] = cmStrCat(localTopLevel, "/", path); + return DeduplicateStatus::Add; + } + + /** + * @brief Compares a folder with already processed folders. + * + * @param path The path of the folder to compare. + * @return DeduplicateStatus indicating whether to add or skip the folder. + */ + DeduplicateStatus CompareFolder(const std::string& path) + { + if (this->Folders.find(path) != this->Folders.end()) { + return DeduplicateStatus::Skip; + } + + this->Folders.emplace(path); + return DeduplicateStatus::Add; + } + + /** + * @brief Compares a symlink with already processed symlinks. + * + * @param path The path of the symlink to compare. + * @return DeduplicateStatus indicating whether to add, skip, or flag an + * error for the symlink. + */ + DeduplicateStatus CompareSymlink(const std::string& path) + { + auto symlinkItr = this->Symlink.find(path); + std::string symlinkValue; + auto status = cmSystemTools::ReadSymlink(path, symlinkValue); + if (!status.IsSuccess()) { + return DeduplicateStatus::Error; + } + + if (symlinkItr != this->Symlink.end()) { + return symlinkValue == symlinkItr->second ? DeduplicateStatus::Skip + : DeduplicateStatus::Error; + } + + this->Symlink[path] = symlinkValue; + return DeduplicateStatus::Add; + } + +public: + /** + * @brief Determines the deduplication status of a given path. + * + * This method identifies whether the given path is a file, folder, or + * symlink and then delegates to the appropriate comparison method. + * + * @param path The path to check for deduplication. + * @param localTopLevel The top-level directory for the path. + * @return DeduplicateStatus indicating the action to take for the given + * path. + */ + DeduplicateStatus IsDeduplicate(const std::string& path, + const std::string& localTopLevel) + { + DeduplicateStatus status; + if (cmSystemTools::FileIsDirectory(path)) { + status = this->CompareFolder(path); + } else if (cmSystemTools::FileIsSymlink(path)) { + status = this->CompareSymlink(path); + } else { + status = this->CompareFile(path, localTopLevel); + } + + return status; + } + +private: + std::unordered_map<std::string, std::string> Symlink; + std::unordered_set<std::string> Folders; + std::unordered_map<std::string, std::string> Files; +}; + cmCPackGenerator* cmCPackArchiveGenerator::Create7ZGenerator() { return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "7zip", @@ -110,7 +227,8 @@ int cmCPackArchiveGenerator::InitializeInternal() } int cmCPackArchiveGenerator::addOneComponentToArchive( - cmArchiveWrite& archive, cmCPackComponent* component) + cmArchiveWrite& archive, cmCPackComponent* component, + Deduplicator* deduplicator) { cmCPackLogger(cmCPackLog::LOG_VERBOSE, " - packaging component: " << component->Name << std::endl); @@ -139,8 +257,25 @@ int cmCPackArchiveGenerator::addOneComponentToArchive( } for (std::string const& file : component->Files) { std::string rp = filePrefix + file; - cmCPackLogger(cmCPackLog::LOG_DEBUG, "Adding file: " << rp << std::endl); - archive.Add(rp, 0, nullptr, false); + + DeduplicateStatus status = DeduplicateStatus::Add; + if (deduplicator != nullptr) { + status = deduplicator->IsDeduplicate(rp, localToplevel); + } + + if (deduplicator == nullptr || status == DeduplicateStatus::Add) { + cmCPackLogger(cmCPackLog::LOG_DEBUG, "Adding file: " << rp << std::endl); + archive.Add(rp, 0, nullptr, false); + } else if (status == DeduplicateStatus::Error) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "ERROR The data in files with the " + "same filename is different."); + return 0; + } else { + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "Passing file: " << rp << std::endl); + } + if (!archive) { cmCPackLogger(cmCPackLog::LOG_ERROR, "ERROR while packaging files: " << archive.GetError() @@ -197,6 +332,8 @@ int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup) std::string packageFileName = std::string(this->toplevel) + "/" + this->GetArchiveComponentFileName(compG.first, true); + Deduplicator deduplicator; + // open a block in order to automatically close archive // at the end of the block { @@ -204,7 +341,7 @@ int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup) // now iterate over the component of this group for (cmCPackComponent* comp : (compG.second).Components) { // Add the files of this component to the archive - this->addOneComponentToArchive(archive, comp); + this->addOneComponentToArchive(archive, comp, &deduplicator); } } // add the generated package to package file names list @@ -231,7 +368,7 @@ int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup) { DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive); // Add the files of this component to the archive - this->addOneComponentToArchive(archive, &(comp.second)); + this->addOneComponentToArchive(archive, &(comp.second), nullptr); } // add the generated package to package file names list this->packageFileNames.push_back(std::move(packageFileName)); @@ -252,7 +389,7 @@ int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup) { DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive); // Add the files of this component to the archive - this->addOneComponentToArchive(archive, &(comp.second)); + this->addOneComponentToArchive(archive, &(comp.second), nullptr); } // add the generated package to package file names list this->packageFileNames.push_back(std::move(packageFileName)); @@ -282,10 +419,12 @@ int cmCPackArchiveGenerator::PackageComponentsAllInOne() << std::endl); DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive); + Deduplicator deduplicator; + // The ALL COMPONENTS in ONE package case for (auto& comp : this->Components) { // Add the files of this component to the archive - this->addOneComponentToArchive(archive, &(comp.second)); + this->addOneComponentToArchive(archive, &(comp.second), &deduplicator); } // archive goes out of scope so it will finalized and closed. diff --git a/Source/CPack/cmCPackArchiveGenerator.h b/Source/CPack/cmCPackArchiveGenerator.h index 8a9bbc6..b8a1afa 100644 --- a/Source/CPack/cmCPackArchiveGenerator.h +++ b/Source/CPack/cmCPackArchiveGenerator.h @@ -47,6 +47,8 @@ private: std::string GetArchiveComponentFileName(const std::string& component, bool isGroupName); + class Deduplicator; + protected: int InitializeInternal() override; /** @@ -54,9 +56,11 @@ protected: * to the provided (already opened) archive. * @param[in,out] archive the archive object * @param[in] component the component whose file will be added to archive + * @param[in] deduplicator file deduplicator utility. */ int addOneComponentToArchive(cmArchiveWrite& archive, - cmCPackComponent* component); + cmCPackComponent* component, + Deduplicator* deduplicator); /** * The main package file method. diff --git a/Source/CPack/cmCPackInnoSetupGenerator.cxx b/Source/CPack/cmCPackInnoSetupGenerator.cxx index b8bf070..bf90b06 100644 --- a/Source/CPack/cmCPackInnoSetupGenerator.cxx +++ b/Source/CPack/cmCPackInnoSetupGenerator.cxx @@ -579,8 +579,9 @@ bool cmCPackInnoSetupGenerator::ProcessFiles() bool cmCPackInnoSetupGenerator::ProcessComponents() { - codeIncludes.push_back("{ The following lines are required by CPack because " - "this script uses components }"); + codeIncludes.emplace_back( + "{ The following lines are required by CPack because " + "this script uses components }"); // Installation types std::vector<cmCPackInstallationType*> types(InstallationTypes.size()); @@ -607,7 +608,7 @@ bool cmCPackInnoSetupGenerator::ProcessComponents() "\"{code:CPackGetCustomInstallationMessage}\""; customTypeParams["Flags"] = "iscustom"; - allTypes.push_back("custom"); + allTypes.emplace_back("custom"); typeInstructions.push_back(ISKeyValueLine(customTypeParams)); // Components @@ -633,6 +634,7 @@ bool cmCPackInnoSetupGenerator::ProcessComponents() } else if (!component->InstallationTypes.empty()) { std::vector<std::string> installationTypes; + installationTypes.reserve(component->InstallationTypes.size()); for (cmCPackInstallationType* j : component->InstallationTypes) { installationTypes.push_back(j->Name); } diff --git a/Source/CPack/cmCPackLog.h b/Source/CPack/cmCPackLog.h index 2ab2f8e..347b0f7 100644 --- a/Source/CPack/cmCPackLog.h +++ b/Source/CPack/cmCPackLog.h @@ -29,7 +29,7 @@ public: cmCPackLog(const cmCPackLog&) = delete; cmCPackLog& operator=(const cmCPackLog&) = delete; - enum __log_tags + enum cm_log_tags { NOTAG = 0, LOG_OUTPUT = 0x1, diff --git a/Source/CTest/cmCTestBZR.cxx b/Source/CTest/cmCTestBZR.cxx index 36df344..87081f0 100644 --- a/Source/CTest/cmCTestBZR.cxx +++ b/Source/CTest/cmCTestBZR.cxx @@ -374,7 +374,7 @@ bool cmCTestBZR::UpdateImpl() // Use "bzr pull" to update the working tree. std::vector<std::string> bzr_update; bzr_update.push_back(this->CommandLineTool); - bzr_update.push_back("pull"); + bzr_update.emplace_back("pull"); cm::append(bzr_update, args); diff --git a/Source/CTest/cmCTestCVS.cxx b/Source/CTest/cmCTestCVS.cxx index ef95b25..badd43e 100644 --- a/Source/CTest/cmCTestCVS.cxx +++ b/Source/CTest/cmCTestCVS.cxx @@ -92,8 +92,8 @@ bool cmCTestCVS::UpdateImpl() // Run "cvs update" to update the work tree. std::vector<std::string> cvs_update; cvs_update.push_back(this->CommandLineTool); - cvs_update.push_back("-z3"); - cvs_update.push_back("update"); + cvs_update.emplace_back("-z3"); + cvs_update.emplace_back("update"); cm::append(cvs_update, args); UpdateParser out(this, "up-out> "); diff --git a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx index af495bb..2c92d77 100644 --- a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx +++ b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx @@ -2,25 +2,27 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestEmptyBinaryDirectoryCommand.h" -#include <sstream> - #include "cmCTestScriptHandler.h" - -class cmExecutionStatus; +#include "cmExecutionStatus.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmStringAlgorithms.h" bool cmCTestEmptyBinaryDirectoryCommand::InitialPass( - std::vector<std::string> const& args, cmExecutionStatus& /*unused*/) + std::vector<std::string> const& args, cmExecutionStatus& status) { if (args.size() != 1) { this->SetError("called with incorrect number of arguments"); return false; } - if (!cmCTestScriptHandler::EmptyBinaryDirectory(args[0])) { - std::ostringstream ostr; - ostr << "problem removing the binary directory: " << args[0]; - this->SetError(ostr.str()); - return false; + std::string err; + if (!cmCTestScriptHandler::EmptyBinaryDirectory(args[0], err)) { + status.GetMakefile().IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Did not remove the binary directory:\n ", args[0], + "\nbecause:\n ", err)); + return true; } return true; diff --git a/Source/CTest/cmCTestGIT.cxx b/Source/CTest/cmCTestGIT.cxx index ca8659e..984c837 100644 --- a/Source/CTest/cmCTestGIT.cxx +++ b/Source/CTest/cmCTestGIT.cxx @@ -159,7 +159,7 @@ bool cmCTestGIT::UpdateByFetchAndReset() // Use "git fetch" to get remote commits. std::vector<std::string> git_fetch; git_fetch.push_back(git); - git_fetch.push_back("fetch"); + git_fetch.emplace_back("fetch"); // Add user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); diff --git a/Source/CTest/cmCTestHG.cxx b/Source/CTest/cmCTestHG.cxx index e1a945d..3d56be0 100644 --- a/Source/CTest/cmCTestHG.cxx +++ b/Source/CTest/cmCTestHG.cxx @@ -137,9 +137,9 @@ bool cmCTestHG::UpdateImpl() // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) std::vector<std::string> hg_update; - hg_update.push_back(this->CommandLineTool.c_str()); - hg_update.push_back("update"); - hg_update.push_back("-v"); + hg_update.emplace_back(this->CommandLineTool); + hg_update.emplace_back("update"); + hg_update.emplace_back("-v"); // Add user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx index ca07a08..7b72f30 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.cxx +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -40,7 +40,7 @@ #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" -#include "cmUVSignalHackRAII.h" // IWYU pragma: keep +#include "cmUVJobServerClient.h" #include "cmWorkingDirectory.h" namespace cmsys { @@ -83,17 +83,12 @@ cmCTestMultiProcessHandler::cmCTestMultiProcessHandler() cmCTestMultiProcessHandler::~cmCTestMultiProcessHandler() = default; // Set the tests -void cmCTestMultiProcessHandler::SetTests(TestMap& tests, - PropertiesMap& properties) +void cmCTestMultiProcessHandler::SetTests(TestMap tests, + PropertiesMap properties) { - this->Tests = tests; - this->Properties = properties; - this->Total = this->Tests.size(); - // set test run map to false for all - for (auto const& t : this->Tests) { - this->TestRunningMap[t.first] = false; - this->TestFinishMap[t.first] = false; - } + this->PendingTests = std::move(tests); + this->Properties = std::move(properties); + this->Total = this->PendingTests.size(); if (!this->CTest->GetShowOnly()) { this->ReadCostData(); this->HasCycles = !this->CheckCycles(); @@ -126,25 +121,50 @@ void cmCTestMultiProcessHandler::SetTestLoad(unsigned long load) } } +bool cmCTestMultiProcessHandler::Complete() +{ + return this->Completed == this->Total; +} + +void cmCTestMultiProcessHandler::InitializeLoop() +{ + this->Loop.init(); + this->StartNextTestsOnIdle_.init(*this->Loop, this); + this->StartNextTestsOnTimer_.init(*this->Loop, this); + + this->JobServerClient = cmUVJobServerClient::Connect( + *this->Loop, /*onToken=*/[this]() { this->JobServerReceivedToken(); }, + /*onDisconnect=*/nullptr); + if (this->JobServerClient) { + cmCTestLog(this->CTest, OUTPUT, + "Connected to MAKE jobserver" << std::endl); + } +} + +void cmCTestMultiProcessHandler::FinalizeLoop() +{ + this->JobServerClient.reset(); + this->StartNextTestsOnTimer_.reset(); + this->StartNextTestsOnIdle_.reset(); + this->Loop.reset(); +} + void cmCTestMultiProcessHandler::RunTests() { this->CheckResume(); if (this->HasCycles || this->HasInvalidGeneratedResourceSpec) { return; } -#ifdef CMAKE_UV_SIGNAL_HACK - cmUVSignalHackRAII hackRAII; -#endif this->TestHandler->SetMaxIndex(this->FindMaxIndex()); - uv_loop_init(&this->Loop); - this->StartNextTests(); - uv_run(&this->Loop, UV_RUN_DEFAULT); - uv_loop_close(&this->Loop); + this->InitializeLoop(); + this->StartNextTestsOnIdle(); + uv_run(this->Loop, UV_RUN_DEFAULT); + this->FinalizeLoop(); if (!this->StopTimePassed && !this->CheckStopOnFailure()) { - assert(this->Completed == this->Total); - assert(this->Tests.empty()); + assert(this->Complete()); + assert(this->PendingTests.empty()); } assert(this->AllResourcesAvailable()); @@ -152,38 +172,17 @@ void cmCTestMultiProcessHandler::RunTests() this->UpdateCostData(); } -bool cmCTestMultiProcessHandler::StartTestProcess(int test) +void cmCTestMultiProcessHandler::StartTestProcess(int test) { - if (this->HaveAffinity && this->Properties[test]->WantAffinity) { - size_t needProcessors = this->GetProcessorsUsed(test); - if (needProcessors > this->ProcessorsAvailable.size()) { - return false; - } - std::vector<size_t> affinity; - affinity.reserve(needProcessors); - for (size_t i = 0; i < needProcessors; ++i) { - auto p = this->ProcessorsAvailable.begin(); - affinity.push_back(*p); - this->ProcessorsAvailable.erase(p); - } - this->Properties[test]->Affinity = std::move(affinity); - } - cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "test " << test << "\n", this->Quiet); - this->TestRunningMap[test] = true; // mark the test as running - // now remove the test itself - this->EraseTest(test); - this->RunningCount += this->GetProcessorsUsed(test); - auto testRun = cm::make_unique<cmCTestRunTest>(*this); + auto testRun = cm::make_unique<cmCTestRunTest>(*this, test); if (this->RepeatMode != cmCTest::Repeat::Never) { testRun->SetRepeatMode(this->RepeatMode); testRun->SetNumberOfRuns(this->RepeatCount); } - testRun->SetIndex(test); - testRun->SetTestProperties(this->Properties[test]); if (this->UseResourceSpec) { testRun->SetUseAllocatedResources(true); testRun->SetAllocatedResources(this->AllocatedResources[test]); @@ -197,22 +196,18 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test) } } - // Always lock the resources we'll be using, even if we fail to set the - // working directory because FinishTestProcess() will try to unlock them - this->LockResources(test); - - if (!this->ResourceAllocationErrors[test].empty()) { + if (!this->ResourceAvailabilityErrors[test].empty()) { std::ostringstream e; e << "Insufficient resources for test " << this->Properties[test]->Name << ":\n\n"; - for (auto const& it : this->ResourceAllocationErrors[test]) { + for (auto const& it : this->ResourceAvailabilityErrors[test]) { switch (it.second) { - case ResourceAllocationError::NoResourceType: + case ResourceAvailabilityError::NoResourceType: e << " Test requested resources of type '" << it.first << "' which does not exist\n"; break; - case ResourceAllocationError::InsufficientResources: + case ResourceAvailabilityError::InsufficientResources: e << " Test requested resources of type '" << it.first << "' in the following amounts:\n"; for (auto const& group : this->Properties[test]->ResourceGroups) { @@ -236,7 +231,7 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test) e << "Resource spec file:\n\n " << this->ResourceSpecFile; cmCTestRunTest::StartFailure(std::move(testRun), this->Total, e.str(), "Insufficient resources"); - return false; + return; } cmWorkingDirectory workdir(this->Properties[test]->Directory); @@ -246,13 +241,12 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test) this->Properties[test]->Directory + " : " + std::strerror(workdir.GetLastResult()), "Failed to change working directory"); - return false; + return; } // Ownership of 'testRun' has moved to another structure. // When the test finishes, FinishTestProcess will be called. - return cmCTestRunTest::StartTest(std::move(testRun), this->Completed, - this->Total); + cmCTestRunTest::StartTest(std::move(testRun), this->Completed, this->Total); } bool cmCTestMultiProcessHandler::AllocateResources(int index) @@ -261,6 +255,12 @@ bool cmCTestMultiProcessHandler::AllocateResources(int index) return true; } + // If the test needs unavailable resources then do not allocate anything + // because it will never run. We will issue the recorded errors instead. + if (!this->ResourceAvailabilityErrors[index].empty()) { + return true; + } + std::map<std::string, std::vector<cmCTestBinPackerAllocation>> allocations; if (!this->TryAllocateResources(index, allocations)) { return false; @@ -285,7 +285,7 @@ bool cmCTestMultiProcessHandler::AllocateResources(int index) bool cmCTestMultiProcessHandler::TryAllocateResources( int index, std::map<std::string, std::vector<cmCTestBinPackerAllocation>>& allocations, - std::map<std::string, ResourceAllocationError>* errors) + std::map<std::string, ResourceAvailabilityError>* errors) { allocations.clear(); @@ -305,7 +305,7 @@ bool cmCTestMultiProcessHandler::TryAllocateResources( for (auto& it : allocations) { if (!availableResources.count(it.first)) { if (errors) { - (*errors)[it.first] = ResourceAllocationError::NoResourceType; + (*errors)[it.first] = ResourceAvailabilityError::NoResourceType; result = false; } else { return false; @@ -313,7 +313,7 @@ bool cmCTestMultiProcessHandler::TryAllocateResources( } else if (!cmAllocateCTestResourcesRoundRobin( availableResources.at(it.first), it.second)) { if (errors) { - (*errors)[it.first] = ResourceAllocationError::InsufficientResources; + (*errors)[it.first] = ResourceAvailabilityError::InsufficientResources; result = false; } else { return false; @@ -360,14 +360,14 @@ bool cmCTestMultiProcessHandler::AllResourcesAvailable() return true; } -void cmCTestMultiProcessHandler::CheckResourcesAvailable() +void cmCTestMultiProcessHandler::CheckResourceAvailability() { if (this->UseResourceSpec) { - for (auto test : this->SortedTests) { + for (auto const& t : this->PendingTests) { std::map<std::string, std::vector<cmCTestBinPackerAllocation>> allocations; - this->TryAllocateResources(test, allocations, - &this->ResourceAllocationErrors[test]); + this->TryAllocateResources(t.first, allocations, + &this->ResourceAvailabilityErrors[t.first]); } } } @@ -403,30 +403,49 @@ void cmCTestMultiProcessHandler::SetStopTimePassed() void cmCTestMultiProcessHandler::LockResources(int index) { - this->LockedResources.insert( - this->Properties[index]->LockedResources.begin(), - this->Properties[index]->LockedResources.end()); + this->RunningCount += this->GetProcessorsUsed(index); + + auto* properties = this->Properties[index]; + + this->ProjectResourcesLocked.insert(properties->ProjectResources.begin(), + properties->ProjectResources.end()); - if (this->Properties[index]->RunSerial) { + if (properties->RunSerial) { this->SerialTestRunning = true; } + + if (this->HaveAffinity && properties->WantAffinity) { + size_t needProcessors = this->GetProcessorsUsed(index); + assert(needProcessors <= this->ProcessorsAvailable.size()); + std::vector<size_t> affinity; + affinity.reserve(needProcessors); + for (size_t i = 0; i < needProcessors; ++i) { + auto p = this->ProcessorsAvailable.begin(); + affinity.push_back(*p); + this->ProcessorsAvailable.erase(p); + } + properties->Affinity = std::move(affinity); + } } void cmCTestMultiProcessHandler::UnlockResources(int index) { - for (std::string const& i : this->Properties[index]->LockedResources) { - this->LockedResources.erase(i); + auto* properties = this->Properties[index]; + + for (auto p : properties->Affinity) { + this->ProcessorsAvailable.insert(p); } - if (this->Properties[index]->RunSerial) { + properties->Affinity.clear(); + + for (std::string const& i : properties->ProjectResources) { + this->ProjectResourcesLocked.erase(i); + } + + if (properties->RunSerial) { this->SerialTestRunning = false; } -} -void cmCTestMultiProcessHandler::EraseTest(int test) -{ - this->Tests.erase(test); - this->SortedTests.erase( - std::find(this->SortedTests.begin(), this->SortedTests.end(), test)); + this->RunningCount -= this->GetProcessorsUsed(index); } inline size_t cmCTestMultiProcessHandler::GetProcessorsUsed(int test) @@ -450,50 +469,40 @@ std::string cmCTestMultiProcessHandler::GetName(int test) return this->Properties[test]->Name; } -bool cmCTestMultiProcessHandler::StartTest(int test) +void cmCTestMultiProcessHandler::StartTest(int test) { - // Check for locked resources - for (std::string const& i : this->Properties[test]->LockedResources) { - if (cm::contains(this->LockedResources, i)) { - return false; - } - } - - // Allocate resources - if (this->ResourceAllocationErrors[test].empty() && - !this->AllocateResources(test)) { - this->DeallocateResources(test); - return false; + if (this->JobServerClient) { + // There is a job server. Request a token and queue the test to run + // when a token is received. Note that if we do not get a token right + // away it's possible that the system load will be higher when the + // token is received and we may violate the test-load limit. However, + // this is unlikely because if we do not get a token right away, some + // other job that's currently running must finish before we get one. + this->JobServerClient->RequestToken(); + this->JobServerQueuedTests.emplace_back(test); + } else { + // There is no job server. Start the test now. + this->StartTestProcess(test); } +} - // if there are no depends left then run this test - if (this->Tests[test].empty()) { - return this->StartTestProcess(test); - } - // This test was not able to start because it is waiting - // on depends to run - this->DeallocateResources(test); - return false; +void cmCTestMultiProcessHandler::JobServerReceivedToken() +{ + assert(!this->JobServerQueuedTests.empty()); + int test = this->JobServerQueuedTests.front(); + this->JobServerQueuedTests.pop_front(); + this->StartTestProcess(test); } void cmCTestMultiProcessHandler::StartNextTests() { - if (this->TestLoadRetryTimer.get() != nullptr) { - // This timer may be waiting to call StartNextTests again. - // Since we have been called it is no longer needed. - uv_timer_stop(this->TestLoadRetryTimer); - } - - if (this->Tests.empty()) { - this->TestLoadRetryTimer.reset(); - return; - } + // One or more events may be scheduled to call this method again. + // Since this method has been called they are no longer needed. + this->StartNextTestsOnIdle_.stop(); + this->StartNextTestsOnTimer_.stop(); - if (this->CheckStopTimePassed()) { - return; - } - - if (this->CheckStopOnFailure() && !this->Failed->empty()) { + if (this->PendingTests.empty() || this->CheckStopTimePassed() || + (this->CheckStopOnFailure() && !this->Failed->empty())) { return; } @@ -545,50 +554,79 @@ void cmCTestMultiProcessHandler::StartNextTests() } } - TestList copy = this->SortedTests; - for (auto const& test : copy) { - // Take a nap if we're currently performing a RUN_SERIAL test. - if (this->SerialTestRunning) { - break; - } + // Start tests in the preferred order, each subject to readiness checks. + auto ti = this->OrderedTests.begin(); + while (numToStart > 0 && !this->SerialTestRunning && + ti != this->OrderedTests.end()) { + // Increment the test iterator now because the current list + // entry may be deleted below. + auto cti = ti++; + int test = *cti; + // We can only start a RUN_SERIAL test if no other tests are also // running. if (this->Properties[test]->RunSerial && this->RunningCount > 0) { continue; } + // Exclude tests that depend on unfinished tests. + if (!this->PendingTests[test].Depends.empty()) { + continue; + } + size_t processors = this->GetProcessorsUsed(test); - bool testLoadOk = true; if (this->TestLoad > 0) { - if (processors <= spareLoad) { - cmCTestLog(this->CTest, DEBUG, - "OK to run " << this->GetName(test) << ", it requires " - << processors << " procs & system load is: " - << systemLoad << std::endl); - allTestsFailedTestLoadCheck = false; - } else { - testLoadOk = false; + // Exclude tests that are too big to fit in the spare load. + if (processors > spareLoad) { + // Keep track of the smallest excluded test to report in message below. + if (processors <= minProcessorsRequired) { + minProcessorsRequired = processors; + testWithMinProcessors = this->GetName(test); + } + continue; } + + // We found a test that fits in the spare load. + allTestsFailedTestLoadCheck = false; + cmCTestLog(this->CTest, DEBUG, + "OK to run " + << this->GetName(test) << ", it requires " << processors + << " procs & system load is: " << systemLoad << std::endl); } - if (processors <= minProcessorsRequired) { - minProcessorsRequired = processors; - testWithMinProcessors = this->GetName(test); + // Exclude tests that are too big to fit in the concurrency limit. + if (processors > numToStart) { + continue; } - if (testLoadOk && processors <= numToStart && this->StartTest(test)) { - numToStart -= processors; - } else if (numToStart == 0) { - break; + // Exclude tests that depend on currently-locked project resources. + for (std::string const& i : this->Properties[test]->ProjectResources) { + if (cm::contains(this->ProjectResourcesLocked, i)) { + continue; + } } + + // Allocate system resources needed by this test. + if (!this->AllocateResources(test)) { + continue; + } + + // Lock resources needed by this test. + this->LockResources(test); + + // The test is ready to run. + numToStart -= processors; + this->OrderedTests.erase(cti); + this->PendingTests.erase(test); + this->StartTest(test); } if (allTestsFailedTestLoadCheck) { // Find out whether there are any non RUN_SERIAL tests left, so that the // correct warning may be displayed. bool onlyRunSerialTestsLeft = true; - for (auto const& test : copy) { - if (!this->Properties[test]->RunSerial) { + for (auto const& t : this->PendingTests) { + if (!this->Properties[t.first]->RunSerial) { onlyRunSerialTestsLeft = false; } } @@ -600,7 +638,7 @@ void cmCTestMultiProcessHandler::StartNextTests() } else if (onlyRunSerialTestsLeft) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Only RUN_SERIAL tests remain, awaiting available slot."); - } else { + } else if (!testWithMinProcessors.empty()) { /* clang-format off */ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "System Load: " << systemLoad << ", " @@ -608,26 +646,43 @@ void cmCTestMultiProcessHandler::StartNextTests() "Smallest test " << testWithMinProcessors << " requires " << minProcessorsRequired); /* clang-format on */ + } else { + /* clang-format off */ + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "System Load: " << systemLoad << ", " + "Max Allowed Load: " << this->TestLoad); + /* clang-format on */ } cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "*****" << std::endl); - // Wait between 1 and 5 seconds before trying again. - unsigned int milliseconds = (cmSystemTools::RandomSeed() % 5 + 1) * 1000; - if (this->FakeLoadForTesting) { - milliseconds = 10; - } - if (this->TestLoadRetryTimer.get() == nullptr) { - this->TestLoadRetryTimer.init(this->Loop, this); - } - this->TestLoadRetryTimer.start( - &cmCTestMultiProcessHandler::OnTestLoadRetryCB, milliseconds, 0); + // Try again later when the load might be lower. + this->StartNextTestsOnTimer(); } } -void cmCTestMultiProcessHandler::OnTestLoadRetryCB(uv_timer_t* timer) +void cmCTestMultiProcessHandler::StartNextTestsOnIdle() { - auto* self = static_cast<cmCTestMultiProcessHandler*>(timer->data); - self->StartNextTests(); + // Start more tests on the next loop iteration. + this->StartNextTestsOnIdle_.start([](uv_idle_t* idle) { + uv_idle_stop(idle); + auto* self = static_cast<cmCTestMultiProcessHandler*>(idle->data); + self->StartNextTests(); + }); +} + +void cmCTestMultiProcessHandler::StartNextTestsOnTimer() +{ + // Wait between 1 and 5 seconds before trying again. + unsigned int const milliseconds = this->FakeLoadForTesting + ? 10 + : (cmSystemTools::RandomSeed() % 5 + 1) * 1000; + this->StartNextTestsOnTimer_.start( + [](uv_timer_t* timer) { + uv_timer_stop(timer); + auto* self = static_cast<cmCTestMultiProcessHandler*>(timer->data); + self->StartNextTests(); + }, + milliseconds, 0); } void cmCTestMultiProcessHandler::FinishTestProcess( @@ -657,26 +712,20 @@ void cmCTestMultiProcessHandler::FinishTestProcess( this->Failed->push_back(properties->Name); } - for (auto& t : this->Tests) { - t.second.erase(test); + for (auto& t : this->PendingTests) { + t.second.Depends.erase(test); } - this->TestFinishMap[test] = true; - this->TestRunningMap[test] = false; this->WriteCheckpoint(test); this->DeallocateResources(test); this->UnlockResources(test); - this->RunningCount -= this->GetProcessorsUsed(test); - - for (auto p : properties->Affinity) { - this->ProcessorsAvailable.insert(p); - } - properties->Affinity.clear(); runner.reset(); - if (started) { - this->StartNextTests(); + + if (this->JobServerClient) { + this->JobServerClient->ReleaseToken(); } + this->StartNextTestsOnIdle(); } void cmCTestMultiProcessHandler::UpdateCostData() @@ -807,7 +856,7 @@ void cmCTestMultiProcessHandler::CreateTestCostList() void cmCTestMultiProcessHandler::CreateParallelTestCostList() { - TestSet alreadySortedTests; + TestSet alreadyOrderedTests; std::list<TestSet> priorityStack; priorityStack.emplace_back(); @@ -815,11 +864,11 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList() // In parallel test runs add previously failed tests to the front // of the cost list and queue other tests for further sorting - for (auto const& t : this->Tests) { + for (auto const& t : this->PendingTests) { if (cm::contains(this->LastTestsFailed, this->Properties[t.first]->Name)) { // If the test failed last time, it should be run first. - this->SortedTests.push_back(t.first); - alreadySortedTests.insert(t.first); + this->OrderedTests.push_back(t.first); + alreadyOrderedTests.insert(t.first); } else { topLevel.insert(t.first); } @@ -834,7 +883,7 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList() TestSet& currentSet = priorityStack.back(); for (auto const& i : previousSet) { - TestSet const& dependencies = this->Tests[i]; + TestSet const& dependencies = this->PendingTests[i].Depends; currentSet.insert(dependencies.begin(), dependencies.end()); } @@ -855,9 +904,9 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList() TestComparator(this)); for (auto const& j : sortedCopy) { - if (!cm::contains(alreadySortedTests, j)) { - this->SortedTests.push_back(j); - alreadySortedTests.insert(j); + if (!cm::contains(alreadyOrderedTests, j)) { + this->OrderedTests.push_back(j); + alreadyOrderedTests.insert(j); } } } @@ -866,7 +915,7 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList() void cmCTestMultiProcessHandler::GetAllTestDependencies(int test, TestList& dependencies) { - TestSet const& dependencySet = this->Tests[test]; + TestSet const& dependencySet = this->PendingTests[test].Depends; for (int i : dependencySet) { this->GetAllTestDependencies(i, dependencies); dependencies.push_back(i); @@ -877,17 +926,17 @@ void cmCTestMultiProcessHandler::CreateSerialTestCostList() { TestList presortedList; - for (auto const& i : this->Tests) { + for (auto const& i : this->PendingTests) { presortedList.push_back(i.first); } std::stable_sort(presortedList.begin(), presortedList.end(), TestComparator(this)); - TestSet alreadySortedTests; + TestSet alreadyOrderedTests; for (int test : presortedList) { - if (cm::contains(alreadySortedTests, test)) { + if (cm::contains(alreadyOrderedTests, test)) { continue; } @@ -895,14 +944,14 @@ void cmCTestMultiProcessHandler::CreateSerialTestCostList() this->GetAllTestDependencies(test, dependencies); for (int testDependency : dependencies) { - if (!cm::contains(alreadySortedTests, testDependency)) { - alreadySortedTests.insert(testDependency); - this->SortedTests.push_back(testDependency); + if (!cm::contains(alreadyOrderedTests, testDependency)) { + alreadyOrderedTests.insert(testDependency); + this->OrderedTests.push_back(testDependency); } } - alreadySortedTests.insert(test); - this->SortedTests.push_back(test); + alreadyOrderedTests.insert(test); + this->OrderedTests.push_back(test); } } @@ -1089,9 +1138,9 @@ static Json::Value DumpCTestProperties( properties.append(DumpCTestProperty( "REQUIRED_FILES", DumpToJsonArray(testProperties.RequiredFiles))); } - if (!testProperties.LockedResources.empty()) { + if (!testProperties.ProjectResources.empty()) { properties.append(DumpCTestProperty( - "RESOURCE_LOCK", DumpToJsonArray(testProperties.LockedResources))); + "RESOURCE_LOCK", DumpToJsonArray(testProperties.ProjectResources))); } if (testProperties.RunSerial) { properties.append( @@ -1259,9 +1308,7 @@ void cmCTestMultiProcessHandler::PrintOutputAsJson() // Don't worry if this fails, we are only showing the test list, not // running the tests cmWorkingDirectory workdir(p.Directory); - cmCTestRunTest testRun(*this); - testRun.SetIndex(p.Index); - testRun.SetTestProperties(&p); + cmCTestRunTest testRun(*this, p.Index); testRun.ComputeArguments(); // Skip tests not available in this configuration. @@ -1298,9 +1345,7 @@ void cmCTestMultiProcessHandler::PrintTestList() // running the tests cmWorkingDirectory workdir(p.Directory); - cmCTestRunTest testRun(*this); - testRun.SetIndex(p.Index); - testRun.SetTestProperties(&p); + cmCTestRunTest testRun(*this, p.Index); testRun.ComputeArguments(); // logs the command in verbose mode if (!p.Labels.empty()) // print the labels @@ -1394,17 +1439,17 @@ void cmCTestMultiProcessHandler::CheckResume() void cmCTestMultiProcessHandler::RemoveTest(int index) { - this->EraseTest(index); + this->OrderedTests.erase( + std::find(this->OrderedTests.begin(), this->OrderedTests.end(), index)); + this->PendingTests.erase(index); this->Properties.erase(index); - this->TestRunningMap[index] = false; - this->TestFinishMap[index] = true; this->Completed++; } int cmCTestMultiProcessHandler::FindMaxIndex() { int max = 0; - for (auto const& i : this->Tests) { + for (auto const& i : this->PendingTests) { if (i.first > max) { max = i.first; } @@ -1418,7 +1463,7 @@ bool cmCTestMultiProcessHandler::CheckCycles() cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Checking test dependency graph..." << std::endl, this->Quiet); - for (auto const& it : this->Tests) { + for (auto const& it : this->PendingTests) { // DFS from each element to itself int root = it.first; std::set<int> visited; @@ -1428,7 +1473,7 @@ bool cmCTestMultiProcessHandler::CheckCycles() int test = s.top(); s.pop(); if (visited.insert(test).second) { - for (auto const& d : this->Tests[test]) { + for (auto const& d : this->PendingTests[test].Depends) { if (d == root) { // cycle exists cmCTestLog( diff --git a/Source/CTest/cmCTestMultiProcessHandler.h b/Source/CTest/cmCTestMultiProcessHandler.h index 3b4e9c5..02589ca 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.h +++ b/Source/CTest/cmCTestMultiProcessHandler.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep #include <cstddef> +#include <list> #include <map> #include <memory> #include <set> @@ -13,13 +14,12 @@ #include <cm/optional> -#include <cm3p/uv.h> - #include "cmCTest.h" #include "cmCTestResourceAllocator.h" #include "cmCTestResourceSpec.h" #include "cmCTestTestHandler.h" #include "cmUVHandlePtr.h" +#include "cmUVJobServerClient.h" struct cmCTestBinPackerAllocation; class cmCTestRunTest; @@ -38,7 +38,11 @@ public: struct TestSet : public std::set<int> { }; - struct TestMap : public std::map<int, TestSet> + struct TestInfo + { + TestSet Depends; + }; + struct TestMap : public std::map<int, TestInfo> { }; struct TestList : public std::vector<int> @@ -57,7 +61,7 @@ public: cmCTestMultiProcessHandler(); virtual ~cmCTestMultiProcessHandler(); // Set the tests - void SetTests(TestMap& tests, PropertiesMap& properties); + void SetTests(TestMap tests, PropertiesMap properties); // Set the max number of tests that can be run at the same time. void SetParallelLevel(size_t); void SetTestLoad(unsigned long load); @@ -99,14 +103,14 @@ public: void SetQuiet(bool b) { this->Quiet = b; } - void CheckResourcesAvailable(); + void CheckResourceAvailability(); protected: // Start the next test or tests as many as are allowed by // ParallelLevel void StartNextTests(); - bool StartTestProcess(int test); - bool StartTest(int test); + void StartTestProcess(int test); + void StartTest(int test); // Mark the checkpoint for the given test void WriteCheckpoint(int index); @@ -124,10 +128,10 @@ protected: // Removes the checkpoint file void MarkFinished(); - void EraseTest(int index); void FinishTestProcess(std::unique_ptr<cmCTestRunTest> runner, bool started); - static void OnTestLoadRetryCB(uv_timer_t* timer); + void StartNextTestsOnIdle(); + void StartNextTestsOnTimer(); void RemoveTest(int index); // Check if we need to resume an interrupted test set @@ -143,21 +147,25 @@ protected: bool CheckStopTimePassed(); void SetStopTimePassed(); + void InitializeLoop(); + void FinalizeLoop(); + void LockResources(int index); void UnlockResources(int index); - enum class ResourceAllocationError + enum class ResourceAvailabilityError { NoResourceType, InsufficientResources, }; + bool Complete(); bool AllocateResources(int index); bool TryAllocateResources( int index, std::map<std::string, std::vector<cmCTestBinPackerAllocation>>& allocations, - std::map<std::string, ResourceAllocationError>* errors = nullptr); + std::map<std::string, ResourceAvailabilityError>* errors = nullptr); void DeallocateResources(int index); bool AllResourcesAvailable(); bool InitResourceAllocator(std::string& error); @@ -170,9 +178,10 @@ protected: cm::optional<std::size_t> ResourceSpecSetupTest; bool HasInvalidGeneratedResourceSpec; - // map from test number to set of depend tests - TestMap Tests; - TestList SortedTests; + // Tests pending selection to start. They may have dependencies. + TestMap PendingTests; + // List of pending test indexes, ordered by cost. + std::list<int> OrderedTests; // Total number of tests we'll be running size_t Total; // Number of tests that are complete @@ -183,25 +192,33 @@ protected: bool StopTimePassed = false; // list of test properties (indices concurrent to the test map) PropertiesMap Properties; - std::map<int, bool> TestRunningMap; - std::map<int, bool> TestFinishMap; std::map<int, std::string> TestOutput; std::vector<std::string>* Passed; std::vector<std::string>* Failed; std::vector<std::string> LastTestsFailed; - std::set<std::string> LockedResources; + std::set<std::string> ProjectResourcesLocked; std::map<int, std::vector<std::map<std::string, std::vector<ResourceAllocation>>>> AllocatedResources; - std::map<int, std::map<std::string, ResourceAllocationError>> - ResourceAllocationErrors; + std::map<int, std::map<std::string, ResourceAvailabilityError>> + ResourceAvailabilityErrors; cmCTestResourceAllocator ResourceAllocator; std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults; size_t ParallelLevel; // max number of process that can be run at once + + // 'make' jobserver client. If connected, we acquire a token + // for each test before running its process. + cm::optional<cmUVJobServerClient> JobServerClient; + // List of tests that are queued to run when a token is available. + std::list<int> JobServerQueuedTests; + // Callback invoked when a token is received. + void JobServerReceivedToken(); + unsigned long TestLoad; unsigned long FakeLoadForTesting; - uv_loop_t Loop; - cm::uv_timer_ptr TestLoadRetryTimer; + cm::uv_loop_ptr Loop; + cm::uv_idle_ptr StartNextTestsOnIdle_; + cm::uv_timer_ptr StartNextTestsOnTimer_; cmCTestTestHandler* TestHandler; cmCTest* CTest; bool HasCycles; diff --git a/Source/CTest/cmCTestP4.cxx b/Source/CTest/cmCTestP4.cxx index 5d71b84..20bd0ec 100644 --- a/Source/CTest/cmCTestP4.cxx +++ b/Source/CTest/cmCTestP4.cxx @@ -151,9 +151,9 @@ cmCTestP4::User cmCTestP4::GetUserData(const std::string& username) if (it == this->Users.end()) { std::vector<std::string> p4_users; this->SetP4Options(p4_users); - p4_users.push_back("users"); - p4_users.push_back("-m"); - p4_users.push_back("1"); + p4_users.emplace_back("users"); + p4_users.emplace_back("-m"); + p4_users.emplace_back("1"); p4_users.push_back(username); UserParser out(this, "users-out> "); @@ -335,10 +335,10 @@ std::string cmCTestP4::GetWorkingRevision() std::vector<std::string> p4_identify; this->SetP4Options(p4_identify); - p4_identify.push_back("changes"); - p4_identify.push_back("-m"); - p4_identify.push_back("1"); - p4_identify.push_back("-t"); + p4_identify.emplace_back("changes"); + p4_identify.emplace_back("-m"); + p4_identify.emplace_back("1"); + p4_identify.emplace_back("-t"); std::string source = this->SourceDirectory + "/...#have"; p4_identify.push_back(source); @@ -403,7 +403,7 @@ bool cmCTestP4::LoadRevisions() .append(",") .append(this->NewRevision); - p4_changes.push_back("changes"); + p4_changes.emplace_back("changes"); p4_changes.push_back(range); ChangesParser out(this, "p4_changes-out> "); @@ -420,8 +420,8 @@ bool cmCTestP4::LoadRevisions() std::vector<std::string> p4_describe; for (std::string const& i : cmReverseRange(this->ChangeLists)) { this->SetP4Options(p4_describe); - p4_describe.push_back("describe"); - p4_describe.push_back("-s"); + p4_describe.emplace_back("describe"); + p4_describe.emplace_back("-s"); p4_describe.push_back(i); DescribeParser outDescribe(this, "p4_describe-out> "); @@ -436,10 +436,10 @@ bool cmCTestP4::LoadModifications() std::vector<std::string> p4_diff; this->SetP4Options(p4_diff); - p4_diff.push_back("diff"); + p4_diff.emplace_back("diff"); // Ideally we would use -Od but not all clients support it - p4_diff.push_back("-dn"); + p4_diff.emplace_back("-dn"); std::string source = this->SourceDirectory + "/..."; p4_diff.push_back(source); @@ -480,7 +480,7 @@ bool cmCTestP4::UpdateImpl() std::vector<std::string> p4_sync; this->SetP4Options(p4_sync); - p4_sync.push_back("sync"); + p4_sync.emplace_back("sync"); // Get user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 8ceb9db..483b3b4 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -25,13 +25,17 @@ #include "cmProcess.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" +#include "cmUVHandlePtr.h" #include "cmWorkingDirectory.h" -cmCTestRunTest::cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler) +cmCTestRunTest::cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler, + int index) : MultiTestHandler(multiHandler) + , Index(index) + , CTest(MultiTestHandler.CTest) + , TestHandler(MultiTestHandler.TestHandler) + , TestProperties(MultiTestHandler.Properties[Index]) { - this->CTest = multiHandler.CTest; - this->TestHandler = multiHandler.TestHandler; } void cmCTestRunTest::CheckOutput(std::string const& line) @@ -161,7 +165,7 @@ cmCTestRunTest::EndTestResult cmCTestRunTest::EndTest(size_t completed, reason = "Invalid resource spec file"; forceFail = true; } else { - this->MultiTestHandler.CheckResourcesAvailable(); + this->MultiTestHandler.CheckResourceAvailability(); } } std::ostringstream outputStream; @@ -526,7 +530,7 @@ std::string cmCTestRunTest::GetTestPrefix(size_t completed, size_t total) const return outputStream.str(); } -bool cmCTestRunTest::StartTest(std::unique_ptr<cmCTestRunTest> runner, +void cmCTestRunTest::StartTest(std::unique_ptr<cmCTestRunTest> runner, size_t completed, size_t total) { auto* testRun = runner.get(); @@ -535,10 +539,7 @@ bool cmCTestRunTest::StartTest(std::unique_ptr<cmCTestRunTest> runner, if (!testRun->StartTest(completed, total)) { testRun->FinalizeTest(false); - return false; } - - return true; } // Starts the execution of a test. Returns once it has started @@ -887,7 +888,7 @@ bool cmCTestRunTest::ForkProcess() this->TestResult.Environment.erase(this->TestResult.Environment.length() - 1); - return this->TestProcess->StartProcess(this->MultiTestHandler.Loop, + return this->TestProcess->StartProcess(*this->MultiTestHandler.Loop, &this->TestProperties->Affinity); } diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h index 34f23c4..71d0865 100644 --- a/Source/CTest/cmCTestRunTest.h +++ b/Source/CTest/cmCTestRunTest.h @@ -24,7 +24,7 @@ class cmCTestRunTest { public: - explicit cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler); + explicit cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler, int index); void SetNumberOfRuns(int n) { @@ -33,18 +33,12 @@ public: } void SetRepeatMode(cmCTest::Repeat r) { this->RepeatMode = r; } - void SetTestProperties(cmCTestTestHandler::cmCTestTestProperties* prop) - { - this->TestProperties = prop; - } cmCTestTestHandler::cmCTestTestProperties* GetTestProperties() { return this->TestProperties; } - void SetIndex(int i) { this->Index = i; } - int GetIndex() { return this->Index; } void AddFailedDependency(const std::string& failedTest) @@ -62,7 +56,7 @@ public: // Read and store output. Returns true if it must be called again. void CheckOutput(std::string const& line); - static bool StartTest(std::unique_ptr<cmCTestRunTest> runner, + static void StartTest(std::unique_ptr<cmCTestRunTest> runner, size_t completed, size_t total); static bool StartAgain(std::unique_ptr<cmCTestRunTest> runner, size_t completed); @@ -124,16 +118,15 @@ private: // Returns "completed/total Test #Index: " std::string GetTestPrefix(size_t completed, size_t total) const; - cmCTestTestHandler::cmCTestTestProperties* TestProperties; - // Pointer back to the "parent"; the handler that invoked this test run - cmCTestTestHandler* TestHandler; + cmCTestMultiProcessHandler& MultiTestHandler; + int Index; cmCTest* CTest; + cmCTestTestHandler* TestHandler; + cmCTestTestHandler::cmCTestTestProperties* TestProperties; + std::unique_ptr<cmProcess> TestProcess; std::string ProcessOutput; - // The test results cmCTestTestHandler::cmCTestTestResult TestResult; - cmCTestMultiProcessHandler& MultiTestHandler; - int Index; std::set<std::string> FailedDependencies; std::string StartTime; std::string ActualCommand; diff --git a/Source/CTest/cmCTestSVN.cxx b/Source/CTest/cmCTestSVN.cxx index 14bc510..fc7051c 100644 --- a/Source/CTest/cmCTestSVN.cxx +++ b/Source/CTest/cmCTestSVN.cxx @@ -34,7 +34,7 @@ cmCTestSVN::~cmCTestSVN() = default; void cmCTestSVN::CleanupImpl() { std::vector<std::string> svn_cleanup; - svn_cleanup.push_back("cleanup"); + svn_cleanup.emplace_back("cleanup"); OutputLogger out(this->Log, "cleanup-out> "); OutputLogger err(this->Log, "cleanup-err> "); this->RunSVNCommand(svn_cleanup, &out, &err); @@ -89,7 +89,7 @@ std::string cmCTestSVN::LoadInfo(SVNInfo& svninfo) { // Run "svn info" to get the repository info from the work tree. std::vector<std::string> svn_info; - svn_info.push_back("info"); + svn_info.emplace_back("info"); svn_info.push_back(svninfo.LocalPath); std::string rev; InfoParser out(this, "info-out> ", rev, svninfo); @@ -252,7 +252,7 @@ bool cmCTestSVN::UpdateImpl() } std::vector<std::string> svn_update; - svn_update.push_back("update"); + svn_update.emplace_back("update"); cm::append(svn_update, args); UpdateParser out(this, "up-out> "); @@ -270,7 +270,7 @@ bool cmCTestSVN::RunSVNCommand(std::vector<std::string> const& parameters, std::vector<std::string> args; args.push_back(this->CommandLineTool); cm::append(args, parameters); - args.push_back("--non-interactive"); + args.emplace_back("--non-interactive"); std::string userOptions = this->CTest->GetCTestConfiguration("SVNOptions"); @@ -388,11 +388,11 @@ bool cmCTestSVN::LoadRevisions(SVNInfo& svninfo) // Run "svn log" to get all global revisions of interest. std::vector<std::string> svn_log; - svn_log.push_back("log"); - svn_log.push_back("--xml"); - svn_log.push_back("-v"); - svn_log.push_back(revs.c_str()); - svn_log.push_back(svninfo.LocalPath.c_str()); + svn_log.emplace_back("log"); + svn_log.emplace_back("--xml"); + svn_log.emplace_back("-v"); + svn_log.emplace_back(revs); + svn_log.emplace_back(svninfo.LocalPath); LogParser out(this, "log-out> ", svninfo); OutputLogger err(this->Log, "log-err> "); return this->RunSVNCommand(svn_log, &out, &err); @@ -467,7 +467,7 @@ bool cmCTestSVN::LoadModifications() { // Run "svn status" which reports local modifications. std::vector<std::string> svn_status; - svn_status.push_back("status"); + svn_status.emplace_back("status"); StatusParser out(this, "status-out> "); OutputLogger err(this->Log, "status-err> "); this->RunSVNCommand(svn_status, &out, &err); @@ -529,7 +529,7 @@ bool cmCTestSVN::LoadRepositories() // Run "svn status" to get the list of external repositories std::vector<std::string> svn_status; - svn_status.push_back("status"); + svn_status.emplace_back("status"); ExternalParser out(this, "external-out> "); OutputLogger err(this->Log, "external-err> "); return this->RunSVNCommand(svn_status, &out, &err); diff --git a/Source/CTest/cmCTestScriptHandler.cxx b/Source/CTest/cmCTestScriptHandler.cxx index 48f8f6d..0beee67 100644 --- a/Source/CTest/cmCTestScriptHandler.cxx +++ b/Source/CTest/cmCTestScriptHandler.cxx @@ -672,9 +672,11 @@ int cmCTestScriptHandler::RunConfigurationDashboard() // clear the binary directory? if (this->EmptyBinDir) { - if (!cmCTestScriptHandler::EmptyBinaryDirectory(this->BinaryDir)) { + std::string err; + if (!cmCTestScriptHandler::EmptyBinaryDirectory(this->BinaryDir, err)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem removing the binary directory" << std::endl); + "Problem removing the binary directory (" + << err << "): " << this->BinaryDir << std::endl); } } @@ -860,10 +862,12 @@ bool cmCTestScriptHandler::RunScript(cmCTest* ctest, cmMakefile* mf, return true; } -bool cmCTestScriptHandler::EmptyBinaryDirectory(const std::string& sname) +bool cmCTestScriptHandler::EmptyBinaryDirectory(const std::string& sname, + std::string& err) { // try to avoid deleting root if (sname.size() < 2) { + err = "path too short"; return false; } @@ -876,20 +880,24 @@ bool cmCTestScriptHandler::EmptyBinaryDirectory(const std::string& sname) std::string check = cmStrCat(sname, "/CMakeCache.txt"); if (!cmSystemTools::FileExists(check)) { + err = "path does not contain an existing CMakeCache.txt file"; return false; } + cmsys::Status status; for (int i = 0; i < 5; ++i) { - if (TryToRemoveBinaryDirectoryOnce(sname)) { + status = TryToRemoveBinaryDirectoryOnce(sname); + if (status) { return true; } cmSystemTools::Delay(100); } + err = status.GetString(); return false; } -bool cmCTestScriptHandler::TryToRemoveBinaryDirectoryOnce( +cmsys::Status cmCTestScriptHandler::TryToRemoveBinaryDirectoryOnce( const std::string& directoryPath) { cmsys::Directory directory; @@ -907,18 +915,18 @@ bool cmCTestScriptHandler::TryToRemoveBinaryDirectoryOnce( bool isDirectory = cmSystemTools::FileIsDirectory(fullPath) && !cmSystemTools::FileIsSymlink(fullPath); + cmsys::Status status; if (isDirectory) { - if (!cmSystemTools::RemoveADirectory(fullPath)) { - return false; - } + status = cmSystemTools::RemoveADirectory(fullPath); } else { - if (!cmSystemTools::RemoveFile(fullPath)) { - return false; - } + status = cmSystemTools::RemoveFile(fullPath); + } + if (!status) { + return status; } } - return static_cast<bool>(cmSystemTools::RemoveADirectory(directoryPath)); + return cmSystemTools::RemoveADirectory(directoryPath); } cmDuration cmCTestScriptHandler::GetRemainingTimeAllowed() diff --git a/Source/CTest/cmCTestScriptHandler.h b/Source/CTest/cmCTestScriptHandler.h index b7764b2..8aa07e7 100644 --- a/Source/CTest/cmCTestScriptHandler.h +++ b/Source/CTest/cmCTestScriptHandler.h @@ -9,6 +9,8 @@ #include <string> #include <vector> +#include "cmsys/Status.hxx" + #include "cmCTestGenericHandler.h" #include "cmDuration.h" @@ -80,7 +82,7 @@ public: /* * Empty Binary Directory */ - static bool EmptyBinaryDirectory(const std::string& dir); + static bool EmptyBinaryDirectory(const std::string& dir, std::string& err); /* * Write an initial CMakeCache.txt from the given contents. @@ -139,7 +141,8 @@ private: std::unique_ptr<cmCTestCommand> command); // Try to remove the binary directory once - static bool TryToRemoveBinaryDirectoryOnce(const std::string& directoryPath); + static cmsys::Status TryToRemoveBinaryDirectoryOnce( + const std::string& directoryPath); std::vector<std::string> ConfigurationScripts; std::vector<bool> ScriptProcessScope; diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index eb3b4dd..1918b2c 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -1381,15 +1381,15 @@ bool cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed, } } } - tests[p.Index] = depends; + tests[p.Index].Depends = depends; properties[p.Index] = &p; } parallel->SetResourceSpecFile(this->ResourceSpecFile); - parallel->SetTests(tests, properties); + parallel->SetTests(std::move(tests), std::move(properties)); parallel->SetPassFailVectors(&passed, &failed); this->TestResults.clear(); parallel->SetTestResults(&this->TestResults); - parallel->CheckResourcesAvailable(); + parallel->CheckResourceAvailability(); if (this->CTest->ShouldPrintLabels()) { parallel->PrintLabels(); @@ -2233,7 +2233,7 @@ bool cmCTestTestHandler::SetTestsProperties( } else if (key == "RESOURCE_LOCK"_s) { cmList lval{ val }; - rt.LockedResources.insert(lval.begin(), lval.end()); + rt.ProjectResources.insert(lval.begin(), lval.end()); } else if (key == "FIXTURES_SETUP"_s) { cmList lval{ val }; diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 23f0a76..b81fcd5 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -165,7 +165,7 @@ public: std::vector<std::string> Environment; std::vector<std::string> EnvironmentModification; std::vector<std::string> Labels; - std::set<std::string> LockedResources; + std::set<std::string> ProjectResources; // RESOURCE_LOCK std::set<std::string> FixturesSetup; std::set<std::string> FixturesCleanup; std::set<std::string> FixturesRequired; diff --git a/Source/CTest/cmUVJobServerClient.cxx b/Source/CTest/cmUVJobServerClient.cxx new file mode 100644 index 0000000..d7d76c9 --- /dev/null +++ b/Source/CTest/cmUVJobServerClient.cxx @@ -0,0 +1,518 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmUVJobServerClient.h" + +#include <cassert> +#include <utility> + +#ifndef _WIN32 +# include <cstdio> +# include <string> +# include <vector> + +# include <fcntl.h> +# include <unistd.h> +#endif + +#include <cm/memory> +#include <cm/optional> +#include <cm/string_view> + +#include "cmRange.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmUVHandlePtr.h" + +class cmUVJobServerClient::Impl +{ +public: + uv_loop_t& Loop; + + cm::uv_idle_ptr ImplicitToken; + std::function<void()> OnToken; + std::function<void(int)> OnDisconnect; + + // The number of tokens held by this client. + unsigned int HeldTokens = 0; + + // The number of tokens we need to receive from the job server. + unsigned int NeedTokens = 0; + + Impl(uv_loop_t& loop); + virtual ~Impl(); + + virtual void SendToken() = 0; + virtual void StartReceivingTokens() = 0; + virtual void StopReceivingTokens() = 0; + + void RequestToken(); + void ReleaseToken(); + void RequestExplicitToken(); + void DecrementNeedTokens(); + void HoldToken(); + void RequestImplicitToken(); + void ReleaseImplicitToken(); + void ReceivedToken(); + void Disconnected(int status); +}; + +cmUVJobServerClient::Impl::Impl(uv_loop_t& loop) + : Loop(loop) +{ + this->ImplicitToken.init(this->Loop, this); +} + +cmUVJobServerClient::Impl::~Impl() = default; + +void cmUVJobServerClient::Impl::RequestToken() +{ + if (this->HeldTokens == 0 && !uv_is_active(this->ImplicitToken)) { + this->RequestImplicitToken(); + } else { + this->RequestExplicitToken(); + } +} + +void cmUVJobServerClient::Impl::ReleaseToken() +{ + assert(this->HeldTokens > 0); + --this->HeldTokens; + if (this->HeldTokens == 0) { + // This was the token implicitly owned by our process. + this->ReleaseImplicitToken(); + } else { + // This was a token we received from the job server. Send it back. + this->SendToken(); + } +} + +void cmUVJobServerClient::Impl::RequestExplicitToken() +{ + ++this->NeedTokens; + this->StartReceivingTokens(); +} + +void cmUVJobServerClient::Impl::DecrementNeedTokens() +{ + assert(this->NeedTokens > 0); + --this->NeedTokens; + if (this->NeedTokens == 0) { + this->StopReceivingTokens(); + } +} + +void cmUVJobServerClient::Impl::HoldToken() +{ + ++this->HeldTokens; + if (this->OnToken) { + this->OnToken(); + } else { + this->ReleaseToken(); + } +} + +void cmUVJobServerClient::Impl::RequestImplicitToken() +{ + assert(this->HeldTokens == 0); + this->ImplicitToken.start([](uv_idle_t* handle) { + uv_idle_stop(handle); + auto* self = static_cast<Impl*>(handle->data); + self->HoldToken(); + }); +} + +void cmUVJobServerClient::Impl::ReleaseImplicitToken() +{ + assert(this->HeldTokens == 0); + // Use the implicit token in place of receiving one from the job server. + if (this->NeedTokens > 0) { + this->DecrementNeedTokens(); + this->RequestImplicitToken(); + } +} + +void cmUVJobServerClient::Impl::ReceivedToken() +{ + this->DecrementNeedTokens(); + this->HoldToken(); +} + +void cmUVJobServerClient::Impl::Disconnected(int status) +{ + if (this->OnDisconnect) { + this->OnDisconnect(status); + } +} + +//--------------------------------------------------------------------------- +// Implementation on POSIX platforms. +// https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html + +#ifndef _WIN32 +namespace { +class ImplPosix : public cmUVJobServerClient::Impl +{ +public: + enum class Connection + { + None, + FDs, + FIFO, + }; + Connection Conn = Connection::None; + + cm::uv_pipe_ptr ConnRead; + cm::uv_pipe_ptr ConnWrite; + cm::uv_pipe_ptr ConnFIFO; + + std::shared_ptr<std::function<void(int)>> OnWrite; + + void Connect(); + void ConnectFDs(int rfd, int wfd); + void ConnectFIFO(const char* path); + void Disconnect(int status); + + cm::uv_pipe_ptr OpenFD(int fd); + + uv_stream_t* GetWriter() const; + uv_stream_t* GetReader() const; + + static void OnAllocateCB(uv_handle_t* handle, size_t suggested_size, + uv_buf_t* buf); + static void OnReadCB(uv_stream_t* stream, ssize_t nread, + const uv_buf_t* buf); + + void OnAllocate(size_t suggested_size, uv_buf_t* buf); + void OnRead(ssize_t nread, const uv_buf_t* buf); + + char ReadBuf = '.'; + + bool ReceivingTokens = false; + + bool IsConnected() const; + + void SendToken() override; + void StartReceivingTokens() override; + void StopReceivingTokens() override; + + ImplPosix(uv_loop_t& loop); + ~ImplPosix() override; +}; + +ImplPosix::ImplPosix(uv_loop_t& loop) + : Impl(loop) + , OnWrite(std::make_shared<std::function<void(int)>>([this](int status) { + if (status != 0) { + this->Disconnect(status); + } + })) +{ + this->Connect(); +} + +ImplPosix::~ImplPosix() +{ + this->Disconnect(0); +} + +void ImplPosix::Connect() +{ + // --jobserver-auth= for gnu make versions >= 4.2 + // --jobserver-fds= for gnu make versions < 4.2 + // -J for bsd make + static const std::vector<cm::string_view> prefixes = { + "--jobserver-auth=", "--jobserver-fds=", "-J" + }; + + cm::optional<std::string> makeflags = cmSystemTools::GetEnvVar("MAKEFLAGS"); + if (!makeflags) { + return; + } + + // Look for the *last* occurrence of jobserver flags. + cm::optional<std::string> auth; + std::vector<std::string> args; + cmSystemTools::ParseUnixCommandLine(makeflags->c_str(), args); + for (cm::string_view arg : cmReverseRange(args)) { + for (cm::string_view prefix : prefixes) { + if (cmHasPrefix(arg, prefix)) { + auth = cmTrimWhitespace(arg.substr(prefix.length())); + break; + } + } + if (auth) { + break; + } + } + + if (!auth) { + return; + } + + // fifo:PATH + if (cmHasLiteralPrefix(*auth, "fifo:")) { + ConnectFIFO(auth->substr(cmStrLen("fifo:")).c_str()); + return; + } + + // reader,writer + int reader; + int writer; + if (std::sscanf(auth->c_str(), "%d,%d", &reader, &writer) == 2) { + ConnectFDs(reader, writer); + } +} + +cm::uv_pipe_ptr ImplPosix::OpenFD(int fd) +{ + // Create a CLOEXEC duplicate so `uv_pipe_ptr` can close it + // without closing the original file descriptor, which our + // child processes might want to use too. + cm::uv_pipe_ptr p; + int fd_dup = dup(fd); + if (fd_dup < 0) { + return p; + } + if (fcntl(fd_dup, F_SETFD, FD_CLOEXEC) == -1) { + close(fd_dup); + return p; + } + p.init(this->Loop, 0, this); + if (uv_pipe_open(p, fd_dup) < 0) { + close(fd_dup); + } + return p; +} + +void ImplPosix::ConnectFDs(int rfd, int wfd) +{ + cm::uv_pipe_ptr connRead = this->OpenFD(rfd); + cm::uv_pipe_ptr connWrite = this->OpenFD(wfd); + + // Verify that the read end is readable and the write end is writable. + if (!connRead || !uv_is_readable(connRead) || // + !connWrite || !uv_is_writable(connWrite)) { + return; + } + + this->ConnRead = std::move(connRead); + this->ConnWrite = std::move(connWrite); + this->Conn = Connection::FDs; +} + +void ImplPosix::ConnectFIFO(const char* path) +{ + int fd = open(path, O_RDWR); + if (fd < 0) { + return; + } + + cm::uv_pipe_ptr connFIFO; + connFIFO.init(this->Loop, 0, this); + if (uv_pipe_open(connFIFO, fd) != 0) { + close(fd); + return; + } + + // Verify that the fifo is both readable and writable. + if (!connFIFO || !uv_is_readable(connFIFO) || !uv_is_writable(connFIFO)) { + return; + } + + this->ConnFIFO = std::move(connFIFO); + this->Conn = Connection::FIFO; +} + +void ImplPosix::Disconnect(int status) +{ + if (this->Conn == Connection::None) { + return; + } + + this->StopReceivingTokens(); + + switch (this->Conn) { + case Connection::FDs: + this->ConnRead.reset(); + this->ConnWrite.reset(); + break; + case Connection::FIFO: + this->ConnFIFO.reset(); + break; + default: + break; + } + + this->Conn = Connection::None; + if (status != 0) { + this->Disconnected(status); + } +} + +uv_stream_t* ImplPosix::GetWriter() const +{ + switch (this->Conn) { + case Connection::FDs: + return this->ConnWrite; + case Connection::FIFO: + return this->ConnFIFO; + default: + return nullptr; + } +} + +uv_stream_t* ImplPosix::GetReader() const +{ + switch (this->Conn) { + case Connection::FDs: + return this->ConnRead; + case Connection::FIFO: + return this->ConnFIFO; + default: + return nullptr; + } +} + +void ImplPosix::OnAllocateCB(uv_handle_t* handle, size_t suggested_size, + uv_buf_t* buf) +{ + auto* self = static_cast<ImplPosix*>(handle->data); + self->OnAllocate(suggested_size, buf); +} + +void ImplPosix::OnReadCB(uv_stream_t* stream, ssize_t nread, + const uv_buf_t* buf) +{ + auto* self = static_cast<ImplPosix*>(stream->data); + self->OnRead(nread, buf); +} + +void ImplPosix::OnAllocate(size_t /*suggested_size*/, uv_buf_t* buf) +{ + *buf = uv_buf_init(&this->ReadBuf, 1); +} + +void ImplPosix::OnRead(ssize_t nread, const uv_buf_t* /*buf*/) +{ + if (nread == 0) { + return; + } + + if (nread < 0) { + auto status = static_cast<int>(nread); + this->Disconnect(status); + return; + } + + assert(nread == 1); + this->ReceivedToken(); +} + +bool ImplPosix::IsConnected() const +{ + return this->Conn != Connection::None; +} + +void ImplPosix::SendToken() +{ + if (this->Conn == Connection::None) { + return; + } + + static char token = '.'; + + uv_buf_t const buf = uv_buf_init(&token, sizeof(token)); + int status = cm::uv_write(this->GetWriter(), &buf, 1, this->OnWrite); + if (status != 0) { + this->Disconnect(status); + } +} + +void ImplPosix::StartReceivingTokens() +{ + if (this->Conn == Connection::None) { + return; + } + if (this->ReceivingTokens) { + return; + } + + int status = uv_read_start(this->GetReader(), &ImplPosix::OnAllocateCB, + &ImplPosix::OnReadCB); + if (status != 0) { + this->Disconnect(status); + return; + } + + this->ReceivingTokens = true; +} + +void ImplPosix::StopReceivingTokens() +{ + if (this->Conn == Connection::None) { + return; + } + if (!this->ReceivingTokens) { + return; + } + + this->ReceivingTokens = false; + uv_read_stop(this->GetReader()); +} +} +#endif + +//--------------------------------------------------------------------------- +// Implementation of public interface. + +cmUVJobServerClient::cmUVJobServerClient(std::unique_ptr<Impl> impl) + : Impl_(std::move(impl)) +{ +} + +cmUVJobServerClient::~cmUVJobServerClient() = default; + +cmUVJobServerClient::cmUVJobServerClient(cmUVJobServerClient&&) noexcept = + default; +cmUVJobServerClient& cmUVJobServerClient::operator=( + cmUVJobServerClient&&) noexcept = default; + +void cmUVJobServerClient::RequestToken() +{ + this->Impl_->RequestToken(); +} + +void cmUVJobServerClient::ReleaseToken() +{ + this->Impl_->ReleaseToken(); +} + +int cmUVJobServerClient::GetHeldTokens() const +{ + return this->Impl_->HeldTokens; +} + +int cmUVJobServerClient::GetNeedTokens() const +{ + return this->Impl_->NeedTokens; +} + +cm::optional<cmUVJobServerClient> cmUVJobServerClient::Connect( + uv_loop_t& loop, std::function<void()> onToken, + std::function<void(int)> onDisconnect) +{ +#if defined(_WIN32) + // FIXME: Windows job server client not yet implemented. + static_cast<void>(loop); + static_cast<void>(onToken); + static_cast<void>(onDisconnect); +#else + auto impl = cm::make_unique<ImplPosix>(loop); + if (impl && impl->IsConnected()) { + impl->OnToken = std::move(onToken); + impl->OnDisconnect = std::move(onDisconnect); + return cmUVJobServerClient(std::move(impl)); + } +#endif + return cm::nullopt; +} diff --git a/Source/CTest/cmUVJobServerClient.h b/Source/CTest/cmUVJobServerClient.h new file mode 100644 index 0000000..bbb5f08 --- /dev/null +++ b/Source/CTest/cmUVJobServerClient.h @@ -0,0 +1,96 @@ +/* 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 <functional> +#include <memory> + +#include <cm/optional> + +#include <cm3p/uv.h> + +/** \class cmUVJobServerClient + * \brief Job server client that can integrate with a libuv event loop. + * + * Use the \a Connect method to connect to an ambient job server as + * described by the MAKEFLAGS environment variable, if any. Request + * a token using the \a RequestToken method. The \a onToken callback + * will be invoked asynchronously when the token is received. Act + * on the token, and then use \a ReleaseToken to release it. + * + * The job server protocol states that a client process implicitly + * has one free token available, corresponding to the token its + * parent used to start it. \a cmUVJobServerClient will use the + * implicit token whenever it is available instead of requesting + * an explicit token from the job server. However, clients of + * this class must still request and receive the token before + * acting on it, and cannot assume that it is always held. + * + * If the job server connection breaks, \a onDisconnect will be + * called with the libuv error. No further tokens can be received + * from the job server, but progress can still be made serially + * using the implicit token. + */ +class cmUVJobServerClient +{ +public: + class Impl; + +private: + std::unique_ptr<Impl> Impl_; + + cmUVJobServerClient(std::unique_ptr<Impl> impl); + +public: + /** + * Disconnect from the job server. + */ + ~cmUVJobServerClient(); + + cmUVJobServerClient(cmUVJobServerClient&&) noexcept; + cmUVJobServerClient(cmUVJobServerClient const&) = delete; + cmUVJobServerClient& operator=(cmUVJobServerClient&&) noexcept; + cmUVJobServerClient& operator=(cmUVJobServerClient const&) = delete; + + /** + * Request a token from the job server. + * When the token is held, the \a onToken callback will be invoked. + */ + void RequestToken(); + + /** + * Release a token to the job server. + * This may be called only after a corresponding \a onToken callback. + */ + void ReleaseToken(); + + /** + * Get the number of implicit and explicit tokens currently held. + * This is the number of times \a onToken has been called but not + * yet followed by a call to \a ReleaseToken. + * This is meant for testing and debugging. + */ + int GetHeldTokens() const; + + /** + * Get the number of explicit tokens currently requested from the + * job server but not yet received. If the implicit token becomes + * available, it is used in place of a requested token, and this + * is decremented without receiving an explicit token. + * This is meant for testing and debugging. + */ + int GetNeedTokens() const; + + /** + * Connect to an ambient job server, if any. + * \param loop The libuv event loop on which to schedule events. + * \param onToken Function to call when a new token is held. + * \param onDisconnect Function to call on disconnect, with libuv error. + * \returns Connected instance, or cm::nullopt. + */ + static cm::optional<cmUVJobServerClient> Connect( + uv_loop_t& loop, std::function<void()> onToken, + std::function<void(int)> onDisconnect); +}; diff --git a/Source/Checks/Curses/CMakeLists.txt b/Source/Checks/Curses/CMakeLists.txt index bc6b906..6f5f145 100644 --- a/Source/Checks/Curses/CMakeLists.txt +++ b/Source/Checks/Curses/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.13...3.26 FATAL_ERROR) +cmake_minimum_required(VERSION 3.13...3.27 FATAL_ERROR) project(CheckCurses C) set(CURSES_NEED_NCURSES TRUE) diff --git a/Source/Checks/Curses/CheckCurses.c b/Source/Checks/Curses/CheckCurses.c index 7d827e6..3264fa0 100644 --- a/Source/Checks/Curses/CheckCurses.c +++ b/Source/Checks/Curses/CheckCurses.c @@ -8,7 +8,7 @@ # include <curses.h> #endif -int main() +int main(void) { curses_version(); return 0; diff --git a/Source/Checks/cm_cxx_filesystem.cxx b/Source/Checks/cm_cxx_filesystem.cxx index c8df589..bdc7c6d 100644 --- a/Source/Checks/cm_cxx_filesystem.cxx +++ b/Source/Checks/cm_cxx_filesystem.cxx @@ -9,12 +9,13 @@ int main() std::filesystem::path p0(L"/a/b/c"); std::filesystem::path p1("/a/b/c"); - std::filesystem::path p2("/a/b/c"); - if (p1 != p2) { + std::filesystem::path p2("/a/b//c"); + if (p1 != p2.lexically_normal()) { return 1; } #if defined(_WIN32) + // "//host/" is not preserved in some environments like GNU under MinGW. std::filesystem::path p3("//host/a/b/../c"); if (p3.lexically_normal().generic_string() != "//host/a/c") { return 1; @@ -24,6 +25,12 @@ int main() if (p4.lexically_normal().generic_string() != "c:/a/") { return 1; } + + std::filesystem::path b1("C:\\path\\y\\..\\"); + if (std::filesystem::weakly_canonical("\\\\?\\C:\\path\\x\\..") != + b1.lexically_normal()) { + return 1; + } #endif // If std::string is copy-on-write, the std::filesystem::path diff --git a/Source/Modules/CMakeBuildUtilities.cmake b/Source/Modules/CMakeBuildUtilities.cmake index 21f04e6..4e7f0fe 100644 --- a/Source/Modules/CMakeBuildUtilities.cmake +++ b/Source/Modules/CMakeBuildUtilities.cmake @@ -287,6 +287,8 @@ else() set(ENABLE_CPIO_SHARED OFF) set(ENABLE_CAT OFF) set(ENABLE_CAT_SHARED OFF) + set(ENABLE_UNZIP OFF) + set(ENABLE_UNZIP_SHARED OFF) set(ENABLE_XATTR OFF) set(ENABLE_ACL OFF) set(ENABLE_ICONV OFF) diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx index b1589ff..ea97287 100644 --- a/Source/cmAddCustomCommandCommand.cxx +++ b/Source/cmAddCustomCommandCommand.cxx @@ -189,8 +189,8 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args, } else if (copy == keyDEPFILE) { doing = doing_depfile; if (!mf.GetGlobalGenerator()->SupportsCustomCommandDepfile()) { - status.SetError("Option DEPFILE not supported by " + - mf.GetGlobalGenerator()->GetName()); + status.SetError(cmStrCat("Option DEPFILE not supported by ", + mf.GetGlobalGenerator()->GetName())); return false; } } else if (copy == keyJOB_POOL) { diff --git a/Source/cmCMakeHostSystemInformationCommand.cxx b/Source/cmCMakeHostSystemInformationCommand.cxx index 0efb9a4..699e23b 100644 --- a/Source/cmCMakeHostSystemInformationCommand.cxx +++ b/Source/cmCMakeHostSystemInformationCommand.cxx @@ -270,7 +270,7 @@ std::map<std::string, std::string> GetOSReleaseVariables( std::map<std::string, std::string> data; // Based on - // https://www.freedesktop.org/software/systemd/man/os-release.html + // https://www.freedesktop.org/software/systemd/man/latest/os-release.html for (auto name : { "/etc/os-release"_s, "/usr/lib/os-release"_s }) { const auto& filename = cmStrCat(sysroot, name); if (cmSystemTools::FileExists(filename)) { diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx index 320c57c..f4b26f3 100644 --- a/Source/cmComputeLinkDepends.cxx +++ b/Source/cmComputeLinkDepends.cxx @@ -16,6 +16,8 @@ #include <cm/string_view> #include <cmext/string_view> +#include "cmsys/RegularExpression.hxx" + #include "cmComputeComponentGraph.h" #include "cmGeneratorExpression.h" #include "cmGeneratorExpressionDAGChecker.h" @@ -26,6 +28,7 @@ #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" +#include "cmPolicies.h" #include "cmRange.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" @@ -184,15 +187,6 @@ items that we know the linker will reuse 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) { @@ -231,9 +225,208 @@ bool IsGroupFeatureSupported(cmMakefile* makefile, cmStrCat("CMAKE_LINK_GROUP_USING_", feature, "_SUPPORTED"); return makefile->GetDefinition(featureSupported).IsOn(); } + +class EntriesProcessing +{ +public: + using LinkEntry = cmComputeLinkDepends::LinkEntry; + using EntryVector = cmComputeLinkDepends::EntryVector; + + EntriesProcessing(const cmGeneratorTarget* target, + const std::string& linkLanguage, EntryVector& entries, + EntryVector& finalEntries) + : Entries(entries) + , FinalEntries(finalEntries) + { + const auto* makefile = target->Makefile; + + switch (target->GetPolicyStatusCMP0156()) { + case cmPolicies::WARN: + if (!makefile->GetCMakeInstance()->GetIsInTryCompile() && + makefile->PolicyOptionalWarningEnabled( + "CMAKE_POLICY_WARNING_CMP0156")) { + makefile->GetCMakeInstance()->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0156), + "\nSince the policy is not set, legacy libraries " + "de-duplication strategy will be applied."), + target->GetBacktrace()); + } + CM_FALLTHROUGH; + case cmPolicies::OLD: + // rely on default initialization of the class + break; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + makefile->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0156), + target->GetBacktrace()); + CM_FALLTHROUGH; + case cmPolicies::NEW: { + if (auto libProcessing = makefile->GetDefinition(cmStrCat( + "CMAKE_", linkLanguage, "_LINK_LIBRARIES_PROCESSING"))) { + cmsys::RegularExpression processingOption{ + "^(ORDER|UNICITY)=(FORWARD|REVERSE|ALL|NONE|SHARED)$" + }; + std::string errorMessage; + for (auto const& option : cmList{ libProcessing }) { + if (processingOption.find(option)) { + if (processingOption.match(1) == "ORDER") { + if (processingOption.match(2) == "FORWARD") { + this->Order = Forward; + } else if (processingOption.match(2) == "REVERSE") { + this->Order = Reverse; + } else { + errorMessage += cmStrCat(" ", option, '\n'); + } + } else if (processingOption.match(1) == "UNICITY") { + if (processingOption.match(2) == "ALL") { + this->Unicity = All; + } else if (processingOption.match(2) == "NONE") { + this->Unicity = None; + } else if (processingOption.match(2) == "SHARED") { + this->Unicity = Shared; + } else { + errorMessage += cmStrCat(" ", option, '\n'); + } + } + } else { + errorMessage += cmStrCat(" ", option, '\n'); + } + } + if (!errorMessage.empty()) { + makefile->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Erroneous option(s) for 'CMAKE_", linkLanguage, + "_LINK_LIBRARIES_PROCESSING':\n", errorMessage), + target->GetBacktrace()); + } + } + } + } + } + + void AddGroups(const std::map<size_t, std::vector<size_t>>& groups) + { + if (!groups.empty()) { + this->Groups = &groups; + // record all libraries as part of groups to ensure correct + // deduplication: libraries as part of groups are always kept. + for (const auto& group : groups) { + for (auto index : group.second) { + this->Emitted.insert(index); + } + } + } + } + + void AddLibraries(const std::vector<size_t>& libEntries) + { + if (this->Order == Reverse) { + // Iterate in reverse order so we can keep only the last occurrence + // of a library. + this->AddLibraries(cmReverseRange(libEntries)); + } else { + this->AddLibraries(cmMakeRange(libEntries)); + } + } + + void AddObjects(const std::vector<size_t>& objectEntries) + { + // Place explicitly linked object files in the front. The linker will + // always use them anyway, and they may depend on symbols from libraries. + if (this->Order == Reverse) { + // Append in reverse order at the end since we reverse the final order. + for (auto index : cmReverseRange(objectEntries)) { + this->FinalEntries.emplace_back(this->Entries[index]); + } + } else { + // Append in reverse order to ensure correct final order + for (auto index : cmReverseRange(objectEntries)) { + this->FinalEntries.emplace(this->FinalEntries.begin(), + this->Entries[index]); + } + } + } + + void Finalize() + { + if (this->Order == Reverse) { + // Reverse the resulting order since we iterated in reverse. + std::reverse(this->FinalEntries.begin(), this->FinalEntries.end()); + } + + // expand groups + if (this->Groups != nullptr) { + for (const auto& group : *this->Groups) { + const LinkEntry& groupEntry = this->Entries[group.first]; + auto it = this->FinalEntries.begin(); + while (true) { + it = std::find_if(it, this->FinalEntries.end(), + [&groupEntry](const LinkEntry& entry) -> bool { + return groupEntry.Item == entry.Item; + }); + if (it == this->FinalEntries.end()) { + break; + } + it->Item.Value = "</LINK_GROUP>"; + for (auto index = group.second.rbegin(); + index != group.second.rend(); ++index) { + it = this->FinalEntries.insert(it, this->Entries[*index]); + } + it = this->FinalEntries.insert(it, groupEntry); + it->Item.Value = "<LINK_GROUP>"; + } + } + } + } + +private: + enum OrderKind + { + Forward, + Reverse + }; + + enum UnicityKind + { + None, + Shared, + All + }; + + bool IncludeEntry(LinkEntry const& entry) const + { + return this->Unicity == None || + (this->Unicity == Shared && + (entry.Target == nullptr || + entry.Target->GetType() != cmStateEnums::SHARED_LIBRARY)) || + (this->Unicity == All && entry.Kind != LinkEntry::Library); + } + + template <typename Range> + void AddLibraries(const Range& libEntries) + { + for (auto index : libEntries) { + LinkEntry const& entry = this->Entries[index]; + if (this->IncludeEntry(entry) || this->Emitted.insert(index).second) { + this->FinalEntries.emplace_back(entry); + } + } + } + + OrderKind Order = Reverse; + UnicityKind Unicity = Shared; + EntryVector& Entries; + EntryVector& FinalEntries; + std::set<size_t> Emitted; + const std::map<size_t, std::vector<size_t>>* Groups = nullptr; +}; } -const std::string cmComputeLinkDepends::LinkEntry::DEFAULT = "DEFAULT"; +std::string const& cmComputeLinkDepends::LinkEntry::DEFAULT = + cmLinkItem::DEFAULT; cmComputeLinkDepends::cmComputeLinkDepends(const cmGeneratorTarget* target, const std::string& config, @@ -376,49 +569,14 @@ cmComputeLinkDepends::Compute() this->OrderLinkEntries(); // Compute the final set of link entries. - // Iterate in reverse order so we can keep only the last occurrence - // of a shared library. - std::set<size_t> emitted; - for (size_t i : cmReverseRange(this->FinalLinkOrder)) { - LinkEntry const& e = this->EntryList[i]; - cmGeneratorTarget const* t = e.Target; - // Entries that we know the linker will reuse do not need to be repeated. - bool uniquify = t && t->GetType() == cmStateEnums::SHARED_LIBRARY; - if (!uniquify || emitted.insert(i).second) { - this->FinalLinkEntries.push_back(e); - } - } - // Place explicitly linked object files in the front. The linker will - // always use them anyway, and they may depend on symbols from libraries. - // Append in reverse order since we reverse the final order below. - for (size_t i : cmReverseRange(this->ObjectEntries)) { - this->FinalLinkEntries.emplace_back(this->EntryList[i]); - } - // 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>"; - } - } - } + EntriesProcessing entriesProcessing{ this->Target, this->LinkLanguage, + this->EntryList, + this->FinalLinkEntries }; + // Add groups first, to ensure that libraries of the groups are always kept. + entriesProcessing.AddGroups(this->GroupItems); + entriesProcessing.AddLibraries(this->FinalLinkOrder); + entriesProcessing.AddObjects(this->ObjectEntries); + entriesProcessing.Finalize(); // Display the final set. if (this->DebugMode) { @@ -466,10 +624,12 @@ std::pair<size_t, bool> cmComputeLinkDepends::AddLinkEntry( LinkEntry& entry = this->EntryList[index]; entry.Item = BT<std::string>(item.AsStr(), item.Backtrace); entry.Target = item.Target; + entry.Feature = item.Feature; if (!entry.Target && entry.Item.Value[0] == '-' && entry.Item.Value[1] != 'l' && entry.Item.Value.substr(0, 10) != "-framework") { entry.Kind = LinkEntry::Flag; + entry.Feature = LinkEntry::DEFAULT; } else if (cmHasPrefix(entry.Item.Value, LG_BEGIN) && cmHasSuffix(entry.Item.Value, '>')) { entry.Kind = LinkEntry::Group; @@ -710,7 +870,6 @@ void cmComputeLinkDepends::AddLinkEntries(size_t depender_index, { // Track inferred dependency sets implied by this list. std::map<size_t, DependSet> dependSets; - std::string feature = LinkEntry::DEFAULT; bool inGroup = false; std::pair<size_t, bool> groupIndex{ @@ -727,34 +886,27 @@ void cmComputeLinkDepends::AddLinkEntries(size_t 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 != cmComputeComponentGraph::INVALID_COMPONENT) { - 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()); - } + // emit a warning if an undefined feature is used as part of + // an imported target + if (item.Feature != LinkEntry::DEFAULT && + depender_index != cmComputeComponentGraph::INVALID_COMPONENT) { + const auto& depender = this->EntryList[depender_index]; + if (depender.Target != nullptr && depender.Target->IsImported() && + !IsFeatureSupported(this->Makefile, this->LinkLanguage, + item.Feature)) { + this->CMakeInstance->IssueMessage( + MessageType::AUTHOR_ERROR, + cmStrCat("The 'IMPORTED' target '", depender.Target->GetName(), + "' uses the generator-expression '$<LINK_LIBRARY>' with " + "the feature '", + item.Feature, + "', which is undefined or unsupported.\nDid you miss to " + "define it by setting variables \"CMAKE_", + this->LinkLanguage, "_LINK_LIBRARY_USING_", item.Feature, + "\" and \"CMAKE_", this->LinkLanguage, + "_LINK_LIBRARY_USING_", item.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) && @@ -815,7 +967,7 @@ void cmComputeLinkDepends::AddLinkEntries(size_t depender_index, dependee_index = ale.first; LinkEntry& entry = this->EntryList[dependee_index]; auto const& itemFeature = - this->GetCurrentFeature(entry.Item.Value, feature); + this->GetCurrentFeature(entry.Item.Value, item.Feature); if (inGroup && ale.second && entry.Target != nullptr && (entry.Target->GetType() == cmStateEnums::TargetType::OBJECT_LIBRARY || entry.Target->GetType() == @@ -834,30 +986,27 @@ void cmComputeLinkDepends::AddLinkEntries(size_t depender_index, " 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; - } + if (ale.second) { + // current item not yet defined + if (itemFeature != LinkEntry::DEFAULT && 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 '", itemFeature, + "', specified as part of a generator-expression " + "'$<LINK_LIBRARY:", + itemFeature, ">', will not be applied to the ", + (entry.Target->GetType() == + cmStateEnums::TargetType::OBJECT_LIBRARY + ? "OBJECT" + : "INTERFACE"), + " library '", entry.Item.Value, "'."), + this->Target->GetBacktrace()); } + entry.Feature = itemFeature; } bool supportedItem = entry.Target == nullptr || diff --git a/Source/cmComputeLinkDepends.h b/Source/cmComputeLinkDepends.h index 3233217..66daa07 100644 --- a/Source/cmComputeLinkDepends.h +++ b/Source/cmComputeLinkDepends.h @@ -49,7 +49,7 @@ public: { } - static const std::string DEFAULT; + static std::string const& DEFAULT; enum EntryKind { diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx index 6f9f541..eba4c57 100644 --- a/Source/cmConditionEvaluator.cxx +++ b/Source/cmConditionEvaluator.cxx @@ -33,6 +33,9 @@ auto const keyCOMMAND = "COMMAND"_s; auto const keyDEFINED = "DEFINED"_s; auto const keyEQUAL = "EQUAL"_s; auto const keyEXISTS = "EXISTS"_s; +auto const keyIS_READABLE = "IS_READABLE"_s; +auto const keyIS_WRITABLE = "IS_WRITABLE"_s; +auto const keyIS_EXECUTABLE = "IS_EXECUTABLE"_s; auto const keyGREATER = "GREATER"_s; auto const keyGREATER_EQUAL = "GREATER_EQUAL"_s; auto const keyIN_LIST = "IN_LIST"_s; @@ -568,6 +571,24 @@ bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&, newArgs.ReduceOneArg(cmSystemTools::FileExists(args.next->GetValue()), args); } + // check if a file is readable + else if (this->IsKeyword(keyIS_READABLE, *args.current)) { + newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( + args.next->GetValue(), cmsys::TEST_FILE_READ), + args); + } + // check if a file is writable + else if (this->IsKeyword(keyIS_WRITABLE, *args.current)) { + newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( + args.next->GetValue(), cmsys::TEST_FILE_WRITE), + args); + } + // check if a file is executable + else if (this->IsKeyword(keyIS_EXECUTABLE, *args.current)) { + newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( + args.next->GetValue(), cmsys::TEST_FILE_EXECUTE), + args); + } // does a directory with this name exist else if (this->IsKeyword(keyIS_DIRECTORY, *args.current)) { newArgs.ReduceOneArg( diff --git a/Source/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx index 8c84ace..a4f36cc 100644 --- a/Source/cmCoreTryCompile.cxx +++ b/Source/cmCoreTryCompile.cxx @@ -14,6 +14,7 @@ #include "cmsys/Directory.hxx" #include "cmsys/FStream.hxx" +#include "cmsys/RegularExpression.hxx" #include "cmArgumentParser.h" #include "cmConfigureLog.h" @@ -83,6 +84,7 @@ std::string const kCMAKE_HIP_PLATFORM = "CMAKE_HIP_PLATFORM"; std::string const kCMAKE_HIP_RUNTIME_LIBRARY = "CMAKE_HIP_RUNTIME_LIBRARY"; std::string const kCMAKE_ISPC_INSTRUCTION_SETS = "CMAKE_ISPC_INSTRUCTION_SETS"; std::string const kCMAKE_ISPC_HEADER_SUFFIX = "CMAKE_ISPC_HEADER_SUFFIX"; +std::string const kCMAKE_LINKER_TYPE = "CMAKE_LINKER_TYPE"; std::string const kCMAKE_LINK_SEARCH_END_STATIC = "CMAKE_LINK_SEARCH_END_STATIC"; std::string const kCMAKE_LINK_SEARCH_START_STATIC = @@ -176,6 +178,7 @@ auto const TryCompileBaseSourcesArgParser = ArgumentParser::ExpectAtLeast{ 0 }) .Bind("LINK_LIBRARIES"_s, &Arguments::LinkLibraries) .Bind("LINK_OPTIONS"_s, &Arguments::LinkOptions) + .Bind("LINKER_LANGUAGE"_s, &Arguments::LinkerLanguage) .Bind("COPY_FILE"_s, &Arguments::CopyFileTo) .Bind("COPY_FILE_ERROR"_s, &Arguments::CopyFileError) .BIND_LANG_PROPS(C) @@ -852,8 +855,30 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode( fclose(fout); return cm::nullopt; } - fprintf(fout, "\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/%s\")\n\n", + fprintf(fout, "\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/%s\")\n", fname.c_str()); + // Create all relevant alias targets + if (arguments.LinkLibraries) { + const auto& aliasTargets = this->Makefile->GetAliasTargets(); + for (std::string const& i : *arguments.LinkLibraries) { + auto alias = aliasTargets.find(i); + if (alias != aliasTargets.end()) { + const auto& aliasTarget = + this->Makefile->FindTargetToUse(alias->second); + // Create equivalent library/executable alias + if (aliasTarget->GetType() == cmStateEnums::EXECUTABLE) { + fprintf(fout, "add_executable(\"%s\" ALIAS \"%s\")\n", i.c_str(), + alias->second.c_str()); + } else { + // Other cases like UTILITY and GLOBAL_TARGET are excluded when + // arguments.LinkLibraries is initially parsed in this function. + fprintf(fout, "add_library(\"%s\" ALIAS \"%s\")\n", i.c_str(), + alias->second.c_str()); + } + } + } + } + fprintf(fout, "\n"); } /* Set the appropriate policy information for ENABLE_EXPORTS */ @@ -877,6 +902,14 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode( ? "NEW" : "OLD"); + /* Set the appropriate policy information for Swift compilation mode */ + fprintf( + fout, "cmake_policy(SET CMP0157 %s)\n", + this->Makefile->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT") + .IsEmpty() + ? "OLD" + : "NEW"); + // Workaround for -Wl,-headerpad_max_install_names issue until we can avoid // adding that flag in the platform and compiler language files fprintf(fout, @@ -1041,6 +1074,19 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode( } } + if (arguments.LinkerLanguage) { + std::string LinkerLanguage = *arguments.LinkerLanguage; + if (testLangs.find(LinkerLanguage) == testLangs.end()) { + this->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + "Linker language '" + LinkerLanguage + + "' must be enabled in project(LANGUAGES)."); + } + + fprintf(fout, "set_property(TARGET %s PROPERTY LINKER_LANGUAGE %s)\n", + targetName.c_str(), LinkerLanguage.c_str()); + } + if (arguments.LinkLibraries) { std::string libsToLink = " "; for (std::string const& i : *arguments.LinkLibraries) { @@ -1112,6 +1158,20 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode( vars.insert(varList.begin(), varList.end()); } + if (this->Makefile->GetDefinition(kCMAKE_LINKER_TYPE)) { + // propagate various variables to support linker selection + vars.insert(kCMAKE_LINKER_TYPE); + auto defs = this->Makefile->GetDefinitions(); + cmsys::RegularExpression linkerTypeDef{ + "^CMAKE_[A-Za-z]+_USING_LINKER_" + }; + for (auto const& def : defs) { + if (linkerTypeDef.find(def)) { + vars.insert(def); + } + } + } + if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0083) == cmPolicies::NEW) { // To ensure full support of PIE, propagate cache variables diff --git a/Source/cmCoreTryCompile.h b/Source/cmCoreTryCompile.h index 3217a1b..6a26e88 100644 --- a/Source/cmCoreTryCompile.h +++ b/Source/cmCoreTryCompile.h @@ -91,6 +91,7 @@ public: cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> LinkLibraries; ArgumentParser::MaybeEmpty<std::vector<std::string>> LinkOptions; + cm::optional<std::string> LinkerLanguage; std::map<std::string, std::string> LangProps; std::string CMakeInternal; cm::optional<std::string> OutputVariable; diff --git a/Source/cmCreateTestSourceList.cxx b/Source/cmCreateTestSourceList.cxx index 75c25e3..9edbafe 100644 --- a/Source/cmCreateTestSourceList.cxx +++ b/Source/cmCreateTestSourceList.cxx @@ -81,8 +81,8 @@ bool cmCreateTestSourceList(std::vector<std::string> const& args, } std::string func_name; if (!cmSystemTools::GetFilenamePath(*i).empty()) { - func_name = cmSystemTools::GetFilenamePath(*i) + "/" + - cmSystemTools::GetFilenameWithoutLastExtension(*i); + func_name = cmStrCat(cmSystemTools::GetFilenamePath(*i), '/', + cmSystemTools::GetFilenameWithoutLastExtension(*i)); } else { func_name = cmSystemTools::GetFilenameWithoutLastExtension(*i); } @@ -93,9 +93,7 @@ bool cmCreateTestSourceList(std::vector<std::string> const& args, tests_func_name.end(); tests_func_name.push_back(func_name); if (!already_declared) { - forwardDeclareCode += "int "; - forwardDeclareCode += func_name; - forwardDeclareCode += "(int, char*[]);\n"; + forwardDeclareCode += cmStrCat("int ", func_name, "(int, char*[]);\n"); } } @@ -105,8 +103,8 @@ bool cmCreateTestSourceList(std::vector<std::string> const& args, ++i, ++j) { std::string func_name; if (!cmSystemTools::GetFilenamePath(*i).empty()) { - func_name = cmSystemTools::GetFilenamePath(*i) + "/" + - cmSystemTools::GetFilenameWithoutLastExtension(*i); + func_name = cmStrCat(cmSystemTools::GetFilenamePath(*i), '/', + cmSystemTools::GetFilenameWithoutLastExtension(*i)); } else { func_name = cmSystemTools::GetFilenameWithoutLastExtension(*i); } @@ -137,12 +135,12 @@ bool cmCreateTestSourceList(std::vector<std::string> const& args, { cmSourceFile* sf = mf.GetOrCreateSource(driver); sf->SetProperty("ABSTRACT", "0"); - sourceListValue = args[1]; + sourceListValue = driver; } for (i = testsBegin; i != tests.end(); ++i) { cmSourceFile* sf = mf.GetOrCreateSource(*i); sf->SetProperty("ABSTRACT", "0"); - sourceListValue += ";"; + sourceListValue += ';'; sourceListValue += *i; } diff --git a/Source/cmDebuggerStackFrame.h b/Source/cmDebuggerStackFrame.h index dc3b2ab..f4e6612 100644 --- a/Source/cmDebuggerStackFrame.h +++ b/Source/cmDebuggerStackFrame.h @@ -28,6 +28,10 @@ public: std::string const& GetFileName() const noexcept { return this->FileName; } int64_t GetLine() const noexcept; cmMakefile* GetMakefile() const noexcept { return this->Makefile; } + cmListFileFunction const& GetFunction() const noexcept + { + return this->Function; + } }; } // namespace cmDebugger diff --git a/Source/cmDebuggerThread.cxx b/Source/cmDebuggerThread.cxx index fd52f5a..f7a1778 100644 --- a/Source/cmDebuggerThread.cxx +++ b/Source/cmDebuggerThread.cxx @@ -13,6 +13,7 @@ #include "cmDebuggerVariables.h" #include "cmDebuggerVariablesHelper.h" #include "cmDebuggerVariablesManager.h" +#include "cmListFileCache.h" namespace cmDebugger { @@ -135,8 +136,7 @@ dap::StackTraceResponse GetStackTraceResponse( #endif stackFrame.line = thread->Frames[i]->GetLine(); stackFrame.column = 1; - stackFrame.name = thread->Frames[i]->GetFileName() + " Line " + - std::to_string(stackFrame.line); + stackFrame.name = thread->Frames[i]->GetFunction().OriginalName(); stackFrame.id = thread->Frames[i]->GetId(); stackFrame.source = source; diff --git a/Source/cmDyndepCollation.cxx b/Source/cmDyndepCollation.cxx index edf56ff..ea46f03 100644 --- a/Source/cmDyndepCollation.cxx +++ b/Source/cmDyndepCollation.cxx @@ -242,14 +242,16 @@ Json::Value CollationInformationExports(cmGeneratorTarget const* gt) auto const& all_build_exports = gt->Makefile->GetExportBuildFileGenerators(); for (auto const& exp : all_build_exports) { - std::vector<std::string> targets; + std::vector<cmExportBuildFileGenerator::TargetExport> targets; exp->GetTargets(targets); // Ignore exports sets which are not for this target. auto const& name = gt->GetName(); bool has_current_target = std::any_of(targets.begin(), targets.end(), - [name](std::string const& tname) { return tname == name; }); + [name](cmExportBuildFileGenerator::TargetExport const& te) { + return te.Name == name; + }); if (!has_current_target) { continue; } diff --git a/Source/cmExportBuildAndroidMKGenerator.cxx b/Source/cmExportBuildAndroidMKGenerator.cxx index c54e6ac..34bda1b 100644 --- a/Source/cmExportBuildAndroidMKGenerator.cxx +++ b/Source/cmExportBuildAndroidMKGenerator.cxx @@ -57,8 +57,8 @@ void cmExportBuildAndroidMKGenerator::GenerateImportTargetCode( } void cmExportBuildAndroidMKGenerator::GenerateImportPropertyCode( - std::ostream&, const std::string&, cmGeneratorTarget const*, - ImportPropertyMap const&) + std::ostream&, const std::string&, const std::string&, + cmGeneratorTarget const*, ImportPropertyMap const&, const std::string&) { } diff --git a/Source/cmExportBuildAndroidMKGenerator.h b/Source/cmExportBuildAndroidMKGenerator.h index 7067488..9562cee 100644 --- a/Source/cmExportBuildAndroidMKGenerator.h +++ b/Source/cmExportBuildAndroidMKGenerator.h @@ -51,10 +51,11 @@ protected: void GenerateExpectedTargetsCode( std::ostream& os, const std::string& expectedTargets) override; void GenerateImportPropertyCode( - std::ostream& os, const std::string& config, - cmGeneratorTarget const* target, - ImportPropertyMap const& properties) override; + std::ostream& os, const std::string& config, const std::string& suffix, + cmGeneratorTarget const* target, ImportPropertyMap const& properties, + const std::string& importedXcFrameworkLocation) override; void GenerateMissingTargetsCheckCode(std::ostream& os) override; + void GenerateFindDependencyCalls(std::ostream&) override {} void GenerateInterfaceProperties( cmGeneratorTarget const* target, std::ostream& os, const ImportPropertyMap& properties) override; diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index bf3bb8b..8ba8d97 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/Source/cmExportBuildFileGenerator.cxx @@ -10,7 +10,6 @@ #include <utility> #include <cm/string_view> -#include <cmext/algorithm> #include <cmext/string_view> #include "cmExportSet.h" @@ -54,15 +53,15 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) { std::string expectedTargets; std::string sep; - std::vector<std::string> targets; + std::vector<TargetExport> targets; bool generatedInterfaceRequired = false; this->GetTargets(targets); - for (std::string const& tei : targets) { - cmGeneratorTarget* te = this->LG->FindGeneratorTargetToUse(tei); + for (auto const& tei : targets) { + cmGeneratorTarget* te = this->LG->FindGeneratorTargetToUse(tei.Name); expectedTargets += sep + this->Namespace + te->GetExportName(); sep = " "; if (this->ExportedTargets.insert(te).second) { - this->Exports.push_back(te); + this->Exports.emplace_back(te, tei.XcFrameworkLocation); } else { std::ostringstream e; e << "given target \"" << te->GetName() << "\" more than once."; @@ -76,13 +75,14 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) } if (generatedInterfaceRequired) { - this->GenerateRequiredCMakeVersion(os, "3.0.0"); + this->SetRequiredCMakeVersion(3, 0, 0); } this->GenerateExpectedTargetsCode(os, expectedTargets); } // Create all the imported targets. - for (cmGeneratorTarget* gte : this->Exports) { + for (auto const& exp : this->Exports) { + cmGeneratorTarget* gte = exp.Target; this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte)); gte->Target->AppendBuildInterfaceIncludes(); @@ -176,7 +176,9 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) void cmExportBuildFileGenerator::GenerateImportTargetsConfig( std::ostream& os, const std::string& config, std::string const& suffix) { - for (cmGeneratorTarget* target : this->Exports) { + for (auto const& exp : this->Exports) { + cmGeneratorTarget* target = exp.Target; + // Collect import properties for this target. ImportPropertyMap properties; @@ -200,7 +202,23 @@ void cmExportBuildFileGenerator::GenerateImportTargetsConfig( // properties); // Generate code in the export file. - this->GenerateImportPropertyCode(os, config, target, properties); + std::string importedXcFrameworkLocation = exp.XcFrameworkLocation; + if (!importedXcFrameworkLocation.empty()) { + importedXcFrameworkLocation = cmGeneratorExpression::Preprocess( + importedXcFrameworkLocation, + cmGeneratorExpression::PreprocessContext::BuildInterface); + importedXcFrameworkLocation = cmGeneratorExpression::Evaluate( + importedXcFrameworkLocation, exp.Target->GetLocalGenerator(), config, + exp.Target, nullptr, exp.Target); + if (!importedXcFrameworkLocation.empty() && + !cmSystemTools::FileIsFullPath(importedXcFrameworkLocation)) { + importedXcFrameworkLocation = + cmStrCat(this->LG->GetCurrentBinaryDirectory(), '/', + importedXcFrameworkLocation); + } + } + this->GenerateImportPropertyCode(os, config, suffix, target, properties, + importedXcFrameworkLocation); } } } @@ -308,7 +326,7 @@ void cmExportBuildFileGenerator::HandleMissingTarget( } void cmExportBuildFileGenerator::GetTargets( - std::vector<std::string>& targets) const + std::vector<TargetExport>& targets) const { if (this->ExportSet) { for (std::unique_ptr<cmTargetExport> const& te : @@ -316,7 +334,7 @@ void cmExportBuildFileGenerator::GetTargets( if (te->NamelinkOnly) { continue; } - targets.push_back(te->TargetName); + targets.emplace_back(te->TargetName, te->XcFrameworkLocation); } return; } @@ -334,9 +352,11 @@ cmExportBuildFileGenerator::FindBuildExportInfo(cmGlobalGenerator* gg, for (auto const& exp : exportSets) { const auto& exportSet = exp.second; - std::vector<std::string> targets; + std::vector<TargetExport> targets; exportSet->GetTargets(targets); - if (cm::contains(targets, name)) { + if (std::any_of( + targets.begin(), targets.end(), + [&name](const TargetExport& te) { return te.Name == name; })) { exportFiles.push_back(exp.first); ns = exportSet->GetNamespace(); } diff --git a/Source/cmExportBuildFileGenerator.h b/Source/cmExportBuildFileGenerator.h index 4636196..9f11d13 100644 --- a/Source/cmExportBuildFileGenerator.h +++ b/Source/cmExportBuildFileGenerator.h @@ -33,15 +33,27 @@ class cmTargetExport; class cmExportBuildFileGenerator : public cmExportFileGenerator { public: + struct TargetExport + { + TargetExport(std::string name, std::string xcFrameworkLocation) + : Name(std::move(name)) + , XcFrameworkLocation(std::move(xcFrameworkLocation)) + { + } + + std::string Name; + std::string XcFrameworkLocation; + }; + cmExportBuildFileGenerator(); /** Set the list of targets to export. */ - void SetTargets(std::vector<std::string> const& targets) + void SetTargets(std::vector<TargetExport> const& targets) { this->Targets = targets; } - void GetTargets(std::vector<std::string>& targets) const; - void AppendTargets(std::vector<std::string> const& targets) + void GetTargets(std::vector<TargetExport>& targets) const; + void AppendTargets(std::vector<TargetExport> const& targets) { cm::append(this->Targets, targets); } @@ -90,6 +102,7 @@ protected: cmTargetExport* te) override; std::string GetFileSetFiles(cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* te) override; + cmExportSet* GetExportSet() const override { return this->ExportSet; } std::string GetCxxModulesDirectory() const override; void GenerateCxxModuleConfigInformation(std::ostream&) const override; @@ -98,9 +111,22 @@ protected: std::pair<std::vector<std::string>, std::string> FindBuildExportInfo( cmGlobalGenerator* gg, const std::string& name); - std::vector<std::string> Targets; + struct TargetExportPrivate + { + TargetExportPrivate(cmGeneratorTarget* target, + std::string xcFrameworkLocation) + : Target(target) + , XcFrameworkLocation(std::move(xcFrameworkLocation)) + { + } + + cmGeneratorTarget* Target; + std::string XcFrameworkLocation; + }; + + std::vector<TargetExport> Targets; cmExportSet* ExportSet; - std::vector<cmGeneratorTarget*> Exports; + std::vector<TargetExportPrivate> Exports; cmLocalGenerator* LG; // The directory for C++ module information. std::string CxxModulesDirectory; diff --git a/Source/cmExportCommand.cxx b/Source/cmExportCommand.cxx index 7e44210..7d23c91 100644 --- a/Source/cmExportCommand.cxx +++ b/Source/cmExportCommand.cxx @@ -8,6 +8,7 @@ #include <cm/memory> #include <cm/optional> +#include <cmext/algorithm> #include <cmext/string_view> #include "cmsys/RegularExpression.hxx" @@ -24,10 +25,12 @@ #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" +#include "cmRange.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" +#include "cmValue.h" #if defined(__HAIKU__) # include <FindDirectory.h> @@ -66,6 +69,11 @@ bool cmExportCommand(std::vector<std::string> const& args, std::string CxxModulesDirectory; bool Append = false; bool ExportOld = false; + + std::vector<std::vector<std::string>> PackageDependencyArgs; + bool ExportPackageDependencies = false; + + std::vector<std::vector<std::string>> TargetArgs; }; auto parser = @@ -75,7 +83,13 @@ bool cmExportCommand(std::vector<std::string> const& args, .Bind("CXX_MODULES_DIRECTORY"_s, &Arguments::CxxModulesDirectory); if (args[0] == "EXPORT") { - parser.Bind("EXPORT"_s, &Arguments::ExportSetName); + parser.Bind("EXPORT"_s, &Arguments::ExportSetName) + .Bind("EXPORT_PACKAGE_DEPENDENCIES"_s, + &Arguments::ExportPackageDependencies); + } else if (args[0] == "SETUP") { + parser.Bind("SETUP"_s, &Arguments::ExportSetName); + parser.Bind("PACKAGE_DEPENDENCY"_s, &Arguments::PackageDependencyArgs); + parser.Bind("TARGET"_s, &Arguments::TargetArgs); } else { parser.Bind("TARGETS"_s, &Arguments::Targets); parser.Bind("ANDROID_MK"_s, &Arguments::AndroidMKFile); @@ -91,6 +105,91 @@ bool cmExportCommand(std::vector<std::string> const& args, return false; } + if (args[0] == "SETUP") { + cmMakefile& mf = status.GetMakefile(); + cmGlobalGenerator* gg = mf.GetGlobalGenerator(); + + cmExportSetMap& setMap = gg->GetExportSets(); + auto& exportSet = setMap[arguments.ExportSetName]; + + struct PackageDependencyArguments + { + std::string Enabled; + ArgumentParser::MaybeEmpty<std::vector<std::string>> ExtraArgs; + }; + + auto packageDependencyParser = + cmArgumentParser<PackageDependencyArguments>{} + .Bind("ENABLED"_s, &PackageDependencyArguments::Enabled) + .Bind("EXTRA_ARGS"_s, &PackageDependencyArguments::ExtraArgs); + + for (auto const& packageDependencyArgs : arguments.PackageDependencyArgs) { + if (packageDependencyArgs.empty()) { + continue; + } + + PackageDependencyArguments const packageDependencyArguments = + packageDependencyParser.Parse( + cmMakeRange(packageDependencyArgs).advance(1), &unknownArgs); + + if (!unknownArgs.empty()) { + status.SetError("Unknown argument: \"" + unknownArgs.front() + "\"."); + return false; + } + + auto& packageDependency = + exportSet.GetPackageDependencyForSetup(packageDependencyArgs.front()); + + if (!packageDependencyArguments.Enabled.empty()) { + if (packageDependencyArguments.Enabled == "AUTO") { + packageDependency.Enabled = + cmExportSet::PackageDependencyExportEnabled::Auto; + } else if (cmIsOff(packageDependencyArguments.Enabled)) { + packageDependency.Enabled = + cmExportSet::PackageDependencyExportEnabled::Off; + } else if (cmIsOn(packageDependencyArguments.Enabled)) { + packageDependency.Enabled = + cmExportSet::PackageDependencyExportEnabled::On; + } else { + status.SetError( + cmStrCat("Invalid enable setting for package dependency: \"", + packageDependencyArguments.Enabled, "\"")); + return false; + } + } + + cm::append(packageDependency.ExtraArguments, + packageDependencyArguments.ExtraArgs); + } + + struct TargetArguments + { + std::string XcFrameworkLocation; + }; + + auto targetParser = cmArgumentParser<TargetArguments>{}.Bind( + "XCFRAMEWORK_LOCATION"_s, &TargetArguments::XcFrameworkLocation); + + for (auto const& targetArgs : arguments.TargetArgs) { + if (targetArgs.empty()) { + continue; + } + + TargetArguments const targetArguments = + targetParser.Parse(cmMakeRange(targetArgs).advance(1), &unknownArgs); + + if (!unknownArgs.empty()) { + status.SetError("Unknown argument: \"" + unknownArgs.front() + "\"."); + return false; + } + + exportSet.SetXcFrameworkLocation(targetArgs.front(), + targetArguments.XcFrameworkLocation); + } + + return true; + } + std::string fname; bool android = false; if (!arguments.AndroidMKFile.empty()) { @@ -133,7 +232,7 @@ bool cmExportCommand(std::vector<std::string> const& args, fname = dir + "/" + fname; } - std::vector<std::string> targets; + std::vector<cmExportBuildFileGenerator::TargetExport> targets; cmGlobalGenerator* gg = mf.GetGlobalGenerator(); @@ -171,7 +270,7 @@ bool cmExportCommand(std::vector<std::string> const& args, status.SetError(e.str()); return false; } - targets.push_back(currentTarget); + targets.emplace_back(currentTarget, std::string{}); } if (arguments.Append) { if (cmExportBuildFileGenerator* ebfg = @@ -224,6 +323,7 @@ bool cmExportCommand(std::vector<std::string> const& args, ebfg->SetTargets(targets); } ebfg->SetExportOld(arguments.ExportOld); + ebfg->SetExportPackageDependencies(arguments.ExportPackageDependencies); // Compute the set of configurations exported. std::vector<std::string> configurationTypes = diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index d0e69fb..e2c3edd 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 <algorithm> #include <array> #include <cassert> #include <cstring> @@ -9,12 +10,15 @@ #include <utility> #include <cm/memory> +#include <cm/optional> #include <cmext/string_view> #include "cmsys/FStream.hxx" #include "cmComputeLinkInformation.h" +#include "cmExportSet.h" #include "cmFileSet.h" +#include "cmFindPackageStack.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmLinkItem.h" @@ -30,6 +34,7 @@ #include "cmSystemTools.h" #include "cmTarget.h" #include "cmValue.h" +#include "cmVersion.h" static std::string cmExportFileGeneratorEscape(std::string const& str) { @@ -93,17 +98,35 @@ bool cmExportFileGenerator::GenerateImportFile() return false; } std::ostream& os = *foutPtr; + std::stringstream mainFileWithHeadersAndFootersBuffer; // Start with the import file header. - this->GeneratePolicyHeaderCode(os); - this->GenerateImportHeaderCode(os); + this->GenerateImportHeaderCode(mainFileWithHeadersAndFootersBuffer); // Create all the imported targets. - bool result = this->GenerateMainFile(os); + std::stringstream mainFileBuffer; + bool result = this->GenerateMainFile(mainFileBuffer); + + // Export find_dependency() calls. Must be done after GenerateMainFile(), + // because that's when target dependencies are gathered, which we need for + // the find_dependency() calls. + if (!this->AppendMode && this->GetExportSet() && + this->ExportPackageDependencies) { + this->SetRequiredCMakeVersion(3, 9, 0); + this->GenerateFindDependencyCalls(mainFileWithHeadersAndFootersBuffer); + } + + // Write cached import code. + mainFileWithHeadersAndFootersBuffer << mainFileBuffer.rdbuf(); // End with the import file footer. - this->GenerateImportFooterCode(os); - this->GeneratePolicyFooterCode(os); + this->GenerateImportFooterCode(mainFileWithHeadersAndFootersBuffer); + this->GeneratePolicyFooterCode(mainFileWithHeadersAndFootersBuffer); + + // This has to be done last, after the minimum CMake version has been + // determined. + this->GeneratePolicyHeaderCode(os); + os << mainFileWithHeadersAndFootersBuffer.rdbuf(); return result; } @@ -156,17 +179,6 @@ void cmExportFileGenerator::PopulateInterfaceProperty( } } -void cmExportFileGenerator::GenerateRequiredCMakeVersion( - std::ostream& os, const char* versionString) -{ - /* clang-format off */ - os << "if(CMAKE_VERSION VERSION_LESS " << versionString << ")\n" - " message(FATAL_ERROR \"This file relies on consumers using " - "CMake " << versionString << " or greater.\")\n" - "endif()\n\n"; - /* clang-format on */ -} - bool cmExportFileGenerator::PopulateInterfaceLinkLibrariesProperty( cmGeneratorTarget const* target, cmGeneratorExpression::PreprocessContext preprocessRule, @@ -620,6 +632,12 @@ bool cmExportFileGenerator::AddTargetNamespace(std::string& input, return false; } + cmFindPackageStack const& pkgStack = tgt->Target->GetFindPackageStack(); + if (!pkgStack.Empty() || + tgt->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME")) { + this->ExternalTargets.emplace(tgt); + } + if (tgt->IsImported()) { input = tgt->GetName(); return true; @@ -867,12 +885,14 @@ void cmExportFileGenerator::SetImportDetailProperties( // Export IMPORTED_LINK_DEPENDENT_LIBRARIES to help consuming linkers // find private dependencies of shared libraries. std::size_t oldMissingTargetsSize = this->MissingTargets.size(); + auto oldExternalTargets = this->ExternalTargets; this->SetImportLinkProperty( suffix, target, "IMPORTED_LINK_DEPENDENT_LIBRARIES", iface->SharedDeps, properties, ImportLinkPropertyTargetNames::Yes); // Avoid enforcing shared library private dependencies as public package // dependencies by ignoring missing targets added for them. this->MissingTargets.resize(oldMissingTargetsSize); + this->ExternalTargets = std::move(oldExternalTargets); if (iface->Multiplicity > 0) { std::string prop = @@ -953,20 +973,29 @@ void cmExportFileGenerator::GeneratePolicyHeaderCode(std::ostream& os) os << "if(\"${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}\" LESS 2.8)\n" << " message(FATAL_ERROR \"CMake >= 2.8.0 required\")\n" << "endif()\n" - << "if(CMAKE_VERSION VERSION_LESS \"2.8.3\")\n" - << " message(FATAL_ERROR \"CMake >= 2.8.3 required\")\n" + << "if(CMAKE_VERSION VERSION_LESS \"" + << this->RequiredCMakeVersionMajor << '.' + << this->RequiredCMakeVersionMinor << '.' + << this->RequiredCMakeVersionPatch << "\")\n" + << " message(FATAL_ERROR \"CMake >= " + << this->RequiredCMakeVersionMajor << '.' + << this->RequiredCMakeVersionMinor << '.' + << this->RequiredCMakeVersionPatch << " required\")\n" << "endif()\n"; /* clang-format on */ // 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.26 (this upper limit may be reviewed + // policy settings for up to CMake 3.27 (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.8.3...3.26)\n"; + << "cmake_policy(VERSION " + << this->RequiredCMakeVersionMajor << '.' + << this->RequiredCMakeVersionMinor << '.' + << this->RequiredCMakeVersionPatch << "...3.27)\n"; /* clang-format on */ } @@ -1125,8 +1154,9 @@ void cmExportFileGenerator::GenerateImportTargetCode( } void cmExportFileGenerator::GenerateImportPropertyCode( - std::ostream& os, const std::string& config, cmGeneratorTarget const* target, - ImportPropertyMap const& properties) + std::ostream& os, const std::string& config, const std::string& suffix, + cmGeneratorTarget const* target, ImportPropertyMap const& properties, + const std::string& importedXcFrameworkLocation) { // Construct the imported target name. std::string targetName = this->Namespace; @@ -1145,12 +1175,98 @@ void cmExportFileGenerator::GenerateImportPropertyCode( } os << ")\n"; os << "set_target_properties(" << targetName << " PROPERTIES\n"; + std::string importedLocationProp = cmStrCat("IMPORTED_LOCATION", suffix); for (auto const& property : properties) { - os << " " << property.first << " " - << cmExportFileGeneratorEscape(property.second) << "\n"; + if (importedXcFrameworkLocation.empty() || + property.first != importedLocationProp) { + os << " " << property.first << " " + << cmExportFileGeneratorEscape(property.second) << "\n"; + } } - os << " )\n" - << "\n"; + os << " )\n"; + if (!importedXcFrameworkLocation.empty()) { + auto importedLocationIt = properties.find(importedLocationProp); + if (importedLocationIt != properties.end()) { + os << "if(NOT CMAKE_VERSION VERSION_LESS \"3.28\" AND IS_DIRECTORY " + << cmExportFileGeneratorEscape(importedXcFrameworkLocation) + << ")\n" + " set_property(TARGET " + << targetName << " PROPERTY " << importedLocationProp << " " + << cmExportFileGeneratorEscape(importedXcFrameworkLocation) + << ")\nelse()\n set_property(TARGET " << targetName << " PROPERTY " + << importedLocationProp << " " + << cmExportFileGeneratorEscape(importedLocationIt->second) + << ")\nendif()\n"; + } + } + os << "\n"; +} + +void cmExportFileGenerator::GenerateFindDependencyCalls(std::ostream& os) +{ + os << "include(CMakeFindDependencyMacro)\n"; + std::map<std::string, cmExportSet::PackageDependency> packageDependencies; + auto* exportSet = this->GetExportSet(); + if (exportSet) { + packageDependencies = exportSet->GetPackageDependencies(); + } + + for (cmGeneratorTarget const* gt : this->ExternalTargets) { + std::string findPackageName; + auto exportFindPackageName = gt->GetProperty("EXPORT_FIND_PACKAGE_NAME"); + cmFindPackageStack pkgStack = gt->Target->GetFindPackageStack(); + if (!exportFindPackageName.IsEmpty()) { + findPackageName = *exportFindPackageName; + } else { + if (!pkgStack.Empty()) { + cmFindPackageCall const& fpc = pkgStack.Top(); + findPackageName = fpc.Name; + } + } + if (!findPackageName.empty()) { + auto& dep = packageDependencies[findPackageName]; + if (!pkgStack.Empty()) { + dep.FindPackageIndex = pkgStack.Top().Index; + } + if (dep.Enabled == cmExportSet::PackageDependencyExportEnabled::Auto) { + dep.Enabled = cmExportSet::PackageDependencyExportEnabled::On; + } + } + } + + std::vector<std::pair<std::string, cmExportSet::PackageDependency>> + packageDependenciesSorted(packageDependencies.begin(), + packageDependencies.end()); + std::sort( + packageDependenciesSorted.begin(), packageDependenciesSorted.end(), + [](const std::pair<std::string, cmExportSet::PackageDependency>& lhs, + const std::pair<std::string, cmExportSet::PackageDependency>& rhs) + -> bool { + if (lhs.second.SpecifiedIndex) { + if (rhs.second.SpecifiedIndex) { + return lhs.second.SpecifiedIndex < rhs.second.SpecifiedIndex; + } + assert(rhs.second.FindPackageIndex); + return true; + } + assert(lhs.second.FindPackageIndex); + if (rhs.second.SpecifiedIndex) { + return false; + } + assert(rhs.second.FindPackageIndex); + return lhs.second.FindPackageIndex < rhs.second.FindPackageIndex; + }); + + for (auto const& it : packageDependenciesSorted) { + if (it.second.Enabled == cmExportSet::PackageDependencyExportEnabled::On) { + os << "find_dependency(" << it.first << " REQUIRED"; + for (auto const& arg : it.second.ExtraArguments) { + os << " " << cmOutputConverter::EscapeForCMake(arg); + } + os << ")\n"; + } + } + os << "\n\n"; } void cmExportFileGenerator::GenerateMissingTargetsCheckCode(std::ostream& os) @@ -1214,10 +1330,16 @@ void cmExportFileGenerator::GenerateImportedFileCheckLoop(std::ostream& os) /* clang-format off */ os << "# Loop over all imported files and verify that they actually exist\n" "foreach(_cmake_target IN LISTS _cmake_import_check_targets)\n" - " foreach(_cmake_file IN LISTS \"_cmake_import_check_files_for_${_cmake_target}\")\n" - " if(NOT EXISTS \"${_cmake_file}\")\n" - " message(FATAL_ERROR \"The imported target \\\"${_cmake_target}\\\"" - " references the file\n" + " if(CMAKE_VERSION VERSION_LESS \"3.28\"\n" + " OR NOT DEFINED " + "_cmake_import_check_xcframework_for_${_cmake_target}\n" + " OR NOT IS_DIRECTORY " + "\"${_cmake_import_check_xcframework_for_${_cmake_target}}\")\n" + " foreach(_cmake_file IN LISTS " + "\"_cmake_import_check_files_for_${_cmake_target}\")\n" + " if(NOT EXISTS \"${_cmake_file}\")\n" + " message(FATAL_ERROR \"The imported target " + "\\\"${_cmake_target}\\\" references the file\n" " \\\"${_cmake_file}\\\"\n" "but this file does not exist. Possible reasons include:\n" "* The file was deleted, renamed, or moved to another location.\n" @@ -1226,8 +1348,9 @@ void cmExportFileGenerator::GenerateImportedFileCheckLoop(std::ostream& os) " \\\"${CMAKE_CURRENT_LIST_FILE}\\\"\n" "but not all the files it references.\n" "\")\n" - " endif()\n" - " endforeach()\n" + " endif()\n" + " endforeach()\n" + " endif()\n" " unset(_cmake_file)\n" " unset(\"_cmake_import_check_files_for_${_cmake_target}\")\n" "endforeach()\n" @@ -1240,15 +1363,18 @@ void cmExportFileGenerator::GenerateImportedFileCheckLoop(std::ostream& os) void cmExportFileGenerator::GenerateImportedFileChecksCode( std::ostream& os, cmGeneratorTarget* target, ImportPropertyMap const& properties, - const std::set<std::string>& importedLocations) + const std::set<std::string>& importedLocations, + const std::string& importedXcFrameworkLocation) { // Construct the imported target name. std::string targetName = cmStrCat(this->Namespace, target->GetExportName()); - os << "list(APPEND _cmake_import_check_targets " << targetName - << " )\n" - "list(APPEND _cmake_import_check_files_for_" - << targetName << " "; + os << "list(APPEND _cmake_import_check_targets " << targetName << " )\n"; + if (!importedXcFrameworkLocation.empty()) { + os << "set(_cmake_import_check_xcframework_for_" << targetName << ' ' + << cmExportFileGeneratorEscape(importedXcFrameworkLocation) << ")\n"; + } + os << "list(APPEND _cmake_import_check_files_for_" << targetName << " "; for (std::string const& li : importedLocations) { auto pi = properties.find(li); @@ -1485,3 +1611,17 @@ void cmExportFileGenerator::GenerateCxxModuleInformation(std::ostream& os) this->GenerateCxxModuleConfigInformation(ap); } + +void cmExportFileGenerator::SetRequiredCMakeVersion(unsigned int major, + unsigned int minor, + unsigned int patch) +{ + if (CMake_VERSION_ENCODE(major, minor, patch) > + CMake_VERSION_ENCODE(this->RequiredCMakeVersionMajor, + this->RequiredCMakeVersionMinor, + this->RequiredCMakeVersionPatch)) { + this->RequiredCMakeVersionMajor = major; + this->RequiredCMakeVersionMinor = minor; + this->RequiredCMakeVersionPatch = patch; + } +} diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index f396e0e..554dd4a 100644 --- a/Source/cmExportFileGenerator.h +++ b/Source/cmExportFileGenerator.h @@ -15,6 +15,7 @@ #include "cmVersion.h" #include "cmVersionConfig.h" +class cmExportSet; class cmFileSet; class cmGeneratorTarget; class cmLocalGenerator; @@ -61,6 +62,11 @@ public: error. */ bool GenerateImportFile(); + void SetExportPackageDependencies(bool exportPackageDependencies) + { + this->ExportPackageDependencies = exportPackageDependencies; + } + protected: using ImportPropertyMap = std::map<std::string, std::string>; @@ -78,16 +84,18 @@ protected: virtual void GenerateImportTargetCode(std::ostream& os, cmGeneratorTarget const* target, cmStateEnums::TargetType targetType); - virtual void GenerateImportPropertyCode(std::ostream& os, - const std::string& config, - cmGeneratorTarget const* target, - ImportPropertyMap const& properties); + virtual void GenerateImportPropertyCode( + std::ostream& os, const std::string& config, const std::string& suffix, + cmGeneratorTarget const* target, ImportPropertyMap const& properties, + const std::string& importedXcFrameworkLocation); virtual void GenerateImportedFileChecksCode( std::ostream& os, cmGeneratorTarget* target, ImportPropertyMap const& properties, - const std::set<std::string>& importedLocations); + const std::set<std::string>& importedLocations, + const std::string& importedXcFrameworkLocation); virtual void GenerateImportedFileCheckLoop(std::ostream& os); virtual void GenerateMissingTargetsCheckCode(std::ostream& os); + virtual void GenerateFindDependencyCalls(std::ostream& os); virtual void GenerateExpectedTargetsCode(std::ostream& os, const std::string& expectedTargets); @@ -173,9 +181,6 @@ protected: std::string& input, cmGeneratorTarget const* target, FreeTargetsReplace replace = NoReplaceFreeTargets); - virtual void GenerateRequiredCMakeVersion(std::ostream& os, - const char* versionString); - bool PopulateCxxModuleExportProperties( cmGeneratorTarget const* gte, ImportPropertyMap& properties, cmGeneratorExpression::PreprocessContext ctx, @@ -196,6 +201,11 @@ protected: cmFileSet* fileSet, cmTargetExport* te) = 0; + virtual cmExportSet* GetExportSet() const { return nullptr; } + + void SetRequiredCMakeVersion(unsigned int major, unsigned int minor, + unsigned int patch); + // The namespace in which the exports are placed in the generated file. std::string Namespace; @@ -216,6 +226,14 @@ protected: std::vector<std::string> MissingTargets; + std::set<cmGeneratorTarget const*> ExternalTargets; + + unsigned int RequiredCMakeVersionMajor = 2; + unsigned int RequiredCMakeVersionMinor = 8; + unsigned int RequiredCMakeVersionPatch = 3; + + bool ExportPackageDependencies = false; + private: void PopulateInterfaceProperty(const std::string&, const std::string&, cmGeneratorTarget const* target, diff --git a/Source/cmExportInstallAndroidMKGenerator.cxx b/Source/cmExportInstallAndroidMKGenerator.cxx index d53254d..eaa85f3 100644 --- a/Source/cmExportInstallAndroidMKGenerator.cxx +++ b/Source/cmExportInstallAndroidMKGenerator.cxx @@ -80,8 +80,8 @@ void cmExportInstallAndroidMKGenerator::GenerateExpectedTargetsCode( } void cmExportInstallAndroidMKGenerator::GenerateImportPropertyCode( - std::ostream&, const std::string&, cmGeneratorTarget const*, - ImportPropertyMap const&) + std::ostream&, const std::string&, const std::string&, + cmGeneratorTarget const*, ImportPropertyMap const&, const std::string&) { } @@ -110,11 +110,6 @@ void cmExportInstallAndroidMKGenerator::GenerateImportPrefix(std::ostream&) { } -void cmExportInstallAndroidMKGenerator::GenerateRequiredCMakeVersion( - std::ostream&, const char*) -{ -} - void cmExportInstallAndroidMKGenerator::CleanupTemporaryVariables( std::ostream&) { @@ -127,7 +122,7 @@ void cmExportInstallAndroidMKGenerator::GenerateImportedFileCheckLoop( void cmExportInstallAndroidMKGenerator::GenerateImportedFileChecksCode( std::ostream&, cmGeneratorTarget*, ImportPropertyMap const&, - const std::set<std::string>&) + const std::set<std::string>&, const std::string&) { } diff --git a/Source/cmExportInstallAndroidMKGenerator.h b/Source/cmExportInstallAndroidMKGenerator.h index 061358d..b1778ef 100644 --- a/Source/cmExportInstallAndroidMKGenerator.h +++ b/Source/cmExportInstallAndroidMKGenerator.h @@ -45,22 +45,22 @@ protected: void GenerateExpectedTargetsCode( std::ostream& os, const std::string& expectedTargets) override; void GenerateImportPropertyCode( - std::ostream& os, const std::string& config, - cmGeneratorTarget const* target, - ImportPropertyMap const& properties) override; + std::ostream& os, const std::string& config, const std::string& suffix, + cmGeneratorTarget const* target, ImportPropertyMap const& properties, + const std::string& importedXcFrameworkLocation) override; void GenerateMissingTargetsCheckCode(std::ostream& os) override; + void GenerateFindDependencyCalls(std::ostream&) override {} void GenerateInterfaceProperties( cmGeneratorTarget const* target, std::ostream& os, const ImportPropertyMap& properties) override; void GenerateImportPrefix(std::ostream& os) override; void LoadConfigFiles(std::ostream&) override; - void GenerateRequiredCMakeVersion(std::ostream& os, - const char* versionString) override; void CleanupTemporaryVariables(std::ostream&) override; void GenerateImportedFileCheckLoop(std::ostream& os) override; void GenerateImportedFileChecksCode( std::ostream& os, cmGeneratorTarget* target, ImportPropertyMap const& properties, - const std::set<std::string>& importedLocations) override; + const std::set<std::string>& importedLocations, + const std::string& importedXcFrameworkLocation) override; bool GenerateImportFileConfig(const std::string& config) override; }; diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index a264f5e..64e694c 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/Source/cmExportInstallFileGenerator.cxx @@ -76,9 +76,6 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os) // Compute the relative import prefix for the file this->GenerateImportPrefix(os); - bool require2_8_12 = false; - bool require3_0_0 = false; - bool require3_1_0 = false; bool requiresConfigFiles = false; // Create all the imported targets. for (cmTargetExport* te : allTargets) { @@ -147,16 +144,16 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os) if (this->PopulateInterfaceLinkLibrariesProperty( gt, cmGeneratorExpression::InstallInterface, properties) && !this->ExportOld) { - require2_8_12 = true; + this->SetRequiredCMakeVersion(2, 8, 12); } } if (targetType == cmStateEnums::INTERFACE_LIBRARY) { - require3_0_0 = true; + this->SetRequiredCMakeVersion(3, 0, 0); } if (gt->GetProperty("INTERFACE_SOURCES")) { // We can only generate INTERFACE_SOURCES in CMake 3.3, but CMake 3.1 // can consume them. - require3_1_0 = true; + this->SetRequiredCMakeVersion(3, 1, 0); } this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gt, @@ -169,14 +166,6 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os) this->GenerateTargetFileSets(gt, os, te); } - if (require3_1_0) { - this->GenerateRequiredCMakeVersion(os, "3.1.0"); - } else if (require3_0_0) { - this->GenerateRequiredCMakeVersion(os, "3.0.0"); - } else if (require2_8_12) { - this->GenerateRequiredCMakeVersion(os, "2.8.12"); - } - this->LoadConfigFiles(os); bool result = true; @@ -386,9 +375,26 @@ void cmExportInstallFileGenerator::GenerateImportTargetsConfig( // properties); // Generate code in the export file. - this->GenerateImportPropertyCode(os, config, gtgt, properties); - this->GenerateImportedFileChecksCode(os, gtgt, properties, - importedLocations); + std::string importedXcFrameworkLocation = te->XcFrameworkLocation; + if (!importedXcFrameworkLocation.empty()) { + importedXcFrameworkLocation = cmGeneratorExpression::Preprocess( + importedXcFrameworkLocation, + cmGeneratorExpression::PreprocessContext::InstallInterface, true); + importedXcFrameworkLocation = cmGeneratorExpression::Evaluate( + importedXcFrameworkLocation, te->Target->GetLocalGenerator(), config, + te->Target, nullptr, te->Target); + if (!importedXcFrameworkLocation.empty() && + !cmSystemTools::FileIsFullPath(importedXcFrameworkLocation) && + !cmHasLiteralPrefix(importedXcFrameworkLocation, + "${_IMPORT_PREFIX}/")) { + importedXcFrameworkLocation = + cmStrCat("${_IMPORT_PREFIX}/", importedXcFrameworkLocation); + } + } + this->GenerateImportPropertyCode(os, config, suffix, gtgt, properties, + importedXcFrameworkLocation); + this->GenerateImportedFileChecksCode( + os, gtgt, properties, importedLocations, importedXcFrameworkLocation); } } } diff --git a/Source/cmExportInstallFileGenerator.h b/Source/cmExportInstallFileGenerator.h index e073a31..9de0c8e 100644 --- a/Source/cmExportInstallFileGenerator.h +++ b/Source/cmExportInstallFileGenerator.h @@ -12,12 +12,13 @@ #include <vector> #include "cmExportFileGenerator.h" +#include "cmInstallExportGenerator.h" #include "cmStateTypes.h" +class cmExportSet; class cmFileSet; class cmGeneratorTarget; class cmGlobalGenerator; -class cmInstallExportGenerator; class cmInstallTargetGenerator; class cmTargetExport; @@ -121,6 +122,11 @@ protected: void GenerateCxxModuleConfigInformation(std::ostream&) const override; bool GenerateImportCxxModuleConfigTargetInclusion(std::string const&); + cmExportSet* GetExportSet() const override + { + return this->IEGen->GetExportSet(); + } + cmInstallExportGenerator* IEGen; // The import file generated for each configuration. diff --git a/Source/cmExportSet.cxx b/Source/cmExportSet.cxx index 3d4ef0a..b32bb8d 100644 --- a/Source/cmExportSet.cxx +++ b/Source/cmExportSet.cxx @@ -1,6 +1,6 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmExportSet.h" +#include "cmExportSet.h" // IWYU pragma: associated #include <algorithm> #include <tuple> @@ -11,7 +11,7 @@ #include "cmMessageType.h" #include "cmStringAlgorithms.h" #include "cmTarget.h" -#include "cmTargetExport.h" +#include "cmTargetExport.h" // IWYU pragma: associated cmExportSet::cmExportSet(std::string name) : Name(std::move(name)) @@ -20,6 +20,17 @@ cmExportSet::cmExportSet(std::string name) cmExportSet::~cmExportSet() = default; +cmExportSet::PackageDependency& cmExportSet::GetPackageDependencyForSetup( + const std::string& name) +{ + auto& dep = this->PackageDependencies[name]; + if (!dep.SpecifiedIndex) { + dep.SpecifiedIndex = this->NextPackageDependencyIndex; + this->NextPackageDependencyIndex++; + } + return dep; +} + bool cmExportSet::Compute(cmLocalGenerator* lg) { for (std::unique_ptr<cmTargetExport>& tgtExport : this->TargetExports) { @@ -61,6 +72,16 @@ void cmExportSet::AddInstallation(cmInstallExportGenerator const* installation) this->Installations.push_back(installation); } +void cmExportSet::SetXcFrameworkLocation(const std::string& name, + const std::string& location) +{ + for (auto& te : this->TargetExports) { + if (name == te->TargetName) { + te->XcFrameworkLocation = location; + } + } +} + cmExportSet& cmExportSetMap::operator[](const std::string& name) { auto it = this->find(name); diff --git a/Source/cmExportSet.h b/Source/cmExportSet.h index b75a26d..f2fc4a7 100644 --- a/Source/cmExportSet.h +++ b/Source/cmExportSet.h @@ -9,6 +9,8 @@ #include <string> #include <vector> +#include <cm/optional> + class cmInstallExportGenerator; class cmLocalGenerator; class cmTargetExport; @@ -31,6 +33,9 @@ public: void AddInstallation(cmInstallExportGenerator const* installation); + void SetXcFrameworkLocation(const std::string& name, + const std::string& location); + std::string const& GetName() const { return this->Name; } std::vector<std::unique_ptr<cmTargetExport>> const& GetTargetExports() const @@ -43,10 +48,36 @@ public: return &this->Installations; } + enum class PackageDependencyExportEnabled + { + Auto, + Off, + On, + }; + + struct PackageDependency + { + PackageDependencyExportEnabled Enabled = + PackageDependencyExportEnabled::Auto; + std::vector<std::string> ExtraArguments; + cm::optional<unsigned int> SpecifiedIndex; + cm::optional<unsigned int> FindPackageIndex; + }; + + PackageDependency& GetPackageDependencyForSetup(const std::string& name); + + const std::map<std::string, PackageDependency>& GetPackageDependencies() + const + { + return this->PackageDependencies; + } + private: std::vector<std::unique_ptr<cmTargetExport>> TargetExports; std::string Name; std::vector<cmInstallExportGenerator const*> Installations; + std::map<std::string, PackageDependency> PackageDependencies; + unsigned int NextPackageDependencyIndex = 0; }; /// A name -> cmExportSet map with overloaded operator[]. diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx index 8b0f309..4524ba6 100644 --- a/Source/cmFileAPI.cxx +++ b/Source/cmFileAPI.cxx @@ -727,7 +727,7 @@ std::string cmFileAPI::NoSupportedVersion( // The "codemodel" object kind. // Update Help/manual/cmake-file-api.7.rst when updating this constant. -static unsigned int const CodeModelV2Minor = 6; +static unsigned int const CodeModelV2Minor = 7; void cmFileAPI::BuildClientRequestCodeModel( ClientRequest& r, std::vector<RequestVersion> const& versions) diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx index d069186..30a7e67 100644 --- a/Source/cmFileAPICodemodel.cxx +++ b/Source/cmFileAPICodemodel.cxx @@ -41,10 +41,12 @@ #include "cmInstallSubdirectoryGenerator.h" #include "cmInstallTargetGenerator.h" #include "cmLinkLineComputer.h" // IWYU pragma: keep +#include "cmList.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" +#include "cmRange.h" #include "cmSourceFile.h" #include "cmSourceGroup.h" #include "cmState.h" @@ -503,6 +505,8 @@ class Target Json::Value DumpDependencies(); Json::Value DumpDependency(cmTargetDepend const& td); Json::Value DumpFolder(); + Json::Value DumpLauncher(const char* name, const char* type); + Json::Value DumpLaunchers(); public: Target(cmGeneratorTarget* gt, std::string const& config); @@ -1223,6 +1227,13 @@ Json::Value Target::Dump() target["archive"] = this->DumpArchive(); } + if (type == cmStateEnums::EXECUTABLE) { + Json::Value launchers = this->DumpLaunchers(); + if (!launchers.empty()) { + target["launchers"] = std::move(launchers); + } + } + Json::Value dependencies = this->DumpDependencies(); if (!dependencies.empty()) { target["dependencies"] = dependencies; @@ -2075,6 +2086,46 @@ Json::Value Target::DumpFolder() } return folder; } + +Json::Value Target::DumpLauncher(const char* name, const char* type) +{ + cmValue property = this->GT->GetProperty(name); + Json::Value launcher; + if (property) { + cmList commandWithArgs{ *property }; + std::string command(commandWithArgs[0]); + cmSystemTools::ConvertToUnixSlashes(command); + launcher = Json::objectValue; + launcher["command"] = RelativeIfUnder(this->TopSource, command); + launcher["type"] = type; + Json::Value args; + for (std::string const& arg : cmMakeRange(commandWithArgs).advance(1)) { + args.append(arg); + } + if (!args.empty()) { + launcher["arguments"] = std::move(args); + } + } + return launcher; +} + +Json::Value Target::DumpLaunchers() +{ + Json::Value launchers; + { + Json::Value launcher = DumpLauncher("TEST_LAUNCHER", "test"); + if (!launcher.empty()) { + launchers.append(std::move(launcher)); + } + } + if (this->GT->Makefile->IsOn("CMAKE_CROSSCOMPILING")) { + Json::Value emulator = DumpLauncher("CROSSCOMPILING_EMULATOR", "emulator"); + if (!emulator.empty()) { + launchers.append(std::move(emulator)); + } + } + return launchers; +} } Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI, unsigned long version) diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index c65136c..c1b7b48 100644 --- a/Source/cmFileCommand.cxx +++ b/Source/cmFileCommand.cxx @@ -1322,13 +1322,15 @@ bool HandleRealPathCommand(std::vector<std::string> const& args, if (oldPolicyPath != realPath) { status.GetMakefile().IssueMessage( MessageType::AUTHOR_WARNING, - cmStrCat( - cmPolicies::GetPolicyWarning(cmPolicies::CMP0152), '\n', - "From input path:\n ", input, - "\nthe policy OLD behavior produces path:\n ", oldPolicyPath, - "\nbut the policy NEW behavior produces path:\n ", realPath, - "\nSince the policy is not set, CMake is using the OLD " - "behavior for compatibility.")); + cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0152), + "\n" + "From input path:\n ", + input, "\nthe policy OLD behavior produces path:\n ", + oldPolicyPath, + "\nbut the policy NEW behavior produces path:\n ", + realPath, + "\nSince the policy is not set, CMake is using the OLD " + "behavior for compatibility.")); } } realPath = oldPolicyPath; diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx index 30458cd..9b51b1a 100644 --- a/Source/cmFindPackageCommand.cxx +++ b/Source/cmFindPackageCommand.cxx @@ -1044,6 +1044,8 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args) PushPopRootPathStack pushPopRootPathStack(*this); SetRestoreFindDefinitions setRestoreFindDefinitions(*this, components, componentVarDefs); + cmMakefile::FindPackageStackRAII findPackageStackRAII(this->Makefile, + this->Name); // See if we have been told to delegate to FetchContent or some other // redirected config package first. We have to check all names that diff --git a/Source/cmFindPackageStack.cxx b/Source/cmFindPackageStack.cxx new file mode 100644 index 0000000..1aeb2a7 --- /dev/null +++ b/Source/cmFindPackageStack.cxx @@ -0,0 +1,7 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#define cmFindPackageStack_cxx +#include "cmFindPackageStack.h" + +#include "cmConstStack.tcc" // IWYU pragma: keep +template class cmConstStack<cmFindPackageCall, cmFindPackageStack>; diff --git a/Source/cmFindPackageStack.h b/Source/cmFindPackageStack.h new file mode 100644 index 0000000..2062fbc --- /dev/null +++ b/Source/cmFindPackageStack.h @@ -0,0 +1,33 @@ +/* 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 <memory> +#include <string> + +#include "cmConstStack.h" + +/** + * Represents one call to find_package. + */ +class cmFindPackageCall +{ +public: + std::string Name; + unsigned int Index; +}; + +/** + * Represents a stack of find_package calls with efficient value semantics. + */ +class cmFindPackageStack + : public cmConstStack<cmFindPackageCall, cmFindPackageStack> +{ + using cmConstStack::cmConstStack; + friend class cmConstStack<cmFindPackageCall, cmFindPackageStack>; +}; +#ifndef cmFindPackageStack_cxx +extern template class cmConstStack<cmFindPackageCall, cmFindPackageStack>; +#endif diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx index d51dbd0..4e46df7 100644 --- a/Source/cmGeneratorExpressionDAGChecker.cxx +++ b/Source/cmGeneratorExpressionDAGChecker.cxx @@ -175,14 +175,15 @@ 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_LIBRARY_OVERRIDE"_s; + property == "LINK_DEPENDS"_s || property == "LINK_LIBRARY_OVERRIDE"_s || + property == "LINKER_TYPE"_s; } bool cmGeneratorExpressionDAGChecker::EvaluatingLinkOptionsExpression() const { cm::string_view property(this->Top()->Property); - return property == "LINK_OPTIONS"_s; + return property == "LINK_OPTIONS"_s || property == "LINKER_TYPE"_s; } bool cmGeneratorExpressionDAGChecker::EvaluatingLinkerLauncher() const diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 9bd9fef..289bb24 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -868,6 +868,31 @@ cmValue cmGeneratorTarget::GetFeature(const std::string& feature, return this->LocalGenerator->GetFeature(feature, config); } +std::string cmGeneratorTarget::GetLinkerTypeProperty( + std::string const& lang, std::string const& config) const +{ + std::string propName{ "LINKER_TYPE" }; + auto linkerType = this->GetProperty(propName); + if (!linkerType.IsEmpty()) { + cmGeneratorExpressionDAGChecker dagChecker(this, propName, nullptr, + nullptr); + auto ltype = + cmGeneratorExpression::Evaluate(*linkerType, this->GetLocalGenerator(), + config, this, &dagChecker, this, lang); + if (this->IsDeviceLink()) { + cmList list{ ltype }; + const auto DL_BEGIN = "<DEVICE_LINK>"_s; + const auto DL_END = "</DEVICE_LINK>"_s; + cm::erase_if(list, [&](const std::string& item) { + return item == DL_BEGIN || item == DL_END; + }); + return list.to_string(); + } + return ltype; + } + return std::string{}; +} + const char* cmGeneratorTarget::GetLinkPIEProperty( const std::string& config) const { @@ -1821,32 +1846,31 @@ std::vector<BT<std::string>> cmGeneratorTarget::GetSourceFilePaths( AddInterfaceEntries(this, config, "INTERFACE_SOURCES", std::string(), &dagChecker, linkInterfaceSourcesEntries, IncludeRuntimeInterface::No, LinkInterfaceFor::Usage); - std::vector<std::string>::size_type numFilesBefore = files.size(); bool contextDependentInterfaceSources = processSources( this, linkInterfaceSourcesEntries, files, uniqueSrcs, debugSources); // Collect TARGET_OBJECTS of direct object link-dependencies. bool contextDependentObjects = false; - std::vector<std::string>::size_type numFilesBefore2 = files.size(); if (this->GetType() != cmStateEnums::OBJECT_LIBRARY) { EvaluatedTargetPropertyEntries linkObjectsEntries; AddObjectEntries(this, config, &dagChecker, linkObjectsEntries); contextDependentObjects = processSources(this, linkObjectsEntries, files, uniqueSrcs, debugSources); + // Note that for imported targets or multi-config generators supporting + // cross-config builds the paths to the object files must be per-config, + // so contextDependentObjects will be true here even if object libraries + // are specified without per-config generator expressions. } // Collect this target's file sets. - std::vector<std::string>::size_type numFilesBefore3 = files.size(); EvaluatedTargetPropertyEntries fileSetEntries; AddFileSetEntries(this, config, &dagChecker, fileSetEntries); bool contextDependentFileSets = processSources(this, fileSetEntries, files, uniqueSrcs, debugSources); // Determine if sources are context-dependent or not. - if (!contextDependentDirectSources && - !(contextDependentInterfaceSources && numFilesBefore < files.size()) && - !(contextDependentObjects && numFilesBefore2 < files.size()) && - !(contextDependentFileSets && numFilesBefore3 < files.size())) { + if (!contextDependentDirectSources && !contextDependentInterfaceSources && + !contextDependentObjects && !contextDependentFileSets) { this->SourcesAreContextDependent = Tribool::False; } else { this->SourcesAreContextDependent = Tribool::True; @@ -5528,6 +5552,50 @@ std::string cmGeneratorTarget::GetLinkerLanguage( return this->GetLinkClosure(config)->LinkerLanguage; } +std::string cmGeneratorTarget::GetLinkerTool(const std::string& config) const +{ + return this->GetLinkerTool(this->GetLinkerLanguage(config), config); +} + +std::string cmGeneratorTarget::GetLinkerTool(const std::string& lang, + const std::string& config) const +{ + auto usingLinker = + cmStrCat("CMAKE_", lang, "_USING_", this->IsDeviceLink() ? "DEVICE_" : "", + "LINKER_"); + auto format = this->Makefile->GetDefinition(cmStrCat(usingLinker, "MODE")); + if (!format || format != "TOOL"_s) { + return this->Makefile->GetDefinition("CMAKE_LINKER"); + } + + auto linkerType = this->GetLinkerTypeProperty(lang, config); + if (linkerType.empty()) { + linkerType = "DEFAULT"; + } + usingLinker = cmStrCat(usingLinker, linkerType); + auto linkerTool = this->Makefile->GetDefinition(usingLinker); + + if (!linkerTool) { + if (this->GetGlobalGenerator()->IsVisualStudio() && + linkerType == "DEFAULT"_s) { + return std::string{}; + } + + // fall-back to generic definition + linkerTool = this->Makefile->GetDefinition("CMAKE_LINKER"); + + if (linkerType != "DEFAULT"_s) { + this->LocalGenerator->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("LINKER_TYPE '", linkerType, + "' is unknown. Did you forgot to define '", usingLinker, + "' variable?")); + } + } + + return linkerTool; +} + std::string cmGeneratorTarget::GetPDBOutputName( const std::string& config) const { @@ -6846,7 +6914,8 @@ bool cmGeneratorTarget::IsLinkLookupScope(std::string const& n, cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem( std::string const& n, cmListFileBacktrace const& bt, - LookupLinkItemScope* scope, LookupSelf lookupSelf) const + std::string const& linkFeature, LookupLinkItemScope* scope, + LookupSelf lookupSelf) const { cm::optional<cmLinkItem> maybeItem; if (this->IsLinkLookupScope(n, scope->LG)) { @@ -6858,7 +6927,8 @@ cm::optional<cmLinkItem> cmGeneratorTarget::LookupLinkItem( (lookupSelf == LookupSelf::No && name == this->GetName())) { return maybeItem; } - maybeItem = this->ResolveLinkItem(BT<std::string>(name, bt), scope->LG); + maybeItem = + this->ResolveLinkItem(BT<std::string>(name, bt), scope->LG, linkFeature); return maybeItem; } @@ -6889,9 +6959,16 @@ void cmGeneratorTarget::ExpandLinkItems( cmList libs{ cge->Evaluate(this->LocalGenerator, config, headTarget, &dagChecker, this, headTarget->LinkerLanguage) }; + + auto linkFeature = cmLinkItem::DEFAULT; for (auto const& lib : libs) { + if (auto maybeLinkFeature = ParseLinkFeature(lib)) { + linkFeature = std::move(*maybeLinkFeature); + continue; + } + if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem( - lib, cge->GetBacktrace(), &scope, + lib, cge->GetBacktrace(), linkFeature, &scope, field == LinkInterfaceField::Libraries ? LookupSelf::No : LookupSelf::Yes)) { cmLinkItem item = std::move(*maybeItem); @@ -7637,9 +7714,16 @@ const cmLinkInterface* cmGeneratorTarget::GetImportLinkInterface( LinkInterfaceField::Libraries, iface); cmList deps{ info->SharedDeps }; LookupLinkItemScope scope{ this->LocalGenerator }; + + auto linkFeature = cmLinkItem::DEFAULT; for (auto const& dep : deps) { + if (auto maybeLinkFeature = ParseLinkFeature(dep)) { + linkFeature = std::move(*maybeLinkFeature); + continue; + } + if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem( - dep, cmListFileBacktrace(), &scope, LookupSelf::No)) { + dep, cmListFileBacktrace(), linkFeature, &scope, LookupSelf::No)) { iface.SharedDeps.emplace_back(std::move(*maybeItem)); } } @@ -8439,7 +8523,13 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries( impl.HadLinkLanguageSensitiveCondition = true; } + auto linkFeature = cmLinkItem::DEFAULT; for (auto const& lib : llibs) { + if (auto maybeLinkFeature = ParseLinkFeature(lib)) { + linkFeature = std::move(*maybeLinkFeature); + continue; + } + if (this->IsLinkLookupScope(lib, lg)) { continue; } @@ -8486,8 +8576,8 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries( } // The entry is meant for this configuration. - cmLinkItem item = - this->ResolveLinkItem(BT<std::string>(name, entry.Backtrace), lg); + cmLinkItem item = this->ResolveLinkItem( + BT<std::string>(name, entry.Backtrace), lg, linkFeature); if (item.Target) { auto depsForTarget = synthTargetsForConfig.find(item.Target); if (depsForTarget != synthTargetsForConfig.end()) { @@ -8535,7 +8625,14 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries( CMP0003_ComputeLinkType(config, debugConfigs); cmTarget::LinkLibraryVectorType const& oldllibs = this->Target->GetOriginalLinkLibraries(); + + auto linkFeature = cmLinkItem::DEFAULT; for (cmTarget::LibraryID const& oldllib : oldllibs) { + if (auto maybeLinkFeature = ParseLinkFeature(oldllib.first)) { + linkFeature = std::move(*maybeLinkFeature); + continue; + } + if (oldllib.second != GENERAL_LibraryType && oldllib.second != linkType) { std::string name = this->CheckCMP0004(oldllib.first); if (name == this->GetName() || name.empty()) { @@ -8543,7 +8640,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries( } // Support OLD behavior for CMP0003. impl.WrongConfigLibraries.push_back( - this->ResolveLinkItem(BT<std::string>(name))); + this->ResolveLinkItem(BT<std::string>(name), linkFeature)); } } } @@ -8569,19 +8666,20 @@ cmGeneratorTarget::TargetOrString cmGeneratorTarget::ResolveTargetReference( } cmLinkItem cmGeneratorTarget::ResolveLinkItem( - BT<std::string> const& name) const + BT<std::string> const& name, std::string const& linkFeature) const { - return this->ResolveLinkItem(name, this->LocalGenerator); + return this->ResolveLinkItem(name, this->LocalGenerator, linkFeature); } -cmLinkItem cmGeneratorTarget::ResolveLinkItem(BT<std::string> const& name, - cmLocalGenerator const* lg) const +cmLinkItem cmGeneratorTarget::ResolveLinkItem( + BT<std::string> const& name, cmLocalGenerator const* lg, + std::string const& linkFeature) const { auto bt = name.Backtrace; TargetOrString resolved = this->ResolveTargetReference(name.Value, lg); if (!resolved.Target) { - return cmLinkItem(resolved.String, false, bt); + return cmLinkItem(resolved.String, false, bt, linkFeature); } // Check deprecation, issue message with `bt` backtrace. @@ -8602,10 +8700,10 @@ cmLinkItem cmGeneratorTarget::ResolveLinkItem(BT<std::string> const& name, // within the project. if (resolved.Target->GetType() == cmStateEnums::EXECUTABLE && !resolved.Target->IsExecutableWithExports()) { - return cmLinkItem(resolved.Target->GetName(), false, bt); + return cmLinkItem(resolved.Target->GetName(), false, bt, linkFeature); } - return cmLinkItem(resolved.Target, false, bt); + return cmLinkItem(resolved.Target, false, bt, linkFeature); } bool cmGeneratorTarget::HasPackageReferences() const diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index c13b2f6..cfb08fa 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -206,6 +206,9 @@ public: cmValue GetFeature(const std::string& feature, const std::string& config) const; + std::string GetLinkerTypeProperty(std::string const& lang, + std::string const& config) const; + const char* GetLinkPIEProperty(const std::string& config) const; bool IsIPOEnabled(std::string const& lang, std::string const& config) const; @@ -447,9 +450,12 @@ public: TargetOrString ResolveTargetReference(std::string const& name, cmLocalGenerator const* lg) const; - cmLinkItem ResolveLinkItem(BT<std::string> const& name) const; - cmLinkItem ResolveLinkItem(BT<std::string> const& name, - cmLocalGenerator const* lg) const; + cmLinkItem ResolveLinkItem( + BT<std::string> const& name, + std::string const& linkFeature = cmLinkItem::DEFAULT) const; + cmLinkItem ResolveLinkItem( + BT<std::string> const& name, cmLocalGenerator const* lg, + std::string const& linkFeature = cmLinkItem::DEFAULT) const; bool HasPackageReferences() const; std::vector<std::string> GetPackageReferences() const; @@ -794,6 +800,10 @@ public: //! Return the preferred linker language for this target std::string GetLinkerLanguage(const std::string& config) const; + //! Return the preferred linker tool for this target + std::string GetLinkerTool(const std::string& config) const; + std::string GetLinkerTool(const std::string& lang, + const std::string& config) const; /** Does this target have a GNU implib to convert to MS format? */ bool HasImplibGNUtoMS(std::string const& config) const; @@ -1175,6 +1185,7 @@ private: }; cm::optional<cmLinkItem> LookupLinkItem(std::string const& n, cmListFileBacktrace const& bt, + std::string const& linkFeature, LookupLinkItemScope* scope, LookupSelf lookupSelf) const; diff --git a/Source/cmGhsMultiTargetGenerator.cxx b/Source/cmGhsMultiTargetGenerator.cxx index 95e2187..940f49d 100644 --- a/Source/cmGhsMultiTargetGenerator.cxx +++ b/Source/cmGhsMultiTargetGenerator.cxx @@ -116,6 +116,19 @@ void cmGhsMultiTargetGenerator::Generate() void cmGhsMultiTargetGenerator::GenerateTarget() { + if (this->GeneratorTarget->GetType() == cmStateEnums::EXECUTABLE && + !this->GeneratorTarget + ->GetLinkerTypeProperty( + this->GeneratorTarget->GetLinkerLanguage(this->ConfigName), + this->ConfigName) + .empty()) { + // Green Hill MULTI does not support this feature. + cmSystemTools::Message( + cmStrCat("'LINKER_TYPE' property, specified on target '", + this->GeneratorTarget->GetName(), + "', is not supported by this generator.")); + } + // Open the target file in copy-if-different mode. std::string fproj = cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/', diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index c2b972d..e74a8b0 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -694,7 +694,22 @@ void cmGlobalGenerator::EnableLanguage( std::string includes = mf->GetSafeDefinition("CMAKE_PROJECT_TOP_LEVEL_INCLUDES"); cmList includesList{ includes }; - for (std::string const& setupFile : includesList) { + for (std::string setupFile : includesList) { + // Any relative path without a .cmake extension is checked for valid + // cmake modules. This logic should be consistent with CMake's include() + // command. Otherwise default to checking relative path w.r.t. source + // directory + if (!cmSystemTools::FileIsFullPath(setupFile) && + !cmHasLiteralSuffix(setupFile, ".cmake")) { + std::string mfile = mf->GetModulesFile(cmStrCat(setupFile, ".cmake")); + if (mfile.empty()) { + cmSystemTools::Error(cmStrCat( + "CMAKE_PROJECT_TOP_LEVEL_INCLUDES module:\n ", setupFile)); + mf->GetState()->SetInTopLevelIncludes(false); + return; + } + setupFile = mfile; + } std::string absSetupFile = cmSystemTools::CollapseFullPath( setupFile, mf->GetCurrentSourceDirectory()); if (!cmSystemTools::FileExists(absSetupFile)) { @@ -859,7 +874,11 @@ void cmGlobalGenerator::EnableLanguage( noCompiler << "The " << compilerName << ":\n" " " << *compilerFile << "\n" - "is not a full path and was not found in the PATH.\n" + "is not a full path and was not found in the PATH." +#ifdef _WIN32 + " Perhaps the extension is missing?" +#endif + "\n" ; /* clang-format on */ } else if (!cmSystemTools::FileExists(*compilerFile)) { @@ -1139,24 +1158,30 @@ std::string cmGlobalGenerator::GetLanguageOutputExtension( { const std::string& lang = source.GetLanguage(); if (!lang.empty()) { - auto const it = this->LanguageToOutputExtension.find(lang); - if (it != this->LanguageToOutputExtension.end()) { - return it->second; - } - } else { - // if no language is found then check to see if it is already an - // output extension for some language. In that case it should be ignored - // and in this map, so it will not be compiled but will just be used. - std::string const& ext = source.GetExtension(); - if (!ext.empty()) { - if (this->OutputExtensions.count(ext)) { - return ext; - } + return this->GetLanguageOutputExtension(lang); + } + // if no language is found then check to see if it is already an + // output extension for some language. In that case it should be ignored + // and in this map, so it will not be compiled but will just be used. + std::string const& ext = source.GetExtension(); + if (!ext.empty()) { + if (this->OutputExtensions.count(ext)) { + return ext; } } return ""; } +std::string cmGlobalGenerator::GetLanguageOutputExtension( + std::string const& lang) const +{ + auto const it = this->LanguageToOutputExtension.find(lang); + if (it != this->LanguageToOutputExtension.end()) { + return it->second; + } + return ""; +} + std::string cmGlobalGenerator::GetLanguageFromExtension(const char* ext) const { // if there is an extension and it starts with . then move past the @@ -2848,6 +2873,14 @@ void cmGlobalGenerator::AddGlobalTarget_Test( gti.Name = this->GetTestTargetName(); gti.Message = "Running tests..."; gti.UsesTerminal = true; + // Unlike the 'install' target, the 'test' target does not depend on 'all' + // by default. Enable it only if CMAKE_SKIP_TEST_ALL_DEPENDENCY is + // explicitly set to OFF. + if (cmValue noall = mf->GetDefinition("CMAKE_SKIP_TEST_ALL_DEPENDENCY")) { + if (cmIsOff(noall)) { + gti.Depends.emplace_back(this->GetAllTargetName()); + } + } cmCustomCommandLine singleLine; singleLine.push_back(cmSystemTools::GetCTestCommand()); singleLine.push_back("--force-new-ctest-process"); diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index aa54f69..d83b669 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -349,6 +349,8 @@ public: int GetLinkerPreference(const std::string& lang) const; //! What is the object file extension for a given source file? std::string GetLanguageOutputExtension(cmSourceFile const&) const; + //! What is the object file extension for a given language? + std::string GetLanguageOutputExtension(std::string const& lang) const; //! What is the configurations directory variable called? virtual const char* GetCMakeCFGIntDir() const { return "."; } diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 22fd90d..55eb9b5 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -378,6 +378,15 @@ void cmGlobalNinjaGenerator::WriteCustomCommandBuild( } { + std::string ninjaDepfilePath; + bool depfileIsOutput = false; + if (!depfile.empty()) { + ninjaDepfilePath = this->ConvertToNinjaPath(depfile); + depfileIsOutput = + std::find(outputs.ExplicitOuts.begin(), outputs.ExplicitOuts.end(), + ninjaDepfilePath) != outputs.ExplicitOuts.end(); + } + cmNinjaBuild build("CUSTOM_COMMAND"); build.Comment = comment; build.Outputs = std::move(outputs.ExplicitOuts); @@ -405,7 +414,13 @@ void cmGlobalNinjaGenerator::WriteCustomCommandBuild( vars["pool"] = job_pool; } if (!depfile.empty()) { - vars["depfile"] = depfile; + vars["depfile"] = ninjaDepfilePath; + // Add the depfile to the `.ninja_deps` database. Since this (generally) + // removes the file, it cannot be declared as an output or byproduct of + // the command. + if (!depfileIsOutput) { + vars["deps"] = "gcc"; + } } if (config.empty()) { this->WriteBuild(*this->GetCommonFileStream(), build); @@ -1359,10 +1374,10 @@ void cmGlobalNinjaGenerator::AppendTargetDepends( } else { cmNinjaDeps outs; - auto computeISPCOuputs = [](cmGlobalNinjaGenerator* gg, - cmGeneratorTarget const* depTarget, - cmNinjaDeps& outputDeps, - const std::string& targetConfig) { + auto computeISPCOutputs = [](cmGlobalNinjaGenerator* gg, + cmGeneratorTarget const* depTarget, + cmNinjaDeps& outputDeps, + const std::string& targetConfig) { if (depTarget->CanCompileSources()) { auto headers = depTarget->GetGeneratedISPCHeaders(targetConfig); if (!headers.empty()) { @@ -1386,10 +1401,10 @@ void cmGlobalNinjaGenerator::AppendTargetDepends( } if (targetDep.IsCross()) { this->AppendTargetOutputs(targetDep, outs, fileConfig, depends); - computeISPCOuputs(this, targetDep, outs, fileConfig); + computeISPCOutputs(this, targetDep, outs, fileConfig); } else { this->AppendTargetOutputs(targetDep, outs, config, depends); - computeISPCOuputs(this, targetDep, outs, config); + computeISPCOutputs(this, targetDep, outs, config); } } std::sort(outs.begin(), outs.end()); @@ -3242,10 +3257,3 @@ std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTarget( return cmStrCat("cmake_object_order_depends_target_", target->GetName(), '_', cmSystemTools::UpperCase(config)); } - -std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTargetPrivate( - cmGeneratorTarget const* target, const std::string& config) const -{ - return cmStrCat(this->OrderDependsTargetForTarget(target, config), - "_private"); -} diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h index 220d393..3443643 100644 --- a/Source/cmGlobalNinjaGenerator.h +++ b/Source/cmGlobalNinjaGenerator.h @@ -349,7 +349,7 @@ public: virtual std::string OrderDependsTargetForTarget( cmGeneratorTarget const* target, const std::string& config) const; - virtual std::string OrderDependsTargetForTargetPrivate( + std::string OrderDependsTargetForTargetPrivate( cmGeneratorTarget const* target, const std::string& config) const; void AppendTargetOutputs(cmGeneratorTarget const* target, @@ -742,9 +742,6 @@ public: std::string OrderDependsTargetForTarget( cmGeneratorTarget const* target, const std::string& config) const override; - std::string OrderDependsTargetForTargetPrivate( - cmGeneratorTarget const* target, const std::string& config) const override; - protected: bool OpenBuildFileStreams() override; void CloseBuildFileStreams() override; diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx index 541db63..c93b140 100644 --- a/Source/cmGlobalVisualStudio10Generator.cxx +++ b/Source/cmGlobalVisualStudio10Generator.cxx @@ -191,6 +191,23 @@ bool cmGlobalVisualStudio10Generator::SetGeneratorToolset( } } + if (this->GeneratorToolsetFortran) { + if (*this->GeneratorToolsetFortran != "ifx" && + *this->GeneratorToolsetFortran != "ifort") { + mf->IssueMessage(MessageType::FATAL_ERROR, + cmStrCat("Generator\n" + " ", + this->GetName(), + "\n" + "given toolset\n" + " fortran=", + *this->GeneratorToolsetFortran, + "\n" + "but the value is not \"ifx\" or \"ifort\".")); + this->GeneratorToolsetFortran = cm::nullopt; + } + } + if (!this->GeneratorToolsetVersion.empty() && this->GeneratorToolsetVersion != "Test Toolset Version"_s) { // If a specific minor version of the MSVC toolset is requested, verify @@ -300,6 +317,9 @@ bool cmGlobalVisualStudio10Generator::SetGeneratorToolset( if (const char* cudaDir = this->GetPlatformToolsetCudaCustomDir()) { mf->AddDefinition("CMAKE_VS_PLATFORM_TOOLSET_CUDA_CUSTOM_DIR", cudaDir); } + if (cm::optional<std::string> fortran = this->GetPlatformToolsetFortran()) { + mf->AddDefinition("CMAKE_VS_PLATFORM_TOOLSET_FORTRAN", *fortran); + } if (const char* vcTargetsDir = this->GetCustomVCTargetsPath()) { mf->AddDefinition("CMAKE_VS_PLATFORM_TOOLSET_VCTARGETS_CUSTOM_DIR", vcTargetsDir); @@ -410,6 +430,10 @@ bool cmGlobalVisualStudio10Generator::ProcessGeneratorToolsetField( cmSystemTools::ConvertToUnixSlashes(this->CustomFlagTableDir); return true; } + if (key == "fortran"_s) { + this->GeneratorToolsetFortran = value; + return true; + } if (key == "version"_s) { this->GeneratorToolsetVersion = value; return true; diff --git a/Source/cmGlobalVisualStudio10Generator.h b/Source/cmGlobalVisualStudio10Generator.h index 40bdd71..a2b351c 100644 --- a/Source/cmGlobalVisualStudio10Generator.h +++ b/Source/cmGlobalVisualStudio10Generator.h @@ -93,6 +93,12 @@ public: * directory */ std::string const& GetPlatformToolsetCudaVSIntegrationSubdirString() const; + /** The fortran toolset name. */ + cm::optional<std::string> GetPlatformToolsetFortran() const override + { + return this->GeneratorToolsetFortran; + } + /** Return whether we need to use No/Debug instead of false/true for GenerateDebugInformation. */ bool GetPlatformToolsetNeedsDebugEnum() const @@ -221,6 +227,7 @@ protected: std::string GeneratorToolsetCudaCustomDir; std::string GeneratorToolsetCudaNvccSubdir; std::string GeneratorToolsetCudaVSIntegrationSubdir; + cm::optional<std::string> GeneratorToolsetFortran; std::string DefaultPlatformToolset; std::string DefaultPlatformToolsetHostArchitecture; std::string DefaultAndroidToolset; diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index 1abdd0b..9739a09 100644 --- a/Source/cmGlobalVisualStudio7Generator.cxx +++ b/Source/cmGlobalVisualStudio7Generator.cxx @@ -94,8 +94,8 @@ const std::string& cmGlobalVisualStudio7Generator::GetIntelProjectVersion() cmSystemTools::ReadRegistryValue(vskey, intelVersion, cmSystemTools::KeyWOW64_32); unsigned int intelVersionNumber = ~0u; - sscanf(intelVersion.c_str(), "%u", &intelVersionNumber); - if (intelVersionNumber >= 11) { + if (sscanf(intelVersion.c_str(), "%u", &intelVersionNumber) != 1 || + intelVersionNumber >= 11) { // Default to latest known project file version. intelVersion = "11.0"; } else if (intelVersionNumber == 10) { diff --git a/Source/cmGlobalVisualStudio7Generator.h b/Source/cmGlobalVisualStudio7Generator.h index 6f6109e..2056b2f 100644 --- a/Source/cmGlobalVisualStudio7Generator.h +++ b/Source/cmGlobalVisualStudio7Generator.h @@ -10,6 +10,8 @@ #include <utility> #include <vector> +#include <cm/optional> + #include <cm3p/json/value.h> #include "cmGlobalVisualStudioGenerator.h" @@ -102,6 +104,10 @@ public: } const std::string& GetIntelProjectVersion(); + virtual cm::optional<std::string> GetPlatformToolsetFortran() const + { + return cm::nullopt; + } bool FindMakeProgram(cmMakefile* mf) override; diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 72dba42..c6bb3df 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -2480,6 +2480,25 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt, buildSettings->AddAttribute("SWIFT_ACTIVE_COMPILATION_CONDITIONS", swiftDefs.CreateList()); } + + if (cm::optional<cmSwiftCompileMode> swiftCompileMode = + this->CurrentLocalGenerator->GetSwiftCompileMode(gtgt, configName)) { + switch (*swiftCompileMode) { + case cmSwiftCompileMode::Wholemodule: + buildSettings->AddAttribute("SWIFT_COMPILATION_MODE", + this->CreateString("wholemodule")); + break; + case cmSwiftCompileMode::Incremental: + case cmSwiftCompileMode::Singlefile: + break; + case cmSwiftCompileMode::Unknown: + this->CurrentLocalGenerator->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat("Unknown Swift_COMPILATION_MODE on target '", + gtgt->GetName(), "'")); + break; + } + } } std::string extraLinkOptionsVar; @@ -2501,6 +2520,9 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt, this->CurrentLocalGenerator->GetStaticLibraryFlags( extraLinkOptions, configName, llang, gtgt); } else { + this->CurrentLocalGenerator->AppendLinkerTypeFlags(extraLinkOptions, gtgt, + configName, llang); + cmValue targetLinkFlags = gtgt->GetProperty("LINK_FLAGS"); if (targetLinkFlags) { this->CurrentLocalGenerator->AppendFlags(extraLinkOptions, @@ -4278,6 +4300,15 @@ void cmGlobalXCodeGenerator::AddEmbeddedResources(cmXCodeObject* target) dstSubfolderSpec, NoActionOnCopyByDefault); } +void cmGlobalXCodeGenerator::AddEmbeddedXPCServices(cmXCodeObject* target) +{ + static const auto dstSubfolderSpec = "16"; + + this->AddEmbeddedObjects( + target, "Embed XPC Services", "XCODE_EMBED_XPC_SERVICES", dstSubfolderSpec, + NoActionOnCopyByDefault, "$(CONTENTS_FOLDER_PATH)/XPCServices"); +} + bool cmGlobalXCodeGenerator::CreateGroups( std::vector<cmLocalGenerator*>& generators) { @@ -4607,6 +4638,10 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects( buildSettings->AddAttribute("CODE_SIGNING_ALLOWED", this->CreateString("NO")); } + + // This code supports the OLD behavior of CMP0157. We should be able to + // remove computing the debug configuration set once the old behavior is + // removed. auto debugConfigs = this->GetCMakeInstance()->GetDebugConfigs(); std::set<std::string> debugConfigSet(debugConfigs.begin(), debugConfigs.end()); @@ -4616,9 +4651,16 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects( cmXCodeObject* buildSettingsForCfg = this->CreateFlatClone(buildSettings); - if (debugConfigSet.count(cmSystemTools::UpperCase(config.first)) == 0) { - buildSettingsForCfg->AddAttribute("SWIFT_COMPILATION_MODE", - this->CreateString("wholemodule")); + // Supports the OLD behavior of CMP0157. CMP0157 OLD behavior globally set + // wholemodule compilation for all non-debug configurations, for all + // targets. + if (this->CurrentMakefile + ->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT") + .IsEmpty()) { + if (debugConfigSet.count(cmSystemTools::UpperCase(config.first)) == 0) { + buildSettingsForCfg->AddAttribute("SWIFT_COMPILATION_MODE", + this->CreateString("wholemodule")); + } } // Put this last so it can override existing settings @@ -4680,6 +4722,7 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects( this->AddEmbeddedAppExtensions(t); this->AddEmbeddedExtensionKitExtensions(t); this->AddEmbeddedResources(t); + this->AddEmbeddedXPCServices(t); // Inherit project-wide values for any target-specific search paths. this->InheritBuildSettingAttribute(t, "HEADER_SEARCH_PATHS"); this->InheritBuildSettingAttribute(t, "SYSTEM_HEADER_SEARCH_PATHS"); diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index da0a4ea..12a5cad 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -225,6 +225,7 @@ private: void AddEmbeddedAppExtensions(cmXCodeObject* target); void AddEmbeddedExtensionKitExtensions(cmXCodeObject* target); void AddEmbeddedResources(cmXCodeObject* target); + void AddEmbeddedXPCServices(cmXCodeObject* target); void AddPositionIndependentLinkAttribute(cmGeneratorTarget* target, cmXCodeObject* buildSettings, const std::string& configName); diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx index 0fc4011..e2755da 100644 --- a/Source/cmInstallCommand.cxx +++ b/Source/cmInstallCommand.cxx @@ -2030,7 +2030,7 @@ bool HandleExportAndroidMKMode(std::vector<std::string> const& args, cm::make_unique<cmInstallExportGenerator>( &exportSet, ica.GetDestination(), ica.GetPermissions(), ica.GetConfigurations(), ica.GetComponent(), message, - ica.GetExcludeFromAll(), fname, name_space, "", exportOld, true, + ica.GetExcludeFromAll(), fname, name_space, "", exportOld, true, false, helper.Makefile->GetBacktrace())); return true; @@ -2054,12 +2054,14 @@ bool HandleExportMode(std::vector<std::string> const& args, bool exportOld = false; std::string filename; std::string cxx_modules_directory; + bool exportPackageDependencies = false; ica.Bind("EXPORT"_s, exp); ica.Bind("NAMESPACE"_s, name_space); ica.Bind("EXPORT_LINK_INTERFACE_LIBRARIES"_s, exportOld); ica.Bind("FILE"_s, filename); ica.Bind("CXX_MODULES_DIRECTORY"_s, cxx_modules_directory); + ica.Bind("EXPORT_PACKAGE_DEPENDENCIES"_s, exportPackageDependencies); std::vector<std::string> unknownArgs; ica.Parse(args, &unknownArgs); @@ -2147,7 +2149,8 @@ bool HandleExportMode(std::vector<std::string> const& args, &exportSet, ica.GetDestination(), ica.GetPermissions(), ica.GetConfigurations(), ica.GetComponent(), message, ica.GetExcludeFromAll(), fname, name_space, cxx_modules_directory, - exportOld, false, helper.Makefile->GetBacktrace())); + exportOld, false, exportPackageDependencies, + helper.Makefile->GetBacktrace())); return true; } diff --git a/Source/cmInstallExportGenerator.cxx b/Source/cmInstallExportGenerator.cxx index 71d5471..72f0ac7 100644 --- a/Source/cmInstallExportGenerator.cxx +++ b/Source/cmInstallExportGenerator.cxx @@ -27,7 +27,7 @@ cmInstallExportGenerator::cmInstallExportGenerator( std::string const& component, MessageLevel message, bool exclude_from_all, std::string filename, std::string name_space, std::string cxx_modules_directory, bool exportOld, bool android, - cmListFileBacktrace backtrace) + bool exportPackageDependencies, cmListFileBacktrace backtrace) : cmInstallGenerator(destination, configurations, component, message, exclude_from_all, false, std::move(backtrace)) , ExportSet(exportSet) @@ -36,6 +36,7 @@ cmInstallExportGenerator::cmInstallExportGenerator( , Namespace(std::move(name_space)) , CxxModulesDirectory(std::move(cxx_modules_directory)) , ExportOld(exportOld) + , ExportPackageDependencies(exportPackageDependencies) { if (android) { #ifndef CMAKE_BOOTSTRAP @@ -119,6 +120,7 @@ void cmInstallExportGenerator::GenerateScript(std::ostream& os) this->EFGen->AddConfiguration(c); } } + this->EFGen->SetExportPackageDependencies(this->ExportPackageDependencies); this->EFGen->GenerateImportFile(); // Perform the main install script generation. diff --git a/Source/cmInstallExportGenerator.h b/Source/cmInstallExportGenerator.h index f2d4a05..5f92851 100644 --- a/Source/cmInstallExportGenerator.h +++ b/Source/cmInstallExportGenerator.h @@ -29,7 +29,8 @@ public: bool exclude_from_all, std::string filename, std::string name_space, std::string cxx_modules_directory, bool exportOld, - bool android, cmListFileBacktrace backtrace); + bool android, bool exportPackageDependencies, + cmListFileBacktrace backtrace); cmInstallExportGenerator(const cmInstallExportGenerator&) = delete; ~cmInstallExportGenerator() override; @@ -70,6 +71,7 @@ protected: std::string const Namespace; std::string const CxxModulesDirectory; bool const ExportOld; + bool const ExportPackageDependencies; cmLocalGenerator* LocalGenerator = nullptr; std::string TempDir; diff --git a/Source/cmJSONState.cxx b/Source/cmJSONState.cxx index 1abdaa6..5c44fba 100644 --- a/Source/cmJSONState.cxx +++ b/Source/cmJSONState.cxx @@ -45,7 +45,7 @@ cmJSONState::cmJSONState(const std::string& filename, Json::Value* root) void cmJSONState::AddError(std::string const& errMsg) { - this->errors.push_back(Error(errMsg)); + this->errors.emplace_back(errMsg); } void cmJSONState::AddErrorAtValue(std::string const& errMsg, @@ -65,7 +65,7 @@ void cmJSONState::AddErrorAtOffset(std::string const& errMsg, this->AddError(errMsg); } else { Location loc = LocateInDocument(offset); - this->errors.push_back(Error(loc, errMsg)); + this->errors.emplace_back(loc, errMsg); } } @@ -118,7 +118,7 @@ const Json::Value* cmJSONState::value_after(std::string const& k) void cmJSONState::push_stack(std::string const& k, const Json::Value* value) { - this->parseStack.push_back(JsonPair(k, value)); + this->parseStack.emplace_back(k, value); } void cmJSONState::pop_stack() diff --git a/Source/cmLinkItem.cxx b/Source/cmLinkItem.cxx index 2dc40ff..3654176 100644 --- a/Source/cmLinkItem.cxx +++ b/Source/cmLinkItem.cxx @@ -4,20 +4,30 @@ #include <utility> // IWYU pragma: keep +#include <cm/optional> +#include <cm/string_view> +#include <cmext/string_view> + #include "cmGeneratorTarget.h" +#include "cmStringAlgorithms.h" + +const std::string cmLinkItem::DEFAULT = "DEFAULT"; cmLinkItem::cmLinkItem() = default; -cmLinkItem::cmLinkItem(std::string n, bool c, cmListFileBacktrace bt) +cmLinkItem::cmLinkItem(std::string n, bool c, cmListFileBacktrace bt, + std::string feature) : String(std::move(n)) + , Feature(std::move(feature)) , Cross(c) , Backtrace(std::move(bt)) { } cmLinkItem::cmLinkItem(cmGeneratorTarget const* t, bool c, - cmListFileBacktrace bt) + cmListFileBacktrace bt, std::string feature) : Target(t) + , Feature(std::move(feature)) , Cross(c) , Backtrace(std::move(bt)) { @@ -73,3 +83,19 @@ cmLinkImplItem::cmLinkImplItem(cmLinkItem item, bool checkCMP0027) , CheckCMP0027(checkCMP0027) { } + +namespace { +const cm::string_view LL_BEGIN = "<LINK_LIBRARY:"_s; +const cm::string_view LL_END = "</LINK_LIBRARY:"_s; +} +cm::optional<std::string> ParseLinkFeature(std::string const& item) +{ + if (cmHasPrefix(item, LL_BEGIN) && cmHasSuffix(item, '>')) { + return item.substr(LL_BEGIN.length(), + item.find('>', LL_BEGIN.length()) - LL_BEGIN.length()); + } + if (cmHasPrefix(item, LL_END) && cmHasSuffix(item, '>')) { + return cmLinkItem::DEFAULT; + } + return cm::nullopt; +} diff --git a/Source/cmLinkItem.h b/Source/cmLinkItem.h index e4444d3..1946c9b 100644 --- a/Source/cmLinkItem.h +++ b/Source/cmLinkItem.h @@ -10,6 +10,7 @@ #include <unordered_map> #include <vector> +#include <cm/optional> #include <cmext/algorithm> #include "cmListFileCache.h" @@ -25,14 +26,20 @@ class cmLinkItem std::string String; public: + // default feature: link library without decoration + static const std::string DEFAULT; + cmLinkItem(); - cmLinkItem(std::string s, bool c, cmListFileBacktrace bt); - cmLinkItem(cmGeneratorTarget const* t, bool c, cmListFileBacktrace bt); + cmLinkItem(std::string s, bool c, cmListFileBacktrace bt, + std::string feature = DEFAULT); + cmLinkItem(cmGeneratorTarget const* t, bool c, cmListFileBacktrace bt, + std::string feature = DEFAULT); std::string const& AsStr() const; cmGeneratorTarget const* Target = nullptr; // The source file representing the external object (used when linking // `$<TARGET_OBJECTS>`) cmSourceFile const* ObjectSource = nullptr; + std::string Feature; bool Cross = false; cmListFileBacktrace Backtrace; friend bool operator<(cmLinkItem const& l, cmLinkItem const& r); @@ -160,3 +167,6 @@ inline cmTargetLinkLibraryType CMP0003_ComputeLinkType( // The current configuration is not a debug configuration. return OPTIMIZED_LibraryType; } + +// Parse LINK_LIBRARY genex markers. +cm::optional<std::string> ParseLinkFeature(std::string const& item); diff --git a/Source/cmLinkItemGraphVisitor.cxx b/Source/cmLinkItemGraphVisitor.cxx index 0ad846b..a63b794 100644 --- a/Source/cmLinkItemGraphVisitor.cxx +++ b/Source/cmLinkItemGraphVisitor.cxx @@ -82,7 +82,7 @@ bool cmLinkItemGraphVisitor::ItemVisited(cmLinkItem const& item) bool cmLinkItemGraphVisitor::LinkVisited(cmLinkItem const& depender, cmLinkItem const& dependee) { - auto const link = std::make_pair<>(depender.AsStr(), dependee.AsStr()); + auto const link = std::make_pair(depender.AsStr(), dependee.AsStr()); bool const linkVisited = this->VisitedLinks.find(link) != this->VisitedLinks.cend(); diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 168cd41..38c49ed 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -82,7 +82,6 @@ static auto ruleReplaceVars = { "CMAKE_${LANG}_COMPILER", "CMAKE_CURRENT_SOURCE_DIR", "CMAKE_CURRENT_BINARY_DIR", "CMAKE_RANLIB", - "CMAKE_LINKER", "CMAKE_MT", "CMAKE_TAPI", "CMAKE_CUDA_HOST_COMPILER", @@ -1367,7 +1366,7 @@ std::vector<BT<std::string>> cmLocalGenerator::GetStaticLibraryFlags( { const std::string configUpper = cmSystemTools::UpperCase(config); std::vector<BT<std::string>> flags; - if (linkLanguage != "Swift") { + if (linkLanguage != "Swift" && !this->IsSplitSwiftBuild()) { std::string staticLibFlags; this->AppendFlags( staticLibFlags, @@ -1604,6 +1603,7 @@ void cmLocalGenerator::GetTargetFlags( } std::string extraLinkFlags; + this->AppendLinkerTypeFlags(extraLinkFlags, target, config, linkLanguage); this->AppendPositionIndependentLinkerFlags(extraLinkFlags, target, config, linkLanguage); this->AppendIPOLinkerFlags(extraLinkFlags, target, config, linkLanguage); @@ -1651,6 +1651,39 @@ std::vector<BT<std::string>> cmLocalGenerator::GetTargetCompileFlags( if (lang == "Fortran") { this->AppendFlags(compileFlags, this->GetTargetFortranFlags(target, config)); + } else if (lang == "Swift") { + // Only set the compile mode if CMP0157 is set + if (cm::optional<cmSwiftCompileMode> swiftCompileMode = + this->GetSwiftCompileMode(target, config)) { + std::string swiftCompileModeFlag; + switch (*swiftCompileMode) { + case cmSwiftCompileMode::Incremental: { + swiftCompileModeFlag = "-incremental"; + if (cmValue flag = + mf->GetDefinition("CMAKE_Swift_COMPILE_OPTIONS_INCREMENTAL")) { + swiftCompileModeFlag = *flag; + } + break; + } + case cmSwiftCompileMode::Wholemodule: { + swiftCompileModeFlag = "-wmo"; + if (cmValue flag = + mf->GetDefinition("CMAKE_Swift_COMPILE_OPTIONS_WMO")) { + swiftCompileModeFlag = *flag; + } + break; + } + case cmSwiftCompileMode::Singlefile: + break; + case cmSwiftCompileMode::Unknown: { + this->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat("Unknown Swift_COMPILATION_MODE on target '", + target->GetName(), "'")); + } + } + this->AppendFlags(compileFlags, swiftCompileModeFlag); + } } this->AddCMP0018Flags(compileFlags, target, lang, config); @@ -2959,6 +2992,40 @@ cm::optional<std::string> cmLocalGenerator::GetMSVCDebugFormatName( return msvcDebugInformationFormat; } +cm::optional<cmSwiftCompileMode> cmLocalGenerator::GetSwiftCompileMode( + cmGeneratorTarget const* target, std::string const& config) +{ + cmMakefile const* mf = this->GetMakefile(); + cmValue const swiftCompileModeDefault = + mf->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT"); + if (!cmNonempty(swiftCompileModeDefault)) { + return {}; + } + cmValue swiftCompileMode = target->GetProperty("Swift_COMPILATION_MODE"); + if (!swiftCompileMode) { + swiftCompileMode = swiftCompileModeDefault; + } + + std::string const expandedCompileMode = + cmGeneratorExpression::Evaluate(*swiftCompileMode, this, config, target); + if (expandedCompileMode == "wholemodule") { + return cmSwiftCompileMode::Wholemodule; + } + if (expandedCompileMode == "singlefile") { + return cmSwiftCompileMode::Singlefile; + } + if (expandedCompileMode == "incremental") { + return cmSwiftCompileMode::Incremental; + } + return cmSwiftCompileMode::Unknown; +} + +bool cmLocalGenerator::IsSplitSwiftBuild() const +{ + return cmNonempty(this->GetMakefile()->GetDefinition( + "CMAKE_Swift_COMPILATION_MODE_DEFAULT")); +} + namespace { inline void RegisterUnitySources(cmGeneratorTarget* target, cmSourceFile* sf, @@ -3067,8 +3134,17 @@ cmLocalGenerator::AddUnityFilesModeAuto( chunk = std::min(itemsLeft, batchSize); - std::string filename = cmStrCat(filename_base, "unity_", batch, - (lang == "C") ? "_c.c" : "_cxx.cxx"); + std::string extension; + if (lang == "C") { + extension = "_c.c"; + } else if (lang == "CXX") { + extension = "_cxx.cxx"; + } else if (lang == "OBJC") { + extension = "_m.m"; + } else if (lang == "OBJCXX") { + extension = "_mm.mm"; + } + std::string filename = cmStrCat(filename_base, "unity_", batch, extension); auto const begin = filtered_sources.begin() + batch * batchSize; auto const end = begin + chunk; unity_files.emplace_back(this->WriteUnitySource( @@ -3159,7 +3235,7 @@ void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target) cmValue afterInclude = target->GetProperty("UNITY_BUILD_CODE_AFTER_INCLUDE"); cmValue unityMode = target->GetProperty("UNITY_BUILD_MODE"); - for (std::string lang : { "C", "CXX" }) { + for (std::string lang : { "C", "CXX", "OBJC", "OBJCXX" }) { std::vector<UnityBatchedSource> filtered_sources; std::copy_if(unitySources.begin(), unitySources.end(), std::back_inserter(filtered_sources), @@ -3205,6 +3281,49 @@ void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target) } } +void cmLocalGenerator::AppendLinkerTypeFlags(std::string& flags, + cmGeneratorTarget* target, + const std::string& config, + const std::string& linkLanguage) +{ + switch (target->GetType()) { + case cmStateEnums::EXECUTABLE: + case cmStateEnums::SHARED_LIBRARY: + case cmStateEnums::MODULE_LIBRARY: + break; + default: + return; + } + + auto usingLinker = + cmStrCat("CMAKE_", linkLanguage, "_USING_", + target->IsDeviceLink() ? "DEVICE_" : "", "LINKER_"); + + auto format = this->Makefile->GetDefinition(cmStrCat(usingLinker, "MODE")); + if (format && format != "FLAG"_s) { + return; + } + + auto linkerType = target->GetLinkerTypeProperty(linkLanguage, config); + if (linkerType.empty()) { + linkerType = "DEFAULT"; + } + usingLinker = cmStrCat(usingLinker, linkerType); + auto linkerTypeFlags = this->Makefile->GetDefinition(usingLinker); + if (linkerTypeFlags) { + if (!linkerTypeFlags.IsEmpty()) { + auto linkerFlags = cmExpandListWithBacktrace(linkerTypeFlags); + target->ResolveLinkerWrapper(linkerFlags, linkLanguage); + this->AppendFlags(flags, linkerFlags); + } + } else if (linkerType != "DEFAULT"_s) { + this->IssueMessage(MessageType::FATAL_ERROR, + cmStrCat("LINKER_TYPE '", linkerType, + "' is unknown. Did you forgot to define '", + usingLinker, "' variable?")); + } +} + void cmLocalGenerator::AppendIPOLinkerFlags(std::string& flags, cmGeneratorTarget* target, const std::string& config, diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index a920cfe..3ec349b 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -67,6 +67,15 @@ enum class cmBuildStep Link }; +/** What compilation mode the swift files are in */ +enum class cmSwiftCompileMode +{ + Wholemodule, + Incremental, + Singlefile, + Unknown, +}; + /** Target and source file which have a specific output. */ struct cmSourcesWithOutput { @@ -177,6 +186,9 @@ public: void AddPchDependencies(cmGeneratorTarget* target); void AddUnityBuild(cmGeneratorTarget* target); virtual void AddXCConfigSources(cmGeneratorTarget* /* target */) {} + void AppendLinkerTypeFlags(std::string& flags, cmGeneratorTarget* target, + const std::string& config, + const std::string& linkLanguage); void AppendIPOLinkerFlags(std::string& flags, cmGeneratorTarget* target, const std::string& config, const std::string& lang); @@ -546,6 +558,14 @@ public: const std::string& prop, const std::string& config); + // Return Swift_COMPILATION_MODE value if CMP0157 is NEW. + cm::optional<cmSwiftCompileMode> GetSwiftCompileMode( + cmGeneratorTarget const* target, std::string const& config); + + // Can we build Swift with a separate object build and link step + // (If CMP0157 is NEW, we can do a split build) + bool IsSplitSwiftBuild() const; + protected: // The default implementation converts to a Windows shortpath to // help older toolchains handle spaces and such. A generator may diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx index 7b02c56..d315f0f 100644 --- a/Source/cmLocalVisualStudio7Generator.cxx +++ b/Source/cmLocalVisualStudio7Generator.cxx @@ -32,6 +32,7 @@ #include "cmList.h" #include "cmListFileCache.h" #include "cmMakefile.h" +#include "cmMessageType.h" #include "cmOutputConverter.h" #include "cmPolicies.h" #include "cmSourceFile.h" @@ -794,6 +795,9 @@ void cmLocalVisualStudio7Generator::WriteConfiguration( target->GetType() == cmStateEnums::OBJECT_LIBRARY ? ".lib" : cmSystemTools::GetFilenameLastExtension(targetNameFull); + if (cm::optional<std::string> fortran = gg->GetPlatformToolsetFortran()) { + fout << "\t\t\tUseCompiler=\"" << *fortran << "Compiler\"\n"; + } /* clang-format off */ fout << "\t\t\tTargetName=\"" << this->EscapeForXML(targetName) << "\"\n" @@ -1085,6 +1089,16 @@ void cmLocalVisualStudio7Generator::OutputBuildTool( cmComputeLinkInformation& cli = *pcli; std::string linkLanguage = cli.GetLinkLanguage(); + if (!target->GetLinkerTypeProperty(linkLanguage, configName).empty()) { + // Visual Studio 10 or upper is required for this feature + this->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("'LINKER_TYPE' property, specified on target '", + target->GetName(), + "', is not supported by this generator."), + target->GetBacktrace()); + } + // Compute the variable name to lookup standard libraries for this // language. std::string standardLibsVar = @@ -1161,6 +1175,16 @@ void cmLocalVisualStudio7Generator::OutputBuildTool( cmComputeLinkInformation& cli = *pcli; std::string linkLanguage = cli.GetLinkLanguage(); + if (!target->GetLinkerTypeProperty(linkLanguage, configName).empty()) { + // Visual Studio 10 or upper is required for this feature + this->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("'LINKER_TYPE' property, specified on target '", + target->GetName(), + "', is not supported by this generator."), + target->GetBacktrace()); + } + bool isWin32Executable = target->IsWin32Executable(configName); // Compute the variable name to lookup standard libraries for this diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 93fb8b4..936b282 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -302,6 +302,11 @@ cmListFileBacktrace cmMakefile::GetBacktrace() const return this->Backtrace; } +cmFindPackageStack cmMakefile::GetFindPackageStack() const +{ + return this->FindPackageStack; +} + void cmMakefile::PrintCommandTrace(cmListFileFunction const& lff, cmListFileBacktrace const& bt, CommandMissingFromStack missing) const @@ -1215,8 +1220,8 @@ cmTarget* cmMakefile::AddCustomCommandToTarget( // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( std::move(cc), - [=](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, - std::unique_ptr<cmCustomCommand> tcc) { + [this, t, type](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, + std::unique_ptr<cmCustomCommand> tcc) { BacktraceGuard guard(this->Backtrace, lfbt); tcc->SetBacktrace(lfbt); detail::AddCustomCommandToTarget(lg, cmCommandOrigin::Project, t, type, @@ -1254,8 +1259,9 @@ void cmMakefile::AddCustomCommandToOutput( // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( std::move(cc), - [=](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, - std::unique_ptr<cmCustomCommand> tcc) { + [this, replace, callback](cmLocalGenerator& lg, + const cmListFileBacktrace& lfbt, + std::unique_ptr<cmCustomCommand> tcc) { BacktraceGuard guard(this->Backtrace, lfbt); tcc->SetBacktrace(lfbt); cmSourceFile* sf = detail::AddCustomCommandToOutput( @@ -1341,7 +1347,8 @@ void cmMakefile::AppendCustomCommandToOutput( if (this->ValidateCustomCommand(commandLines)) { // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( - [=](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt) { + [this, output, depends, implicit_depends, + commandLines](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt) { BacktraceGuard guard(this->Backtrace, lfbt); detail::AppendCustomCommandToOutput(lg, lfbt, output, depends, implicit_depends, commandLines); @@ -1372,8 +1379,8 @@ cmTarget* cmMakefile::AddUtilityCommand(const std::string& utilityName, // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( std::move(cc), - [=](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, - std::unique_ptr<cmCustomCommand> tcc) { + [this, target](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, + std::unique_ptr<cmCustomCommand> tcc) { BacktraceGuard guard(this->Backtrace, lfbt); tcc->SetBacktrace(lfbt); detail::AddUtilityCommand(lg, cmCommandOrigin::Project, target, @@ -4633,12 +4640,13 @@ bool cmMakefile::SetPolicy(cmPolicies::PolicyID id, } // Deprecate old policies. - if (status == cmPolicies::OLD && id <= cmPolicies::CMP0120 && + if (status == cmPolicies::OLD && id <= cmPolicies::CMP0126 && !(this->GetCMakeInstance()->GetIsInTryCompile() && ( // Policies set by cmCoreTryCompile::TryCompileCode. id == cmPolicies::CMP0065 || id == cmPolicies::CMP0083 || - id == cmPolicies::CMP0091 || id == cmPolicies::CMP0104)) && + id == cmPolicies::CMP0091 || id == cmPolicies::CMP0104 || + id == cmPolicies::CMP0123 || id == cmPolicies::CMP0126)) && (!this->IsSet("CMAKE_WARN_DEPRECATED") || this->IsOn("CMAKE_WARN_DEPRECATED"))) { this->IssueMessage(MessageType::DEPRECATION_WARNING, @@ -4771,6 +4779,36 @@ cmMakefile::MacroPushPop::~MacroPushPop() this->Makefile->PopMacroScope(this->ReportError); } +cmMakefile::FindPackageStackRAII::FindPackageStackRAII(cmMakefile* mf, + std::string const& name) + : Makefile(mf) +{ + this->Makefile->FindPackageStack = + this->Makefile->FindPackageStack.Push(cmFindPackageCall{ + name, + this->Makefile->FindPackageStackNextIndex, + }); + this->Makefile->FindPackageStackNextIndex++; +} + +cmMakefile::FindPackageStackRAII::~FindPackageStackRAII() +{ + this->Makefile->FindPackageStackNextIndex = + this->Makefile->FindPackageStack.Top().Index + 1; + this->Makefile->FindPackageStack = this->Makefile->FindPackageStack.Pop(); + + if (!this->Makefile->FindPackageStack.Empty()) { + auto top = this->Makefile->FindPackageStack.Top(); + this->Makefile->FindPackageStack = this->Makefile->FindPackageStack.Pop(); + + top.Index = this->Makefile->FindPackageStackNextIndex; + this->Makefile->FindPackageStackNextIndex++; + + this->Makefile->FindPackageStack = + this->Makefile->FindPackageStack.Push(top); + } +} + cmMakefile::DebugFindPkgRAII::DebugFindPkgRAII(cmMakefile* mf, std::string const& pkg) : Makefile(mf) diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 24daa72..e5edbae 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -25,6 +25,7 @@ #include "cmAlgorithms.h" #include "cmCustomCommand.h" +#include "cmFindPackageStack.h" #include "cmFunctionBlocker.h" #include "cmListFileCache.h" #include "cmMessageType.h" // IWYU pragma: keep @@ -660,6 +661,11 @@ public: cmListFileBacktrace GetBacktrace() const; /** + * Get the current stack of find_package calls. + */ + cmFindPackageStack GetFindPackageStack() const; + + /** * Get the vector of files created by this makefile */ const std::vector<std::string>& GetOutputFiles() const @@ -1020,6 +1026,15 @@ public: // searches std::deque<std::vector<std::string>> FindPackageRootPathStack; + class FindPackageStackRAII + { + cmMakefile* Makefile; + + public: + FindPackageStackRAII(cmMakefile* mf, std::string const& pkg); + ~FindPackageStackRAII(); + }; + class DebugFindPkgRAII { cmMakefile* Makefile; @@ -1210,6 +1225,9 @@ private: std::vector<BT<GeneratorAction>> GeneratorActions; bool GeneratorActionsInvoked = false; + cmFindPackageStack FindPackageStack; + unsigned int FindPackageStackNextIndex = 0; + bool DebugFindPkg = false; bool CheckSystemVars; diff --git a/Source/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx index 4a2b9e8..96a0d5c 100644 --- a/Source/cmMakefileExecutableTargetGenerator.cxx +++ b/Source/cmMakefileExecutableTargetGenerator.cxx @@ -344,6 +344,8 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink) return; } + auto linker = this->GeneratorTarget->GetLinkerTool(this->GetConfigName()); + // Build list of dependencies. std::vector<std::string> depends; this->AppendLinkDepends(depends, linkLanguage); @@ -533,6 +535,7 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink) vars.CMTargetType = cmState::GetTargetTypeName(this->GeneratorTarget->GetType()).c_str(); vars.Language = linkLanguage.c_str(); + vars.Linker = linker.c_str(); vars.AIXExports = aixExports.c_str(); vars.Objects = buildObjs.c_str(); std::string objectDir = this->GeneratorTarget->GetSupportDirectory(); diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx index fc3caa1..bc48a3b 100644 --- a/Source/cmMakefileLibraryTargetGenerator.cxx +++ b/Source/cmMakefileLibraryTargetGenerator.cxx @@ -218,6 +218,9 @@ void cmMakefileLibraryTargetGenerator::WriteModuleLibraryRules(bool relink) extraFlags, this->GeneratorTarget, linkLineComputer.get(), this->GetConfigName()); + this->UseLWYU = this->LocalGenerator->AppendLWYUFlags( + extraFlags, this->GeneratorTarget, linkLanguage); + this->WriteLibraryRules(linkRuleVar, extraFlags, relink); } @@ -441,6 +444,8 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules( return; } + auto linker = this->GeneratorTarget->GetLinkerTool(this->GetConfigName()); + // Build list of dependencies. std::vector<std::string> depends; this->AppendLinkDepends(depends, linkLanguage); @@ -766,6 +771,7 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules( vars.CMTargetType = cmState::GetTargetTypeName(this->GeneratorTarget->GetType()).c_str(); vars.Language = linkLanguage.c_str(); + vars.Linker = linker.c_str(); vars.AIXExports = aixExports.c_str(); vars.Objects = buildObjs.c_str(); std::string objectDir = this->GeneratorTarget->GetSupportDirectory(); diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 74b4b75..8fda774 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -153,6 +153,8 @@ void cmMakefileTargetGenerator::GetTargetLinkFlags( this->LocalGenerator->AppendCompileOptions(flags, opts); this->LocalGenerator->SetLinkScriptShell(false); + this->LocalGenerator->AppendLinkerTypeFlags( + flags, this->GeneratorTarget, this->GetConfigName(), linkLanguage); this->LocalGenerator->AppendPositionIndependentLinkerFlags( flags, this->GeneratorTarget, this->GetConfigName(), linkLanguage); this->LocalGenerator->AppendDependencyInfoLinkerFlags( diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 48c30b6..99ea009 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -294,6 +294,9 @@ void cmNinjaNormalTargetGenerator::WriteNvidiaDeviceLinkRule( .c_str(); vars.Language = "CUDA"; + std::string linker = + this->GetGeneratorTarget()->GetLinkerTool("CUDA", config); + vars.Linker = linker.c_str(); // build response file name std::string responseFlag = this->GetMakefile()->GetSafeDefinition( @@ -400,6 +403,9 @@ void cmNinjaNormalTargetGenerator::WriteDeviceLinkRules( vars.Fatbinary = "$FATBIN"; vars.RegisterFile = "$REGISTER"; vars.LinkFlags = "$LINK_FLAGS"; + std::string linker = + this->GetGeneratorTarget()->GetLinkerTool("CUDA", config); + vars.Linker = linker.c_str(); std::string flags = this->GetFlags("CUDA", config); vars.Flags = flags.c_str(); @@ -441,6 +447,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkRule(bool useResponseFile, vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str(); vars.CMTargetType = cmState::GetTargetTypeName(targetType).c_str(); + std::string linker = this->GetGeneratorTarget()->GetLinkerTool(config); + vars.Linker = linker.c_str(); std::string lang = this->TargetLinkLanguage(config); vars.Language = lang.c_str(); vars.AIXExports = "$AIX_EXPORTS"; @@ -1201,7 +1209,10 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement( globalGen->GetByproductsForCleanTarget(config).push_back(targetOutputReal); } - if (this->TargetLinkLanguage(config) == "Swift") { + // If we can't split the Swift build model (CMP0157 is OLD or unset), fall + // back on the old one-step "build/link" logic. + if (!this->GetLocalGenerator()->IsSplitSwiftBuild() && + this->TargetLinkLanguage(config) == "Swift") { vars["SWIFT_LIBRARY_NAME"] = [this, config]() -> std::string { cmGeneratorTarget::Names targetNames = this->GetGeneratorTarget()->GetLibraryNames(config); @@ -1214,12 +1225,12 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement( cmOutputConverter::SHELL); vars["SWIFT_SOURCES"] = [this, config]() -> std::string { - std::vector<cmSourceFile const*> sources; + std::vector<cmSourceFile const*> sourceFiles; std::stringstream oss; - this->GetGeneratorTarget()->GetObjectSources(sources, config); + this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config); cmLocalGenerator const* LocalGen = this->GetLocalGenerator(); - for (const auto& source : sources) { + for (const auto& source : sourceFiles) { const std::string sourcePath = source->GetLanguage() == "Swift" ? this->GetCompiledSourceNinjaPath(source) : this->GetObjectFilePath(source, config); @@ -1237,10 +1248,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement( vars["FLAGS"] = this->GetFlags("Swift", config); vars["INCLUDES"] = this->GetIncludes("Swift", config); this->GenerateSwiftOutputFileMap(config, vars["FLAGS"]); - } - // Compute specific libraries to link with. - if (this->TargetLinkLanguage(config) == "Swift") { + // Compute specific libraries to link with. std::vector<cmSourceFile const*> sources; gt->GetObjectSources(sources, config); for (const auto& source : sources) { diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index 1d90194..7ea479e 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -14,6 +14,7 @@ #include <utility> #include <cm/memory> +#include <cm/optional> #include <cm/string_view> #include <cmext/algorithm> #include <cmext/string_view> @@ -31,6 +32,7 @@ #include "cmGlobalCommonGenerator.h" #include "cmGlobalNinjaGenerator.h" #include "cmList.h" +#include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmLocalNinjaGenerator.h" #include "cmMakefile.h" @@ -47,6 +49,7 @@ #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" +#include "cmTargetDepend.h" #include "cmValue.h" #include "cmake.h" @@ -145,7 +148,7 @@ std::string cmNinjaTargetGenerator::LanguageScanRule( bool cmNinjaTargetGenerator::NeedExplicitPreprocessing( std::string const& lang) const { - return lang == "Fortran"; + return lang == "Fortran" || lang == "Swift"; } bool cmNinjaTargetGenerator::CompileWithDefines(std::string const& lang) const @@ -1136,9 +1139,19 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements( std::vector<cmSourceFile const*> objectSources; this->GeneratorTarget->GetObjectSources(objectSources, config); + std::vector<cmSourceFile const*> swiftSources; + for (cmSourceFile const* sf : objectSources) { - this->WriteObjectBuildStatement(sf, config, fileConfig, firstForConfig); + if (this->GetLocalGenerator()->IsSplitSwiftBuild() && + sf->GetLanguage() == "Swift") { + swiftSources.push_back(sf); + } else { + this->WriteObjectBuildStatement(sf, config, fileConfig, + firstForConfig); + } } + WriteSwiftObjectBuildStatement(swiftSources, config, fileConfig, + firstForConfig); } { @@ -1234,9 +1247,11 @@ void cmNinjaTargetGenerator::GenerateSwiftOutputFileMap( if (cmValue name = target->GetProperty("Swift_DEPENDENCIES_FILE")) { return *name; } - return this->ConvertToNinjaPath(cmStrCat(target->GetSupportDirectory(), - '/', config, '/', - target->GetName(), ".swiftdeps")); + return this->GetLocalGenerator()->ConvertToOutputFormat( + this->ConvertToNinjaPath(cmStrCat(target->GetSupportDirectory(), '/', + config, '/', target->GetName(), + ".swiftdeps")), + cmOutputConverter::SHELL); }(); std::string mapFilePath = @@ -1254,8 +1269,10 @@ void cmNinjaTargetGenerator::GenerateSwiftOutputFileMap( // Add flag this->LocalGenerator->AppendFlags(flags, "-output-file-map"); - this->LocalGenerator->AppendFlagEscape(flags, - ConvertToNinjaPath(mapFilePath)); + this->LocalGenerator->AppendFlagEscape( + flags, + this->GetLocalGenerator()->ConvertToOutputFormat( + ConvertToNinjaPath(mapFilePath), cmOutputConverter::SHELL)); } namespace { @@ -1433,10 +1450,13 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( } } + this->SetMsvcTargetPdbVariable(vars, config); + if (firstForConfig) { this->ExportObjectCompileCommand( language, sourceFilePath, objectDir, objectFileName, objectFileDir, - vars["FLAGS"], vars["DEFINES"], vars["INCLUDES"], config, withScanning); + vars["FLAGS"], vars["DEFINES"], vars["INCLUDES"], + vars["TARGET_COMPILE_PDB"], vars["TARGET_PDB"], config, withScanning); } objBuild.Outputs.push_back(objectFileName); @@ -1627,8 +1647,6 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( } } - this->SetMsvcTargetPdbVariable(vars, config); - objBuild.RspFile = cmStrCat(objectFileName, ".rsp"); if (language == "ISPC") { @@ -1779,10 +1797,13 @@ void cmNinjaTargetGenerator::WriteCxxModuleBmiBuildStatement( vars["CLANG_TIDY_EXPORT_FIXES"] = fixesFile; } + this->SetMsvcTargetPdbVariable(vars, config); + if (firstForConfig) { this->ExportObjectCompileCommand( language, sourceFilePath, bmiDir, bmiFileName, bmiFileDir, vars["FLAGS"], - vars["DEFINES"], vars["INCLUDES"], config, WithScanning::Yes); + vars["DEFINES"], vars["INCLUDES"], vars["TARGET_COMPILE_PDB"], + vars["TARGET_PDB"], config, WithScanning::Yes); } bmiBuild.Outputs.push_back(bmiFileName); @@ -1853,14 +1874,220 @@ void cmNinjaTargetGenerator::WriteCxxModuleBmiBuildStatement( this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(), vars); - this->SetMsvcTargetPdbVariable(vars, config); - bmiBuild.RspFile = cmStrCat(bmiFileName, ".rsp"); this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), bmiBuild, commandLineLengthLimit); } +void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( + std::vector<cmSourceFile const*> const& sources, std::string const& config, + std::string const& fileConfig, bool firstForConfig) +{ + // Swift sources are compiled as a module, not individually like with C/C++. + // Flags, header search paths, and definitions are passed to the entire + // module build, but we still need to emit compile-commands for each source + // file in order to support CMAKE_EXPORT_COMPILE_COMMANDS. + // In whole-module mode, with a single thread, the Swift compiler will + // only emit a single object file, but if more than one thread is specified, + // or building in other modes, the compiler will emit multiple object files. + // When building a single-output, we do not provide an output-file-map (OFM), + // and instead pass `-o` to tell the compiler where to write the object. + // When building multiple outputs, we provide an OFM to tell the compiler + // where to put each object. + // + // + // Per-Target (module): + // - Flags + // - Definitions + // - Include paths + // - (single-output) output object filename + // - Swiftmodule + // + // Per-File: + // - compile-command + // - (multi-output) OFM data + // - (multi-output) output object filename + // + // Note: Due to the differences in the build models, we are only able to + // build the object build-graph if we know what mode the target is built in. + // For that, we need the "NEW" behavior for CMP0157. Otherwise, we have to + // fall back on the old "linker" build. Otherwise, this should be + // indistinguishable from the old behavior. + // + // FIXME(#25490): Add response file support to Swift object build step + // FIXME(#25491): Include all files in module in compile_commands.json + + if (sources.empty()) { + return; + } + + cmSwiftCompileMode compileMode; + if (cm::optional<cmSwiftCompileMode> optionalCompileMode = + this->LocalGenerator->GetSwiftCompileMode(this->GeneratorTarget, + config)) { + compileMode = *optionalCompileMode; + } else { + // CMP0157 is not NEW, bailing early! + return; + } + + auto getTargetPropertyOrDefault = + [](cmGeneratorTarget const& target, std::string const& property, + std::string defaultValue) -> std::string { + if (cmValue value = target.GetProperty(property)) { + return *value; + } + return defaultValue; + }; + + std::string const language = "Swift"; + std::string const objectDir = this->ConvertToNinjaPath( + cmStrCat(this->GeneratorTarget->GetSupportDirectory(), + this->GetGlobalGenerator()->ConfigDirectory(config))); + + cmGeneratorTarget const& target = *this->GeneratorTarget; + cmNinjaBuild objBuild( + this->LanguageCompilerRule(language, config, WithScanning::No)); + cmNinjaVars& vars = objBuild.Variables; + + std::string const moduleName = + getTargetPropertyOrDefault(target, "Swift_MODULE_NAME", target.GetName()); + std::string const moduleDirectory = getTargetPropertyOrDefault( + target, "Swift_MODULE_DIRECTORY", + target.LocalGenerator->GetCurrentBinaryDirectory()); + std::string const moduleFilename = getTargetPropertyOrDefault( + target, "Swift_MODULE", cmStrCat(moduleName, ".swiftmodule")); + std::string const moduleFilepath = + this->ConvertToNinjaPath(cmStrCat(moduleDirectory, '/', moduleFilename)); + + bool const isSingleOutput = [this, compileMode]() -> bool { + bool isMultiThread = false; + if (cmValue numThreadStr = + this->GetMakefile()->GetDefinition("CMAKE_Swift_NUM_THREADS")) { + unsigned long numThreads; + cmStrToULong(*numThreadStr, &numThreads); + // numThreads == 1 is multi-threaded according to swiftc + isMultiThread = numThreads > 0; + } + return !isMultiThread && compileMode == cmSwiftCompileMode::Wholemodule; + }(); + + // Swift modules only make sense to emit from things that can be imported. + // Executables that don't export symbols can't be imported, so don't try to + // emit a swiftmodule for them. It will break. + if (target.GetType() != cmStateEnums::EXECUTABLE || + target.IsExecutableWithExports()) { + std::string const emitModuleFlag = "-emit-module"; + std::string const modulePathFlag = "-emit-module-path"; + this->LocalGenerator->AppendFlags( + vars["FLAGS"], { emitModuleFlag, modulePathFlag, moduleFilepath }); + objBuild.Outputs.push_back(moduleFilepath); + + std::string const moduleNameFlag = "-module-name"; + this->LocalGenerator->AppendFlags( + vars["FLAGS"], cmStrCat(moduleNameFlag, ' ', moduleName)); + } + + if (target.GetType() != cmStateEnums::EXECUTABLE) { + std::string const libraryLinkNameFlag = "-module-link-name"; + std::string const libraryLinkName = + this->GetGeneratorTarget()->GetLibraryNames(config).Base; + this->LocalGenerator->AppendFlags( + vars["FLAGS"], cmStrCat(libraryLinkNameFlag, ' ', libraryLinkName)); + } + + // Without `-emit-library` or `-emit-executable`, targets with a single + // source file parse as a Swift script instead of like normal source. For + // non-executable targets, append this to ensure that they are parsed like a + // normal source. + if (target.GetType() != cmStateEnums::EXECUTABLE) { + this->LocalGenerator->AppendFlags(vars["FLAGS"], "-parse-as-library"); + } + + this->LocalGenerator->AppendFlags(vars["FLAGS"], + this->GetFlags(language, config)); + vars["DEFINES"] = this->GetDefines(language, config); + vars["INCLUDES"] = this->GetIncludes(language, config); + + // target-level object filename + std::string const targetObjectFilename = this->ConvertToNinjaPath(cmStrCat( + objectDir, '/', moduleName, + this->GetGlobalGenerator()->GetLanguageOutputExtension(language))); + + if (isSingleOutput) { + this->LocalGenerator->AppendFlags(vars["FLAGS"], + cmStrCat("-o ", targetObjectFilename)); + objBuild.Outputs.push_back(targetObjectFilename); + this->Configs[config].Objects.push_back(targetObjectFilename); + } + + for (cmSourceFile const* sf : sources) { + // Add dependency to object build on each source file + std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(sf); + objBuild.ExplicitDeps.push_back(sourceFilePath); + + if (isSingleOutput) { + if (firstForConfig) { + this->ExportObjectCompileCommand( + language, sourceFilePath, objectDir, targetObjectFilename, + cmSystemTools::GetFilenamePath(targetObjectFilename), vars["FLAGS"], + vars["DEFINES"], vars["INCLUDES"], + /*compile pdb*/ "", /*target pdb*/ "", config, WithScanning::No); + } + } else { + // Object outputs + std::string const objectFilepath = + this->ConvertToNinjaPath(this->GetObjectFilePath(sf, config)); + this->EnsureParentDirectoryExists(objectFilepath); + objBuild.Outputs.push_back(objectFilepath); + this->Configs[config].Objects.push_back(objectFilepath); + + // Add OFM data + this->EmitSwiftDependencyInfo(sf, config); + + // Emit compile commands + if (firstForConfig) { + this->ExportObjectCompileCommand( + language, sourceFilePath, objectDir, objectFilepath, + cmSystemTools::GetFilenamePath(objectFilepath), vars["FLAGS"], + vars["DEFINES"], vars["INCLUDES"], + /*compile pdb*/ "", + /*target pdb*/ "", config, WithScanning::No); + } + } + } + + if (!isSingleOutput) { + this->GenerateSwiftOutputFileMap(config, vars["FLAGS"]); + } + + for (cmTargetDepend const& dep : + this->GetGlobalGenerator()->GetTargetDirectDepends(&target)) { + if (!dep->IsLanguageUsed("Swift", config)) { + continue; + } + // Add dependencies on the emitted swiftmodule file from swift targets we + // depend on + std::string const depModuleName = + getTargetPropertyOrDefault(*dep, "Swift_MODULE_NAME", dep->GetName()); + std::string const depModuleDir = getTargetPropertyOrDefault( + *dep, "Swift_MODULE_DIRECTORY", + dep->LocalGenerator->GetCurrentBinaryDirectory()); + std::string const depModuleFilename = getTargetPropertyOrDefault( + *dep, "Swift_MODULE", cmStrCat(depModuleName, ".swiftmodule")); + std::string const depModuleFilepath = + this->ConvertToNinjaPath(cmStrCat(depModuleDir, '/', depModuleFilename)); + objBuild.ImplicitDeps.push_back(depModuleFilepath); + } + + objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config)); + + // Write object build + this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), + objBuild); +} + void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang, const std::string& config) { @@ -1994,6 +2221,7 @@ void cmNinjaTargetGenerator::ExportObjectCompileCommand( std::string const& objectDir, std::string const& objectFileName, std::string const& objectFileDir, std::string const& flags, std::string const& defines, std::string const& includes, + std::string const& targetCompilePdb, std::string const& targetPdb, std::string const& outputConfig, WithScanning withScanning) { if (!this->GeneratorTarget->GetPropertyAsBool("EXPORT_COMPILE_COMMANDS")) { @@ -2042,6 +2270,8 @@ void cmNinjaTargetGenerator::ExportObjectCompileCommand( compileObjectVars.Flags = fullFlags.c_str(); compileObjectVars.Defines = defines.c_str(); compileObjectVars.Includes = includes.c_str(); + compileObjectVars.TargetCompilePDB = targetCompilePdb.c_str(); + compileObjectVars.TargetPDB = targetPdb.c_str(); // Rule for compiling object file. std::string cudaCompileMode; diff --git a/Source/cmNinjaTargetGenerator.h b/Source/cmNinjaTargetGenerator.h index a9bff1d..b55c460 100644 --- a/Source/cmNinjaTargetGenerator.h +++ b/Source/cmNinjaTargetGenerator.h @@ -171,6 +171,9 @@ protected: const std::string& config, const std::string& fileConfig, bool firstForConfig); + void WriteSwiftObjectBuildStatement( + std::vector<cmSourceFile const*> const& sources, const std::string& config, + const std::string& fileConfig, bool firstForConfig); void WriteObjectBuildStatement(cmSourceFile const* source, const std::string& config, const std::string& fileConfig, @@ -189,6 +192,7 @@ protected: std::string const& objectDir, std::string const& objectFileName, std::string const& objectFileDir, std::string const& flags, std::string const& defines, std::string const& includes, + std::string const& targetCompilePdb, std::string const& targetPdb, std::string const& outputConfig, WithScanning withScanning); void AdditionalCleanFiles(const std::string& config); diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index 8838de4..f038c6b 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -473,6 +473,17 @@ class cmMakefile; SELECT(POLICY, CMP0155, \ "C++ sources in targets with at least C++20 are scanned for " \ "imports when supported.", \ + 3, 28, 0, cmPolicies::WARN) \ + SELECT( \ + POLICY, CMP0156, \ + "De-duplicate libraries on link lines based on linker capabilities.", 3, \ + 29, 0, cmPolicies::WARN) \ + SELECT(POLICY, CMP0157, \ + "Swift compilation mode selected by an abstraction.", 3, 29, 0, \ + cmPolicies::WARN) \ + SELECT(POLICY, CMP0158, \ + "add_test() honors CMAKE_CROSSCOMPILING_EMULATOR only when " \ + "cross-compiling.", \ 3, 28, 0, cmPolicies::WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) @@ -513,7 +524,9 @@ class cmMakefile; F(CMP0131) \ F(CMP0142) \ F(CMP0154) \ - F(CMP0155) + F(CMP0155) \ + F(CMP0156) \ + F(CMP0157) #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F) \ F(CMP0116) \ diff --git a/Source/cmProjectCommand.cxx b/Source/cmProjectCommand.cxx index 3aef299..53166c1 100644 --- a/Source/cmProjectCommand.cxx +++ b/Source/cmProjectCommand.cxx @@ -11,6 +11,7 @@ #include "cmsys/RegularExpression.hxx" #include "cmExecutionStatus.h" +#include "cmList.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" @@ -371,29 +372,55 @@ static bool IncludeByVariable(cmExecutionStatus& status, if (!include) { return true; } + cmList includeFiles{ *include }; + + bool failed = false; + for (auto filePath : includeFiles) { + // Any relative path without a .cmake extension is checked for valid cmake + // modules. This logic should be consistent with CMake's include() command. + // Otherwise default to checking relative path w.r.t. source directory + if (!cmSystemTools::FileIsFullPath(filePath) && + !cmHasLiteralSuffix(filePath, ".cmake")) { + std::string mfile = mf.GetModulesFile(cmStrCat(filePath, ".cmake")); + if (mfile.empty()) { + status.SetError( + cmStrCat("could not find requested module:\n ", filePath)); + failed = true; + continue; + } + filePath = mfile; + } + std::string includeFile = cmSystemTools::CollapseFullPath( + filePath, mf.GetCurrentSourceDirectory()); + if (!cmSystemTools::FileExists(includeFile)) { + status.SetError( + cmStrCat("could not find requested file:\n ", filePath)); + failed = true; + continue; + } + if (cmSystemTools::FileIsDirectory(includeFile)) { + status.SetError( + cmStrCat("requested file is a directory:\n ", filePath)); + failed = true; + continue; + } - std::string includeFile = - cmSystemTools::CollapseFullPath(*include, mf.GetCurrentSourceDirectory()); - if (!cmSystemTools::FileExists(includeFile)) { - status.SetError(cmStrCat("could not find requested file:\n ", *include)); - return false; - } - if (cmSystemTools::FileIsDirectory(includeFile)) { - status.SetError(cmStrCat("requested file is a directory:\n ", *include)); - return false; - } + const bool readit = mf.ReadDependentFile(filePath); + if (readit) { + // If the included file ran successfully, continue to the next file + continue; + } - const bool readit = mf.ReadDependentFile(*include); - if (readit) { - return true; - } + if (cmSystemTools::GetFatalErrorOccurred()) { + failed = true; + continue; + } - if (cmSystemTools::GetFatalErrorOccurred()) { - return true; + status.SetError(cmStrCat("could not load requested file:\n ", filePath)); + failed = true; } - - status.SetError(cmStrCat("could not load requested file:\n ", *include)); - return false; + // At this point all files were processed + return !failed; } static void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name, diff --git a/Source/cmQtAutoGen.cxx b/Source/cmQtAutoGen.cxx index adbdba8..0a394b5 100644 --- a/Source/cmQtAutoGen.cxx +++ b/Source/cmQtAutoGen.cxx @@ -76,13 +76,6 @@ static void MergeOptions(std::vector<std::string>& baseOpts, unsigned int const cmQtAutoGen::ParallelMax = 64; -#ifdef _WIN32 -// Actually 32767 (see -// https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553) but we -// allow for a small margin -size_t const cmQtAutoGen::CommandLineLengthMax = 32000; -#endif - cm::string_view cmQtAutoGen::GeneratorName(GenT genType) { switch (genType) { diff --git a/Source/cmQtAutoGen.h b/Source/cmQtAutoGen.h index d111422..5a23ae9 100644 --- a/Source/cmQtAutoGen.h +++ b/Source/cmQtAutoGen.h @@ -64,11 +64,6 @@ public: /// @brief Maximum number of parallel threads/processes in a generator static unsigned int const ParallelMax; -#ifdef _WIN32 - /// @brief Maximum number of characters on command line - static size_t const CommandLineLengthMax; -#endif - /// @brief Returns the generator name static cm::string_view GeneratorName(GenT genType); /// @brief Returns the generator name in upper case diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx index 50b0ebe..8213274 100644 --- a/Source/cmQtAutoGenInitializer.cxx +++ b/Source/cmQtAutoGenInitializer.cxx @@ -485,6 +485,38 @@ bool cmQtAutoGenInitializer::InitCustomTargets() } } +#ifdef _WIN32 + { + const auto& value = + this->GenTarget->GetProperty("AUTOGEN_COMMAND_LINE_LENGTH_MAX"); + if (value.IsSet()) { + using maxCommandLineLengthType = + decltype(this->AutogenTarget.MaxCommandLineLength); + unsigned long propInt = 0; + if (cmStrToULong(value, &propInt) && propInt > 0 && + propInt <= std::numeric_limits<maxCommandLineLengthType>::max()) { + this->AutogenTarget.MaxCommandLineLength = + static_cast<maxCommandLineLengthType>(propInt); + } else { + // Warn the project author that AUTOGEN_PARALLEL is not valid. + this->Makefile->IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat("AUTOGEN_COMMAND_LINE_LENGTH_MAX=\"", *value, + "\" for target \"", this->GenTarget->GetName(), + "\" is not valid. Using no limit for " + "AUTOGEN_COMMAND_LINE_LENGTH_MAX")); + this->AutogenTarget.MaxCommandLineLength = + std::numeric_limits<maxCommandLineLengthType>::max(); + } + } else { + // Actually 32767 (see + // https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553) but + // we allow for a small margin + this->AutogenTarget.MaxCommandLineLength = 32000; + } + } +#endif + // Autogen target info and settings files { // Info file @@ -1692,6 +1724,10 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo() // General info.SetBool("MULTI_CONFIG", this->MultiConfig); info.SetUInt("PARALLEL", this->AutogenTarget.Parallel); +#ifdef _WIN32 + info.SetUInt("AUTOGEN_COMMAND_LINE_LENGTH_MAX", + this->AutogenTarget.MaxCommandLineLength); +#endif info.SetUInt("VERBOSITY", this->Verbosity); // Directories diff --git a/Source/cmQtAutoGenInitializer.h b/Source/cmQtAutoGenInitializer.h index a44d33f..3f7ab9f 100644 --- a/Source/cmQtAutoGenInitializer.h +++ b/Source/cmQtAutoGenInitializer.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep #include <cstddef> +#include <limits> #include <memory> #include <set> #include <string> @@ -194,6 +195,8 @@ private: bool GlobalTarget = false; // Settings unsigned int Parallel = 1; + unsigned int MaxCommandLineLength = + std::numeric_limits<unsigned int>::max(); // Configuration files std::string InfoFile; ConfigString SettingsFile; diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx index ece657d..a49125e 100644 --- a/Source/cmQtAutoMocUic.cxx +++ b/Source/cmQtAutoMocUic.cxx @@ -5,6 +5,7 @@ #include <algorithm> #include <atomic> #include <cstddef> +#include <limits> #include <map> #include <mutex> #include <set> @@ -172,6 +173,8 @@ public: bool MultiConfig = false; IntegerVersion QtVersion = { 4, 0 }; unsigned int ThreadCount = 0; + unsigned int MaxCommandLineLength = + std::numeric_limits<unsigned int>::max(); // - Directories std::string AutogenBuildDir; std::string AutogenIncludeDir; @@ -190,7 +193,7 @@ public: { public: // -- Parse Cache - std::atomic<bool> ParseCacheChanged = ATOMIC_VAR_INIT(false); + std::atomic<bool> ParseCacheChanged{ false }; cmFileTime ParseCacheTime; ParseCacheT ParseCache; @@ -333,6 +336,13 @@ public: std::vector<std::string> const& command, std::string const& output) const; + /* + * Check if command line exceeds maximum length supported by OS + * (if on Windows) and switch to using a response file instead. + */ + void MaybeWriteResponseFile(std::string const& outputFile, + std::vector<std::string>& cmd) const; + /** @brief Run an external process. Use only during Process() call! */ bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result, std::vector<std::string> const& command, @@ -498,10 +508,6 @@ public: protected: ParseCacheT::FileHandleT CacheEntry; - - private: - void MaybeWriteMocResponseFile(std::string const& outputFile, - std::vector<std::string>& cmd) const; }; /** uic compiles a file. */ @@ -583,7 +589,7 @@ private: std::string SettingsStringMoc_; std::string SettingsStringUic_; // -- Worker thread pool - std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false); + std::atomic<bool> JobError_{ false }; cmWorkerPool WorkerPool_; // -- Concurrent processing mutable std::mutex CMakeLibMutex_; @@ -795,6 +801,51 @@ void cmQtAutoMocUicT::JobT::LogCommandError( this->Gen()->Log().ErrorCommand(genType, message, command, output); } +/* + * Check if command line exceeds maximum length supported by OS + * (if on Windows) and switch to using a response file instead. + */ +void cmQtAutoMocUicT::JobT::MaybeWriteResponseFile( + std::string const& outputFile, std::vector<std::string>& cmd) const +{ +#ifdef _WIN32 + // Ensure cmd is less than CommandLineLengthMax characters + size_t commandLineLength = cmd.size(); // account for separating spaces + for (std::string const& str : cmd) { + commandLineLength += str.length(); + } + if (commandLineLength >= this->BaseConst().MaxCommandLineLength) { + // Command line exceeds maximum size allowed by OS + // => create response file + std::string const responseFile = cmStrCat(outputFile, ".rsp"); + + cmsys::ofstream fout(responseFile.c_str()); + if (!fout) { + this->LogError( + GenT::MOC, + cmStrCat("AUTOMOC was unable to create a response file at\n ", + this->MessagePath(responseFile))); + return; + } + + auto it = cmd.begin(); + while (++it != cmd.end()) { + fout << *it << "\n"; + } + fout.close(); + + // Keep all but executable + cmd.resize(1); + + // Specify response file + cmd.emplace_back(cmStrCat('@', responseFile)); + } +#else + static_cast<void>(outputFile); + static_cast<void>(cmd); +#endif +} + bool cmQtAutoMocUicT::JobT::RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result, std::vector<std::string> const& command, @@ -836,6 +887,8 @@ void cmQtAutoMocUicT::JobMocPredefsT::Process() cm::append(cmd, this->MocConst().OptionsDefinitions); // Add includes cm::append(cmd, this->MocConst().OptionsIncludes); + // Check if response file is necessary + MaybeWriteResponseFile(this->MocConst().PredefsFileAbs, cmd); // Execute command if (!this->RunProcess(GenT::MOC, result, cmd, reason.get())) { this->LogCommandError(GenT::MOC, @@ -2034,7 +2087,7 @@ void cmQtAutoMocUicT::JobCompileMocT::Process() // Add source file cmd.push_back(sourceFile); - MaybeWriteMocResponseFile(outputFile, cmd); + MaybeWriteResponseFile(outputFile, cmd); } // Execute moc command @@ -2080,51 +2133,6 @@ void cmQtAutoMocUicT::JobCompileMocT::Process() } } -/* - * Check if command line exceeds maximum length supported by OS - * (if on Windows) and switch to using a response file instead. - */ -void cmQtAutoMocUicT::JobCompileMocT::MaybeWriteMocResponseFile( - std::string const& outputFile, std::vector<std::string>& cmd) const -{ -#ifdef _WIN32 - // Ensure cmd is less than CommandLineLengthMax characters - size_t commandLineLength = cmd.size(); // account for separating spaces - for (std::string const& str : cmd) { - commandLineLength += str.length(); - } - if (commandLineLength >= CommandLineLengthMax) { - // Command line exceeds maximum size allowed by OS - // => create response file - std::string const responseFile = cmStrCat(outputFile, ".rsp"); - - cmsys::ofstream fout(responseFile.c_str()); - if (!fout) { - this->LogError( - GenT::MOC, - cmStrCat("AUTOMOC was unable to create a response file at\n ", - this->MessagePath(responseFile))); - return; - } - - auto it = cmd.begin(); - while (++it != cmd.end()) { - fout << *it << "\n"; - } - fout.close(); - - // Keep all but executable - cmd.resize(1); - - // Specify response file - cmd.emplace_back(cmStrCat('@', responseFile)); - } -#else - static_cast<void>(outputFile); - static_cast<void>(cmd); -#endif -} - void cmQtAutoMocUicT::JobCompileUicT::Process() { std::string const& sourceFile = this->Mapping->SourceFile->FileName; @@ -2377,6 +2385,10 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info) !info.GetUInt("QT_VERSION_MINOR", this->BaseConst_.QtVersion.Minor, true) || !info.GetUInt("PARALLEL", this->BaseConst_.ThreadCount, false) || +#ifdef _WIN32 + !info.GetUInt("AUTOGEN_COMMAND_LINE_LENGTH_MAX", + this->BaseConst_.MaxCommandLineLength, false) || +#endif !info.GetString("BUILD_DIR", this->BaseConst_.AutogenBuildDir, true) || !info.GetStringConfig("INCLUDE_DIR", this->BaseConst_.AutogenIncludeDir, true) || diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx index 638bb42..a8c81d0 100644 --- a/Source/cmRulePlaceholderExpander.cxx +++ b/Source/cmRulePlaceholderExpander.cxx @@ -27,6 +27,19 @@ std::string cmRulePlaceholderExpander::ExpandVariable( return this->ReplaceValues->LinkFlags; } } + if (this->ReplaceValues->Linker) { + if (variable == "CMAKE_LINKER") { + auto result = this->OutputConverter->ConvertToOutputForExisting( + this->ReplaceValues->Linker); + if (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. + result = cmStrCat(this->ReplaceValues->Launcher, " ", result); + } + return result; + } + } if (this->ReplaceValues->Manifests) { if (variable == "MANIFESTS") { return this->ReplaceValues->Manifests; @@ -325,17 +338,7 @@ std::string cmRulePlaceholderExpander::ExpandVariable( auto mapIt = this->VariableMappings.find(variable); if (mapIt != this->VariableMappings.end()) { if (variable.find("_FLAG") == std::string::npos) { - std::string ret = - this->OutputConverter->ConvertToOutputForExisting(mapIt->second); - - 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(this->ReplaceValues->Launcher, " ", ret); - } - - return ret; + return this->OutputConverter->ConvertToOutputForExisting(mapIt->second); } return mapIt->second; } diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h index 5d1f199..225abd4 100644 --- a/Source/cmRulePlaceholderExpander.h +++ b/Source/cmRulePlaceholderExpander.h @@ -53,6 +53,7 @@ public: const char* SONameFlag = nullptr; const char* TargetSOName = nullptr; const char* TargetInstallNameDir = nullptr; + const char* Linker = nullptr; const char* LinkFlags = nullptr; const char* Manifests = nullptr; const char* LanguageCompileFlags = nullptr; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index abbf29e..832bf57 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -20,6 +20,7 @@ #include "cmAlgorithms.h" #include "cmCustomCommand.h" #include "cmFileSet.h" +#include "cmFindPackageStack.h" #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" @@ -438,6 +439,7 @@ TargetProperty const StaticTargetProperties[] = { // ---- Swift { "Swift_LANGUAGE_VERSION"_s, IC::CanCompileSources }, { "Swift_MODULE_DIRECTORY"_s, IC::CanCompileSources }, + { "Swift_COMPILATION_MODE"_s, IC::CanCompileSources }, // ---- moc { "AUTOMOC"_s, IC::CanCompileSources }, { "AUTOMOC_COMPILER_PREDEFINES"_s, IC::CanCompileSources }, @@ -456,6 +458,7 @@ TargetProperty const StaticTargetProperties[] = { { "AUTORCC_EXECUTABLE"_s, IC::CanCompileSources }, // Linking properties + { "LINKER_TYPE"_s, IC::CanCompileSources }, { "ENABLE_EXPORTS"_s, IC::TargetWithSymbolExports }, { "LINK_LIBRARIES_ONLY_TARGETS"_s, IC::NormalNonImportedTarget }, { "LINK_SEARCH_START_STATIC"_s, IC::CanCompileSources }, @@ -549,6 +552,7 @@ TargetProperty const StaticTargetProperties[] = { { "ANDROID_PROCESS_MAX"_s, IC::CanCompileSources }, { "ANDROID_SKIP_ANT_STEP"_s, IC::CanCompileSources }, // -- Autogen + { "AUTOGEN_COMMAND_LINE_LENGTH_MAX"_s, IC::CanCompileSources }, { "AUTOGEN_ORIGIN_DEPENDS"_s, IC::CanCompileSources }, { "AUTOGEN_PARALLEL"_s, IC::CanCompileSources }, { "AUTOGEN_USE_SYSTEM_INCLUDE"_s, IC::CanCompileSources }, @@ -584,11 +588,13 @@ TargetProperty const StaticTargetProperties[] = { // Usage requirement properties { "LINK_INTERFACE_LIBRARIES"_s, IC::CanCompileSources }, { "MAP_IMPORTED_CONFIG_"_s, IC::NormalTarget, R::PerConfig }, + { "EXPORT_FIND_PACKAGE_NAME"_s, IC::NormalTarget }, // Metadata { "CROSSCOMPILING_EMULATOR"_s, IC::ExecutableTarget }, { "EXPORT_COMPILE_COMMANDS"_s, IC::CanCompileSources }, { "FOLDER"_s }, + { "TEST_LAUNCHER"_s, IC::ExecutableTarget }, // Xcode properties { "XCODE_GENERATE_SCHEME"_s, IC::NeedsXcode }, @@ -660,6 +666,7 @@ public: TLLCommands; std::map<std::string, cmFileSet> FileSets; cmListFileBacktrace Backtrace; + cmFindPackageStack FindPackageStack; UsageRequirementProperty IncludeDirectories; UsageRequirementProperty CompileOptions; @@ -960,6 +967,9 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type, // Save the backtrace of target construction. this->impl->Backtrace = this->impl->Makefile->GetBacktrace(); + if (this->impl->IsImported()) { + this->impl->FindPackageStack = this->impl->Makefile->GetFindPackageStack(); + } if (this->IsNormal()) { // Initialize the INCLUDE_DIRECTORIES property based on the current value @@ -1247,6 +1257,11 @@ cmListFileBacktrace const& cmTarget::GetBacktrace() const return this->impl->Backtrace; } +cmFindPackageStack const& cmTarget::GetFindPackageStack() const +{ + return this->impl->FindPackageStack; +} + bool cmTarget::IsExecutableWithExports() const { return (this->GetType() == cmStateEnums::EXECUTABLE && diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 584856a..385dfe7 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -24,6 +24,7 @@ class cmCustomCommand; class cmFileSet; +class cmFindPackageStack; class cmGlobalGenerator; class cmInstallTargetGenerator; class cmMakefile; @@ -239,6 +240,9 @@ public: //! Get a backtrace from the creation of the target. cmListFileBacktrace const& GetBacktrace() const; + //! Get a find_package call stack from the creation of the target. + cmFindPackageStack const& GetFindPackageStack() const; + void InsertInclude(BT<std::string> const& entry, bool before = false); void InsertCompileOption(BT<std::string> const& entry, bool before = false); void InsertCompileDefinition(BT<std::string> const& entry); diff --git a/Source/cmTargetExport.h b/Source/cmTargetExport.h index 1cef888..caeb54d 100644 --- a/Source/cmTargetExport.h +++ b/Source/cmTargetExport.h @@ -4,6 +4,7 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include <map> #include <string> class cmFileSet; @@ -37,4 +38,5 @@ public: ///@} bool NamelinkOnly = false; + std::string XcFrameworkLocation; }; diff --git a/Source/cmTest.cxx b/Source/cmTest.cxx index b0d9c2d..7c9969c 100644 --- a/Source/cmTest.cxx +++ b/Source/cmTest.cxx @@ -9,6 +9,7 @@ cmTest::cmTest(cmMakefile* mf) : Backtrace(mf->GetBacktrace()) + , PolicyStatusCMP0158(mf->GetPolicyStatus(cmPolicies::CMP0158)) { this->Makefile = mf; this->OldStyle = true; diff --git a/Source/cmTest.h b/Source/cmTest.h index 8b50b87..480966a 100644 --- a/Source/cmTest.h +++ b/Source/cmTest.h @@ -9,6 +9,7 @@ #include <vector> #include "cmListFileCache.h" +#include "cmPolicies.h" #include "cmPropertyMap.h" #include "cmValue.h" @@ -60,6 +61,12 @@ public: bool GetOldStyle() const { return this->OldStyle; } void SetOldStyle(bool b) { this->OldStyle = b; } + /** Get/Set if CMP0158 policy is NEW */ + bool GetCMP0158IsNew() const + { + return this->PolicyStatusCMP0158 == cmPolicies::NEW; + } + /** Set/Get whether lists in command lines should be expanded. */ bool GetCommandExpandLists() const; void SetCommandExpandLists(bool b); @@ -74,4 +81,5 @@ private: cmMakefile* Makefile; cmListFileBacktrace Backtrace; + cmPolicies::PolicyStatus PolicyStatusCMP0158; }; diff --git a/Source/cmTestGenerator.cxx b/Source/cmTestGenerator.cxx index ca1226a..3194d8f 100644 --- a/Source/cmTestGenerator.cxx +++ b/Source/cmTestGenerator.cxx @@ -167,10 +167,24 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os, if (target && target->GetType() == cmStateEnums::EXECUTABLE) { // Use the target file on disk. exe = target->GetFullPath(config); + auto useEmulator = !this->GetTest()->GetCMP0158IsNew() || + this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING"); + + // Prepend with the test launcher if specified. + cmValue launcher = target->GetProperty("TEST_LAUNCHER"); + if (cmNonempty(launcher)) { + cmList launcherWithArgs{ *launcher }; + std::string launcherExe(launcherWithArgs[0]); + cmSystemTools::ConvertToUnixSlashes(launcherExe); + os << cmOutputConverter::EscapeForCMake(launcherExe) << " "; + for (std::string const& arg : cmMakeRange(launcherWithArgs).advance(1)) { + os << cmOutputConverter::EscapeForCMake(arg) << " "; + } + } // Prepend with the emulator when cross compiling if required. cmValue emulator = target->GetProperty("CROSSCOMPILING_EMULATOR"); - if (cmNonempty(emulator)) { + if (cmNonempty(emulator) && useEmulator) { cmList emulatorWithArgs{ *emulator }; std::string emulatorExe(emulatorWithArgs[0]); cmSystemTools::ConvertToUnixSlashes(emulatorExe); diff --git a/Source/cmTransformDepfile.cxx b/Source/cmTransformDepfile.cxx index 914172b..ffc4de9 100644 --- a/Source/cmTransformDepfile.cxx +++ b/Source/cmTransformDepfile.cxx @@ -16,6 +16,9 @@ #include "cmGccDepfileReaderTypes.h" #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmStringAlgorithms.h" #include "cmSystemTools.h" namespace { @@ -121,6 +124,10 @@ bool cmTransformDepfile(cmDepfileFormat format, const cmLocalGenerator& lg, return false; } content = *std::move(result); + } else { + lg.GetMakefile()->IssueMessage( + MessageType::WARNING, + cmStrCat("Expected depfile does not exist.\n ", infile)); } cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(outfile)); diff --git a/Source/cmUVHandlePtr.cxx b/Source/cmUVHandlePtr.cxx index 34e6a70..168f1a6 100644 --- a/Source/cmUVHandlePtr.cxx +++ b/Source/cmUVHandlePtr.cxx @@ -6,6 +6,9 @@ #include <cassert> #include <cstdlib> #include <mutex> +#include <utility> + +#include <cm/memory> #include <cm3p/uv.h> @@ -44,7 +47,7 @@ void uv_loop_ptr::reset() this->loop.reset(); } -uv_loop_ptr::operator uv_loop_t*() +uv_loop_ptr::operator uv_loop_t*() const { return this->loop.get(); } @@ -54,6 +57,11 @@ uv_loop_t* uv_loop_ptr::operator->() const noexcept return this->loop.get(); } +uv_loop_t& uv_loop_ptr::operator*() const +{ + return *this->loop; +} + uv_loop_t* uv_loop_ptr::get() const { return this->loop.get(); @@ -97,13 +105,19 @@ void uv_handle_ptr_base_<T>::allocate(void* data) } template <typename T> +uv_handle_ptr_base_<T>::operator bool() const +{ + return this->handle.get(); +} + +template <typename T> void uv_handle_ptr_base_<T>::reset() { this->handle.reset(); } template <typename T> -uv_handle_ptr_base_<T>::operator uv_handle_t*() +uv_handle_ptr_base_<T>::operator uv_handle_t*() const { return reinterpret_cast<uv_handle_t*>(this->handle.get()); } @@ -235,6 +249,12 @@ int uv_timer_ptr::start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat) return uv_timer_start(*this, cb, timeout, repeat); } +void uv_timer_ptr::stop() +{ + assert(this->handle); + uv_timer_stop(*this); +} + #ifndef CMAKE_BOOTSTRAP uv_tty_ptr::operator uv_stream_t*() const { @@ -248,12 +268,32 @@ int uv_tty_ptr::init(uv_loop_t& loop, int fd, int readable, void* data) } #endif +int uv_idle_ptr::init(uv_loop_t& loop, void* data) +{ + this->allocate(data); + return uv_idle_init(&loop, *this); +} + +int uv_idle_ptr::start(uv_idle_cb cb) +{ + assert(this->handle); + return uv_idle_start(*this, cb); +} + +void uv_idle_ptr::stop() +{ + assert(this->handle); + uv_idle_stop(*this); +} + template class uv_handle_ptr_base_<uv_handle_t>; #define UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(NAME) \ template class uv_handle_ptr_base_<uv_##NAME##_t>; \ template class uv_handle_ptr_<uv_##NAME##_t>; +UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(idle) + UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(signal) UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(pipe) @@ -269,4 +309,38 @@ UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async) UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(tty) #endif + +namespace { +struct write_req : public uv_write_t +{ + std::weak_ptr<std::function<void(int)>> cb_; + write_req(std::weak_ptr<std::function<void(int)>> wcb) + : cb_(std::move(wcb)) + { + } +}; + +void write_req_cb(uv_write_t* req, int status) +{ + // Ownership has been transferred from the event loop. + std::unique_ptr<write_req> self(static_cast<write_req*>(req)); + + // Notify the original uv_write caller if it is still interested. + if (auto cb = self->cb_.lock()) { + (*cb)(status); + } +} +} + +int uv_write(uv_stream_t* handle, const uv_buf_t bufs[], unsigned int nbufs, + std::weak_ptr<std::function<void(int)>> cb) +{ + auto req = cm::make_unique<write_req>(std::move(cb)); + int status = uv_write(req.get(), handle, bufs, nbufs, write_req_cb); + if (status == 0) { + // Ownership has been transferred to the event loop. + static_cast<void>(req.release()); + } + return status; +} } diff --git a/Source/cmUVHandlePtr.h b/Source/cmUVHandlePtr.h index 027d690..b8b3491 100644 --- a/Source/cmUVHandlePtr.h +++ b/Source/cmUVHandlePtr.h @@ -5,6 +5,7 @@ #include <cstddef> #include <cstdint> +#include <functional> #include <memory> #include <type_traits> @@ -61,10 +62,11 @@ public: * Allow less verbose calling of uv_loop_* functions * @return reinterpreted handle */ - operator uv_loop_t*(); + operator uv_loop_t*() const; uv_loop_t* get() const; uv_loop_t* operator->() const noexcept; + uv_loop_t& operator*() const; }; /*** @@ -130,6 +132,16 @@ public: uv_handle_ptr_base_(std::nullptr_t) {} ~uv_handle_ptr_base_() { this->reset(); } +#if defined(__SUNPRO_CC) + // The Oracle Studio compiler recognizes 'explicit operator bool()' in + // 'if(foo)' but not 'if(foo && ...)'. The purpose of 'explicit' here + // is to avoid accidental conversion in non-boolean contexts. Just + // leave it out on this compiler so we can compile valid code. + operator bool() const; +#else + explicit operator bool() const; +#endif + /** * Properly close the handle if needed and sets the inner handle to nullptr */ @@ -139,7 +151,7 @@ public: * Allow less verbose calling of uv_handle_* functions * @return reinterpreted handle */ - operator uv_handle_t*(); + operator uv_handle_t*() const; T* get() const; T* operator->() const noexcept; @@ -194,6 +206,17 @@ public: void send(); }; +struct uv_idle_ptr : public uv_handle_ptr_<uv_idle_t> +{ + CM_INHERIT_CTOR(uv_idle_ptr, uv_handle_ptr_, <uv_idle_t>); + + int init(uv_loop_t& loop, void* data = nullptr); + + int start(uv_idle_cb cb); + + void stop(); +}; + struct uv_signal_ptr : public uv_handle_ptr_<uv_signal_t> { CM_INHERIT_CTOR(uv_signal_ptr, uv_handle_ptr_, <uv_signal_t>); @@ -229,6 +252,8 @@ struct uv_timer_ptr : public uv_handle_ptr_<uv_timer_t> int init(uv_loop_t& loop, void* data = nullptr); int start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat); + + void stop(); }; struct uv_tty_ptr : public uv_handle_ptr_<uv_tty_t> @@ -253,6 +278,8 @@ extern template class uv_handle_ptr_base_<uv_handle_t>; UV_HANDLE_PTR_INSTANTIATE_EXTERN(async) +UV_HANDLE_PTR_INSTANTIATE_EXTERN(idle) + UV_HANDLE_PTR_INSTANTIATE_EXTERN(signal) UV_HANDLE_PTR_INSTANTIATE_EXTERN(pipe) @@ -268,4 +295,27 @@ UV_HANDLE_PTR_INSTANTIATE_EXTERN(tty) # undef UV_HANDLE_PTR_INSTANTIATE_EXTERN #endif + +/** + * Wraps uv_write to add synchronous cancellation. + * + * libuv provides no way to synchronously cancel a write request. + * Closing a write handle will cancel its pending write request, but its + * callback will still be called asynchronously later with UV_ECANCELED. + * + * This wrapper provides a solution by handing ownership of the uv_write_t + * request object to the event loop and taking it back in the callback. + * Use this in combination with uv_loop_ptr to ensure the event loop + * runs to completion and cleans up all resources. + * + * The caller may optionally provide a callback it owns with std::shared_ptr. + * If the caller's lifetime ends before the write request completes, the + * callback can be safely deleted and will not be called. + * + * The bufs array does not need to live beyond this call, but the memory + * referenced by the uv_buf_t values must remain alive until the callback + * is made or the stream is closed. + */ +int uv_write(uv_stream_t* handle, const uv_buf_t bufs[], unsigned int nbufs, + std::weak_ptr<std::function<void(int)>> cb); } diff --git a/Source/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx index 655e52a..4c72261 100644 --- a/Source/cmUVProcessChain.cxx +++ b/Source/cmUVProcessChain.cxx @@ -38,7 +38,8 @@ struct cmUVProcessChain::InternalData bool Valid = false; - cm::uv_loop_ptr Loop; + cm::uv_loop_ptr BuiltinLoop; + uv_loop_t* Loop; StreamData InputStreamData; StreamData OutputStreamData; @@ -74,6 +75,19 @@ cmUVProcessChainBuilder& cmUVProcessChainBuilder::AddCommand( return *this; } +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinLoop() +{ + this->Loop = nullptr; + return *this; +} + +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalLoop( + uv_loop_t& loop) +{ + this->Loop = &loop; + return *this; +} + cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetNoStream(Stream stdio) { switch (stdio) { @@ -135,6 +149,11 @@ cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetWorkingDirectory( return *this; } +uv_loop_t* cmUVProcessChainBuilder::GetLoop() const +{ + return this->Loop; +} + cmUVProcessChain cmUVProcessChainBuilder::Start() const { cmUVProcessChain chain; @@ -158,6 +177,13 @@ bool cmUVProcessChain::InternalData::Prepare( { this->Builder = builder; + if (this->Builder->Loop) { + this->Loop = this->Builder->Loop; + } else { + this->BuiltinLoop.init(); + this->Loop = this->BuiltinLoop; + } + auto const& input = this->Builder->Stdio[cmUVProcessChainBuilder::Stream_INPUT]; auto& inputData = this->InputStreamData; @@ -353,7 +379,6 @@ void cmUVProcessChain::InternalData::Finish() cmUVProcessChain::cmUVProcessChain() : Data(cm::make_unique<InternalData>()) { - this->Data->Loop.init(); } cmUVProcessChain::cmUVProcessChain(cmUVProcessChain&& other) noexcept diff --git a/Source/cmUVProcessChain.h b/Source/cmUVProcessChain.h index 0f37e7d..139588b 100644 --- a/Source/cmUVProcessChain.h +++ b/Source/cmUVProcessChain.h @@ -30,12 +30,16 @@ public: cmUVProcessChainBuilder& AddCommand( const std::vector<std::string>& arguments); + cmUVProcessChainBuilder& SetBuiltinLoop(); + cmUVProcessChainBuilder& SetExternalLoop(uv_loop_t& loop); cmUVProcessChainBuilder& SetNoStream(Stream stdio); cmUVProcessChainBuilder& SetBuiltinStream(Stream stdio); cmUVProcessChainBuilder& SetMergedBuiltinStreams(); cmUVProcessChainBuilder& SetExternalStream(Stream stdio, int fd); cmUVProcessChainBuilder& SetWorkingDirectory(std::string dir); + uv_loop_t* GetLoop() const; + cmUVProcessChain Start() const; private: @@ -63,6 +67,7 @@ private: std::vector<ProcessConfiguration> Processes; std::string WorkingDirectory; bool MergedBuiltinStreams = false; + uv_loop_t* Loop = nullptr; }; class cmUVProcessChain diff --git a/Source/cmUVSignalHackRAII.h b/Source/cmUVSignalHackRAII.h deleted file mode 100644 index 60e4ca8..0000000 --- a/Source/cmUVSignalHackRAII.h +++ /dev/null @@ -1,45 +0,0 @@ -/* 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 <cm3p/uv.h> - -#if defined(CMAKE_USE_SYSTEM_LIBUV) && !defined(_WIN32) && \ - UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR < 19 -# define CMAKE_UV_SIGNAL_HACK -# include "cmUVHandlePtr.h" -/* - libuv does not use SA_RESTART on its signal handler, but C++ streams - depend on it for reliable i/o operations. This RAII helper convinces - libuv to install its handler, and then revises the handler to add the - SA_RESTART flag. We use a distinct uv loop that never runs to avoid - ever really getting a callback. libuv may fill the hack loop's signal - pipe and then stop writing, but that won't break any real loops. - */ -class cmUVSignalHackRAII -{ - uv_loop_t HackLoop; - cm::uv_signal_ptr HackSignal; - static void HackCB(uv_signal_t*, int) {} - -public: - cmUVSignalHackRAII() - { - uv_loop_init(&this->HackLoop); - this->HackSignal.init(this->HackLoop); - this->HackSignal.start(HackCB, SIGCHLD); - struct sigaction hack_sa; - sigaction(SIGCHLD, nullptr, &hack_sa); - if (!(hack_sa.sa_flags & SA_RESTART)) { - hack_sa.sa_flags |= SA_RESTART; - sigaction(SIGCHLD, &hack_sa, nullptr); - } - } - ~cmUVSignalHackRAII() - { - this->HackSignal.stop(); - uv_loop_close(&this->HackLoop); - } -}; -#endif diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx index 4860d9a..1bbd934 100644 --- a/Source/cmVisualStudio10TargetGenerator.cxx +++ b/Source/cmVisualStudio10TargetGenerator.cxx @@ -2996,6 +2996,16 @@ void cmVisualStudio10TargetGenerator::WritePathAndIncrementalLinkOptions( e1.WritePlatformConfigTag( "IntDir", cond, R"($(Platform)\$(Configuration)\$(ProjectName)\)"); } else { + if (ttype == cmStateEnums::SHARED_LIBRARY || + ttype == cmStateEnums::MODULE_LIBRARY || + ttype == cmStateEnums::EXECUTABLE) { + auto linker = this->GeneratorTarget->GetLinkerTool(config); + if (!linker.empty()) { + ConvertToWindowsSlash(linker); + e1.WritePlatformConfigTag("LinkToolExe", cond, linker); + } + } + std::string intermediateDir = cmStrCat( this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), '/', config, '/'); diff --git a/Source/cmVisualStudioGeneratorOptions.cxx b/Source/cmVisualStudioGeneratorOptions.cxx index 6188134..9dd2e6c 100644 --- a/Source/cmVisualStudioGeneratorOptions.cxx +++ b/Source/cmVisualStudioGeneratorOptions.cxx @@ -137,14 +137,18 @@ bool cmVisualStudioGeneratorOptions::IsManaged() const bool cmVisualStudioGeneratorOptions::UsingUnicode() const { // Look for a _UNICODE definition. - return std::any_of(this->Defines.begin(), this->Defines.end(), - [](std::string const& di) { return di == "_UNICODE"_s; }); + return std::any_of( + this->Defines.begin(), this->Defines.end(), [](std::string const& di) { + return di == "_UNICODE"_s || cmHasLiteralPrefix(di, "_UNICODE="); + }); } bool cmVisualStudioGeneratorOptions::UsingSBCS() const { // Look for a _SBCS definition. - return std::any_of(this->Defines.begin(), this->Defines.end(), - [](std::string const& di) { return di == "_SBCS"_s; }); + return std::any_of( + this->Defines.begin(), this->Defines.end(), [](std::string const& di) { + return di == "_SBCS"_s || cmHasLiteralPrefix(di, "_SBCS="); + }); } void cmVisualStudioGeneratorOptions::FixCudaCodeGeneration() diff --git a/Source/cmWorkerPool.cxx b/Source/cmWorkerPool.cxx index 27cdbba..dd8f459 100644 --- a/Source/cmWorkerPool.cxx +++ b/Source/cmWorkerPool.cxx @@ -18,7 +18,6 @@ #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmUVHandlePtr.h" -#include "cmUVSignalHackRAII.h" // IWYU pragma: keep /** * @brief libuv pipe buffer class @@ -516,9 +515,6 @@ public: static void UVSlotEnd(uv_async_t* handle); // -- UV loop -#ifdef CMAKE_UV_SIGNAL_HACK - std::unique_ptr<cmUVSignalHackRAII> UVHackRAII; -#endif std::unique_ptr<uv_loop_t> UVLoop; cm::uv_async_ptr UVRequestBegin; cm::uv_async_ptr UVRequestEnd; @@ -563,9 +559,6 @@ cmWorkerPoolInternal::cmWorkerPoolInternal(cmWorkerPool* pool) { // Initialize libuv loop uv_disable_stdio_inheritance(); -#ifdef CMAKE_UV_SIGNAL_HACK - UVHackRAII = cm::make_unique<cmUVSignalHackRAII>(); -#endif this->UVLoop = cm::make_unique<uv_loop_t>(); uv_loop_init(this->UVLoop.get()); } diff --git a/Source/cmake.cxx b/Source/cmake.cxx index f54196b..32064b4 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -1576,14 +1576,15 @@ void cmake::SetArgs(const std::vector<std::string>& args) if (!expandedPreset->ArchitectureStrategy || expandedPreset->ArchitectureStrategy == cmCMakePresetsGraph::ArchToolsetStrategy::Set) { - if (!this->GeneratorPlatformSet) { + if (!this->GeneratorPlatformSet && + !expandedPreset->Architecture.empty()) { this->SetGeneratorPlatform(expandedPreset->Architecture); } } if (!expandedPreset->ToolsetStrategy || expandedPreset->ToolsetStrategy == cmCMakePresetsGraph::ArchToolsetStrategy::Set) { - if (!this->GeneratorToolsetSet) { + if (!this->GeneratorToolsetSet && !expandedPreset->Toolset.empty()) { this->SetGeneratorToolset(expandedPreset->Toolset); } } @@ -2395,8 +2396,15 @@ int cmake::ActualConfigure() cmSystemTools::RemoveADirectory(redirectsDir); if (!cmSystemTools::MakeDirectory(redirectsDir)) { cmSystemTools::Error( - "Unable to (re)create the private pkgRedirects directory:\n" + - redirectsDir); + cmStrCat("Unable to (re)create the private pkgRedirects directory:\n ", + redirectsDir, + "\n" + "This may be caused by not having read/write access to " + "the build directory.\n" + "Try specifying a location with read/write access like:\n" + " cmake -B build\n" + "If using a CMake presets file, ensure that preset parameter\n" + "'binaryDir' expands to a writable directory.\n")); return -1; } this->AddCacheEntry("CMAKE_FIND_PACKAGE_REDIRECTS_DIR", redirectsDir, @@ -2508,6 +2516,16 @@ int cmake::ActualConfigure() "Name of generator toolset.", cmStateEnums::INTERNAL); } + if (!this->State->GetInitializedCacheValue("CMAKE_TEST_LAUNCHER")) { + cm::optional<std::string> testLauncher = + cmSystemTools::GetEnvVar("CMAKE_TEST_LAUNCHER"); + if (testLauncher && !testLauncher->empty()) { + std::string message = "Test launcher to run tests executable."; + this->AddCacheEntry("CMAKE_TEST_LAUNCHER", *testLauncher, message, + cmStateEnums::STRING); + } + } + if (!this->State->GetInitializedCacheValue( "CMAKE_CROSSCOMPILING_EMULATOR")) { cm::optional<std::string> emulator = diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 43a945f..93b0086 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -203,11 +203,16 @@ bool cmTarFilesFrom(std::string const& file, std::vector<std::string>& files) void cmCatFile(const std::string& fileToAppend) { #ifdef _WIN32 + _setmode(fileno(stdin), _O_BINARY); _setmode(fileno(stdout), _O_BINARY); #endif - cmsys::ifstream source(fileToAppend.c_str(), - (std::ios::binary | std::ios::in)); - std::cout << source.rdbuf(); + std::streambuf* buf = std::cin.rdbuf(); + cmsys::ifstream source; + if (fileToAppend != "-") { + source.open(fileToAppend.c_str(), (std::ios::binary | std::ios::in)); + buf = source.rdbuf(); + } + std::cout << buf; } bool cmRemoveDirectory(const std::string& dir, bool recursive = true) @@ -1147,7 +1152,12 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, int return_value = 0; bool doing_options = true; for (auto const& arg : cmMakeRange(args).advance(2)) { - if (doing_options && cmHasLiteralPrefix(arg, "-")) { + if (arg == "-") { + doing_options = false; + // Destroy console buffers to drop cout/cerr encoding transform. + consoleBuf.reset(); + cmCatFile(arg); + } else if (doing_options && cmHasLiteralPrefix(arg, "-")) { if (arg == "--") { doing_options = false; } else { diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt index 2b7f2cc..562d5e6 100644 --- a/Source/kwsys/CMakeLists.txt +++ b/Source/kwsys/CMakeLists.txt @@ -298,14 +298,6 @@ endif() set(KWSYS_HEADER_INSTALL_DIR) set(KWSYS_LIBRARY_INSTALL_DIR) -# Generated source files will need this header. -string(COMPARE EQUAL "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}" - KWSYS_IN_SOURCE_BUILD) -if(NOT KWSYS_IN_SOURCE_BUILD) - configure_file(${PROJECT_SOURCE_DIR}/kwsysPrivate.h - ${PROJECT_BINARY_DIR}/kwsysPrivate.h COPYONLY IMMEDIATE) -endif() - # Select plugin module file name convention. if(NOT KWSYS_DynamicLoader_PREFIX) set(KWSYS_DynamicLoader_PREFIX ${CMAKE_SHARED_MODULE_PREFIX}) diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx index 3bb7869..53b55f6 100644 --- a/Source/kwsys/SystemTools.cxx +++ b/Source/kwsys/SystemTools.cxx @@ -412,18 +412,6 @@ inline void Realpath(const std::string& path, std::string& resolved_path, } #endif -#if !defined(_WIN32) && defined(__COMO__) -// Hack for como strict mode to avoid defining _SVID_SOURCE or _BSD_SOURCE. -extern "C" { -extern FILE* popen(__const char* __command, __const char* __modes) __THROW; -extern int pclose(FILE* __stream) __THROW; -extern char* realpath(__const char* __restrict __name, - char* __restrict __resolved) __THROW; -extern char* strdup(__const char* __s) __THROW; -extern int putenv(char* __string) __THROW; -} -#endif - namespace KWSYS_NAMESPACE { double SystemTools::GetTime() @@ -777,12 +765,16 @@ const char* SystemTools::GetEnv(const std::string& key) bool SystemTools::GetEnv(const char* key, std::string& result) { #if defined(_WIN32) - const std::wstring wkey = Encoding::ToWide(key); - const wchar_t* wv = _wgetenv(wkey.c_str()); - if (wv) { - result = Encoding::ToNarrow(wv); - return true; + auto wide_key = Encoding::ToWide(key); + auto result_size = GetEnvironmentVariableW(wide_key.data(), nullptr, 0); + if (result_size <= 0) { + return false; } + std::wstring wide_result; + wide_result.resize(result_size - 1); + GetEnvironmentVariableW(wide_key.data(), &wide_result[0], result_size); + result = Encoding::ToNarrow(wide_result); + return true; #else const char* v = getenv(key); if (v) { @@ -2802,14 +2794,14 @@ Status SystemTools::RemoveFile(std::string const& source) Status SystemTools::RemoveADirectory(std::string const& source) { - // Add write permission to the directory so we can modify its - // content to remove files and directories from it. + // Add read and write permission to the directory so we can read + // and modify its content to remove files and directories from it. mode_t mode = 0; if (SystemTools::GetPermissions(source, mode)) { #if defined(_WIN32) && !defined(__CYGWIN__) - mode |= S_IWRITE; + mode |= S_IREAD | S_IWRITE; #else - mode |= S_IWUSR; + mode |= S_IRUSR | S_IWUSR; #endif SystemTools::SetPermissions(source, mode); } |