diff options
Diffstat (limited to 'Source')
132 files changed, 5491 insertions, 6012 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index ca56d3a..c5b67c0 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -181,8 +181,6 @@ set(SRCS cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.h cmCacheManager.cxx cmCacheManager.h - cmCheckCustomOutputs.h - cmCheckCustomOutputs.cxx cmCLocaleEnvironmentScope.h cmCLocaleEnvironmentScope.cxx cmCMakePath.h @@ -227,6 +225,8 @@ set(SRCS cmDependsJava.h cmDependsJavaParserHelper.cxx cmDependsJavaParserHelper.h + cmDependsCompiler.cxx + cmDependsCompiler.h cmDocumentation.cxx cmDocumentationFormatter.cxx cmDocumentationSection.cxx @@ -444,6 +444,8 @@ set(SRCS cmTest.h cmTestGenerator.cxx cmTestGenerator.h + cmTransformDepfile.cxx + cmTransformDepfile.h cmUuid.cxx cmUVHandlePtr.cxx cmUVHandlePtr.h @@ -508,6 +510,8 @@ set(SRCS cmCMakeLanguageCommand.h cmCMakeMinimumRequired.cxx cmCMakeMinimumRequired.h + cmCMakePathCommand.h + cmCMakePathCommand.cxx cmCMakePolicyCommand.cxx cmCMakePolicyCommand.h cmConditionEvaluator.cxx @@ -833,6 +837,7 @@ endif() # Ninja support set(SRCS ${SRCS} + cmScanDepFormat.cxx cmGlobalNinjaGenerator.cxx cmGlobalNinjaGenerator.h cmNinjaTypes.h @@ -1157,20 +1162,6 @@ add_executable(cmake cmakemain.cxx cmcmd.cxx cmcmd.h ${MANIFEST_FILE}) list(APPEND _tools cmake) target_link_libraries(cmake CMakeLib) -add_library(CMakeServerLib - cmConnection.h cmConnection.cxx - cmFileMonitor.cxx cmFileMonitor.h - cmJsonObjectDictionary.h - cmJsonObjects.h - cmJsonObjects.cxx - cmPipeConnection.cxx cmPipeConnection.h - cmServer.cxx cmServer.h - cmServerConnection.cxx cmServerConnection.h - cmServerProtocol.cxx cmServerProtocol.h - ) -target_link_libraries(CMakeServerLib CMakeLib) -target_link_libraries(cmake CMakeServerLib) - # Build CTest executable add_executable(ctest ctest.cxx ${MANIFEST_FILE}) list(APPEND _tools ctest) diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index 799b2fc..29929a2 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 19) -set(CMake_VERSION_PATCH 2) +set(CMake_VERSION_PATCH 20210105) #set(CMake_VERSION_RC 0) set(CMake_VERSION_IS_DIRTY 0) diff --git a/Source/CPack/IFW/cmCPackIFWInstaller.cxx b/Source/CPack/IFW/cmCPackIFWInstaller.cxx index 4bad598..8e00ad6 100644 --- a/Source/CPack/IFW/cmCPackIFWInstaller.cxx +++ b/Source/CPack/IFW/cmCPackIFWInstaller.cxx @@ -174,6 +174,26 @@ void cmCPackIFWInstaller::ConfigureFromOptions() this->WizardDefaultHeight = option; } + // WizardShowPageList + if (const char* option = + this->GetOption("CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST")) { + if (!this->IsVersionLess("4.0")) { + if (this->IsSetToOff("CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST")) { + this->WizardShowPageList = "false"; + } else if (this->IsOn("CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST")) { + this->WizardShowPageList = "true"; + } else { + this->WizardShowPageList.clear(); + } + } else { + cmCPackIFWLogger( + WARNING, + "Option CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST is set to value \"" + << option << "\". But has no any effect for QtIFW less than 4.0 " + << "and will be skipped." << std::endl); + } + } + // TitleColor if (const char* option = this->GetOption("CPACK_IFW_PACKAGE_TITLE_COLOR")) { this->TitleColor = option; @@ -408,6 +428,11 @@ void cmCPackIFWInstaller::GenerateInstallerFile() xout.Element("WizardDefaultHeight", this->WizardDefaultHeight); } + // WizardShowPageList + if (!this->IsVersionLess("4.0") && !this->WizardShowPageList.empty()) { + xout.Element("WizardShowPageList", this->WizardShowPageList); + } + // TitleColor if (!this->TitleColor.empty()) { xout.Element("TitleColor", this->TitleColor); diff --git a/Source/CPack/IFW/cmCPackIFWInstaller.h b/Source/CPack/IFW/cmCPackIFWInstaller.h index 6f398e3..a031fc2 100644 --- a/Source/CPack/IFW/cmCPackIFWInstaller.h +++ b/Source/CPack/IFW/cmCPackIFWInstaller.h @@ -80,6 +80,10 @@ public: /// Wizard height std::string WizardDefaultHeight; + /// Set to false if the widget listing installer pages on the left side + /// of the wizard should not be shown + std::string WizardShowPageList; + /// Title color std::string TitleColor; diff --git a/Source/CPack/cmCPackDebGenerator.cxx b/Source/CPack/cmCPackDebGenerator.cxx index 560e5c1..6f21d87 100644 --- a/Source/CPack/cmCPackDebGenerator.cxx +++ b/Source/CPack/cmCPackDebGenerator.cxx @@ -507,7 +507,8 @@ int cmCPackDebGenerator::PackageOnePack(std::string const& initialTopLevel, this->GetOption("GEN_CPACK_OUTPUT_FILE_NAME")); packageFileNames.push_back(std::move(packageFileName)); - if (this->IsOn("GEN_CPACK_DEBIAN_DEBUGINFO_PACKAGE")) { + if (this->IsOn("GEN_CPACK_DEBIAN_DEBUGINFO_PACKAGE") && + this->GetOption("GEN_DBGSYMDIR")) { cmsys::Glob gl; std::string findExpr(this->GetOption("GEN_DBGSYMDIR")); findExpr += "/*"; diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx index a9fe91c..8b544b4 100644 --- a/Source/CPack/cmCPackGenerator.cxx +++ b/Source/CPack/cmCPackGenerator.cxx @@ -1329,7 +1329,7 @@ bool cmCPackGenerator::ConfigureFile(const std::string& inName, bool copyOnly /* = false */) { return this->MakefileMap->ConfigureFile(inName, outName, copyOnly, true, - false, true) == 1; + false) == 1; } int cmCPackGenerator::CleanTemporaryDirectory() diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx index 1cc267e..4151fde 100644 --- a/Source/CTest/cmCTestBuildCommand.cxx +++ b/Source/CTest/cmCTestBuildCommand.cxx @@ -54,22 +54,21 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() // cmProp ctestBuildConfiguration = this->Makefile->GetDefinition("CTEST_BUILD_CONFIGURATION"); - const std::string* cmakeBuildConfiguration = !this->Configuration.empty() - ? &this->Configuration - : (cmNonempty(ctestBuildConfiguration) ? ctestBuildConfiguration - : &this->CTest->GetConfigType()); - - const std::string* cmakeBuildAdditionalFlags = !this->Flags.empty() - ? &this->Flags - : this->Makefile->GetDefinition("CTEST_BUILD_FLAGS"); - const std::string* cmakeBuildTarget = !this->Target.empty() - ? &this->Target - : this->Makefile->GetDefinition("CTEST_BUILD_TARGET"); + std::string cmakeBuildConfiguration = cmNonempty(this->Configuration) + ? this->Configuration + : cmNonempty(ctestBuildConfiguration) ? *ctestBuildConfiguration + : this->CTest->GetConfigType(); + + const std::string& cmakeBuildAdditionalFlags = cmNonempty(this->Flags) + ? this->Flags + : this->Makefile->GetSafeDefinition("CTEST_BUILD_FLAGS"); + const std::string& cmakeBuildTarget = cmNonempty(this->Target) + ? this->Target + : this->Makefile->GetSafeDefinition("CTEST_BUILD_TARGET"); if (cmNonempty(cmakeGeneratorName)) { - if (!cmakeBuildConfiguration) { - static const std::string sRelease = "Release"; - cmakeBuildConfiguration = &sRelease; + if (cmakeBuildConfiguration.empty()) { + cmakeBuildConfiguration = "Release"; } if (this->GlobalGenerator) { if (this->GlobalGenerator->GetName() != *cmakeGeneratorName) { @@ -88,24 +87,18 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() return nullptr; } } - if (cmakeBuildConfiguration->empty()) { - const std::string* config = nullptr; + if (cmakeBuildConfiguration.empty()) { #ifdef CMAKE_INTDIR - static const std::string sIntDir = CMAKE_INTDIR; - config = &sIntDir; + cmakeBuildConfiguration = CMAKE_INTDIR; +#else + cmakeBuildConfiguration = "Debug"; #endif - if (!config) { - static const std::string sDebug = "Debug"; - config = &sDebug; - } - cmakeBuildConfiguration = config; } std::string dir = this->CTest->GetCTestConfiguration("BuildDirectory"); std::string buildCommand = this->GlobalGenerator->GenerateCMakeBuildCommand( - cmakeBuildTarget ? *cmakeBuildTarget : "", *cmakeBuildConfiguration, - cmakeBuildAdditionalFlags ? *cmakeBuildAdditionalFlags : "", + cmakeBuildTarget, cmakeBuildConfiguration, cmakeBuildAdditionalFlags, this->Makefile->IgnoreErrorsCMP0061()); cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "SetMakeCommand:" << buildCommand << "\n", diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 4d1a589..84bb791 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -122,7 +122,7 @@ bool cmCTestSubdirCommand(std::vector<std::string> const& args, readit = status.GetMakefile().ReadDependentFile(fname); } if (!readit) { - status.SetError(cmStrCat("Could not find include file: ", fname)); + status.SetError(cmStrCat("Could not load include file: ", fname)); return false; } } diff --git a/Source/CTest/cmCTestUpdateCommand.cxx b/Source/CTest/cmCTestUpdateCommand.cxx index 6fef90a..0ba2c41 100644 --- a/Source/CTest/cmCTestUpdateCommand.cxx +++ b/Source/CTest/cmCTestUpdateCommand.cxx @@ -5,7 +5,6 @@ #include "cmCTest.h" #include "cmCTestUpdateHandler.h" #include "cmMakefile.h" -#include "cmProperty.h" #include "cmSystemTools.h" cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler() @@ -18,7 +17,7 @@ cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler() this->CTest->SetCTestConfiguration( "SourceDirectory", cmSystemTools::CollapseFullPath( - cmToCStrSafe(this->Makefile->GetDefinition("CTEST_SOURCE_DIRECTORY"))), + this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")), this->Quiet); } std::string source_dir = diff --git a/Source/LexerParser/cmGccDepfileLexer.cxx b/Source/LexerParser/cmGccDepfileLexer.cxx index a98969d..3630f4e 100644 --- a/Source/LexerParser/cmGccDepfileLexer.cxx +++ b/Source/LexerParser/cmGccDepfileLexer.cxx @@ -994,7 +994,7 @@ case 5: YY_RULE_SETUP { // A line continuation ends the current file name. - yyextra->newDependency(); + yyextra->newRuleOrDependency(); } YY_BREAK case 6: diff --git a/Source/LexerParser/cmGccDepfileLexer.in.l b/Source/LexerParser/cmGccDepfileLexer.in.l index 08f8577..c83cb75 100644 --- a/Source/LexerParser/cmGccDepfileLexer.in.l +++ b/Source/LexerParser/cmGccDepfileLexer.in.l @@ -42,7 +42,7 @@ NEWLINE \r?\n } {WSPACE}*\\{NEWLINE} { // A line continuation ends the current file name. - yyextra->newDependency(); + yyextra->newRuleOrDependency(); } {NEWLINE} { // A newline ends the current file name and the current rule. diff --git a/Source/QtDialog/CMakeSetup.cxx b/Source/QtDialog/CMakeSetup.cxx index c1555a2..861c6e3 100644 --- a/Source/QtDialog/CMakeSetup.cxx +++ b/Source/QtDialog/CMakeSetup.cxx @@ -146,7 +146,7 @@ int main(int argc, char** argv) QIcon appIcon; appIcon.addFile(":/Icons/CMakeSetup32.png"); appIcon.addFile(":/Icons/CMakeSetup128.png"); - QApplication::setWindowIcon(appIcon); + QApplication::setWindowIcon(QIcon::fromTheme("cmake-gui", appIcon)); CMakeSetupDialog dialog; dialog.show(); diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx index 231a2d6..ff2cc3e 100644 --- a/Source/cmAddCustomCommandCommand.cxx +++ b/Source/cmAddCustomCommandCommand.cxx @@ -5,11 +5,11 @@ #include <sstream> #include <unordered_set> -#include "cmCheckCustomOutputs.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmCustomCommandTypes.h" #include "cmExecutionStatus.h" +#include "cmGeneratorExpression.h" #include "cmGlobalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" @@ -188,17 +188,10 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args, case doing_output: case doing_outputs: case doing_byproducts: - if (!cmSystemTools::FileIsFullPath(copy)) { + if (!cmSystemTools::FileIsFullPath(copy) && + cmGeneratorExpression::Find(copy) != 0) { // This is an output to be generated, so it should be - // under the build tree. CMake 2.4 placed this under the - // source tree. However the only case that this change - // will break is when someone writes - // - // add_custom_command(OUTPUT out.txt ...) - // - // and later references "${CMAKE_CURRENT_SOURCE_DIR}/out.txt". - // This is fairly obscure so we can wait for someone to - // complain. + // under the build tree. filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/'); } filename += copy; @@ -215,8 +208,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args, } if (cmSystemTools::FileIsFullPath(filename)) { - filename = cmSystemTools::CollapseFullPath( - filename, status.GetMakefile().GetHomeOutputDirectory()); + filename = cmSystemTools::CollapseFullPath(filename); } switch (doing) { case doing_depfile: @@ -304,26 +296,18 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args, status.SetError("given APPEND option with no OUTPUT."); return false; } - - // Make sure the output names and locations are safe. - if (!cmCheckCustomOutputs(output, "OUTPUT", status) || - !cmCheckCustomOutputs(outputs, "OUTPUTS", status) || - !cmCheckCustomOutputs(byproducts, "BYPRODUCTS", status)) { + if (!implicit_depends.empty() && !depfile.empty() && + mf.GetGlobalGenerator()->GetName() != "Ninja") { + // Makefiles generators does not support both at the same time + status.SetError("IMPLICIT_DEPENDS and DEPFILE can not both be specified."); return false; } // Check for an append request. if (append) { - if (mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends, - commandLines)) { - return true; - } - - // No command for this output exists. - status.SetError( - cmStrCat("given APPEND option with output\n ", output[0], - "\nwhich is not already a custom command output.")); - return false; + mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends, + commandLines); + return true; } if (uses_terminal && !job_pool.empty()) { diff --git a/Source/cmAddCustomTargetCommand.cxx b/Source/cmAddCustomTargetCommand.cxx index aa98d89..104065f 100644 --- a/Source/cmAddCustomTargetCommand.cxx +++ b/Source/cmAddCustomTargetCommand.cxx @@ -4,7 +4,6 @@ #include <utility> -#include "cmCheckCustomOutputs.h" #include "cmCustomCommandLines.h" #include "cmExecutionStatus.h" #include "cmGeneratorExpression.h" @@ -120,12 +119,16 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args, break; case doing_byproducts: { std::string filename; - if (!cmSystemTools::FileIsFullPath(copy)) { + if (!cmSystemTools::FileIsFullPath(copy) && + cmGeneratorExpression::Find(copy) != 0) { filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/'); } filename += copy; cmSystemTools::ConvertToUnixSlashes(filename); - byproducts.push_back(cmSystemTools::CollapseFullPath(filename)); + if (cmSystemTools::FileIsFullPath(filename)) { + filename = cmSystemTools::CollapseFullPath(filename); + } + byproducts.push_back(filename); } break; case doing_depends: { std::string dep = copy; @@ -206,11 +209,6 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args, return false; } - // Make sure the byproduct names and locations are safe. - if (!cmCheckCustomOutputs(byproducts, "BYPRODUCTS", status)) { - return false; - } - // Add the utility target to the makefile. bool escapeOldStyle = !verbatim; cmTarget* target = mf.AddUtilityCommand( diff --git a/Source/cmCMakePathCommand.cxx b/Source/cmCMakePathCommand.cxx index 720f582..85e7d9e 100644 --- a/Source/cmCMakePathCommand.cxx +++ b/Source/cmCMakePathCommand.cxx @@ -250,9 +250,48 @@ bool HandleGetCommand(std::vector<std::string> const& args, return true; } +bool HandleSetCommand(std::vector<std::string> const& args, + cmExecutionStatus& status) +{ + if (args.size() < 3 || args.size() > 4) { + status.SetError("SET must be called with two or three arguments."); + return false; + } + + if (args[1].empty()) { + status.SetError("Invalid name for path variable."); + return false; + } + + static NormalizeParser const parser; + + const auto arguments = parser.Parse(args); + + if (parser.GetInputs().size() != 1) { + status.SetError("SET called with unexpected arguments."); + return false; + } + + auto path = + cmCMakePath(parser.GetInputs().front(), cmCMakePath::native_format); + + if (arguments.Normalize) { + path = path.Normal(); + } + + status.GetMakefile().AddDefinition(args[1], path.GenericString()); + + return true; +} + bool HandleAppendCommand(std::vector<std::string> const& args, cmExecutionStatus& status) { + if (args[1].empty()) { + status.SetError("Invalid name for path variable."); + return false; + } + static OutputVariableParser const parser{}; const auto arguments = parser.Parse(args); @@ -272,8 +311,8 @@ bool HandleAppendCommand(std::vector<std::string> const& args, return true; } -bool HandleConcatCommand(std::vector<std::string> const& args, - cmExecutionStatus& status) +bool HandleAppendStringCommand(std::vector<std::string> const& args, + cmExecutionStatus& status) { static OutputVariableParser const parser{}; @@ -546,16 +585,6 @@ bool HandleRelativePathCommand(std::vector<std::string> const& args, }); } -bool HandleProximatePathCommand(std::vector<std::string> const& args, - cmExecutionStatus& status) -{ - return HandleTransformPathCommand( - args, status, - [](const cmCMakePath& path, const std::string& base) -> cmCMakePath { - return path.Proximate(base); - }); -} - bool HandleAbsolutePathCommand(std::vector<std::string> const& args, cmExecutionStatus& status) { @@ -567,40 +596,6 @@ bool HandleAbsolutePathCommand(std::vector<std::string> const& args, true); } -bool HandleCMakePathCommand(std::vector<std::string> const& args, - cmExecutionStatus& status) -{ - if (args.size() < 3 || args.size() > 4) { - status.SetError("CMAKE_PATH must be called with two or three arguments."); - return false; - } - - static NormalizeParser const parser; - - const auto arguments = parser.Parse(args); - - if (parser.GetInputs().size() != 1) { - status.SetError("CMAKE_PATH called with unexpected arguments."); - return false; - } - - if (args[1].empty()) { - status.SetError("Invalid name for output variable."); - return false; - } - - auto path = - cmCMakePath(parser.GetInputs().front(), cmCMakePath::native_format); - - if (arguments.Normalize) { - path = path.Normal(); - } - - status.GetMakefile().AddDefinition(args[1], path.GenericString()); - - return true; -} - bool HandleNativePathCommand(std::vector<std::string> const& args, cmExecutionStatus& status) { @@ -737,12 +732,7 @@ bool HandleCompareCommand(std::vector<std::string> const& args, return false; } - std::string inputPath; - if (!getInputPath(args[1], status, inputPath)) { - return false; - } - - cmCMakePath path1(inputPath); + cmCMakePath path1(args[1]); cmCMakePath path2(args[3]); auto result = op->second(path1, path2); @@ -987,17 +977,16 @@ bool cmCMakePathCommand(std::vector<std::string> const& args, static cmSubcommandTable const subcommand{ { "GET"_s, HandleGetCommand }, + { "SET"_s, HandleSetCommand }, { "APPEND"_s, HandleAppendCommand }, - { "CONCAT"_s, HandleConcatCommand }, + { "APPEND_STRING"_s, HandleAppendStringCommand }, { "REMOVE_FILENAME"_s, HandleRemoveFilenameCommand }, { "REPLACE_FILENAME"_s, HandleReplaceFilenameCommand }, { "REMOVE_EXTENSION"_s, HandleRemoveExtensionCommand }, { "REPLACE_EXTENSION"_s, HandleReplaceExtensionCommand }, { "NORMAL_PATH"_s, HandleNormalPathCommand }, { "RELATIVE_PATH"_s, HandleRelativePathCommand }, - { "PROXIMATE_PATH"_s, HandleProximatePathCommand }, { "ABSOLUTE_PATH"_s, HandleAbsolutePathCommand }, - { "CMAKE_PATH"_s, HandleCMakePathCommand }, { "NATIVE_PATH"_s, HandleNativePathCommand }, { "CONVERT"_s, HandleConvertCommand }, { "COMPARE"_s, HandleCompareCommand }, diff --git a/Source/cmCMakePolicyCommand.cxx b/Source/cmCMakePolicyCommand.cxx index b7f08d2..1f99043 100644 --- a/Source/cmCMakePolicyCommand.cxx +++ b/Source/cmCMakePolicyCommand.cxx @@ -191,8 +191,7 @@ bool HandleVersionMode(std::vector<std::string> const& args, return false; } - status.GetMakefile().SetPolicyVersion(version_min, version_max); - return true; + return status.GetMakefile().SetPolicyVersion(version_min, version_max); } bool HandleGetWarningMode(std::vector<std::string> const& args, diff --git a/Source/cmCPluginAPI.cxx b/Source/cmCPluginAPI.cxx index 8ebf6d2..9e3582c 100644 --- a/Source/cmCPluginAPI.cxx +++ b/Source/cmCPluginAPI.cxx @@ -550,6 +550,11 @@ void* CCONV cmAddSource(void* arg, void* arg2) // Create the real cmSourceFile instance and copy over saved information. cmSourceFile* rsf = mf->GetOrCreateSource(osf->FullPath); rsf->SetProperties(osf->Properties); + // In case the properties contain the GENERATED property, + // mark the real cmSourceFile as generated. + if (rsf->GetIsGenerated()) { + rsf->MarkAsGenerated(); + } for (std::string const& d : osf->Depends) { rsf->AddDepend(d); } @@ -583,14 +588,12 @@ const char* CCONV cmSourceFileGetProperty(void* arg, const char* prop) { cmCPluginAPISourceFile* sf = static_cast<cmCPluginAPISourceFile*>(arg); if (cmSourceFile* rsf = sf->RealSourceFile) { - cmProp p = rsf->GetProperty(prop); - return cmToCStr(p); + return cmToCStr(rsf->GetProperty(prop)); } if (!strcmp(prop, "LOCATION")) { return sf->FullPath.c_str(); } - cmProp retVal = sf->Properties.GetPropertyValue(prop); - return cmToCStr(retVal); + return cmToCStr(sf->Properties.GetPropertyValue(prop)); } int CCONV cmSourceFileGetPropertyAsBool(void* arg, const char* prop) diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index 8cf5ae9..014ce4e 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -4,6 +4,7 @@ #include <algorithm> #include <cctype> +#include <cerrno> #include <chrono> #include <cstdio> #include <cstdlib> @@ -179,6 +180,7 @@ struct cmCTest::Private // information for the --build-and-test options std::string BinaryDir; + std::string TestDir; std::string NotesFiles; @@ -1017,6 +1019,17 @@ int cmCTest::ProcessSteps() } if (res != 0) { cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest" << std::endl); + if (!this->Impl->OutputTestOutputOnTestFailure) { + const std::string lastTestLog = + this->GetBinaryDir() + "/Testing/Temporary/LastTest.log"; + cmCTestLog(this, ERROR_MESSAGE, + "Output from these tests are in: " << lastTestLog + << std::endl); + cmCTestLog(this, ERROR_MESSAGE, + "Use \"--rerun-failed --output-on-failure\" to re-run the " + "failed cases verbosely." + << std::endl); + } } return res; } @@ -2048,6 +2061,13 @@ bool cmCTest::HandleCommandLineArguments(size_t& i, i++; this->SetNotesFiles(args[i]); return true; + } else if (this->CheckArgument(arg, "--test-dir"_s)) { + if (i >= args.size() - 1) { + errormsg = "'--test-dir' requires an argument"; + return false; + } + i++; + this->Impl->TestDir = std::string(args[i]); } cm::string_view noTestsPrefix = "--no-tests="; @@ -2456,8 +2476,26 @@ int cmCTest::ExecuteTests() handler->SetVerbose(this->Impl->Verbose); handler->SetSubmitIndex(this->Impl->SubmitIndex); } - std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); - if (!this->Initialize(cwd.c_str(), nullptr)) { + + const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory(); + std::string workDir = currDir; + if (!this->Impl->TestDir.empty()) { + workDir = cmSystemTools::CollapseFullPath(this->Impl->TestDir); + } + + if (currDir != workDir) { + cmCTestLog(this, OUTPUT, + "Internal ctest changing into directory: " << workDir + << std::endl); + if (cmSystemTools::ChangeDirectory(workDir) != 0) { + auto msg = "Failed to change working directory to \"" + workDir + + "\" : " + std::strerror(errno) + "\n"; + cmCTestLog(this, ERROR_MESSAGE, msg); + return 1; + } + } + + if (!this->Initialize(workDir.c_str(), nullptr)) { res = 12; cmCTestLog(this, ERROR_MESSAGE, "Problem initializing the dashboard." << std::endl); @@ -2465,6 +2503,10 @@ int cmCTest::ExecuteTests() res = this->ProcessSteps(); } this->Finalize(); + + if (currDir != workDir) { + cmSystemTools::ChangeDirectory(currDir); + } } if (res != 0) { cmCTestLog(this, DEBUG, diff --git a/Source/cmCheckCustomOutputs.cxx b/Source/cmCheckCustomOutputs.cxx deleted file mode 100644 index 7645c88..0000000 --- a/Source/cmCheckCustomOutputs.cxx +++ /dev/null @@ -1,36 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmCheckCustomOutputs.h" - -#include "cmExecutionStatus.h" -#include "cmMakefile.h" -#include "cmStringAlgorithms.h" -#include "cmSystemTools.h" - -bool cmCheckCustomOutputs(const std::vector<std::string>& outputs, - cm::string_view keyword, cmExecutionStatus& status) -{ - cmMakefile& mf = status.GetMakefile(); - - for (std::string const& o : outputs) { - // Make sure the file will not be generated into the source - // directory during an out of source build. - if (!mf.CanIWriteThisFile(o)) { - status.SetError( - cmStrCat("attempted to have a file\n ", o, - "\nin a source directory as an output of custom command.")); - cmSystemTools::SetFatalErrorOccured(); - return false; - } - - // Make sure the output file name has no invalid characters. - std::string::size_type pos = o.find_first_of("#<>"); - if (pos != std::string::npos) { - status.SetError(cmStrCat("called with ", keyword, " containing a \"", - o[pos], "\". This character is not allowed.")); - return false; - } - } - - return true; -} diff --git a/Source/cmCheckCustomOutputs.h b/Source/cmCheckCustomOutputs.h deleted file mode 100644 index 2752ed4..0000000 --- a/Source/cmCheckCustomOutputs.h +++ /dev/null @@ -1,15 +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 <string> -#include <vector> - -#include <cm/string_view> - -class cmExecutionStatus; - -bool cmCheckCustomOutputs(const std::vector<std::string>& outputs, - cm::string_view keyword, cmExecutionStatus& status); diff --git a/Source/cmCommands.cxx b/Source/cmCommands.cxx index c94f128..9e5b783 100644 --- a/Source/cmCommands.cxx +++ b/Source/cmCommands.cxx @@ -17,6 +17,7 @@ #include "cmBreakCommand.h" #include "cmBuildCommand.h" #include "cmCMakeMinimumRequired.h" +#include "cmCMakePathCommand.h" #include "cmCMakePolicyCommand.h" #include "cmCommand.h" #include "cmConfigureFileCommand.h" @@ -118,11 +119,19 @@ void GetScriptingCommands(cmState* state) { - state->AddBuiltinCommand("break", cmBreakCommand); + state->AddFlowControlCommand("break", cmBreakCommand); + state->AddFlowControlCommand("continue", cmContinueCommand); + state->AddFlowControlCommand("foreach", cmForEachCommand); + state->AddFlowControlCommand("function", cmFunctionCommand); + state->AddFlowControlCommand("if", cmIfCommand); + state->AddFlowControlCommand("macro", cmMacroCommand); + state->AddFlowControlCommand("return", cmReturnCommand); + state->AddFlowControlCommand("while", cmWhileCommand); + state->AddBuiltinCommand("cmake_minimum_required", cmCMakeMinimumRequired); + state->AddBuiltinCommand("cmake_path", cmCMakePathCommand); state->AddBuiltinCommand("cmake_policy", cmCMakePolicyCommand); state->AddBuiltinCommand("configure_file", cmConfigureFileCommand); - state->AddBuiltinCommand("continue", cmContinueCommand); state->AddBuiltinCommand("exec_program", cmExecProgramCommand); state->AddBuiltinCommand("execute_process", cmExecuteProcessCommand); state->AddBuiltinCommand("file", cmFileCommand); @@ -131,26 +140,21 @@ void GetScriptingCommands(cmState* state) state->AddBuiltinCommand("find_package", cmFindPackage); state->AddBuiltinCommand("find_path", cmFindPath); state->AddBuiltinCommand("find_program", cmFindProgram); - state->AddBuiltinCommand("foreach", cmForEachCommand); - state->AddBuiltinCommand("function", cmFunctionCommand); state->AddBuiltinCommand("get_cmake_property", cmGetCMakePropertyCommand); state->AddBuiltinCommand("get_directory_property", cmGetDirectoryPropertyCommand); state->AddBuiltinCommand("get_filename_component", cmGetFilenameComponentCommand); state->AddBuiltinCommand("get_property", cmGetPropertyCommand); - state->AddBuiltinCommand("if", cmIfCommand); state->AddBuiltinCommand("include", cmIncludeCommand); state->AddBuiltinCommand("include_guard", cmIncludeGuardCommand); state->AddBuiltinCommand("list", cmListCommand); - state->AddBuiltinCommand("macro", cmMacroCommand); state->AddBuiltinCommand("make_directory", cmMakeDirectoryCommand); state->AddBuiltinCommand("mark_as_advanced", cmMarkAsAdvancedCommand); state->AddBuiltinCommand("math", cmMathCommand); state->AddBuiltinCommand("message", cmMessageCommand); state->AddBuiltinCommand("option", cmOptionCommand); state->AddBuiltinCommand("cmake_parse_arguments", cmParseArgumentsCommand); - state->AddBuiltinCommand("return", cmReturnCommand); state->AddBuiltinCommand("separate_arguments", cmSeparateArgumentsCommand); state->AddBuiltinCommand("set", cmSetCommand); state->AddBuiltinCommand("set_directory_properties", @@ -159,7 +163,6 @@ void GetScriptingCommands(cmState* state) state->AddBuiltinCommand("site_name", cmSiteNameCommand); state->AddBuiltinCommand("string", cmStringCommand); state->AddBuiltinCommand("unset", cmUnsetCommand); - state->AddBuiltinCommand("while", cmWhileCommand); state->AddUnexpectedCommand( "else", diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx index 201a9d9..6e1fac0 100644 --- a/Source/cmComputeLinkInformation.cxx +++ b/Source/cmComputeLinkInformation.cxx @@ -699,9 +699,13 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item, } else { // This is not a CMake target. Use the name given. if (cmSystemTools::FileIsFullPath(item.Value)) { - if (cmSystemTools::FileIsDirectory(item.Value)) { + if (cmSystemTools::IsPathToFramework(item.Value) && + this->Makefile->IsOn("APPLE")) { + // This is a framework. + this->AddFrameworkItem(item.Value); + } else if (cmSystemTools::FileIsDirectory(item.Value)) { // This is a directory. - this->AddDirectoryItem(item.Value); + this->DropDirectoryItem(item.Value); } else { // Use the full path given to the library file. this->Depends.push_back(item.Value); @@ -1306,16 +1310,6 @@ void cmComputeLinkInformation::AddFrameworkItem(std::string const& item) } } -void cmComputeLinkInformation::AddDirectoryItem(std::string const& item) -{ - if (this->Makefile->IsOn("APPLE") && - cmSystemTools::IsPathToFramework(item)) { - this->AddFrameworkItem(item); - } else { - this->DropDirectoryItem(item); - } -} - void cmComputeLinkInformation::DropDirectoryItem(std::string const& item) { // A full path to a directory was found as a link item. Warn the diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h index 543b6d7..ec8d73c 100644 --- a/Source/cmComputeLinkInformation.h +++ b/Source/cmComputeLinkInformation.h @@ -155,7 +155,6 @@ private: void AddFullItem(BT<std::string> const& item); bool CheckImplicitDirItem(std::string const& item); void AddUserItem(BT<std::string> const& item, bool pathNotKnown); - void AddDirectoryItem(std::string const& item); void AddFrameworkItem(std::string const& item); void DropDirectoryItem(std::string const& item); bool CheckSharedLibNoSOName(std::string const& item); diff --git a/Source/cmConfigureFileCommand.cxx b/Source/cmConfigureFileCommand.cxx index 68322cc..edd261d 100644 --- a/Source/cmConfigureFileCommand.cxx +++ b/Source/cmConfigureFileCommand.cxx @@ -3,11 +3,15 @@ #include "cmConfigureFileCommand.h" #include <set> +#include <sstream> #include <cm/string_view> #include <cmext/string_view> +#include <sys/types.h> + #include "cmExecutionStatus.h" +#include "cmFSPermissions.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmNewLineStyle.h" @@ -60,7 +64,19 @@ bool cmConfigureFileCommand(std::vector<std::string> const& args, } bool copyOnly = false; bool escapeQuotes = false; - bool use_source_permissions = true; + bool useSourcePermissions = false; + bool noSourcePermissions = false; + bool filePermissions = false; + std::vector<std::string> filePermissionOptions; + + enum class Doing + { + DoingNone, + DoingFilePermissions, + DoneFilePermissions + }; + + Doing doing = Doing::DoingNone; static std::set<cm::string_view> noopOptions = { /* Legacy. */ @@ -78,6 +94,9 @@ bool cmConfigureFileCommand(std::vector<std::string> const& args, bool atOnly = false; for (unsigned int i = 2; i < args.size(); ++i) { if (args[i] == "COPYONLY") { + if (doing == Doing::DoingFilePermissions) { + doing = Doing::DoneFilePermissions; + } copyOnly = true; if (newLineStyle.IsValid()) { status.SetError("COPYONLY could not be used in combination " @@ -85,13 +104,49 @@ bool cmConfigureFileCommand(std::vector<std::string> const& args, return false; } } else if (args[i] == "ESCAPE_QUOTES") { + if (doing == Doing::DoingFilePermissions) { + doing = Doing::DoneFilePermissions; + } escapeQuotes = true; } else if (args[i] == "@ONLY") { + if (doing == Doing::DoingFilePermissions) { + doing = Doing::DoneFilePermissions; + } atOnly = true; } else if (args[i] == "NO_SOURCE_PERMISSIONS") { - use_source_permissions = false; + if (doing == Doing::DoingFilePermissions) { + status.SetError(" given both FILE_PERMISSIONS and " + "NO_SOURCE_PERMISSIONS. Only one option allowed."); + return false; + } + noSourcePermissions = true; + } else if (args[i] == "USE_SOURCE_PERMISSIONS") { + if (doing == Doing::DoingFilePermissions) { + status.SetError(" given both FILE_PERMISSIONS and " + "USE_SOURCE_PERMISSIONS. Only one option allowed."); + return false; + } + useSourcePermissions = true; + } else if (args[i] == "FILE_PERMISSIONS") { + if (useSourcePermissions) { + status.SetError(" given both FILE_PERMISSIONS and " + "USE_SOURCE_PERMISSIONS. Only one option allowed."); + return false; + } + if (noSourcePermissions) { + status.SetError(" given both FILE_PERMISSIONS and " + "NO_SOURCE_PERMISSIONS. Only one option allowed."); + return false; + } + + if (doing == Doing::DoingNone) { + doing = Doing::DoingFilePermissions; + filePermissions = true; + } } else if (noopOptions.find(args[i]) != noopOptions.end()) { /* Ignore no-op options. */ + } else if (doing == Doing::DoingFilePermissions) { + filePermissionOptions.push_back(args[i]); } else { unknown_args += " "; unknown_args += args[i]; @@ -104,9 +159,53 @@ bool cmConfigureFileCommand(std::vector<std::string> const& args, status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, msg); } - if (!status.GetMakefile().ConfigureFile( - inputFile, outputFile, copyOnly, atOnly, escapeQuotes, - use_source_permissions, newLineStyle)) { + if (useSourcePermissions && noSourcePermissions) { + status.SetError(" given both USE_SOURCE_PERMISSIONS and " + "NO_SOURCE_PERMISSIONS. Only one option allowed."); + return false; + } + + mode_t permisiions = 0; + + if (filePermissions) { + if (filePermissionOptions.empty()) { + status.SetError(" given FILE_PERMISSIONS without any options."); + return false; + } + + std::vector<std::string> invalidOptions; + for (auto const& e : filePermissionOptions) { + if (!cmFSPermissions::stringToModeT(e, permisiions)) { + invalidOptions.push_back(e); + } + } + + if (!invalidOptions.empty()) { + std::ostringstream oss; + oss << " given invalid permission "; + for (auto i = 0u; i < invalidOptions.size(); i++) { + if (i == 0u) { + oss << "\"" << invalidOptions[i] << "\""; + } else { + oss << ",\"" << invalidOptions[i] << "\""; + } + } + oss << "."; + status.SetError(oss.str()); + return false; + } + } + + if (noSourcePermissions) { + permisiions |= cmFSPermissions::mode_owner_read; + permisiions |= cmFSPermissions::mode_owner_write; + permisiions |= cmFSPermissions::mode_group_read; + permisiions |= cmFSPermissions::mode_world_read; + } + + if (!status.GetMakefile().ConfigureFile(inputFile, outputFile, copyOnly, + atOnly, escapeQuotes, permisiions, + newLineStyle)) { status.SetError("Problem configuring file"); return false; } diff --git a/Source/cmConnection.cxx b/Source/cmConnection.cxx deleted file mode 100644 index e4d0cf1..0000000 --- a/Source/cmConnection.cxx +++ /dev/null @@ -1,173 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmConnection.h" - -#include <cassert> -#include <cstring> - -#include <cm3p/uv.h> - -#include "cmServer.h" - -struct write_req_t -{ - uv_write_t req; - uv_buf_t buf; -}; - -void cmEventBasedConnection::on_alloc_buffer(uv_handle_t* handle, - size_t suggested_size, - uv_buf_t* buf) -{ - (void)(handle); -#ifndef __clang_analyzer__ - char* rawBuffer = new char[suggested_size]; - *buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size)); -#else - (void)(suggested_size); - (void)(buf); -#endif /* __clang_analyzer__ */ -} - -void cmEventBasedConnection::on_read(uv_stream_t* stream, ssize_t nread, - const uv_buf_t* buf) -{ - auto conn = static_cast<cmEventBasedConnection*>(stream->data); - if (conn) { - if (nread >= 0) { - conn->ReadData(std::string(buf->base, buf->base + nread)); - } else { - conn->OnDisconnect(static_cast<int>(nread)); - } - } - - delete[](buf->base); -} - -void cmEventBasedConnection::on_close(uv_handle_t* /*handle*/) -{ -} - -void cmEventBasedConnection::on_write(uv_write_t* req, int status) -{ - (void)(status); - - // Free req and buffer - write_req_t* wr = reinterpret_cast<write_req_t*>(req); - delete[](wr->buf.base); - delete wr; -} - -void cmEventBasedConnection::on_new_connection(uv_stream_t* stream, int status) -{ - (void)(status); - auto conn = static_cast<cmEventBasedConnection*>(stream->data); - - if (conn) { - conn->Connect(stream); - } -} - -bool cmEventBasedConnection::IsOpen() const -{ - return this->WriteStream != nullptr; -} - -void cmEventBasedConnection::WriteData(const std::string& _data) -{ -#ifndef NDEBUG - auto curr_thread_id = uv_thread_self(); - assert(this->Server); - assert(uv_thread_equal(&curr_thread_id, &this->Server->ServeThreadId)); -#endif - -#ifndef __clang_analyzer__ - auto data = _data; - assert(this->WriteStream.get()); - if (BufferStrategy) { - data = BufferStrategy->BufferOutMessage(data); - } - - auto ds = data.size(); - - write_req_t* req = new write_req_t; - req->req.data = this; - req->buf = uv_buf_init(new char[ds], static_cast<unsigned int>(ds)); - memcpy(req->buf.base, data.c_str(), ds); - uv_write(reinterpret_cast<uv_write_t*>(req), this->WriteStream, &req->buf, 1, - on_write); -#else - (void)(_data); -#endif /* __clang_analyzer__ */ -} - -void cmEventBasedConnection::ReadData(const std::string& data) -{ - this->RawReadBuffer += data; - if (BufferStrategy) { - std::string packet = BufferStrategy->BufferMessage(this->RawReadBuffer); - while (!packet.empty()) { - ProcessRequest(packet); - packet = BufferStrategy->BufferMessage(this->RawReadBuffer); - } - } else { - ProcessRequest(this->RawReadBuffer); - this->RawReadBuffer.clear(); - } -} - -cmEventBasedConnection::cmEventBasedConnection( - cmConnectionBufferStrategy* bufferStrategy) - : BufferStrategy(bufferStrategy) -{ -} - -void cmEventBasedConnection::Connect(uv_stream_t* server) -{ - (void)server; - Server->OnConnected(nullptr); -} - -void cmEventBasedConnection::OnDisconnect(int onerror) -{ - (void)onerror; - this->OnConnectionShuttingDown(); - if (this->Server) { - this->Server->OnDisconnect(this); - } -} - -cmConnection::~cmConnection() = default; - -bool cmConnection::OnConnectionShuttingDown() -{ - this->Server = nullptr; - return true; -} - -void cmConnection::SetServer(cmServerBase* s) -{ - Server = s; -} - -void cmConnection::ProcessRequest(const std::string& request) -{ - Server->ProcessRequest(this, request); -} - -bool cmConnection::OnServeStart(std::string* errString) -{ - (void)errString; - return true; -} - -bool cmEventBasedConnection::OnConnectionShuttingDown() -{ - if (this->WriteStream.get()) { - this->WriteStream->data = nullptr; - } - - WriteStream.reset(); - - return true; -} diff --git a/Source/cmConnection.h b/Source/cmConnection.h deleted file mode 100644 index 5335a7f..0000000 --- a/Source/cmConnection.h +++ /dev/null @@ -1,137 +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 <cstddef> -#include <memory> -#include <string> - -#include <cm3p/uv.h> - -#include "cmUVHandlePtr.h" - -class cmServerBase; - -/*** - * Given a sequence of bytes with any kind of buffering, instances of this - * class arrange logical chunks according to whatever the use case is for - * the connection. - */ -class cmConnectionBufferStrategy -{ -public: - virtual ~cmConnectionBufferStrategy(); - - /*** - * Called whenever with an active raw buffer. If a logical chunk - * becomes available, that chunk is returned and that portion is - * removed from the rawBuffer - * - * @param rawBuffer in/out parameter. Receive buffer; the buffer strategy is - * free to manipulate this buffer anyway it needs to. - * - * @return Next chunk from the stream. Returns the empty string if a chunk - * isn't ready yet. Users of this interface should repeatedly call this - * function until an empty string is returned since its entirely possible - * multiple chunks come in a single raw buffer. - */ - virtual std::string BufferMessage(std::string& rawBuffer) = 0; - - /*** - * Called to properly buffer an outgoing message. - * - * @param rawBuffer Message to format in the correct way - * - * @return Formatted message - */ - virtual std::string BufferOutMessage(const std::string& rawBuffer) const - { - return rawBuffer; - }; - /*** - * Resets the internal state of the buffering - */ - virtual void clear(); - - // TODO: There should be a callback / flag set for errors -}; - -class cmConnection -{ -public: - cmConnection() = default; - - cmConnection(cmConnection const&) = delete; - cmConnection& operator=(cmConnection const&) = delete; - - virtual void WriteData(const std::string& data) = 0; - - virtual ~cmConnection(); - - virtual bool OnConnectionShuttingDown(); - - virtual bool IsOpen() const = 0; - - virtual void SetServer(cmServerBase* s); - - virtual void ProcessRequest(const std::string& request); - - virtual bool OnServeStart(std::string* pString); - -protected: - cmServerBase* Server = nullptr; -}; - -/*** - * Abstraction of a connection; ties in event callbacks from libuv and notifies - * the server when appropriate - */ -class cmEventBasedConnection : public cmConnection -{ - -public: - /*** - * @param bufferStrategy If no strategy is given, it will process the raw - * chunks as they come in. The connection - * owns the pointer given. - */ - cmEventBasedConnection(cmConnectionBufferStrategy* bufferStrategy = nullptr); - - virtual void Connect(uv_stream_t* server); - - virtual void ReadData(const std::string& data); - - bool IsOpen() const override; - - void WriteData(const std::string& data) override; - bool OnConnectionShuttingDown() override; - - virtual void OnDisconnect(int errorCode); - - static void on_close(uv_handle_t* handle); - - template <typename T> - static void on_close_delete(uv_handle_t* handle) - { - delete reinterpret_cast<T*>(handle); - } - -protected: - cm::uv_stream_ptr WriteStream; - - std::string RawReadBuffer; - - std::unique_ptr<cmConnectionBufferStrategy> BufferStrategy; - - static void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf); - - static void on_write(uv_write_t* req, int status); - - static void on_new_connection(uv_stream_t* stream, int status); - - static void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size, - uv_buf_t* buf); -}; diff --git a/Source/cmCreateTestSourceList.cxx b/Source/cmCreateTestSourceList.cxx index 9c4deea..3001ae0 100644 --- a/Source/cmCreateTestSourceList.cxx +++ b/Source/cmCreateTestSourceList.cxx @@ -125,9 +125,9 @@ bool cmCreateTestSourceList(std::vector<std::string> const& args, mf.AddDefinition("CMAKE_TESTDRIVER_ARGVC_FUNCTION", function); } mf.AddDefinition("CMAKE_FORWARD_DECLARE_TESTS", forwardDeclareCode); - mf.AddDefinition("CMAKE_FUNCTION_TABLE_ENTIRES", functionMapCode); + mf.AddDefinition("CMAKE_FUNCTION_TABLE_ENTRIES", functionMapCode); bool res = true; - if (!mf.ConfigureFile(configFile, driver, false, true, false, true)) { + if (!mf.ConfigureFile(configFile, driver, false, true, false)) { res = false; } diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index 60504ba..c67497a 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -6,45 +6,147 @@ #include <memory> #include <utility> +#include <cm/optional> +#include <cm/string_view> #include <cmext/algorithm> +#include <cmext/string_view> +#include "cmCryptoHash.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmProperty.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" +#include "cmTransformDepfile.h" namespace { -void AppendPaths(const std::vector<std::string>& inputs, - cmGeneratorExpression const& ge, cmLocalGenerator* lg, - std::string const& config, std::vector<std::string>& output) +std::string EvaluateSplitConfigGenex( + cm::string_view input, cmGeneratorExpression const& ge, cmLocalGenerator* lg, + bool useOutputConfig, std::string const& outputConfig, + std::string const& commandConfig, + std::set<BT<std::pair<std::string, bool>>>* utils = nullptr) { - for (std::string const& in : inputs) { - std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(in); - std::vector<std::string> result = - cmExpandedList(cge->Evaluate(lg, config)); - for (std::string& it : result) { - cmSystemTools::ConvertToUnixSlashes(it); - if (cmSystemTools::FileIsFullPath(it)) { - it = cmSystemTools::CollapseFullPath( - it, lg->GetMakefile()->GetHomeOutputDirectory()); + std::string result; + + while (!input.empty()) { + // Copy non-genex content directly to the result. + std::string::size_type pos = input.find("$<"); + result += input.substr(0, pos); + if (pos == std::string::npos) { + break; + } + input = input.substr(pos); + + // Find the balanced end of this regex. + size_t nestingLevel = 1; + for (pos = 2; pos < input.size(); ++pos) { + cm::string_view cur = input.substr(pos); + if (cmHasLiteralPrefix(cur, "$<")) { + ++nestingLevel; + ++pos; + continue; + } + if (cmHasLiteralPrefix(cur, ">")) { + --nestingLevel; + if (nestingLevel == 0) { + ++pos; + break; + } + } + } + + // Split this genex from following input. + cm::string_view genex = input.substr(0, pos); + input = input.substr(pos); + + // Convert an outer COMMAND_CONFIG or OUTPUT_CONFIG to the matching config. + std::string const* config = + useOutputConfig ? &outputConfig : &commandConfig; + if (nestingLevel == 0) { + static cm::string_view const COMMAND_CONFIG = "$<COMMAND_CONFIG:"_s; + static cm::string_view const OUTPUT_CONFIG = "$<OUTPUT_CONFIG:"_s; + if (cmHasPrefix(genex, COMMAND_CONFIG)) { + genex.remove_prefix(COMMAND_CONFIG.size()); + genex.remove_suffix(1); + useOutputConfig = false; + config = &commandConfig; + } else if (cmHasPrefix(genex, OUTPUT_CONFIG)) { + genex.remove_prefix(OUTPUT_CONFIG.size()); + genex.remove_suffix(1); + useOutputConfig = true; + config = &outputConfig; + } + } + + // Evaluate this genex in the selected configuration. + std::unique_ptr<cmCompiledGeneratorExpression> cge = + ge.Parse(std::string(genex)); + result += cge->Evaluate(lg, *config); + + // Record targets referenced by the genex. + if (utils) { + // FIXME: What is the proper condition for a cross-dependency? + bool const cross = !useOutputConfig; + for (cmGeneratorTarget* gt : cge->GetTargets()) { + utils->emplace(BT<std::pair<std::string, bool>>( + { gt->GetName(), cross }, cge->GetBacktrace())); } } - cm::append(output, result); } + + return result; +} + +std::vector<std::string> EvaluateDepends(std::vector<std::string> const& paths, + cmGeneratorExpression const& ge, + cmLocalGenerator* lg, + std::string const& outputConfig, + std::string const& commandConfig) +{ + std::vector<std::string> depends; + for (std::string const& p : paths) { + std::string const& ep = + EvaluateSplitConfigGenex(p, ge, lg, /*useOutputConfig=*/true, + /*outputConfig=*/outputConfig, + /*commandConfig=*/commandConfig); + cm::append(depends, cmExpandedList(ep)); + } + for (std::string& p : depends) { + if (cmSystemTools::FileIsFullPath(p)) { + p = cmSystemTools::CollapseFullPath(p); + } else { + cmSystemTools::ConvertToUnixSlashes(p); + } + } + return depends; +} + +std::vector<std::string> EvaluateOutputs(std::vector<std::string> const& paths, + cmGeneratorExpression const& ge, + cmLocalGenerator* lg, + std::string const& config) +{ + std::vector<std::string> outputs; + for (std::string const& p : paths) { + std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p); + cm::append(outputs, lg->ExpandCustomCommandOutputPaths(*cge, config)); + } + return outputs; } } -cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc, - std::string config, - cmLocalGenerator* lg) - : CC(cc) - , Config(std::move(config)) +cmCustomCommandGenerator::cmCustomCommandGenerator( + cmCustomCommand const& cc, std::string config, cmLocalGenerator* lg, + bool transformDepfile, cm::optional<std::string> crossConfig) + : CC(&cc) + , OutputConfig(crossConfig ? *crossConfig : config) + , CommandConfig(std::move(config)) , LG(lg) , OldStyle(cc.GetEscapeOldStyle()) , MakeVars(cc.GetEscapeAllowMakeVars()) @@ -52,38 +154,87 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc, { cmGeneratorExpression ge(cc.GetBacktrace()); - const cmCustomCommandLines& cmdlines = this->CC.GetCommandLines(); + const cmCustomCommandLines& cmdlines = this->CC->GetCommandLines(); for (cmCustomCommandLine const& cmdline : cmdlines) { cmCustomCommandLine argv; + // For the command itself, we default to the COMMAND_CONFIG. + bool useOutputConfig = false; for (std::string const& clarg : cmdline) { - std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(clarg); - std::string parsed_arg = cge->Evaluate(this->LG, this->Config); - if (this->CC.GetCommandExpandLists()) { + std::string parsed_arg = EvaluateSplitConfigGenex( + clarg, ge, this->LG, useOutputConfig, this->OutputConfig, + this->CommandConfig, &this->Utilities); + if (this->CC->GetCommandExpandLists()) { cm::append(argv, cmExpandedList(parsed_arg)); } else { argv.push_back(std::move(parsed_arg)); } + + // For remaining arguments, we default to the OUTPUT_CONFIG. + useOutputConfig = true; } - // Later code assumes at least one entry exists, but expanding - // lists on an empty command may have left this empty. - // FIXME: Should we define behavior for removing empty commands? - if (argv.empty()) { + if (!argv.empty()) { + // If the command references an executable target by name, + // collect the target to add a target-level dependency on it. + cmGeneratorTarget* gt = this->LG->FindGeneratorTargetToUse(argv.front()); + if (gt && gt->GetType() == cmStateEnums::EXECUTABLE) { + // FIXME: What is the proper condition for a cross-dependency? + bool const cross = true; + this->Utilities.emplace(BT<std::pair<std::string, bool>>( + { gt->GetName(), cross }, cc.GetBacktrace())); + } + } else { + // Later code assumes at least one entry exists, but expanding + // lists on an empty command may have left this empty. + // FIXME: Should we define behavior for removing empty commands? argv.emplace_back(); } this->CommandLines.push_back(std::move(argv)); } - AppendPaths(cc.GetByproducts(), ge, this->LG, this->Config, - this->Byproducts); - AppendPaths(cc.GetDepends(), ge, this->LG, this->Config, this->Depends); + if (transformDepfile && !this->CommandLines.empty() && + !cc.GetDepfile().empty() && + this->LG->GetGlobalGenerator()->DepfileFormat()) { + cmCustomCommandLine argv; + argv.push_back(cmSystemTools::GetCMakeCommand()); + argv.emplace_back("-E"); + argv.emplace_back("cmake_transform_depfile"); + switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) { + case cmDepfileFormat::GccDepfile: + argv.emplace_back("gccdepfile"); + break; + case cmDepfileFormat::VsTlog: + argv.emplace_back("vstlog"); + break; + } + if (this->LG->GetCurrentBinaryDirectory() == + this->LG->GetBinaryDirectory()) { + argv.emplace_back("./"); + } else { + argv.push_back(cmStrCat(this->LG->MaybeConvertToRelativePath( + this->LG->GetBinaryDirectory(), + this->LG->GetCurrentBinaryDirectory()), + '/')); + } + argv.push_back(this->GetFullDepfile()); + argv.push_back(this->GetInternalDepfile()); + + this->CommandLines.push_back(std::move(argv)); + } + + this->Outputs = + EvaluateOutputs(cc.GetOutputs(), ge, this->LG, this->OutputConfig); + this->Byproducts = + EvaluateOutputs(cc.GetByproducts(), ge, this->LG, this->OutputConfig); + this->Depends = EvaluateDepends(cc.GetDepends(), ge, this->LG, + this->OutputConfig, this->CommandConfig); - const std::string& workingdirectory = this->CC.GetWorkingDirectory(); + const std::string& workingdirectory = this->CC->GetWorkingDirectory(); if (!workingdirectory.empty()) { - std::unique_ptr<cmCompiledGeneratorExpression> cge = - ge.Parse(workingdirectory); - this->WorkingDirectory = cge->Evaluate(this->LG, this->Config); + this->WorkingDirectory = + EvaluateSplitConfigGenex(workingdirectory, ge, this->LG, true, + this->OutputConfig, this->CommandConfig); // Convert working directory to a full path. if (!this->WorkingDirectory.empty()) { std::string const& build_dir = this->LG->GetCurrentBinaryDirectory(); @@ -97,7 +248,7 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc, unsigned int cmCustomCommandGenerator::GetNumberOfCommands() const { - return static_cast<unsigned int>(this->CC.GetCommandLines().size()); + return static_cast<unsigned int>(this->CommandLines.size()); } void cmCustomCommandGenerator::FillEmulatorsWithArguments() @@ -140,7 +291,7 @@ const char* cmCustomCommandGenerator::GetArgv0Location(unsigned int c) const (target->IsImported() || target->GetProperty("CROSSCOMPILING_EMULATOR") || !this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING"))) { - return target->GetLocation(this->Config).c_str(); + return target->GetLocation(this->CommandConfig).c_str(); } return nullptr; } @@ -234,9 +385,43 @@ void cmCustomCommandGenerator::AppendArguments(unsigned int c, } } +std::string cmCustomCommandGenerator::GetFullDepfile() const +{ + std::string depfile = this->CC->GetDepfile(); + if (depfile.empty()) { + return ""; + } + + if (!cmSystemTools::FileIsFullPath(depfile)) { + depfile = cmStrCat(this->LG->GetCurrentBinaryDirectory(), '/', depfile); + } + return cmSystemTools::CollapseFullPath(depfile); +} + +std::string cmCustomCommandGenerator::GetInternalDepfile() const +{ + std::string depfile = this->GetFullDepfile(); + if (depfile.empty()) { + return ""; + } + + cmCryptoHash hash(cmCryptoHash::AlgoSHA256); + std::string extension; + switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) { + case cmDepfileFormat::GccDepfile: + extension = ".d"; + break; + case cmDepfileFormat::VsTlog: + extension = ".tlog"; + break; + } + return cmStrCat(this->LG->GetBinaryDirectory(), "/CMakeFiles/d/", + hash.HashString(depfile), extension); +} + const char* cmCustomCommandGenerator::GetComment() const { - return this->CC.GetComment(); + return this->CC->GetComment(); } std::string cmCustomCommandGenerator::GetWorkingDirectory() const @@ -246,7 +431,7 @@ std::string cmCustomCommandGenerator::GetWorkingDirectory() const std::vector<std::string> const& cmCustomCommandGenerator::GetOutputs() const { - return this->CC.GetOutputs(); + return this->Outputs; } std::vector<std::string> const& cmCustomCommandGenerator::GetByproducts() const @@ -258,3 +443,9 @@ std::vector<std::string> const& cmCustomCommandGenerator::GetDepends() const { return this->Depends; } + +std::set<BT<std::pair<std::string, bool>>> const& +cmCustomCommandGenerator::GetUtilities() const +{ + return this->Utilities; +} diff --git a/Source/cmCustomCommandGenerator.h b/Source/cmCustomCommandGenerator.h index 412eba8..4be5b3f 100644 --- a/Source/cmCustomCommandGenerator.h +++ b/Source/cmCustomCommandGenerator.h @@ -4,26 +4,34 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include <set> #include <string> +#include <utility> #include <vector> +#include <cm/optional> + #include "cmCustomCommandLines.h" +#include "cmListFileCache.h" class cmCustomCommand; class cmLocalGenerator; class cmCustomCommandGenerator { - cmCustomCommand const& CC; - std::string Config; + cmCustomCommand const* CC; + std::string OutputConfig; + std::string CommandConfig; cmLocalGenerator* LG; bool OldStyle; bool MakeVars; cmCustomCommandLines CommandLines; std::vector<std::vector<std::string>> EmulatorsWithArguments; + std::vector<std::string> Outputs; std::vector<std::string> Byproducts; std::vector<std::string> Depends; std::string WorkingDirectory; + std::set<BT<std::pair<std::string, bool>>> Utilities; void FillEmulatorsWithArguments(); std::vector<std::string> GetCrossCompilingEmulator(unsigned int c) const; @@ -31,11 +39,14 @@ class cmCustomCommandGenerator public: cmCustomCommandGenerator(cmCustomCommand const& cc, std::string config, - cmLocalGenerator* lg); + cmLocalGenerator* lg, bool transformDepfile = true, + cm::optional<std::string> crossConfig = {}); cmCustomCommandGenerator(const cmCustomCommandGenerator&) = delete; + cmCustomCommandGenerator(cmCustomCommandGenerator&&) = default; cmCustomCommandGenerator& operator=(const cmCustomCommandGenerator&) = delete; - cmCustomCommand const& GetCC() const { return this->CC; } + cmCustomCommandGenerator& operator=(cmCustomCommandGenerator&&) = default; + cmCustomCommand const& GetCC() const { return *(this->CC); } unsigned int GetNumberOfCommands() const; std::string GetCommand(unsigned int c) const; void AppendArguments(unsigned int c, std::string& cmd) const; @@ -44,5 +55,11 @@ public: std::vector<std::string> const& GetOutputs() const; std::vector<std::string> const& GetByproducts() const; std::vector<std::string> const& GetDepends() const; + std::set<BT<std::pair<std::string, bool>>> const& GetUtilities() const; bool HasOnlyEmptyCommandLines() const; + std::string GetFullDepfile() const; + std::string GetInternalDepfile() const; + + const std::string& GetOutputConfig() const { return this->OutputConfig; } + const std::string& GetCommandConfig() const { return this->CommandConfig; } }; diff --git a/Source/cmCustomCommandTypes.h b/Source/cmCustomCommandTypes.h index 5c900ce..324da9e 100644 --- a/Source/cmCustomCommandTypes.h +++ b/Source/cmCustomCommandTypes.h @@ -27,10 +27,3 @@ enum class cmObjectLibraryCommands Reject, Accept }; - -/** Utility target output source file name. */ -struct cmUtilityOutput -{ - std::string Name; - std::string NameCMP0049; -}; diff --git a/Source/cmDependsCompiler.cxx b/Source/cmDependsCompiler.cxx new file mode 100644 index 0000000..beb080f --- /dev/null +++ b/Source/cmDependsCompiler.cxx @@ -0,0 +1,252 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDependsCompiler.h" + +#include <algorithm> +#include <map> +#include <memory> +#include <string> +#include <unordered_set> +#include <utility> + +#include <cm/optional> +#include <cm/string_view> +#include <cm/vector> +#include <cmext/string_view> + +#include "cmsys/FStream.hxx" + +#include "cmFileTime.h" +#include "cmGccDepfileReader.h" +#include "cmGccDepfileReaderTypes.h" +#include "cmGlobalUnixMakefileGenerator3.h" +#include "cmLocalUnixMakefileGenerator3.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" + +bool cmDependsCompiler::CheckDependencies( + const std::string& internalDepFile, const std::vector<std::string>& depFiles, + cmDepends::DependencyMap& dependencies, + const std::function<bool(const std::string&)>& isValidPath) +{ + bool status = true; + bool forceReadDeps = true; + + cmFileTime internalDepFileTime; + // read cached dependencies stored in internal file + if (cmSystemTools::FileExists(internalDepFile)) { + internalDepFileTime.Load(internalDepFile); + forceReadDeps = false; + + // read current dependencies + cmsys::ifstream fin(internalDepFile.c_str()); + if (fin) { + std::string line; + std::string depender; + std::vector<std::string>* currentDependencies = nullptr; + while (std::getline(fin, line)) { + if (line.empty() || line.front() == '#') { + continue; + } + // Drop carriage return character at the end + if (line.back() == '\r') { + line.pop_back(); + if (line.empty()) { + continue; + } + } + // Check if this a depender line + if (line.front() != ' ') { + depender = std::move(line); + currentDependencies = &dependencies[depender]; + continue; + } + // This is a dependee line + if (currentDependencies != nullptr) { + currentDependencies->emplace_back(line.substr(1)); + } + } + fin.close(); + } + } + + // Now, update dependencies map with all new compiler generated + // dependencies files + cmFileTime depFileTime; + for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) { + const auto& source = *dep++; + const auto& target = *dep++; + const auto& format = *dep++; + const auto& depFile = *dep; + + if (!cmSystemTools::FileExists(depFile)) { + continue; + } + + if (!forceReadDeps) { + depFileTime.Load(depFile); + } + if (forceReadDeps || depFileTime.Compare(internalDepFileTime) >= 0) { + status = false; + if (this->Verbose) { + cmSystemTools::Stdout(cmStrCat("Dependencies file \"", depFile, + "\" is newer than depends file \"", + internalDepFile, "\".\n")); + } + + std::vector<std::string> depends; + if (format == "custom"_s) { + std::string prefix; + if (this->LocalGenerator->GetCurrentBinaryDirectory() != + this->LocalGenerator->GetBinaryDirectory()) { + prefix = + cmStrCat(this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), + this->LocalGenerator->GetCurrentBinaryDirectory()), + '/'); + } + + auto deps = cmReadGccDepfile(depFile.c_str(), prefix); + if (!deps) { + continue; + } + + for (auto& entry : *deps) { + depends = std::move(entry.paths); + if (isValidPath) { + cm::erase_if(depends, isValidPath); + } + // copy depends for each target, except first one, which can be + // moved + for (auto index = entry.rules.size() - 1; index > 0; --index) { + dependencies[entry.rules[index]] = depends; + } + dependencies[entry.rules.front()] = std::move(depends); + } + } else { + if (format == "msvc"_s) { + cmsys::ifstream fin(depFile.c_str()); + if (!fin) { + continue; + } + + std::string line; + if (!isValidPath) { + // insert source as first dependency + depends.push_back(source); + } + while (cmSystemTools::GetLineFromStream(fin, line)) { + depends.emplace_back(std::move(line)); + } + } else if (format == "gcc"_s) { + auto deps = cmReadGccDepfile(depFile.c_str()); + if (!deps) { + continue; + } + + // dependencies generated by the compiler contains only one target + depends = std::move(deps->front().paths); + if (depends.empty()) { + // unexpectedly empty, ignore it and continue + continue; + } + + // depending of the effective format of the dependencies file + // generated by the compiler, the target can be wrongly identified + // as a dependency so remove it from the list + if (depends.front() == target) { + depends.erase(depends.begin()); + } + + // ensure source file is the first dependency + if (depends.front() != source) { + cm::erase(depends, source); + if (!isValidPath) { + depends.insert(depends.begin(), source); + } + } else if (isValidPath) { + // remove first dependency because it must not be filtered out + depends.erase(depends.begin()); + } + } else { + // unknown format, ignore it + continue; + } + + if (isValidPath) { + cm::erase_if(depends, isValidPath); + // insert source as first dependency + depends.insert(depends.begin(), source); + } + + dependencies[target] = std::move(depends); + } + } + } + + return status; +} + +void cmDependsCompiler::WriteDependencies( + const cmDepends::DependencyMap& dependencies, std::ostream& makeDepends, + std::ostream& internalDepends) +{ + // dependencies file consumed by make tool + const auto& lineContinue = static_cast<cmGlobalUnixMakefileGenerator3*>( + this->LocalGenerator->GetGlobalGenerator()) + ->LineContinueDirective; + const auto& binDir = this->LocalGenerator->GetBinaryDirectory(); + cmDepends::DependencyMap makeDependencies(dependencies); + std::unordered_set<cm::string_view> phonyTargets; + + // external dependencies file + for (auto& node : makeDependencies) { + auto target = LocalGenerator->ConvertToMakefilePath( + this->LocalGenerator->MaybeConvertToRelativePath(binDir, node.first)); + auto& deps = node.second; + std::transform( + deps.cbegin(), deps.cend(), deps.begin(), + [this, &binDir](const std::string& dep) { + return LocalGenerator->ConvertToMakefilePath( + this->LocalGenerator->MaybeConvertToRelativePath(binDir, dep)); + }); + + bool first_dep = true; + makeDepends << target << ": "; + for (const auto& dep : deps) { + if (first_dep) { + first_dep = false; + makeDepends << dep; + } else { + makeDepends << ' ' << lineContinue << " " << dep; + } + + phonyTargets.emplace(dep.data(), dep.length()); + } + makeDepends << std::endl << std::endl; + } + + // add phony targets + for (const auto& target : phonyTargets) { + makeDepends << std::endl << target << ':' << std::endl; + } + + // internal dependencies file + for (const auto& node : dependencies) { + internalDepends << node.first << std::endl; + for (const auto& dep : node.second) { + internalDepends << ' ' << dep << std::endl; + } + internalDepends << std::endl; + } +} + +void cmDependsCompiler::ClearDependencies( + const std::vector<std::string>& depFiles) +{ + for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) { + dep += 3; + cmSystemTools::RemoveFile(*dep); + } +} diff --git a/Source/cmDependsCompiler.h b/Source/cmDependsCompiler.h new file mode 100644 index 0000000..838156d --- /dev/null +++ b/Source/cmDependsCompiler.h @@ -0,0 +1,60 @@ +/* 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 <iosfwd> +#include <string> +#include <vector> + +#include "cmDepends.h" + +class cmLocalUnixMakefileGenerator3; + +/** \class cmDepends + * \brief Dependencies files manager. + * + * This class is responsible for maintaining a compiler_depends.make file in + * the build tree corresponding to an object file. + */ +class cmDependsCompiler +{ +public: + cmDependsCompiler() = default; + ~cmDependsCompiler() = default; + + /** should this be verbose in its output */ + void SetVerbose(bool verb) { this->Verbose = verb; } + + /** Set the local generator for the directory in which we are + scanning dependencies. This is not a full local generator; it + has been setup to do relative path conversions for the current + directory. */ + void SetLocalGenerator(cmLocalUnixMakefileGenerator3* lg) + { + this->LocalGenerator = lg; + } + + /** Read dependencies for the target file. Return true if + dependencies didn't changed and false if not. + Up-to-date Dependencies will be stored in deps. */ + bool CheckDependencies( + const std::string& internalDepFile, + const std::vector<std::string>& depFiles, + cmDepends::DependencyMap& dependencies, + const std::function<bool(const std::string&)>& isValidPath); + + /** Write dependencies for the target file. */ + void WriteDependencies(const cmDepends::DependencyMap& dependencies, + std::ostream& makeDepends, + std::ostream& internalDepends); + + /** Clear dependencies for the target so they will be regenerated. */ + void ClearDependencies(const std::vector<std::string>& depFiles); + +private: + bool Verbose = false; + cmLocalUnixMakefileGenerator3* LocalGenerator = nullptr; +}; diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index cbae4e5..a853bb1 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -924,13 +924,13 @@ void cmExportFileGenerator::GeneratePolicyHeaderCode(std::ostream& os) // Isolate the file policy level. // Support CMake versions as far back as 2.6 but also support using NEW - // policy settings for up to CMake 3.17 (this upper limit may be reviewed + // policy settings for up to CMake 3.18 (this upper limit may be reviewed // and increased from time to time). This reduces the opportunity for CMake // warnings when an older export file is later used with newer CMake // versions. /* clang-format off */ os << "cmake_policy(PUSH)\n" - << "cmake_policy(VERSION 2.6...3.17)\n"; + << "cmake_policy(VERSION 2.6...3.18)\n"; /* clang-format on */ } diff --git a/Source/cmExtraSublimeTextGenerator.cxx b/Source/cmExtraSublimeTextGenerator.cxx index 7c36144..249dfaf 100644 --- a/Source/cmExtraSublimeTextGenerator.cxx +++ b/Source/cmExtraSublimeTextGenerator.cxx @@ -349,6 +349,11 @@ std::string cmExtraSublimeTextGenerator::ComputeFlagsForObject( if (language.empty()) { language = "C"; } + + // Explicitly add the explicit language flag before any other flag + // so user flags can override it. + gtgt->AddExplicitLanguageFlags(flags, *source); + std::string const& config = lg->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE"); diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index 8a3aad2..9815d9d 100644 --- a/Source/cmFileCommand.cxx +++ b/Source/cmFileCommand.cxx @@ -15,6 +15,7 @@ #include <vector> #include <cm/memory> +#include <cm/string_view> #include <cmext/algorithm> #include <cmext/string_view> @@ -2290,7 +2291,7 @@ void AddEvaluationFile(const std::string& inputName, const std::string& targetName, const std::string& outputExpr, const std::string& condition, bool inputIsContent, - cmExecutionStatus& status) + mode_t permissions, cmExecutionStatus& status) { cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace(); @@ -2304,7 +2305,7 @@ void AddEvaluationFile(const std::string& inputName, status.GetMakefile().AddEvaluationFile( inputName, targetName, std::move(outputCge), std::move(conditionCge), - inputIsContent); + permissions, inputIsContent); } bool HandleGenerateCommand(std::vector<std::string> const& args, @@ -2314,49 +2315,156 @@ bool HandleGenerateCommand(std::vector<std::string> const& args, status.SetError("Incorrect arguments to GENERATE subcommand."); return false; } - if (args[1] != "OUTPUT") { + + struct Arguments + { + std::string Output; + std::string Input; + std::string Content; + std::string Condition; + std::string Target; + bool NoSourcePermissions = false; + bool UseSourcePermissions = false; + std::vector<std::string> FilePermissions; + }; + + static auto const parser = + cmArgumentParser<Arguments>{} + .Bind("OUTPUT"_s, &Arguments::Output) + .Bind("INPUT"_s, &Arguments::Input) + .Bind("CONTENT"_s, &Arguments::Content) + .Bind("CONDITION"_s, &Arguments::Condition) + .Bind("TARGET"_s, &Arguments::Target) + .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions) + .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions) + .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions); + + std::vector<std::string> unparsedArguments; + std::vector<std::string> keywordsMissingValues; + std::vector<std::string> parsedKeywords; + Arguments const arguments = + parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments, + &keywordsMissingValues, &parsedKeywords); + + if (!keywordsMissingValues.empty()) { status.SetError("Incorrect arguments to GENERATE subcommand."); return false; } - std::string condition; - std::string target; - - for (std::size_t i = 5; i < args.size();) { - const std::string& arg = args[i++]; + if (!unparsedArguments.empty()) { + status.SetError("Unknown argument to GENERATE subcommand."); + return false; + } - if (args.size() - i == 0) { - status.SetError("Incorrect arguments to GENERATE subcommand."); - return false; + bool mandatoryOptionsSpecified = false; + if (parsedKeywords.size() > 1) { + const bool outputOprionSpecified = parsedKeywords[0] == "OUTPUT"_s; + const bool inputOrContentSpecified = + parsedKeywords[1] == "INPUT"_s || parsedKeywords[1] == "CONTENT"_s; + if (outputOprionSpecified && inputOrContentSpecified) { + mandatoryOptionsSpecified = true; } + } + if (!mandatoryOptionsSpecified) { + status.SetError("Incorrect arguments to GENERATE subcommand."); + return false; + } + + const bool conditionOptionSpecified = + std::find(parsedKeywords.begin(), parsedKeywords.end(), "CONDITION"_s) != + parsedKeywords.end(); + if (conditionOptionSpecified && arguments.Condition.empty()) { + status.SetError("CONDITION of sub-command GENERATE must not be empty " + "if specified."); + return false; + } - const std::string& value = args[i++]; + const bool targetOptionSpecified = + std::find(parsedKeywords.begin(), parsedKeywords.end(), "TARGET"_s) != + parsedKeywords.end(); + if (targetOptionSpecified && arguments.Target.empty()) { + status.SetError("TARGET of sub-command GENERATE must not be empty " + "if specified."); + return false; + } - if (value.empty()) { - status.SetError( - arg + " of sub-command GENERATE must not be empty if specified."); + const bool outputOptionSpecified = + std::find(parsedKeywords.begin(), parsedKeywords.end(), "OUTPUT"_s) != + parsedKeywords.end(); + if (outputOptionSpecified && parsedKeywords[0] != "OUTPUT"_s) { + status.SetError("Incorrect arguments to GENERATE subcommand."); + return false; + } + + const bool inputIsContent = parsedKeywords[1] != "INPUT"_s; + if (inputIsContent && parsedKeywords[1] != "CONTENT") { + status.SetError("Unknown argument to GENERATE subcommand."); + } + + std::string input = arguments.Input; + if (inputIsContent) { + input = arguments.Content; + } + + if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) { + status.SetError("given both NO_SOURCE_PERMISSIONS and " + "USE_SOURCE_PERMISSIONS. Only one option allowed."); + return false; + } + + if (!arguments.FilePermissions.empty()) { + if (arguments.NoSourcePermissions) { + status.SetError("given both NO_SOURCE_PERMISSIONS and " + "FILE_PERMISSIONS. Only one option allowed."); return false; } + if (arguments.UseSourcePermissions) { + status.SetError("given both USE_SOURCE_PERMISSIONS and " + "FILE_PERMISSIONS. Only one option allowed."); + return false; + } + } - if (arg == "CONDITION") { - condition = value; - } else if (arg == "TARGET") { - target = value; - } else { - status.SetError("Unknown argument to GENERATE subcommand."); + if (arguments.UseSourcePermissions) { + if (inputIsContent) { + status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT."); return false; } } - std::string output = args[2]; - const bool inputIsContent = args[3] != "INPUT"; - if (inputIsContent && args[3] != "CONTENT") { - status.SetError("Incorrect arguments to GENERATE subcommand."); - return false; + mode_t permisiions = 0; + if (arguments.NoSourcePermissions) { + permisiions |= cmFSPermissions::mode_owner_read; + permisiions |= cmFSPermissions::mode_owner_write; + permisiions |= cmFSPermissions::mode_group_read; + permisiions |= cmFSPermissions::mode_world_read; + } + + if (!arguments.FilePermissions.empty()) { + std::vector<std::string> invalidOptions; + for (auto const& e : arguments.FilePermissions) { + if (!cmFSPermissions::stringToModeT(e, permisiions)) { + invalidOptions.push_back(e); + } + } + if (!invalidOptions.empty()) { + std::ostringstream oss; + oss << "given invalid permission "; + for (auto i = 0u; i < invalidOptions.size(); i++) { + if (i == 0u) { + oss << "\"" << invalidOptions[i] << "\""; + } else { + oss << ",\"" << invalidOptions[i] << "\""; + } + } + oss << "."; + status.SetError(oss.str()); + return false; + } } - std::string input = args[4]; - AddEvaluationFile(input, target, output, condition, inputIsContent, status); + AddEvaluationFile(input, arguments.Target, arguments.Output, + arguments.Condition, inputIsContent, permisiions, status); return true; } @@ -2902,17 +3010,60 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args, bool HandleConfigureCommand(std::vector<std::string> const& args, cmExecutionStatus& status) { - if (args.size() < 5) { - status.SetError("Incorrect arguments to CONFIGURE subcommand."); + struct Arguments + { + std::string Output; + std::string Content; + bool EscapeQuotes = false; + bool AtOnly = false; + std::string NewlineStyle; + }; + + static auto const parser = + cmArgumentParser<Arguments>{} + .Bind("OUTPUT"_s, &Arguments::Output) + .Bind("CONTENT"_s, &Arguments::Content) + .Bind("ESCAPE_QUOTES"_s, &Arguments::EscapeQuotes) + .Bind("@ONLY"_s, &Arguments::AtOnly) + .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle); + + std::vector<std::string> unrecognizedArguments; + std::vector<std::string> keywordsMissingArguments; + std::vector<std::string> parsedKeywords; + auto parsedArgs = + parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments, + &keywordsMissingArguments, &parsedKeywords); + + auto argIt = unrecognizedArguments.begin(); + if (argIt != unrecognizedArguments.end()) { + status.SetError( + cmStrCat("CONFIGURE Unrecognized argument: \"", *argIt, "\"")); + cmSystemTools::SetFatalErrorOccured(); return false; } - if (args[1] != "OUTPUT") { - status.SetError("Incorrect arguments to CONFIGURE subcommand."); - return false; + + std::vector<std::string> mandatoryOptions{ "OUTPUT", "CONTENT" }; + for (auto const& e : mandatoryOptions) { + const bool optionHasNoValue = + std::find(keywordsMissingArguments.begin(), + keywordsMissingArguments.end(), + e) != keywordsMissingArguments.end(); + if (optionHasNoValue) { + status.SetError(cmStrCat("CONFIGURE ", e, " option needs a value.")); + cmSystemTools::SetFatalErrorOccured(); + return false; + } } - if (args[3] != "CONTENT") { - status.SetError("Incorrect arguments to CONFIGURE subcommand."); - return false; + + for (auto const& e : mandatoryOptions) { + const bool optionGiven = + std::find(parsedKeywords.begin(), parsedKeywords.end(), e) != + parsedKeywords.end(); + if (!optionGiven) { + status.SetError(cmStrCat("CONFIGURE ", e, " option is mandatory.")); + cmSystemTools::SetFatalErrorOccured(); + return false; + } } std::string errorMessage; @@ -2922,28 +3073,9 @@ bool HandleConfigureCommand(std::vector<std::string> const& args, return false; } - bool escapeQuotes = false; - bool atOnly = false; - for (unsigned int i = 5; i < args.size(); ++i) { - if (args[i] == "@ONLY") { - atOnly = true; - } else if (args[i] == "ESCAPE_QUOTES") { - escapeQuotes = true; - } else if (args[i] == "NEWLINE_STYLE" || args[i] == "LF" || - args[i] == "UNIX" || args[i] == "CRLF" || args[i] == "WIN32" || - args[i] == "DOS") { - /* Options handled by NewLineStyle member above. */ - } else { - status.SetError( - cmStrCat("CONFIGURE Unrecognized argument \"", args[i], "\"")); - return false; - } - } - // Check for generator expressions - const std::string input = args[4]; std::string outputFile = cmSystemTools::CollapseFullPath( - args[2], status.GetMakefile().GetCurrentBinaryDirectory()); + parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory()); std::string::size_type pos = outputFile.find_first_of("<>"); if (pos != std::string::npos) { @@ -2992,12 +3124,13 @@ bool HandleConfigureCommand(std::vector<std::string> const& args, fout.SetCopyIfDifferent(true); // copy input to output and expand variables from input at the same time - std::stringstream sin(input, std::ios::in); + std::stringstream sin(parsedArgs.Content, std::ios::in); std::string inLine; std::string outLine; while (cmSystemTools::GetLineFromStream(sin, inLine)) { outLine.clear(); - makeFile.ConfigureString(inLine, outLine, atOnly, escapeQuotes); + makeFile.ConfigureString(inLine, outLine, parsedArgs.AtOnly, + parsedArgs.EscapeQuotes); fout << outLine << newLineCharacters; } diff --git a/Source/cmFileMonitor.cxx b/Source/cmFileMonitor.cxx deleted file mode 100644 index 8cfdb2d..0000000 --- a/Source/cmFileMonitor.cxx +++ /dev/null @@ -1,383 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmFileMonitor.h" - -#include <cassert> -#include <cstddef> -#include <unordered_map> -#include <utility> - -#include <cm/memory> - -#include "cmsys/SystemTools.hxx" - -namespace { -void on_directory_change(uv_fs_event_t* handle, const char* filename, - int events, int status); -void on_fs_close(uv_handle_t* handle); -} // namespace - -class cmIBaseWatcher -{ -public: - virtual ~cmIBaseWatcher() = default; - - virtual void Trigger(const std::string& pathSegment, int events, - int status) const = 0; - virtual std::string Path() const = 0; - virtual uv_loop_t* Loop() const = 0; - - virtual void StartWatching() = 0; - virtual void StopWatching() = 0; - - virtual std::vector<std::string> WatchedFiles() const = 0; - virtual std::vector<std::string> WatchedDirectories() const = 0; -}; - -class cmVirtualDirectoryWatcher : public cmIBaseWatcher -{ -public: - ~cmVirtualDirectoryWatcher() override = default; - - cmIBaseWatcher* Find(const std::string& ps) - { - const auto i = this->Children.find(ps); - return (i == this->Children.end()) ? nullptr : i->second.get(); - } - - void Trigger(const std::string& pathSegment, int events, - int status) const final - { - if (pathSegment.empty()) { - for (auto const& child : this->Children) { - child.second->Trigger(std::string(), events, status); - } - } else { - const auto i = this->Children.find(pathSegment); - if (i != this->Children.end()) { - i->second->Trigger(std::string(), events, status); - } - } - } - - void StartWatching() override - { - for (auto const& child : this->Children) { - child.second->StartWatching(); - } - } - - void StopWatching() override - { - for (auto const& child : this->Children) { - child.second->StopWatching(); - } - } - - std::vector<std::string> WatchedFiles() const final - { - std::vector<std::string> result; - for (auto const& child : this->Children) { - for (std::string const& f : child.second->WatchedFiles()) { - result.push_back(f); - } - } - return result; - } - - std::vector<std::string> WatchedDirectories() const override - { - std::vector<std::string> result; - for (auto const& child : this->Children) { - for (std::string const& dir : child.second->WatchedDirectories()) { - result.push_back(dir); - } - } - return result; - } - - void Reset() { this->Children.clear(); } - - void AddChildWatcher(const std::string& ps, cmIBaseWatcher* watcher) - { - assert(!ps.empty()); - assert(this->Children.find(ps) == this->Children.end()); - assert(watcher); - - this->Children.emplace(ps, std::unique_ptr<cmIBaseWatcher>(watcher)); - } - -private: - std::unordered_map<std::string, std::unique_ptr<cmIBaseWatcher>> - Children; // owned! -}; - -// Root of all the different (on windows!) root directories: -class cmRootWatcher : public cmVirtualDirectoryWatcher -{ -public: - cmRootWatcher(uv_loop_t* loop) - : mLoop(loop) - { - assert(loop); - } - - std::string Path() const final - { - assert(false); - return std::string(); - } - uv_loop_t* Loop() const final { return this->mLoop; } - -private: - uv_loop_t* const mLoop; // no ownership! -}; - -// Real directories: -class cmRealDirectoryWatcher : public cmVirtualDirectoryWatcher -{ -public: - cmRealDirectoryWatcher(cmVirtualDirectoryWatcher* p, const std::string& ps) - : Parent(p) - , PathSegment(ps) - { - assert(p); - assert(!ps.empty()); - - p->AddChildWatcher(ps, this); - } - - void StartWatching() final - { - if (!this->Handle) { - this->Handle = new uv_fs_event_t; - - uv_fs_event_init(this->Loop(), this->Handle); - this->Handle->data = this; - uv_fs_event_start(this->Handle, &on_directory_change, Path().c_str(), 0); - } - cmVirtualDirectoryWatcher::StartWatching(); - } - - void StopWatching() final - { - if (this->Handle) { - uv_fs_event_stop(this->Handle); - if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(this->Handle))) { - uv_close(reinterpret_cast<uv_handle_t*>(this->Handle), &on_fs_close); - } - this->Handle = nullptr; - } - cmVirtualDirectoryWatcher::StopWatching(); - } - - uv_loop_t* Loop() const final { return this->Parent->Loop(); } - - std::vector<std::string> WatchedDirectories() const override - { - std::vector<std::string> result = { Path() }; - for (std::string const& dir : - cmVirtualDirectoryWatcher::WatchedDirectories()) { - result.push_back(dir); - } - return result; - } - -protected: - cmVirtualDirectoryWatcher* const Parent; - const std::string PathSegment; - -private: - uv_fs_event_t* Handle = nullptr; // owner! -}; - -// Root directories: -class cmRootDirectoryWatcher : public cmRealDirectoryWatcher -{ -public: - cmRootDirectoryWatcher(cmRootWatcher* p, const std::string& ps) - : cmRealDirectoryWatcher(p, ps) - { - } - - std::string Path() const final { return this->PathSegment; } -}; - -// Normal directories below root: -class cmDirectoryWatcher : public cmRealDirectoryWatcher -{ -public: - cmDirectoryWatcher(cmRealDirectoryWatcher* p, const std::string& ps) - : cmRealDirectoryWatcher(p, ps) - { - } - - std::string Path() const final - { - return this->Parent->Path() + this->PathSegment + "/"; - } -}; - -class cmFileWatcher : public cmIBaseWatcher -{ -public: - cmFileWatcher(cmRealDirectoryWatcher* p, const std::string& ps, - cmFileMonitor::Callback cb) - : Parent(p) - , PathSegment(ps) - , CbList({ std::move(cb) }) - { - assert(p); - assert(!ps.empty()); - p->AddChildWatcher(ps, this); - } - - void StartWatching() final {} - - void StopWatching() final {} - - void AppendCallback(cmFileMonitor::Callback const& cb) - { - this->CbList.push_back(cb); - } - - std::string Path() const final - { - return this->Parent->Path() + this->PathSegment; - } - - std::vector<std::string> WatchedDirectories() const final { return {}; } - - std::vector<std::string> WatchedFiles() const final - { - return { this->Path() }; - } - - void Trigger(const std::string& ps, int events, int status) const final - { - assert(ps.empty()); - assert(status == 0); - static_cast<void>(ps); - - const std::string path = this->Path(); - for (cmFileMonitor::Callback const& cb : this->CbList) { - cb(path, events, status); - } - } - - uv_loop_t* Loop() const final { return this->Parent->Loop(); } - -private: - cmRealDirectoryWatcher* Parent; - const std::string PathSegment; - std::vector<cmFileMonitor::Callback> CbList; -}; - -namespace { - -void on_directory_change(uv_fs_event_t* handle, const char* filename, - int events, int status) -{ - const cmIBaseWatcher* const watcher = - static_cast<const cmIBaseWatcher*>(handle->data); - const std::string pathSegment(filename ? filename : ""); - watcher->Trigger(pathSegment, events, status); -} - -void on_fs_close(uv_handle_t* handle) -{ - delete reinterpret_cast<uv_fs_event_t*>(handle); -} - -} // namespace - -cmFileMonitor::cmFileMonitor(uv_loop_t* l) - : Root(cm::make_unique<cmRootWatcher>(l)) -{ -} - -cmFileMonitor::~cmFileMonitor() = default; - -void cmFileMonitor::MonitorPaths(const std::vector<std::string>& paths, - Callback const& cb) -{ - for (std::string const& p : paths) { - std::vector<std::string> pathSegments; - cmsys::SystemTools::SplitPath(p, pathSegments, true); - const bool pathIsFile = !cmsys::SystemTools::FileIsDirectory(p); - - const size_t segmentCount = pathSegments.size(); - if (segmentCount < 2) { // Expect at least rootdir and filename - continue; - } - cmVirtualDirectoryWatcher* currentWatcher = this->Root.get(); - for (size_t i = 0; i < segmentCount; ++i) { - assert(currentWatcher); - - const bool fileSegment = (i == segmentCount - 1 && pathIsFile); - const bool rootSegment = (i == 0); - assert( - !(fileSegment && - rootSegment)); // Can not be both filename and root part of the path! - - const std::string& currentSegment = pathSegments[i]; - if (currentSegment.empty()) { - continue; - } - - cmIBaseWatcher* nextWatcher = currentWatcher->Find(currentSegment); - if (!nextWatcher) { - if (rootSegment) { // Root part - assert(currentWatcher == this->Root.get()); - nextWatcher = - new cmRootDirectoryWatcher(this->Root.get(), currentSegment); - assert(currentWatcher->Find(currentSegment) == nextWatcher); - } else if (fileSegment) { // File part - assert(currentWatcher != this->Root.get()); - nextWatcher = new cmFileWatcher( - dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher), - currentSegment, cb); - assert(currentWatcher->Find(currentSegment) == nextWatcher); - } else { // Any normal directory in between - nextWatcher = new cmDirectoryWatcher( - dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher), - currentSegment); - assert(currentWatcher->Find(currentSegment) == nextWatcher); - } - } else { - if (fileSegment) { - auto filePtr = dynamic_cast<cmFileWatcher*>(nextWatcher); - assert(filePtr); - filePtr->AppendCallback(cb); - continue; - } - } - currentWatcher = dynamic_cast<cmVirtualDirectoryWatcher*>(nextWatcher); - } - } - this->Root->StartWatching(); -} - -void cmFileMonitor::StopMonitoring() -{ - this->Root->StopWatching(); - this->Root->Reset(); -} - -std::vector<std::string> cmFileMonitor::WatchedFiles() const -{ - std::vector<std::string> result; - if (this->Root) { - result = this->Root->WatchedFiles(); - } - return result; -} - -std::vector<std::string> cmFileMonitor::WatchedDirectories() const -{ - std::vector<std::string> result; - if (this->Root) { - result = this->Root->WatchedDirectories(); - } - return result; -} diff --git a/Source/cmFileMonitor.h b/Source/cmFileMonitor.h deleted file mode 100644 index fc75b0c..0000000 --- a/Source/cmFileMonitor.h +++ /dev/null @@ -1,35 +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 <functional> -#include <memory> -#include <string> -#include <vector> - -#include <cm3p/uv.h> - -class cmRootWatcher; - -class cmFileMonitor -{ - -public: - cmFileMonitor(uv_loop_t* l); - ~cmFileMonitor(); - - cmFileMonitor(cmFileMonitor const&) = delete; - cmFileMonitor& operator=(cmFileMonitor const&) = delete; - - using Callback = std::function<void(const std::string&, int, int)>; - void MonitorPaths(const std::vector<std::string>& paths, Callback const& cb); - void StopMonitoring(); - - std::vector<std::string> WatchedFiles() const; - std::vector<std::string> WatchedDirectories() const; - -private: - std::unique_ptr<cmRootWatcher> Root; -}; diff --git a/Source/cmFindCommon.cxx b/Source/cmFindCommon.cxx index dee91d7..7952336 100644 --- a/Source/cmFindCommon.cxx +++ b/Source/cmFindCommon.cxx @@ -182,7 +182,7 @@ void cmFindCommon::SelectDefaultSearchModes() { this->NoCMakeSystemPath, "CMAKE_FIND_USE_CMAKE_SYSTEM_PATH" } } }; - for (auto& path : search_paths) { + for (auto const& path : search_paths) { cmProp def = this->Makefile->GetDefinition(path.second); if (def) { path.first = !cmIsOn(*def); diff --git a/Source/cmFunctionCommand.cxx b/Source/cmFunctionCommand.cxx index 71c82d6..1359009 100644 --- a/Source/cmFunctionCommand.cxx +++ b/Source/cmFunctionCommand.cxx @@ -163,8 +163,11 @@ bool cmFunctionFunctionBlocker::Replay( f.FilePath = this->GetStartingContext().FilePath; f.Line = this->GetStartingContext().Line; mf.RecordPolicies(f.Policies); - mf.GetState()->AddScriptedCommand(this->Args.front(), std::move(f)); - return true; + return mf.GetState()->AddScriptedCommand( + this->Args.front(), + BT<cmState::Command>(std::move(f), + mf.GetBacktrace().Push(this->GetStartingContext())), + mf); } } // anonymous namespace diff --git a/Source/cmGccDepfileLexerHelper.cxx b/Source/cmGccDepfileLexerHelper.cxx index 957896f..c782bcd 100644 --- a/Source/cmGccDepfileLexerHelper.cxx +++ b/Source/cmGccDepfileLexerHelper.cxx @@ -27,23 +27,30 @@ bool cmGccDepfileLexerHelper::readFile(const char* filePath) if (!file) { return false; } - newEntry(); + this->newEntry(); yyscan_t scanner; cmGccDepfile_yylex_init(&scanner); cmGccDepfile_yyset_extra(this, scanner); cmGccDepfile_yyrestart(file, scanner); cmGccDepfile_yylex(scanner); cmGccDepfile_yylex_destroy(scanner); - sanitizeContent(); + this->sanitizeContent(); fclose(file); - return true; + return this->HelperState != State::Failed; } void cmGccDepfileLexerHelper::newEntry() { + if (this->HelperState == State::Rule && !this->Content.empty()) { + if (!this->Content.back().rules.empty() && + !this->Content.back().rules.back().empty()) { + this->HelperState = State::Failed; + } + return; + } this->HelperState = State::Rule; this->Content.emplace_back(); - newRule(); + this->newRule(); } void cmGccDepfileLexerHelper::newRule() @@ -56,20 +63,22 @@ void cmGccDepfileLexerHelper::newRule() void cmGccDepfileLexerHelper::newDependency() { - // printf("NEW DEP\n"); + if (this->HelperState == State::Failed) { + return; + } this->HelperState = State::Dependency; - if (this->Content.back().paths.empty() || - !this->Content.back().paths.back().empty()) { - this->Content.back().paths.emplace_back(); + auto& entry = this->Content.back(); + if (entry.paths.empty() || !entry.paths.back().empty()) { + entry.paths.emplace_back(); } } void cmGccDepfileLexerHelper::newRuleOrDependency() { if (this->HelperState == State::Rule) { - newRule(); - } else { - newDependency(); + this->newRule(); + } else if (this->HelperState == State::Dependency) { + this->newDependency(); } } @@ -93,6 +102,8 @@ void cmGccDepfileLexerHelper::addToCurrentPath(const char* s) } dst = &dep->paths.back(); } break; + case State::Failed: + return; } dst->append(s); } diff --git a/Source/cmGccDepfileLexerHelper.h b/Source/cmGccDepfileLexerHelper.h index 07ca61d..91132f5 100644 --- a/Source/cmGccDepfileLexerHelper.h +++ b/Source/cmGccDepfileLexerHelper.h @@ -29,7 +29,8 @@ private: enum class State { Rule, - Dependency + Dependency, + Failed, }; State HelperState = State::Rule; }; diff --git a/Source/cmGccDepfileReader.cxx b/Source/cmGccDepfileReader.cxx index 9d70ede..96a562e 100644 --- a/Source/cmGccDepfileReader.cxx +++ b/Source/cmGccDepfileReader.cxx @@ -4,15 +4,50 @@ #include <type_traits> #include <utility> +#include <vector> + +#include <cm/optional> #include "cmGccDepfileLexerHelper.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" -cmGccDepfileContent cmReadGccDepfile(const char* filePath) +cm::optional<cmGccDepfileContent> cmReadGccDepfile(const char* filePath) { - cmGccDepfileContent result; cmGccDepfileLexerHelper helper; if (helper.readFile(filePath)) { - result = std::move(helper).extractContent(); + return cm::make_optional(std::move(helper).extractContent()); } - return result; + return cm::nullopt; +} + +cm::optional<cmGccDepfileContent> cmReadGccDepfile(const char* filePath, + const std::string& prefix) +{ + auto deps = cmReadGccDepfile(filePath); + + if (prefix.empty() || !deps) { + return deps; + } + + for (auto& dep : *deps) { + for (auto& rule : dep.rules) { + if (!cmSystemTools::FileIsFullPath(rule)) { + rule = cmStrCat(prefix, rule); + } + if (cmSystemTools::FileIsFullPath(rule)) { + rule = cmSystemTools::CollapseFullPath(rule); + } + } + for (auto& path : dep.paths) { + if (!cmSystemTools::FileIsFullPath(path)) { + path = cmStrCat(prefix, path); + } + if (cmSystemTools::FileIsFullPath(path)) { + path = cmSystemTools::CollapseFullPath(path); + } + } + } + + return deps; } diff --git a/Source/cmGccDepfileReader.h b/Source/cmGccDepfileReader.h index 395dd77..66ff75d 100644 --- a/Source/cmGccDepfileReader.h +++ b/Source/cmGccDepfileReader.h @@ -2,6 +2,16 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #pragma once +#include <string> + +#include <cm/optional> + #include "cmGccDepfileReaderTypes.h" -cmGccDepfileContent cmReadGccDepfile(const char* filePath); +cm::optional<cmGccDepfileContent> cmReadGccDepfile(const char* filePath); + +/* + * Read dependencies file and append prefix to all relative paths + */ +cm::optional<cmGccDepfileContent> cmReadGccDepfile(const char* filePath, + const std::string& prefix); diff --git a/Source/cmGeneratorExpressionEvaluationFile.cxx b/Source/cmGeneratorExpressionEvaluationFile.cxx index 9e5023d..af129d3 100644 --- a/Source/cmGeneratorExpressionEvaluationFile.cxx +++ b/Source/cmGeneratorExpressionEvaluationFile.cxx @@ -15,20 +15,21 @@ #include "cmMakefile.h" #include "cmMessageType.h" #include "cmSourceFile.h" -#include "cmSourceFileLocationKind.h" #include "cmSystemTools.h" cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile( std::string input, std::string target, std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr, std::unique_ptr<cmCompiledGeneratorExpression> condition, - bool inputIsContent, cmPolicies::PolicyStatus policyStatusCMP0070) + bool inputIsContent, mode_t permissions, + cmPolicies::PolicyStatus policyStatusCMP0070) : Input(std::move(input)) , Target(std::move(target)) , OutputFileExpr(std::move(outputFileExpr)) , Condition(std::move(condition)) , InputIsContent(inputIsContent) , PolicyStatusCMP0070(policyStatusCMP0070) + , Permissions(permissions) { } @@ -99,11 +100,7 @@ void cmGeneratorExpressionEvaluationFile::CreateOutputFile( for (std::string const& le : enabledLanguages) { std::string const name = this->GetOutputFileName(lg, target, config, le); - cmSourceFile* sf = lg->GetMakefile()->GetOrCreateSource( - name, false, cmSourceFileLocationKind::Known); - // Tell TraceDependencies that the file is not expected to exist - // on disk yet. We generate it after that runs. - sf->SetProperty("GENERATED", "1"); + cmSourceFile* sf = lg->GetMakefile()->GetOrCreateGeneratedSource(name); // Tell the build system generators that there is no build rule // to generate the file. @@ -116,14 +113,15 @@ void cmGeneratorExpressionEvaluationFile::CreateOutputFile( void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg) { - mode_t perm = 0; std::string inputContent; if (this->InputIsContent) { inputContent = this->Input; } else { const std::string inputFileName = this->GetInputFileName(lg); lg->GetMakefile()->AddCMakeDependFile(inputFileName); - cmSystemTools::GetPermissions(inputFileName.c_str(), perm); + if (!this->Permissions) { + cmSystemTools::GetPermissions(inputFileName.c_str(), this->Permissions); + } cmsys::ifstream fin(inputFileName.c_str()); if (!fin) { std::ostringstream e; @@ -157,7 +155,8 @@ void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg) for (std::string const& le : enabledLanguages) { for (std::string const& li : allConfigs) { - this->Generate(lg, li, le, inputExpression.get(), outputFiles, perm); + this->Generate(lg, li, le, inputExpression.get(), outputFiles, + this->Permissions); if (cmSystemTools::GetFatalErrorOccured()) { return; } diff --git a/Source/cmGeneratorExpressionEvaluationFile.h b/Source/cmGeneratorExpressionEvaluationFile.h index 2cd35ae..5ad5e23 100644 --- a/Source/cmGeneratorExpressionEvaluationFile.h +++ b/Source/cmGeneratorExpressionEvaluationFile.h @@ -24,7 +24,8 @@ public: std::string input, std::string target, std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr, std::unique_ptr<cmCompiledGeneratorExpression> condition, - bool inputIsContent, cmPolicies::PolicyStatus policyStatusCMP0070); + bool inputIsContent, mode_t permissions, + cmPolicies::PolicyStatus policyStatusCMP0070); void Generate(cmLocalGenerator* lg); @@ -59,4 +60,5 @@ private: std::vector<std::string> Files; const bool InputIsContent; cmPolicies::PolicyStatus PolicyStatusCMP0070; + mode_t Permissions; }; diff --git a/Source/cmGeneratorExpressionEvaluator.h b/Source/cmGeneratorExpressionEvaluator.h index 3e7737e..af2afd6 100644 --- a/Source/cmGeneratorExpressionEvaluator.h +++ b/Source/cmGeneratorExpressionEvaluator.h @@ -60,7 +60,7 @@ struct TextContent : public cmGeneratorExpressionEvaluator void Extend(size_t length) { this->Length += length; } - size_t GetLength() { return this->Length; } + size_t GetLength() const { return this->Length; } private: const char* Content; diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index 4ca7405..e40316e 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -677,7 +677,7 @@ struct CompilerIdNode : public cmGeneratorExpressionNode } static cmsys::RegularExpression compilerIdValidator("^[A-Za-z0-9_]*$"); - for (auto& param : parameters) { + for (auto const& param : parameters) { if (!compilerIdValidator.find(param)) { reportError(context, content->GetOriginalExpression(), @@ -805,7 +805,7 @@ struct PlatformIdNode : public cmGeneratorExpressionNode return parameters.front().empty() ? "1" : "0"; } - for (auto& param : parameters) { + for (auto const& param : parameters) { if (param == platformId) { return "1"; } @@ -901,7 +901,7 @@ static const struct ConfigurationTestNode : public cmGeneratorExpressionNode return std::string(); } context->HadContextSensitiveCondition = true; - for (auto& param : parameters) { + for (auto const& param : parameters) { if (context->Config.empty()) { if (param.empty()) { return "1"; @@ -927,7 +927,7 @@ static const struct ConfigurationTestNode : public cmGeneratorExpressionNode if (cmProp mapValue = context->CurrentTarget->GetProperty(mapProp)) { cmExpandList(cmSystemTools::UpperCase(*mapValue), mappedConfigs); - for (auto& param : parameters) { + for (auto const& param : parameters) { if (cm::contains(mappedConfigs, cmSystemTools::UpperCase(param))) { return "1"; } @@ -995,7 +995,7 @@ static const struct CompileLanguageNode : public cmGeneratorExpressionNode return context->Language; } - for (auto& param : parameters) { + for (auto const& param : parameters) { if (context->Language == param) { return "1"; } @@ -1101,7 +1101,7 @@ static const struct LinkLanguageNode : public cmGeneratorExpressionNode return context->Language; } - for (auto& param : parameters) { + for (auto const& param : parameters) { if (context->Language == param) { return "1"; } @@ -1129,7 +1129,7 @@ struct LinkerId } static cmsys::RegularExpression linkerIdValidator("^[A-Za-z0-9_]*$"); - for (auto& param : parameters) { + for (auto const& param : parameters) { if (!linkerIdValidator.find(param)) { reportError(context, content->GetOriginalExpression(), "Expression syntax not recognized."); diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index c299dad..e2943d6 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -24,9 +24,7 @@ #include "cmAlgorithms.h" #include "cmComputeLinkInformation.h" -#include "cmCustomCommand.h" #include "cmCustomCommandGenerator.h" -#include "cmCustomCommandLines.h" #include "cmFileTimes.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" @@ -254,7 +252,7 @@ EvaluatedTargetPropertyEntries EvaluateTargetPropertyEntries( { EvaluatedTargetPropertyEntries out; out.Entries.reserve(in.size()); - for (auto& entry : in) { + for (auto const& entry : in) { out.Entries.emplace_back(EvaluateTargetPropertyEntry( thisTarget, config, lang, dagChecker, *entry)); } @@ -332,7 +330,7 @@ cmGeneratorTarget::~cmGeneratorTarget() = default; const std::string& cmGeneratorTarget::GetSourcesProperty() const { std::vector<std::string> values; - for (auto& se : this->SourceEntries) { + for (auto const& se : this->SourceEntries) { values.push_back(se->GetInput()); } static std::string value; @@ -992,9 +990,8 @@ cmProp cmGeneratorTarget::GetLanguageExtensions(std::string const& lang) const bool cmGeneratorTarget::GetLanguageStandardRequired( std::string const& lang) const { - cmProp p = - this->GetPropertyWithPairedLanguageSupport(lang, "_STANDARD_REQUIRED"); - return cmIsOn(p); + return cmIsOn( + this->GetPropertyWithPairedLanguageSupport(lang, "_STANDARD_REQUIRED")); } void cmGeneratorTarget::GetModuleDefinitionSources( @@ -1539,10 +1536,14 @@ bool processSources(cmGeneratorTarget const* tgt, for (std::string& src : entry.Values) { cmSourceFile* sf = mf->GetOrCreateSource(src); std::string e; - std::string fullPath = sf->ResolveFullPath(&e); + std::string w; + std::string fullPath = sf->ResolveFullPath(&e, &w); + cmake* cm = tgt->GetLocalGenerator()->GetCMakeInstance(); + if (!w.empty()) { + cm->IssueMessage(MessageType::AUTHOR_WARNING, w, tgt->GetBacktrace()); + } if (fullPath.empty()) { if (!e.empty()) { - cmake* cm = tgt->GetLocalGenerator()->GetCMakeInstance(); cm->IssueMessage(MessageType::FATAL_ERROR, e, tgt->GetBacktrace()); } return contextDependent; @@ -2500,7 +2501,7 @@ public: } } - bool GetHadLinkLanguageSensitiveCondition() + bool GetHadLinkLanguageSensitiveCondition() const { return HadLinkLanguageSensitiveCondition; } @@ -2888,9 +2889,6 @@ private: bool IsUtility(std::string const& dep); void CheckCustomCommand(cmCustomCommand const& cc); void CheckCustomCommands(const std::vector<cmCustomCommand>& commands); - void FollowCommandDepends(cmCustomCommand const& cc, - const std::string& config, - std::set<std::string>& emitted); }; cmTargetTraceDependencies::cmTargetTraceDependencies(cmGeneratorTarget* target) @@ -2986,7 +2984,8 @@ void cmTargetTraceDependencies::FollowName(std::string const& name) auto i = this->NameMap.lower_bound(name); if (i == this->NameMap.end() || i->first != name) { // Check if we know how to generate this file. - cmSourcesWithOutput sources = this->Makefile->GetSourcesWithOutput(name); + cmSourcesWithOutput sources = + this->LocalGenerator->GetSourcesWithOutput(name); // If we failed to find a target or source and we have a relative path, it // might be a valid source if made relative to the current binary // directory. @@ -2996,7 +2995,7 @@ void cmTargetTraceDependencies::FollowName(std::string const& name) cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', name); fullname = cmSystemTools::CollapseFullPath( fullname, this->Makefile->GetHomeOutputDirectory()); - sources = this->Makefile->GetSourcesWithOutput(fullname); + sources = this->LocalGenerator->GetSourcesWithOutput(fullname); } i = this->NameMap.emplace_hint(i, name, sources); } @@ -3065,7 +3064,7 @@ bool cmTargetTraceDependencies::IsUtility(std::string const& dep) } else { // The original name of the dependency was not a full path. It // must name a target, so add the target-level dependency. - this->GeneratorTarget->Target->AddUtility(util, false); + this->GeneratorTarget->Target->AddUtility(util, true); return true; } } @@ -3076,71 +3075,28 @@ bool cmTargetTraceDependencies::IsUtility(std::string const& dep) void cmTargetTraceDependencies::CheckCustomCommand(cmCustomCommand const& cc) { - // Transform command names that reference targets built in this - // project to corresponding target-level dependencies. - cmGeneratorExpression ge(cc.GetBacktrace()); - - // Add target-level dependencies referenced by generator expressions. - std::set<cmGeneratorTarget*> targets; - - for (cmCustomCommandLine const& cCmdLine : cc.GetCommandLines()) { - std::string const& command = cCmdLine.front(); - // Check for a target with this name. - if (cmGeneratorTarget* t = - this->LocalGenerator->FindGeneratorTargetToUse(command)) { - if (t->GetType() == cmStateEnums::EXECUTABLE) { - // The command refers to an executable target built in - // this project. Add the target-level dependency to make - // sure the executable is up to date before this custom - // command possibly runs. - this->GeneratorTarget->Target->AddUtility(command, true); + // Collect dependencies referenced by all configurations. + std::set<std::string> depends; + for (std::string const& config : + this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig)) { + for (cmCustomCommandGenerator const& ccg : + this->LocalGenerator->MakeCustomCommandGenerators(cc, config)) { + // Collect target-level dependencies referenced in command lines. + for (auto const& util : ccg.GetUtilities()) { + this->GeneratorTarget->Target->AddUtility(util); } - } - // Check for target references in generator expressions. - std::vector<std::string> const& configs = - this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); - for (std::string const& c : configs) { - for (std::string const& cl : cCmdLine) { - const std::unique_ptr<cmCompiledGeneratorExpression> cge = - ge.Parse(cl); - cge->SetQuiet(true); - cge->Evaluate(this->GeneratorTarget->GetLocalGenerator(), c); - std::set<cmGeneratorTarget*> geTargets = cge->GetTargets(); - targets.insert(geTargets.begin(), geTargets.end()); - } + // Collect file-level dependencies referenced in DEPENDS. + depends.insert(ccg.GetDepends().begin(), ccg.GetDepends().end()); } } - for (cmGeneratorTarget* target : targets) { - this->GeneratorTarget->Target->AddUtility(target->GetName(), true); - } - - // Queue the custom command dependencies. - std::set<std::string> emitted; - std::vector<std::string> const& configs = - this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); - for (std::string const& conf : configs) { - this->FollowCommandDepends(cc, conf, emitted); - } -} - -void cmTargetTraceDependencies::FollowCommandDepends( - cmCustomCommand const& cc, const std::string& config, - std::set<std::string>& emitted) -{ - cmCustomCommandGenerator ccg(cc, config, - this->GeneratorTarget->LocalGenerator); - - const std::vector<std::string>& depends = ccg.GetDepends(); - + // Queue file-level dependencies. for (std::string const& dep : depends) { - if (emitted.insert(dep).second) { - if (!this->IsUtility(dep)) { - // The dependency does not name a target and may be a file we - // know how to generate. Queue it. - this->FollowName(dep); - } + if (!this->IsUtility(dep)) { + // The dependency does not name a target and may be a file we + // know how to generate. Queue it. + this->FollowName(dep); } } } @@ -3200,6 +3156,30 @@ void cmGeneratorTarget::GetAppleArchs(const std::string& config, } } +void cmGeneratorTarget::AddExplicitLanguageFlags(std::string& flags, + cmSourceFile const& sf) const +{ + cmProp lang = sf.GetProperty("LANGUAGE"); + if (!lang) { + return; + } + + switch (this->GetPolicyStatusCMP0119()) { + case cmPolicies::WARN: + case cmPolicies::OLD: + // The OLD behavior is to not add explicit language flags. + return; + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::NEW: + // The NEW behavior is to add explicit language flags. + break; + } + + this->LocalGenerator->AppendFeatureOptions(flags, *lang, + "EXPLICIT_LANGUAGE"); +} + void cmGeneratorTarget::AddCUDAArchitectureFlags(std::string& flags) const { const std::string& property = this->GetSafeProperty("CUDA_ARCHITECTURES"); @@ -7059,7 +7039,7 @@ const cmLinkImplementation* cmGeneratorTarget::GetLinkImplementation( return &impl; } -bool cmGeneratorTarget::GetConfigCommonSourceFiles( +bool cmGeneratorTarget::GetConfigCommonSourceFilesForXcode( std::vector<cmSourceFile*>& files) const { std::vector<std::string> const& configs = diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index 2517b72..51369c2 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -430,8 +430,9 @@ public: /** Get source files common to all configurations and diagnose cases with per-config sources. Excludes sources added by a TARGET_OBJECTS - generator expression. */ - bool GetConfigCommonSourceFiles(std::vector<cmSourceFile*>& files) const; + generator expression. Do not use outside the Xcode generator. */ + bool GetConfigCommonSourceFilesForXcode( + std::vector<cmSourceFile*>& files) const; bool HaveBuildTreeRPATH(const std::string& config) const; @@ -447,6 +448,9 @@ public: void GetAppleArchs(const std::string& config, std::vector<std::string>& archVec) const; + void AddExplicitLanguageFlags(std::string& flags, + cmSourceFile const& sf) const; + void AddCUDAArchitectureFlags(std::string& flags) const; void AddCUDAToolkitFlags(std::string& flags) const; diff --git a/Source/cmGetDirectoryPropertyCommand.cxx b/Source/cmGetDirectoryPropertyCommand.cxx index c2098c0..7fbd479 100644 --- a/Source/cmGetDirectoryPropertyCommand.cxx +++ b/Source/cmGetDirectoryPropertyCommand.cxx @@ -50,6 +50,10 @@ bool cmGetDirectoryPropertyCommand(std::vector<std::string> const& args, return false; } ++i; + if (i == args.end()) { + status.SetError("called with incorrect number of arguments"); + return false; + } } // OK, now we have the directory to process, we just get the requested @@ -67,27 +71,30 @@ bool cmGetDirectoryPropertyCommand(std::vector<std::string> const& args, return true; } + if (i->empty()) { + status.SetError("given empty string for the property name to get"); + return false; + } + const char* prop = nullptr; - if (!i->empty()) { - if (*i == "DEFINITIONS") { - switch (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0059)) { - case cmPolicies::WARN: - status.GetMakefile().IssueMessage( - MessageType::AUTHOR_WARNING, - cmPolicies::GetPolicyWarning(cmPolicies::CMP0059)); - CM_FALLTHROUGH; - case cmPolicies::OLD: - StoreResult(status.GetMakefile(), variable, - status.GetMakefile().GetDefineFlagsCMP0059()); - return true; - case cmPolicies::NEW: - case cmPolicies::REQUIRED_ALWAYS: - case cmPolicies::REQUIRED_IF_USED: - break; - } + if (*i == "DEFINITIONS") { + switch (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0059)) { + case cmPolicies::WARN: + status.GetMakefile().IssueMessage( + MessageType::AUTHOR_WARNING, + cmPolicies::GetPolicyWarning(cmPolicies::CMP0059)); + CM_FALLTHROUGH; + case cmPolicies::OLD: + StoreResult(status.GetMakefile(), variable, + status.GetMakefile().GetDefineFlagsCMP0059()); + return true; + case cmPolicies::NEW: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::REQUIRED_IF_USED: + break; } - prop = cmToCStr(dir->GetProperty(*i)); } + prop = cmToCStr(dir->GetProperty(*i)); StoreResult(status.GetMakefile(), variable, prop); return true; } diff --git a/Source/cmGetPropertyCommand.cxx b/Source/cmGetPropertyCommand.cxx index 3a5b39d..cb657f9 100644 --- a/Source/cmGetPropertyCommand.cxx +++ b/Source/cmGetPropertyCommand.cxx @@ -172,7 +172,7 @@ bool cmGetPropertyCommand(std::vector<std::string> const& args, std::vector<cmMakefile*> source_file_directory_makefiles; bool file_scopes_handled = - SetPropertyCommand::HandleAndValidateSourceFileDirectortoryScopes( + SetPropertyCommand::HandleAndValidateSourceFileDirectoryScopes( status, source_file_directory_option_enabled, source_file_target_option_enabled, source_file_directories, source_file_target_directories, source_file_directory_makefiles); @@ -280,8 +280,9 @@ bool HandleGlobalMode(cmExecutionStatus& status, const std::string& name, // Get the property. cmake* cm = status.GetMakefile().GetCMakeInstance(); - cmProp p = cm->GetState()->GetGlobalProperty(propertyName); - return StoreResult(infoType, status.GetMakefile(), variable, cmToCStr(p)); + return StoreResult( + infoType, status.GetMakefile(), variable, + cmToCStr(cm->GetState()->GetGlobalProperty(propertyName))); } bool HandleDirectoryMode(cmExecutionStatus& status, const std::string& name, @@ -327,8 +328,8 @@ bool HandleDirectoryMode(cmExecutionStatus& status, const std::string& name, } // Get the property. - cmProp p = mf->GetProperty(propertyName); - return StoreResult(infoType, status.GetMakefile(), variable, cmToCStr(p)); + return StoreResult(infoType, status.GetMakefile(), variable, + cmToCStr(mf->GetProperty(propertyName))); } bool HandleTargetMode(cmExecutionStatus& status, const std::string& name, @@ -358,15 +359,14 @@ bool HandleTargetMode(cmExecutionStatus& status, const std::string& name, } return StoreResult(infoType, status.GetMakefile(), variable, nullptr); } - cmProp prop_cstr = nullptr; cmListFileBacktrace bt = status.GetMakefile().GetBacktrace(); cmMessenger* messenger = status.GetMakefile().GetMessenger(); - prop_cstr = target->GetComputedProperty(propertyName, messenger, bt); - if (!prop_cstr) { - prop_cstr = target->GetProperty(propertyName); + cmProp prop = target->GetComputedProperty(propertyName, messenger, bt); + if (!prop) { + prop = target->GetProperty(propertyName); } return StoreResult(infoType, status.GetMakefile(), variable, - prop_cstr ? prop_cstr->c_str() : nullptr); + cmToCStr(prop)); } status.SetError(cmStrCat("could not find TARGET ", name, ". Perhaps it has not yet been created.")); @@ -391,7 +391,7 @@ bool HandleSourceMode(cmExecutionStatus& status, const std::string& name, if (cmSourceFile* sf = directory_makefile.GetOrCreateSource(source_file_absolute_path)) { return StoreResult(infoType, status.GetMakefile(), variable, - sf->GetPropertyForUser(propertyName)); + cmToCStr(sf->GetPropertyForUser(propertyName))); } status.SetError( cmStrCat("given SOURCE name that could not be found or created: ", diff --git a/Source/cmGetSourceFilePropertyCommand.cxx b/Source/cmGetSourceFilePropertyCommand.cxx index 5395bc8..5301b66 100644 --- a/Source/cmGetSourceFilePropertyCommand.cxx +++ b/Source/cmGetSourceFilePropertyCommand.cxx @@ -4,6 +4,7 @@ #include "cmExecutionStatus.h" #include "cmMakefile.h" +#include "cmProperty.h" #include "cmSetPropertyCommand.h" #include "cmSourceFile.h" @@ -34,7 +35,7 @@ bool cmGetSourceFilePropertyCommand(std::vector<std::string> const& args, std::vector<cmMakefile*> source_file_directory_makefiles; bool file_scopes_handled = - SetPropertyCommand::HandleAndValidateSourceFileDirectortoryScopes( + SetPropertyCommand::HandleAndValidateSourceFileDirectoryScopes( status, source_file_directory_option_enabled, source_file_target_option_enabled, source_file_directories, source_file_target_directories, source_file_directory_makefiles); @@ -57,14 +58,14 @@ bool cmGetSourceFilePropertyCommand(std::vector<std::string> const& args, } if (sf) { - const char* prop = nullptr; + cmProp prop = nullptr; if (!args[property_arg_index].empty()) { prop = sf->GetPropertyForUser(args[property_arg_index]); } if (prop) { // Set the value on the original Makefile scope, not the scope of the // requested directory. - status.GetMakefile().AddDefinition(var, prop); + status.GetMakefile().AddDefinition(var, *prop); return true; } } diff --git a/Source/cmGhsMultiTargetGenerator.cxx b/Source/cmGhsMultiTargetGenerator.cxx index a8f8f57..aabe43c 100644 --- a/Source/cmGhsMultiTargetGenerator.cxx +++ b/Source/cmGhsMultiTargetGenerator.cxx @@ -721,8 +721,7 @@ void cmGhsMultiTargetGenerator::WriteObjectLangOverride( bool cmGhsMultiTargetGenerator::DetermineIfIntegrityApp() { - cmProp p = this->GeneratorTarget->GetProperty("ghs_integrity_app"); - if (p) { + if (cmProp p = this->GeneratorTarget->GetProperty("ghs_integrity_app")) { return cmIsOn(*p); } std::vector<cmSourceFile*> sources; @@ -763,9 +762,9 @@ bool cmGhsMultiTargetGenerator::VisitCustomCommand( /* set temporary mark; check if revisit*/ if (temp.insert(si).second) { for (auto& di : si->GetCustomCommand()->GetDepends()) { - cmSourceFile const* sf = this->GeneratorTarget->GetLocalGenerator() - ->GetMakefile() - ->GetSourceFileWithOutput(di); + cmSourceFile const* sf = + this->GeneratorTarget->GetLocalGenerator()->GetSourceFileWithOutput( + di); /* if sf exists then visit */ if (sf && this->VisitCustomCommand(temp, perm, order, sf)) { return true; diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index fc40d63..63aaf27 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -1821,6 +1821,7 @@ void cmGlobalGenerator::ClearGeneratorMembers() this->RuleHashes.clear(); this->DirectoryContentMap.clear(); this->BinaryDirectories.clear(); + this->GeneratedFiles.clear(); } void cmGlobalGenerator::ComputeTargetObjectDirectory( @@ -2146,6 +2147,16 @@ void cmGlobalGenerator::AddInstallComponent(const std::string& component) } } +void cmGlobalGenerator::MarkAsGeneratedFile(const std::string& filepath) +{ + this->GeneratedFiles.insert(filepath); +} + +bool cmGlobalGenerator::IsGeneratedFile(const std::string& filepath) +{ + return this->GeneratedFiles.find(filepath) != this->GeneratedFiles.end(); +} + void cmGlobalGenerator::EnableInstallTarget() { this->InstallTargetEnabled = true; @@ -2608,7 +2619,7 @@ void cmGlobalGenerator::AddGlobalTarget_Test( } void cmGlobalGenerator::AddGlobalTarget_EditCache( - std::vector<GlobalTargetInfo>& targets) + std::vector<GlobalTargetInfo>& targets) const { const char* editCacheTargetName = this->GetEditCacheTargetName(); if (!editCacheTargetName) { @@ -2642,7 +2653,7 @@ void cmGlobalGenerator::AddGlobalTarget_EditCache( } void cmGlobalGenerator::AddGlobalTarget_RebuildCache( - std::vector<GlobalTargetInfo>& targets) + std::vector<GlobalTargetInfo>& targets) const { const char* rebuildCacheTargetName = this->GetRebuildCacheTargetName(); if (!rebuildCacheTargetName) { @@ -2765,7 +2776,7 @@ void cmGlobalGenerator::AddGlobalTarget_Install( } } -std::string cmGlobalGenerator::GetPredefinedTargetsFolder() +std::string cmGlobalGenerator::GetPredefinedTargetsFolder() const { cmProp prop = this->GetCMakeInstance()->GetState()->GetGlobalProperty( "PREDEFINED_TARGETS_FOLDER"); diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index c106258..69373bd 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -11,9 +11,11 @@ #include <set> #include <string> #include <unordered_map> +#include <unordered_set> #include <utility> #include <vector> +#include <cm/optional> #include <cmext/algorithm> #include "cm_codecvt.hxx" @@ -26,6 +28,7 @@ #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetDepend.h" +#include "cmTransformDepfile.h" #if !defined(CMAKE_BOOTSTRAP) # include <cm3p/json/value.h> @@ -288,6 +291,11 @@ public: void AddInstallComponent(const std::string& component); + /** Mark the (absolute path to a) file as generated. */ + void MarkAsGeneratedFile(const std::string& filepath); + /** Determine if the absolute filepath belongs to a generated file. */ + bool IsGeneratedFile(const std::string& filepath); + const std::set<std::string>* GetInstallComponents() const { return &this->InstallComponents; @@ -452,6 +460,10 @@ public: virtual bool ShouldStripResourcePath(cmMakefile*) const; virtual bool SupportsCustomCommandDepfile() const { return false; } + virtual cm::optional<cmDepfileFormat> DepfileFormat() const + { + return cm::nullopt; + } std::string GetSharedLibFlagsForLanguage(std::string const& lang) const; @@ -568,8 +580,9 @@ protected: void AddGlobalTarget_Package(std::vector<GlobalTargetInfo>& targets); void AddGlobalTarget_PackageSource(std::vector<GlobalTargetInfo>& targets); void AddGlobalTarget_Test(std::vector<GlobalTargetInfo>& targets); - void AddGlobalTarget_EditCache(std::vector<GlobalTargetInfo>& targets); - void AddGlobalTarget_RebuildCache(std::vector<GlobalTargetInfo>& targets); + void AddGlobalTarget_EditCache(std::vector<GlobalTargetInfo>& targets) const; + void AddGlobalTarget_RebuildCache( + std::vector<GlobalTargetInfo>& targets) const; void AddGlobalTarget_Install(std::vector<GlobalTargetInfo>& targets); cmTarget CreateGlobalTarget(GlobalTargetInfo const& gti, cmMakefile* mf); @@ -595,7 +608,7 @@ protected: cmGeneratorTarget* FindGeneratorTargetImpl(std::string const& name) const; - std::string GetPredefinedTargetsFolder(); + std::string GetPredefinedTargetsFolder() const; enum class FindMakeProgramStage { @@ -726,6 +739,8 @@ private: std::map<std::string, std::string> RealPaths; + std::unordered_set<std::string> GeneratedFiles; + #if !defined(CMAKE_BOOTSTRAP) // Pool of file locks cmFileLockPool FileLockPool; diff --git a/Source/cmGlobalGhsMultiGenerator.cxx b/Source/cmGlobalGhsMultiGenerator.cxx index c08c9cf..33bf830 100644 --- a/Source/cmGlobalGhsMultiGenerator.cxx +++ b/Source/cmGlobalGhsMultiGenerator.cxx @@ -704,7 +704,7 @@ bool cmGlobalGhsMultiGenerator::ComputeTargetBuildOrder( std::set<cmGeneratorTarget const*> temp; std::set<cmGeneratorTarget const*> perm; - for (auto ti : tgt) { + for (auto const ti : tgt) { bool r = VisitTarget(temp, perm, build, ti); if (r) { return r; @@ -726,7 +726,7 @@ bool cmGlobalGhsMultiGenerator::VisitTarget( * in the same order */ OrderedTargetDependSet sortedTargets(this->GetTargetDirectDepends(ti), ""); - for (auto& di : sortedTargets) { + for (auto const& di : sortedTargets) { if (this->VisitTarget(temp, perm, order, di)) { return true; } diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 02ffaf7..a098f81 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -34,6 +34,7 @@ #include "cmOutputConverter.h" #include "cmProperty.h" #include "cmRange.h" +#include "cmScanDepFormat.h" #include "cmState.h" #include "cmStateDirectory.h" #include "cmStateSnapshot.h" @@ -55,6 +56,52 @@ std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd ."; std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":"; #endif +bool operator==( + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs, + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs) +{ + return lhs.Target == rhs.Target && lhs.Config == rhs.Config && + lhs.GenexOutput == rhs.GenexOutput; +} + +bool operator!=( + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs, + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs) +{ + return !(lhs == rhs); +} + +bool operator<( + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs, + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs) +{ + return lhs.Target < rhs.Target || + (lhs.Target == rhs.Target && + (lhs.Config < rhs.Config || + (lhs.Config == rhs.Config && lhs.GenexOutput < rhs.GenexOutput))); +} + +bool operator>( + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs, + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs) +{ + return rhs < lhs; +} + +bool operator<=( + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs, + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs) +{ + return !(lhs > rhs); +} + +bool operator>=( + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs, + const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs) +{ + return rhs <= lhs; +} + void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count) { for (int i = 0; i < count; ++i) { @@ -1056,7 +1103,7 @@ void cmGlobalNinjaGenerator::CloseCompileCommandsStream() } } -void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os) +void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os) const { os << "# CMAKE generated file: DO NOT EDIT!\n" << "# Generated by \"" << this->GetName() << "\"" @@ -1087,7 +1134,7 @@ std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget( void cmGlobalNinjaGenerator::AppendTargetOutputs( cmGeneratorTarget const* target, cmNinjaDeps& outputs, - const std::string& config, cmNinjaTargetDepends depends) + const std::string& config, cmNinjaTargetDepends depends) const { // for frameworks, we want the real name, not smple name // frameworks always appear versioned, and the build.ninja @@ -1193,23 +1240,30 @@ void cmGlobalNinjaGenerator::AppendTargetDepends( void cmGlobalNinjaGenerator::AppendTargetDependsClosure( cmGeneratorTarget const* target, cmNinjaDeps& outputs, - const std::string& config) + const std::string& config, const std::string& fileConfig, bool genexOutput) { cmNinjaOuts outs; - this->AppendTargetDependsClosure(target, outs, config, true); + this->AppendTargetDependsClosure(target, outs, config, fileConfig, + genexOutput, true); cm::append(outputs, outs); } void cmGlobalNinjaGenerator::AppendTargetDependsClosure( cmGeneratorTarget const* target, cmNinjaOuts& outputs, - const std::string& config, bool omit_self) + const std::string& config, const std::string& fileConfig, bool genexOutput, + bool omit_self) { // try to locate the target in the cache - auto find = this->Configs[config].TargetDependsClosures.lower_bound(target); + ByConfig::TargetDependsClosureKey key{ + target, + config, + genexOutput, + }; + auto find = this->Configs[fileConfig].TargetDependsClosures.lower_bound(key); - if (find == this->Configs[config].TargetDependsClosures.end() || - find->first != target) { + if (find == this->Configs[fileConfig].TargetDependsClosures.end() || + find->first != key) { // We now calculate the closure outputs by inspecting the dependent // targets recursively. // For that we have to distinguish between a local result set that is only @@ -1219,18 +1273,27 @@ void cmGlobalNinjaGenerator::AppendTargetDependsClosure( cmNinjaOuts this_outs; // this will be the new cache entry for (auto const& dep_target : this->GetTargetDirectDepends(target)) { - if (!dep_target->IsInBuildSystem() || - (target->GetType() != cmStateEnums::UTILITY && - dep_target->GetType() != cmStateEnums::UTILITY && - this->EnableCrossConfigBuild() && !dep_target.IsCross())) { + if (!dep_target->IsInBuildSystem()) { continue; } - // Collect the dependent targets for _this_ target - this->AppendTargetDependsClosure(dep_target, this_outs, config, false); + if (!this->IsSingleConfigUtility(target) && + !this->IsSingleConfigUtility(dep_target) && + this->EnableCrossConfigBuild() && !dep_target.IsCross() && + !genexOutput) { + continue; + } + + if (dep_target.IsCross()) { + this->AppendTargetDependsClosure(dep_target, this_outs, fileConfig, + fileConfig, genexOutput, false); + } else { + this->AppendTargetDependsClosure(dep_target, this_outs, config, + fileConfig, genexOutput, false); + } } - find = this->Configs[config].TargetDependsClosures.emplace_hint( - find, target, std::move(this_outs)); + find = this->Configs[fileConfig].TargetDependsClosures.emplace_hint( + find, key, std::move(this_outs)); } // now fill the outputs of the final result from the newly generated cache @@ -2016,6 +2079,8 @@ void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix( cmStripSuffixIfExists(path, this->OutputPathPrefix); } +#if !defined(CMAKE_BOOTSTRAP) + /* We use the following approach to support Fortran. Each target already @@ -2095,16 +2160,6 @@ Compilation of source files within a target is split into the following steps: (because the latter consumes the module). */ -struct cmSourceInfo -{ - // Set of provided and required modules. - std::set<std::string> Provides; - std::set<std::string> Requires; - - // Set of files included in the translation unit. - std::set<std::string> Includes; -}; - static std::unique_ptr<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran( std::string const& arg_tdi, std::string const& arg_pp); @@ -2112,6 +2167,7 @@ int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg, std::vector<std::string>::const_iterator argEnd) { std::string arg_tdi; + std::string arg_src; std::string arg_pp; std::string arg_dep; std::string arg_obj; @@ -2120,6 +2176,8 @@ int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg, for (std::string const& arg : cmMakeRange(argBeg, argEnd)) { if (cmHasLiteralPrefix(arg, "--tdi=")) { arg_tdi = arg.substr(6); + } else if (cmHasLiteralPrefix(arg, "--src=")) { + arg_src = arg.substr(6); } else if (cmHasLiteralPrefix(arg, "--pp=")) { arg_pp = arg.substr(5); } else if (cmHasLiteralPrefix(arg, "--dep=")) { @@ -2160,6 +2218,9 @@ int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg, cmSystemTools::Error("-E cmake_ninja_depends requires value for --lang="); return 1; } + if (arg_src.empty()) { + arg_src = cmStrCat("<", arg_obj, " input file>"); + } std::unique_ptr<cmSourceInfo> info; if (arg_lang == "Fortran") { @@ -2176,6 +2237,8 @@ int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg, return 1; } + info->PrimaryOutput = arg_obj; + { cmGeneratedFileStream depfile(arg_dep); depfile << cmSystemTools::ConvertToUnixOutputPath(arg_pp) << ":"; @@ -2185,24 +2248,7 @@ int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg, depfile << "\n"; } - Json::Value ddi(Json::objectValue); - ddi["object"] = arg_obj; - - Json::Value& ddi_provides = ddi["provides"] = Json::arrayValue; - for (std::string const& provide : info->Provides) { - ddi_provides.append(provide); - } - Json::Value& ddi_requires = ddi["requires"] = Json::arrayValue; - for (std::string const& r : info->Requires) { - // Require modules not provided in the same source. - if (!info->Provides.count(r)) { - ddi_requires.append(r); - } - } - - cmGeneratedFileStream ddif(arg_ddi); - ddif << ddi; - if (!ddif) { + if (!cmScanDepFormat_P1689_Write(arg_ddi, arg_src, *info)) { cmSystemTools::Error( cmStrCat("-E cmake_ninja_depends failed to write ", arg_ddi)); return 1; @@ -2260,19 +2306,28 @@ std::unique_ptr<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran( } auto info = cm::make_unique<cmSourceInfo>(); - info->Provides = finfo.Provides; - info->Requires = finfo.Requires; - info->Includes = finfo.Includes; + for (std::string const& provide : finfo.Provides) { + cmSourceReqInfo src_info; + src_info.LogicalName = provide; + src_info.CompiledModulePath = provide; + info->Provides.emplace_back(src_info); + } + for (std::string const& require : finfo.Requires) { + // Require modules not provided in the same source. + if (finfo.Provides.count(require)) { + continue; + } + cmSourceReqInfo src_info; + src_info.LogicalName = require; + src_info.CompiledModulePath = require; + info->Requires.emplace_back(src_info); + } + for (std::string const& include : finfo.Includes) { + info->Includes.push_back(include); + } return info; } -struct cmDyndepObjectInfo -{ - std::string Object; - std::vector<std::string> Provides; - std::vector<std::string> Requires; -}; - bool cmGlobalNinjaGenerator::WriteDyndepFile( std::string const& dir_top_src, std::string const& dir_top_bld, std::string const& dir_cur_src, std::string const& dir_cur_bld, @@ -2294,34 +2349,14 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( this->LocalGenerators.push_back(std::move(lgd)); } - std::vector<cmDyndepObjectInfo> objects; + std::vector<cmSourceInfo> objects; for (std::string const& arg_ddi : arg_ddis) { - // Load the ddi file and compute the module file paths it provides. - Json::Value ddio; - Json::Value const& ddi = ddio; - cmsys::ifstream ddif(arg_ddi.c_str(), std::ios::in | std::ios::binary); - Json::Reader reader; - if (!reader.parse(ddif, ddio, false)) { - cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", - arg_ddi, - reader.getFormattedErrorMessages())); + cmSourceInfo info; + if (!cmScanDepFormat_P1689_Parse(arg_ddi, &info)) { + cmSystemTools::Error( + cmStrCat("-E cmake_ninja_dyndep failed to parse ddi file ", arg_ddi)); return false; } - - cmDyndepObjectInfo info; - info.Object = ddi["object"].asString(); - Json::Value const& ddi_provides = ddi["provides"]; - if (ddi_provides.isArray()) { - for (auto const& ddi_provide : ddi_provides) { - info.Provides.push_back(ddi_provide.asString()); - } - } - Json::Value const& ddi_requires = ddi["requires"]; - if (ddi_requires.isArray()) { - for (auto const& ddi_require : ddi_requires) { - info.Requires.push_back(ddi_require.asString()); - } - } objects.push_back(std::move(info)); } @@ -2352,11 +2387,12 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( // We do this after loading the modules provided by linked targets // in case we have one of the same name that must be preferred. Json::Value tm = Json::objectValue; - for (cmDyndepObjectInfo const& object : objects) { - for (std::string const& p : object.Provides) { - std::string const mod = cmStrCat(module_dir, p); - mod_files[p] = mod; - tm[p] = mod; + for (cmSourceInfo const& object : objects) { + for (auto const& p : object.Provides) { + std::string const mod = cmStrCat( + module_dir, cmSystemTools::GetFilenameName(p.CompiledModulePath)); + mod_files[p.LogicalName] = mod; + tm[p.LogicalName] = mod; } } @@ -2366,15 +2402,16 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( { cmNinjaBuild build("dyndep"); build.Outputs.emplace_back(""); - for (cmDyndepObjectInfo const& object : objects) { - build.Outputs[0] = object.Object; + for (cmSourceInfo const& object : objects) { + build.Outputs[0] = this->ConvertToNinjaPath(object.PrimaryOutput); build.ImplicitOuts.clear(); - for (std::string const& p : object.Provides) { - build.ImplicitOuts.push_back(this->ConvertToNinjaPath(mod_files[p])); + for (auto const& p : object.Provides) { + build.ImplicitOuts.push_back( + this->ConvertToNinjaPath(mod_files[p.LogicalName])); } build.ImplicitDeps.clear(); - for (std::string const& r : object.Requires) { - auto mit = mod_files.find(r); + for (auto const& r : object.Requires) { + auto mit = mod_files.find(r.LogicalName); if (mit != mod_files.end()) { build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second)); } @@ -2398,11 +2435,6 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( return true; } -bool cmGlobalNinjaGenerator::EnableCrossConfigBuild() const -{ - return !this->CrossConfigs.empty(); -} - int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg, std::vector<std::string>::const_iterator argEnd) { @@ -2484,6 +2516,13 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg, return 0; } +#endif + +bool cmGlobalNinjaGenerator::EnableCrossConfigBuild() const +{ + return !this->CrossConfigs.empty(); +} + void cmGlobalNinjaGenerator::AppendDirectoryForConfig( const std::string& prefix, const std::string& config, const std::string& suffix, std::string& dir) @@ -2501,6 +2540,13 @@ std::set<std::string> cmGlobalNinjaGenerator::GetCrossConfigs( return result; } +bool cmGlobalNinjaGenerator::IsSingleConfigUtility( + cmGeneratorTarget const* target) const +{ + return target->GetType() == cmStateEnums::UTILITY && + !this->PerConfigUtilityTargets.count(target->GetName()); +} + const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE = "CMakeFiles/common.ninja"; const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja"; @@ -2651,30 +2697,17 @@ void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs( bool cmGlobalNinjaMultiGenerator::InspectConfigTypeVariables() { - this->GetCMakeInstance()->MarkCliAsUsed("CMAKE_DEFAULT_BUILD_TYPE"); - this->GetCMakeInstance()->MarkCliAsUsed("CMAKE_CROSS_CONFIGS"); - this->GetCMakeInstance()->MarkCliAsUsed("CMAKE_DEFAULT_CONFIGS"); - return this->ReadCacheEntriesForBuild(*this->Makefiles.front()->GetState()); -} - -std::string cmGlobalNinjaMultiGenerator::GetDefaultBuildConfig() const -{ - return ""; -} - -bool cmGlobalNinjaMultiGenerator::ReadCacheEntriesForBuild( - const cmState& state) -{ std::vector<std::string> configsVec; - cmExpandList(state.GetSafeCacheEntryValue("CMAKE_CONFIGURATION_TYPES"), - configsVec); + cmExpandList( + this->Makefiles.front()->GetSafeDefinition("CMAKE_CONFIGURATION_TYPES"), + configsVec); if (configsVec.empty()) { configsVec.emplace_back(); } std::set<std::string> configs(configsVec.cbegin(), configsVec.cend()); this->DefaultFileConfig = - state.GetSafeCacheEntryValue("CMAKE_DEFAULT_BUILD_TYPE"); + this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_BUILD_TYPE"); if (this->DefaultFileConfig.empty()) { this->DefaultFileConfig = configsVec.front(); } @@ -2689,8 +2722,9 @@ bool cmGlobalNinjaMultiGenerator::ReadCacheEntriesForBuild( } std::vector<std::string> crossConfigsVec; - cmExpandList(state.GetSafeCacheEntryValue("CMAKE_CROSS_CONFIGS"), - crossConfigsVec); + cmExpandList( + this->Makefiles.front()->GetSafeDefinition("CMAKE_CROSS_CONFIGS"), + crossConfigsVec); auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsVec); if (!crossConfigs) { std::ostringstream msg; @@ -2703,7 +2737,7 @@ bool cmGlobalNinjaMultiGenerator::ReadCacheEntriesForBuild( this->CrossConfigs = *crossConfigs; auto defaultConfigsString = - state.GetSafeCacheEntryValue("CMAKE_DEFAULT_CONFIGS"); + this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_CONFIGS"); if (defaultConfigsString.empty()) { defaultConfigsString = this->DefaultFileConfig; } @@ -2737,6 +2771,11 @@ bool cmGlobalNinjaMultiGenerator::ReadCacheEntriesForBuild( return true; } +std::string cmGlobalNinjaMultiGenerator::GetDefaultBuildConfig() const +{ + return ""; +} + std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTarget( cmGeneratorTarget const* target, const std::string& config) const { diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h index b668773..5e9defe 100644 --- a/Source/cmGlobalNinjaGenerator.h +++ b/Source/cmGlobalNinjaGenerator.h @@ -24,6 +24,7 @@ #include "cmNinjaTypes.h" #include "cmPolicies.h" #include "cmStringAlgorithms.h" +#include "cmTransformDepfile.h" class cmCustomCommand; class cmGeneratorTarget; @@ -31,7 +32,6 @@ class cmLinkLineComputer; class cmLocalGenerator; class cmMakefile; class cmOutputConverter; -class cmState; class cmStateDirectory; class cmake; struct cmDocumentationEntry; @@ -211,6 +211,10 @@ public: const char* GetCleanTargetName() const override { return "clean"; } bool SupportsCustomCommandDepfile() const override { return true; } + cm::optional<cmDepfileFormat> DepfileFormat() const override + { + return cmDepfileFormat::GccDepfile; + } virtual cmGeneratedFileStream* GetImplFileStream( const std::string& /*config*/) const @@ -248,7 +252,7 @@ public: : GG(gg) { } - std::string operator()(std::string const& path) + std::string operator()(std::string const& path) const { return this->GG->ConvertToNinjaPath(path); } @@ -319,17 +323,21 @@ public: void AppendTargetOutputs(cmGeneratorTarget const* target, cmNinjaDeps& outputs, const std::string& config, - cmNinjaTargetDepends depends); + cmNinjaTargetDepends depends) const; void AppendTargetDepends(cmGeneratorTarget const* target, cmNinjaDeps& outputs, const std::string& config, const std::string& fileConfig, cmNinjaTargetDepends depends); void AppendTargetDependsClosure(cmGeneratorTarget const* target, cmNinjaDeps& outputs, - const std::string& config); + const std::string& config, + const std::string& fileConfig, + bool genexOutput); void AppendTargetDependsClosure(cmGeneratorTarget const* target, cmNinjaOuts& outputs, - const std::string& config, bool omit_self); + const std::string& config, + const std::string& fileConfig, + bool genexOutput, bool omit_self); void AppendDirectoryForConfig(const std::string& prefix, const std::string& config, @@ -425,6 +433,18 @@ public: return this->DefaultConfigs; } + const std::set<std::string>& GetPerConfigUtilityTargets() const + { + return this->PerConfigUtilityTargets; + } + + void AddPerConfigUtilityTarget(const std::string& name) + { + this->PerConfigUtilityTargets.insert(name); + } + + bool IsSingleConfigUtility(cmGeneratorTarget const* target) const; + protected: void Generate() override; @@ -462,7 +482,7 @@ private: void CleanMetaData(); /// Write the common disclaimer text at the top of each build file. - void WriteDisclaimer(std::ostream& os); + void WriteDisclaimer(std::ostream& os) const; void WriteAssumedSourceDependencies(); @@ -518,6 +538,9 @@ private: /// The mapping from source file to assumed dependencies. std::map<std::string, std::set<std::string>> AssumedSourceDependencies; + /// Utility targets which have per-config outputs + std::set<std::string> PerConfigUtilityTargets; + struct TargetAlias { cmGeneratorTarget* GeneratorTarget; @@ -557,7 +580,14 @@ private: /// The set of custom commands we have seen. std::set<cmCustomCommand const*> CustomCommands; - std::map<cmGeneratorTarget const*, cmNinjaOuts> TargetDependsClosures; + struct TargetDependsClosureKey + { + cmGeneratorTarget const* Target; + std::string Config; + bool GenexOutput; + }; + + std::map<TargetDependsClosureKey, cmNinjaOuts> TargetDependsClosures; TargetAliasMap TargetAliases; @@ -566,6 +596,19 @@ private: std::map<std::string, ByConfig> Configs; cmNinjaDeps ByproductsForCleanTarget; + + friend bool operator==(const ByConfig::TargetDependsClosureKey& lhs, + const ByConfig::TargetDependsClosureKey& rhs); + friend bool operator!=(const ByConfig::TargetDependsClosureKey& lhs, + const ByConfig::TargetDependsClosureKey& rhs); + friend bool operator<(const ByConfig::TargetDependsClosureKey& lhs, + const ByConfig::TargetDependsClosureKey& rhs); + friend bool operator>(const ByConfig::TargetDependsClosureKey& lhs, + const ByConfig::TargetDependsClosureKey& rhs); + friend bool operator<=(const ByConfig::TargetDependsClosureKey& lhs, + const ByConfig::TargetDependsClosureKey& rhs); + friend bool operator>=(const ByConfig::TargetDependsClosureKey& lhs, + const ByConfig::TargetDependsClosureKey& rhs); }; class cmGlobalNinjaMultiGenerator : public cmGlobalNinjaGenerator @@ -651,8 +694,6 @@ public: std::string GetDefaultBuildConfig() const override; - bool ReadCacheEntriesForBuild(const cmState& state) override; - bool SupportsDefaultBuildType() const override { return true; } bool SupportsCrossConfigs() const override { return true; } bool SupportsDefaultConfigs() const override { return true; } diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx index 2c934e1..11e2cd6 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.cxx +++ b/Source/cmGlobalUnixMakefileGenerator3.cxx @@ -44,6 +44,7 @@ cmGlobalUnixMakefileGenerator3::cmGlobalUnixMakefileGenerator3(cmake* cm) #endif this->IncludeDirective = "include"; + this->LineContinueDirective = "\\\n"; this->DefineWindowsNULL = false; this->PassMakeflags = false; this->UnixCD = true; @@ -589,10 +590,16 @@ cmGlobalUnixMakefileGenerator3::GenerateBuildCommand( } makeCommand.Add(this->SelectMakeProgram(makeProgram)); + // Explicitly tell the make tool to use the Makefile written by + // cmLocalUnixMakefileGenerator3::WriteLocalMakefile + makeCommand.Add("-f"); + makeCommand.Add("Makefile"); + if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) { - makeCommand.Add("-j"); - if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) { - makeCommand.Add(std::to_string(jobs)); + if (jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL) { + makeCommand.Add("-j"); + } else { + makeCommand.Add("-j" + std::to_string(jobs)); } } diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h index 77d0827..09679a7 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.h +++ b/Source/cmGlobalUnixMakefileGenerator3.h @@ -93,6 +93,12 @@ public: */ static bool SupportsPlatform() { return false; } + /** + * Utilized to determine if this generator + * supports DEPFILE option. + */ + bool SupportsCustomCommandDepfile() const override { return true; } + /** Get the documentation entry for this generator. */ static void GetDocumentation(cmDocumentationEntry& entry); @@ -127,6 +133,12 @@ public: void WriteConvenienceRules(std::ostream& ruleFileStream, std::set<std::string>& emitted); + // Make tool supports dependency files generated by compiler + bool SupportsCompilerDependencies() + { + return this->ToolSupportsCompilerDependencies; + } + /** Get the command to use for a target that has no rule. This is used for multiple output dependencies and for cmake_force. */ std::string GetEmptyRuleHackCommand() { return this->EmptyRuleHackCommand; } @@ -170,6 +182,7 @@ public: void ComputeTargetObjectDirectory(cmGeneratorTarget* gt) const override; std::string IncludeDirective; + std::string LineContinueDirective; bool DefineWindowsNULL; bool PassMakeflags; bool UnixCD; @@ -218,6 +231,10 @@ protected: bool CheckALLOW_DUPLICATE_CUSTOM_TARGETS() const override { return true; } + // Specify if the make tool is able to consume dependency files + // generated by the compiler + bool ToolSupportsCompilerDependencies = true; + // Some make programs (Borland) do not keep a rule if there are no // dependencies or commands. This is a problem for creating rules // that might not do anything but might have other dependencies diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index 6267205..75cd714 100644 --- a/Source/cmGlobalVisualStudio7Generator.cxx +++ b/Source/cmGlobalVisualStudio7Generator.cxx @@ -380,9 +380,10 @@ void cmGlobalVisualStudio7Generator::WriteTargetsToSolution( std::string project = target->GetName(); std::string location = *expath; - cmProp p = target->GetProperty("VS_PROJECT_TYPE"); - this->WriteExternalProject(fout, project, location, cmToCStr(p), - target->GetUtilities()); + this->WriteExternalProject( + fout, project, location, + cmToCStr(target->GetProperty("VS_PROJECT_TYPE")), + target->GetUtilities()); written = true; } else { cmProp vcprojName = target->GetProperty("GENERATOR_FILE_NAME"); diff --git a/Source/cmGlobalWatcomWMakeGenerator.cxx b/Source/cmGlobalWatcomWMakeGenerator.cxx index d6a7afa..3e2d92d 100644 --- a/Source/cmGlobalWatcomWMakeGenerator.cxx +++ b/Source/cmGlobalWatcomWMakeGenerator.cxx @@ -25,6 +25,7 @@ cmGlobalWatcomWMakeGenerator::cmGlobalWatcomWMakeGenerator(cmake* cm) #endif cm->GetState()->SetWatcomWMake(true); this->IncludeDirective = "!include"; + this->LineContinueDirective = "&\n"; this->DefineWindowsNULL = true; this->UnixCD = false; this->MakeSilentFlag = "-h"; @@ -37,7 +38,6 @@ void cmGlobalWatcomWMakeGenerator::EnableLanguage( mf->AddDefinition("WATCOM", "1"); mf->AddDefinition("CMAKE_QUOTE_INCLUDE_PATHS", "1"); mf->AddDefinition("CMAKE_MANGLE_OBJECT_FILE_NAMES", "1"); - mf->AddDefinition("CMAKE_MAKE_LINE_CONTINUE", "&"); mf->AddDefinition("CMAKE_MAKE_SYMBOLIC_RULE", ".SYMBOLIC"); mf->AddDefinition("CMAKE_GENERATOR_CC", "wcl386"); mf->AddDefinition("CMAKE_GENERATOR_CXX", "wcl386"); diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 7ee94b2..d59d382 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -503,16 +503,15 @@ cmGlobalXCodeGenerator::GenerateBuildCommand( } } - if (this->XcodeBuildSystem >= BuildSystem::Twelve) { + if ((this->XcodeBuildSystem >= BuildSystem::Twelve) || + (jobs != cmake::NO_BUILD_PARALLEL_LEVEL)) { makeCommand.Add("-parallelizeTargets"); } makeCommand.Add("-configuration", (config.empty() ? "Debug" : config)); - if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) { - makeCommand.Add("-jobs"); - if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) { - makeCommand.Add(std::to_string(jobs)); - } + if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) && + (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) { + makeCommand.Add("-jobs", std::to_string(jobs)); } if (this->XcodeVersion >= 70) { @@ -774,7 +773,9 @@ void cmGlobalXCodeGenerator::ClearXCodeObjects() this->TargetGroup.clear(); this->FileRefs.clear(); this->ExternalLibRefs.clear(); + this->EmbeddedLibRefs.clear(); this->FileRefToBuildFileMap.clear(); + this->FileRefToEmbedBuildFileMap.clear(); this->CommandsVisited.clear(); } @@ -937,6 +938,11 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile( default: break; } + + // Explicitly add the explicit language flag before any other flag + // so user flags can override it. + gtgt->AddExplicitLanguageFlags(flags, *sf); + const std::string COMPILE_FLAGS("COMPILE_FLAGS"); if (cmProp cflags = sf->GetProperty(COMPILE_FLAGS)) { lg->AppendFlags(flags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS)); @@ -1198,7 +1204,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath( } } // Make a copy so that we can override it later - std::string path = fullpath; + std::string path = cmSystemTools::CollapseFullPath(fullpath); // Compute the extension without leading '.'. std::string ext = cmSystemTools::GetFilenameLastExtension(path); if (!ext.empty()) { @@ -1378,7 +1384,7 @@ bool cmGlobalXCodeGenerator::CreateXCodeTarget( // organize the sources std::vector<cmSourceFile*> commonSourceFiles; - if (!gtgt->GetConfigCommonSourceFiles(commonSourceFiles)) { + if (!gtgt->GetConfigCommonSourceFilesForXcode(commonSourceFiles)) { return false; } @@ -1643,6 +1649,14 @@ void cmGlobalXCodeGenerator::ForceLinkerLanguage(cmGeneratorTarget* gtgt) } } + // Allow empty source file list for iOS Sticker packs + if (const char* productType = GetTargetProductType(gtgt)) { + if (strcmp(productType, + "com.apple.product-type.app-extension.messages-sticker-pack") == + 0) + return; + } + // Add an empty source file to the target that compiles with the // linker language. This should convince Xcode to choose the proper // language. @@ -1734,7 +1748,7 @@ void cmGlobalXCodeGenerator::CreateCustomCommands( this->CreateRunScriptBuildPhase("CMake PostBuild Rules", postbuild); } else { std::vector<cmSourceFile*> classes; - if (!gtgt->GetConfigCommonSourceFiles(classes)) { + if (!gtgt->GetConfigCommonSourceFilesForXcode(classes)) { return; } // add all the sources @@ -1794,6 +1808,10 @@ void cmGlobalXCodeGenerator::CreateCustomCommands( if (frameworkBuildPhase) { buildPhases->AddObject(frameworkBuildPhase); } + + // When this build phase is present, it must be last. More build phases may + // be added later for embedding things and they will insert themselves just + // before this last build phase. if (postBuildPhase) { buildPhases->AddObject(postBuildPhase); } @@ -1803,7 +1821,7 @@ void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases( cmXCodeObject* buildPhases, cmGeneratorTarget const* gt) { std::vector<cmSourceFile*> sources; - if (!gt->GetConfigCommonSourceFiles(sources)) { + if (!gt->GetConfigCommonSourceFilesForXcode(sources)) { return; } auto& visited = this->CommandsVisited[gt]; @@ -2946,7 +2964,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateUtilityTarget( if (gtgt->GetType() != cmStateEnums::GLOBAL_TARGET && gtgt->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET) { std::vector<cmSourceFile*> sources; - if (!gtgt->GetConfigCommonSourceFiles(sources)) { + if (!gtgt->GetConfigCommonSourceFilesForXcode(sources)) { return nullptr; } @@ -3195,36 +3213,43 @@ void cmGlobalXCodeGenerator::AppendOrAddBuildSetting(cmXCodeObject* settings, if (!attr) { settings->AddAttribute(attribute, value); } else { - if (value->GetType() != cmXCodeObject::OBJECT_LIST && - value->GetType() != cmXCodeObject::STRING) { - cmSystemTools::Error("Unsupported value type for appending: " + - std::string(attribute)); - return; - } - if (attr->GetType() == cmXCodeObject::OBJECT_LIST) { - if (value->GetType() == cmXCodeObject::OBJECT_LIST) { - for (auto* obj : value->GetObjectList()) { - attr->AddObject(obj); - } - } else { - attr->AddObject(value); - } - } else if (attr->GetType() == cmXCodeObject::STRING) { - if (value->GetType() == cmXCodeObject::OBJECT_LIST) { - // Add old value as a list item to new object list - // and replace the attribute with the new list - value->PrependObject(attr); - settings->AddAttribute(attribute, value); - } else { - std::string newValue = - cmStrCat(attr->GetString(), ' ', value->GetString()); - attr->SetString(newValue); - } - } else { - cmSystemTools::Error("Unsupported attribute type for appending: " + - std::string(attribute)); + this->AppendBuildSettingAttribute(settings, attribute, attr, value); + } + } +} + +void cmGlobalXCodeGenerator::AppendBuildSettingAttribute( + cmXCodeObject* settings, const char* attribute, cmXCodeObject* attr, + cmXCodeObject* value) +{ + if (value->GetType() != cmXCodeObject::OBJECT_LIST && + value->GetType() != cmXCodeObject::STRING) { + cmSystemTools::Error("Unsupported value type for appending: " + + std::string(attribute)); + return; + } + if (attr->GetType() == cmXCodeObject::OBJECT_LIST) { + if (value->GetType() == cmXCodeObject::OBJECT_LIST) { + for (auto* obj : value->GetObjectList()) { + attr->AddObject(obj); } + } else { + attr->AddObject(value); + } + } else if (attr->GetType() == cmXCodeObject::STRING) { + if (value->GetType() == cmXCodeObject::OBJECT_LIST) { + // Add old value as a list item to new object list + // and replace the attribute with the new list + value->PrependObject(attr); + settings->AddAttribute(attribute, value); + } else { + std::string newValue = + cmStrCat(attr->GetString(), ' ', value->GetString()); + attr->SetString(newValue); } + } else { + cmSystemTools::Error("Unsupported attribute type for appending: " + + std::string(attribute)); } } @@ -3247,6 +3272,24 @@ void cmGlobalXCodeGenerator::AppendBuildSettingAttribute( } } +void cmGlobalXCodeGenerator::InheritBuildSettingAttribute( + cmXCodeObject* target, const char* attribute) +{ + cmXCodeObject* configurationList = + target->GetAttribute("buildConfigurationList")->GetObject(); + cmXCodeObject* buildConfigs = + configurationList->GetAttribute("buildConfigurations"); + for (auto obj : buildConfigs->GetObjectList()) { + cmXCodeObject* settings = obj->GetAttribute("buildSettings"); + if (cmXCodeObject* attr = settings->GetAttribute(attribute)) { + BuildObjectListOrString inherited(this, true); + inherited.Add("$(inherited)"); + this->AppendBuildSettingAttribute(settings, attribute, attr, + inherited.CreateList()); + } + } +} + void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) { cmGeneratorTarget* gt = target->GetTarget(); @@ -3563,11 +3606,11 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) for (auto& libDir : linkSearchPaths) { libSearchPaths.Add(this->XCodeEscapePath(libDir)); } - // Add paths defined in project-wide build settings - libSearchPaths.Add("$(inherited)"); - this->AppendBuildSettingAttribute(target, "LIBRARY_SEARCH_PATHS", - libSearchPaths.CreateList(), - configName); + if (!libSearchPaths.IsEmpty()) { + this->AppendBuildSettingAttribute(target, "LIBRARY_SEARCH_PATHS", + libSearchPaths.CreateList(), + configName); + } } // add framework search paths @@ -3578,11 +3621,11 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) for (auto& fwDir : frameworkSearchPaths) { fwSearchPaths.Add(this->XCodeEscapePath(fwDir)); } - // Add paths defined in project-wide build settings - fwSearchPaths.Add("$(inherited)"); - this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS", - fwSearchPaths.CreateList(), - configName); + if (!fwSearchPaths.IsEmpty()) { + this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS", + fwSearchPaths.CreateList(), + configName); + } } // now add the left-over link libraries @@ -3633,6 +3676,130 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) } } +void cmGlobalXCodeGenerator::AddEmbeddedFrameworks(cmXCodeObject* target) +{ + cmGeneratorTarget* gt = target->GetTarget(); + if (!gt) { + cmSystemTools::Error("Error no target on xobject\n"); + return; + } + if (!gt->IsInBuildSystem()) { + return; + } + bool isFrameworkTarget = gt->IsFrameworkOnApple(); + bool isBundleTarget = gt->GetPropertyAsBool("MACOSX_BUNDLE"); + bool isCFBundleTarget = gt->IsCFBundleOnApple(); + if (!(isFrameworkTarget || isBundleTarget || isCFBundleTarget)) { + return; + } + cmProp files = gt->GetProperty("XCODE_EMBED_FRAMEWORKS"); + if (!files) { + return; + } + + // Create an "Embedded Frameworks" build phase + auto* copyFilesBuildPhase = + this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase); + std::string copyFilesBuildPhaseName = "Embed Frameworks"; + std::string destinationFrameworks = "10"; + copyFilesBuildPhase->SetComment(copyFilesBuildPhaseName); + copyFilesBuildPhase->AddAttribute("buildActionMask", + this->CreateString("2147483647")); + copyFilesBuildPhase->AddAttribute("dstSubfolderSpec", + this->CreateString(destinationFrameworks)); + copyFilesBuildPhase->AddAttribute( + "name", this->CreateString(copyFilesBuildPhaseName)); + if (cmProp fwEmbedPath = gt->GetProperty("XCODE_EMBED_FRAMEWORKS_PATH")) { + copyFilesBuildPhase->AddAttribute("dstPath", + this->CreateString(*fwEmbedPath)); + } else { + copyFilesBuildPhase->AddAttribute("dstPath", this->CreateString("")); + } + copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", + this->CreateString("0")); + cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); + // Collect all embedded frameworks and add them to build phase + std::vector<std::string> relFiles = cmExpandedList(*files); + for (std::string const& relFile : relFiles) { + cmXCodeObject* buildFile{ nullptr }; + std::string filePath = relFile; + auto* genTarget = FindGeneratorTarget(relFile); + if (genTarget) { + // This is a target - get it's product path reference + auto* xcTarget = FindXCodeTarget(genTarget); + if (!xcTarget) { + cmSystemTools::Error("Can not find a target for " + + genTarget->GetName()); + continue; + } + // Add the target output file as a build reference for other targets + // to link against + auto* fileRefObject = xcTarget->GetAttribute("productReference"); + if (!fileRefObject) { + cmSystemTools::Error("Target " + genTarget->GetName() + + " is missing product reference"); + continue; + } + auto it = FileRefToEmbedBuildFileMap.find(fileRefObject); + if (it == FileRefToEmbedBuildFileMap.end()) { + buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile); + buildFile->AddAttribute("fileRef", fileRefObject); + FileRefToEmbedBuildFileMap[fileRefObject] = buildFile; + } else { + buildFile = it->second; + } + } else if (cmSystemTools::IsPathToFramework(relFile)) { + // This is a regular string path - create file reference + auto it = EmbeddedLibRefs.find(relFile); + if (it == EmbeddedLibRefs.end()) { + cmXCodeObject* fileRef = + this->CreateXCodeFileReferenceFromPath(relFile, gt, "", nullptr); + if (fileRef) { + buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile); + buildFile->SetComment(fileRef->GetComment()); + buildFile->AddAttribute("fileRef", + this->CreateObjectReference(fileRef)); + } + if (!buildFile) { + cmSystemTools::Error("Can't create build file for " + relFile); + continue; + } + this->EmbeddedLibRefs.emplace(filePath, buildFile); + } else { + buildFile = it->second; + } + } + if (!buildFile) { + cmSystemTools::Error("Can't find a build file for " + relFile); + continue; + } + // Set build file configuration + cmXCodeObject* settings = + this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); + cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST); + const auto& rmHeadersProp = + gt->GetSafeProperty("XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY"); + if (cmIsOn(rmHeadersProp)) { + attrs->AddObject(this->CreateString("RemoveHeadersOnCopy")); + } + const auto& codeSignProp = + gt->GetSafeProperty("XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY"); + if (cmIsOn(codeSignProp)) { + attrs->AddObject(this->CreateString("CodeSignOnCopy")); + } + settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs); + buildFile->AddAttributeIfNotEmpty("settings", settings); + if (!buildFiles->HasObject(buildFile)) { + buildFiles->AddObject(buildFile); + } + } + copyFilesBuildPhase->AddAttribute("files", buildFiles); + auto* buildPhases = target->GetAttribute("buildPhases"); + // Insert embed build phase right before the post-build command + buildPhases->InsertObject(buildPhases->GetObjectCount() - 1, + copyFilesBuildPhase); +} + bool cmGlobalXCodeGenerator::CreateGroups( std::vector<cmLocalGenerator*>& generators) { @@ -4011,7 +4178,16 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects( // loop over all targets and add link and depend info for (auto t : targets) { this->AddDependAndLinkInformation(t); + this->AddEmbeddedFrameworks(t); + // Inherit project-wide values for any target-specific search paths. + this->InheritBuildSettingAttribute(t, "HEADER_SEARCH_PATHS"); + this->InheritBuildSettingAttribute(t, "SYSTEM_HEADER_SEARCH_PATHS"); + this->InheritBuildSettingAttribute(t, "FRAMEWORK_SEARCH_PATHS"); + this->InheritBuildSettingAttribute(t, "SYSTEM_FRAMEWORK_SEARCH_PATHS"); + this->InheritBuildSettingAttribute(t, "LIBRARY_SEARCH_PATHS"); + this->InheritBuildSettingAttribute(t, "LD_RUNPATH_SEARCH_PATHS"); } + if (this->XcodeBuildSystem == BuildSystem::One) { this->CreateXCodeDependHackMakefile(targets); } diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index ab5eeb2..14db1dc 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -182,11 +182,17 @@ private: cmGeneratorTarget* gtgt); void AppendOrAddBuildSetting(cmXCodeObject* settings, const char* attr, cmXCodeObject* value); + void AppendBuildSettingAttribute(cmXCodeObject* settings, + const char* attribute, cmXCodeObject* attr, + cmXCodeObject* value); void AppendBuildSettingAttribute(cmXCodeObject* target, const char* attr, cmXCodeObject* value, const std::string& configName); + void InheritBuildSettingAttribute(cmXCodeObject* target, + const char* attribute); cmXCodeObject* CreateUtilityTarget(cmGeneratorTarget* gtgt); void AddDependAndLinkInformation(cmXCodeObject* target); + void AddEmbeddedFrameworks(cmXCodeObject* target); void AddPositionIndependentLinkAttribute(cmGeneratorTarget* target, cmXCodeObject* buildSettings, const std::string& configName); @@ -324,8 +330,10 @@ private: std::map<std::string, cmXCodeObject*> TargetGroup; std::map<std::string, cmXCodeObject*> FileRefs; std::map<std::string, cmXCodeObject*> ExternalLibRefs; + std::map<std::string, cmXCodeObject*> EmbeddedLibRefs; std::map<cmGeneratorTarget const*, cmXCodeObject*> XCodeObjectMap; std::map<cmXCodeObject*, cmXCodeObject*> FileRefToBuildFileMap; + std::map<cmXCodeObject*, cmXCodeObject*> FileRefToEmbedBuildFileMap; std::vector<std::string> Architectures; std::string ObjectDirArchDefault; std::string ObjectDirArch; diff --git a/Source/cmIncludeCommand.cxx b/Source/cmIncludeCommand.cxx index ae801bb..ce1f030 100644 --- a/Source/cmIncludeCommand.cxx +++ b/Source/cmIncludeCommand.cxx @@ -21,6 +21,7 @@ bool cmIncludeCommand(std::vector<std::string> const& args, static std::map<std::string, cmPolicies::PolicyID> DeprecatedModules; if (DeprecatedModules.empty()) { DeprecatedModules["Documentation"] = cmPolicies::CMP0106; + DeprecatedModules["WriteCompilerDetectionHeader"] = cmPolicies::CMP0120; } if (args.empty() || args.size() > 4) { @@ -146,11 +147,24 @@ bool cmIncludeCommand(std::vector<std::string> const& args, std::string listFile = cmSystemTools::CollapseFullPath( fname, status.GetMakefile().GetCurrentSourceDirectory()); - if (optional && !cmSystemTools::FileExists(listFile)) { + + const bool fileDoesnotExist = !cmSystemTools::FileExists(listFile); + const bool fileIsDirectory = cmSystemTools::FileIsDirectory(listFile); + if (fileDoesnotExist || fileIsDirectory) { if (!resultVarName.empty()) { status.GetMakefile().AddDefinition(resultVarName, "NOTFOUND"); } - return true; + if (optional) { + return true; + } + if (fileDoesnotExist) { + status.SetError(cmStrCat("could not find requested file:\n ", fname)); + return false; + } + if (fileIsDirectory) { + status.SetError(cmStrCat("requested file is a directory:\n ", fname)); + return false; + } } bool readit = @@ -163,9 +177,7 @@ bool cmIncludeCommand(std::vector<std::string> const& args, } if (!optional && !readit && !cmSystemTools::GetFatalErrorOccured()) { - std::string m = cmStrCat("could not find load file:\n" - " ", - fname); + std::string m = cmStrCat("could not load requested file:\n ", fname); status.SetError(m); return false; } diff --git a/Source/cmJsonObjectDictionary.h b/Source/cmJsonObjectDictionary.h deleted file mode 100644 index 8a2b529..0000000 --- a/Source/cmJsonObjectDictionary.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 <string> - -// Vocabulary: - -static const std::string kARTIFACTS_KEY = "artifacts"; -static const std::string kBUILD_DIRECTORY_KEY = "buildDirectory"; -static const std::string kCOMPILE_FLAGS_KEY = "compileFlags"; -static const std::string kCONFIGURATIONS_KEY = "configurations"; -static const std::string kDEFINES_KEY = "defines"; -static const std::string kFILE_GROUPS_KEY = "fileGroups"; -static const std::string kFRAMEWORK_PATH_KEY = "frameworkPath"; -static const std::string kFULL_NAME_KEY = "fullName"; -static const std::string kINCLUDE_PATH_KEY = "includePath"; -static const std::string kIS_CMAKE_KEY = "isCMake"; -static const std::string kIS_GENERATED_KEY = "isGenerated"; -static const std::string kIS_SYSTEM_KEY = "isSystem"; -static const std::string kIS_TEMPORARY_KEY = "isTemporary"; -static const std::string kKEY_KEY = "key"; -static const std::string kLANGUAGE_KEY = "language"; -static const std::string kLINKER_LANGUAGE_KEY = "linkerLanguage"; -static const std::string kLINK_FLAGS_KEY = "linkFlags"; -static const std::string kLINK_LANGUAGE_FLAGS_KEY = "linkLanguageFlags"; -static const std::string kLINK_LIBRARIES_KEY = "linkLibraries"; -static const std::string kLINK_PATH_KEY = "linkPath"; -static const std::string kNAME_KEY = "name"; -static const std::string kPATH_KEY = "path"; -static const std::string kPROJECTS_KEY = "projects"; -static const std::string kPROPERTIES_KEY = "properties"; -static const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory"; -static const std::string kSOURCES_KEY = "sources"; -static const std::string kSYSROOT_KEY = "sysroot"; -static const std::string kTARGETS_KEY = "targets"; -static const std::string kTYPE_KEY = "type"; -static const std::string kVALUE_KEY = "value"; -static const std::string kHAS_INSTALL_RULE = "hasInstallRule"; -static const std::string kINSTALL_PATHS = "installPaths"; -static const std::string kCTEST_NAME = "ctestName"; -static const std::string kCTEST_COMMAND = "ctestCommand"; -static const std::string kCTEST_INFO = "ctestInfo"; -static const std::string kMINIMUM_CMAKE_VERSION = "minimumCMakeVersion"; -static const std::string kIS_GENERATOR_PROVIDED_KEY = "isGeneratorProvided"; diff --git a/Source/cmJsonObjects.cxx b/Source/cmJsonObjects.cxx deleted file mode 100644 index 3a7ae0c..0000000 --- a/Source/cmJsonObjects.cxx +++ /dev/null @@ -1,692 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmJsonObjects.h" // IWYU pragma: keep - -#include <algorithm> -#include <cassert> -#include <cstddef> -#include <functional> -#include <limits> -#include <map> -#include <memory> -#include <set> -#include <string> -#include <unordered_map> -#include <utility> -#include <vector> - -#include <cmext/algorithm> - -#include "cmGeneratorExpression.h" -#include "cmGeneratorTarget.h" -#include "cmGlobalGenerator.h" -#include "cmInstallGenerator.h" -#include "cmInstallSubdirectoryGenerator.h" -#include "cmInstallTargetGenerator.h" -#include "cmJsonObjectDictionary.h" -#include "cmJsonObjects.h" -#include "cmLinkLineComputer.h" -#include "cmLocalGenerator.h" -#include "cmMakefile.h" -#include "cmProperty.h" -#include "cmPropertyMap.h" -#include "cmSourceFile.h" -#include "cmState.h" -#include "cmStateDirectory.h" -#include "cmStateSnapshot.h" -#include "cmStateTypes.h" -#include "cmStringAlgorithms.h" -#include "cmSystemTools.h" -#include "cmTarget.h" -#include "cmTest.h" -#include "cmake.h" - -namespace { - -std::vector<std::string> getConfigurations(const cmake* cm) -{ - std::vector<std::string> configurations; - const auto& makefiles = cm->GetGlobalGenerator()->GetMakefiles(); - if (makefiles.empty()) { - return configurations; - } - - return makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); -} - -bool hasString(const Json::Value& v, const std::string& s) -{ - return !v.isNull() && - std::any_of(v.begin(), v.end(), - [s](const Json::Value& i) { return i.asString() == s; }); -} - -template <class T> -Json::Value fromStringList(const T& in) -{ - Json::Value result = Json::arrayValue; - for (std::string const& i : in) { - result.append(i); - } - return result; -} - -} // namespace - -void cmGetCMakeInputs(const cmGlobalGenerator* gg, - const std::string& sourceDir, - const std::string& buildDir, - std::vector<std::string>* internalFiles, - std::vector<std::string>* explicitFiles, - std::vector<std::string>* tmpFiles) -{ - const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot() + '/'; - auto const& makefiles = gg->GetMakefiles(); - for (const auto& mf : makefiles) { - for (std::string const& lf : mf->GetListFiles()) { - - const std::string startOfFile = lf.substr(0, cmakeRootDir.size()); - const bool isInternal = (startOfFile == cmakeRootDir); - const bool isTemporary = - !isInternal && (cmHasPrefix(lf, buildDir + '/')); - - std::string toAdd = lf; - if (!sourceDir.empty()) { - const std::string& relative = - cmSystemTools::RelativePath(sourceDir, lf); - if (toAdd.size() > relative.size()) { - toAdd = relative; - } - } - - if (isInternal) { - if (internalFiles) { - internalFiles->push_back(std::move(toAdd)); - } - } else { - if (isTemporary) { - if (tmpFiles) { - tmpFiles->push_back(std::move(toAdd)); - } - } else { - if (explicitFiles) { - explicitFiles->push_back(std::move(toAdd)); - } - } - } - } - } -} - -Json::Value cmDumpCMakeInputs(const cmake* cm) -{ - const cmGlobalGenerator* gg = cm->GetGlobalGenerator(); - const std::string& buildDir = cm->GetHomeOutputDirectory(); - const std::string& sourceDir = cm->GetHomeDirectory(); - - std::vector<std::string> internalFiles; - std::vector<std::string> explicitFiles; - std::vector<std::string> tmpFiles; - cmGetCMakeInputs(gg, sourceDir, buildDir, &internalFiles, &explicitFiles, - &tmpFiles); - - Json::Value array = Json::arrayValue; - - Json::Value tmp = Json::objectValue; - tmp[kIS_CMAKE_KEY] = true; - tmp[kIS_TEMPORARY_KEY] = false; - tmp[kSOURCES_KEY] = fromStringList(internalFiles); - array.append(tmp); - - tmp = Json::objectValue; - tmp[kIS_CMAKE_KEY] = false; - tmp[kIS_TEMPORARY_KEY] = false; - tmp[kSOURCES_KEY] = fromStringList(explicitFiles); - array.append(tmp); - - tmp = Json::objectValue; - tmp[kIS_CMAKE_KEY] = false; - tmp[kIS_TEMPORARY_KEY] = true; - tmp[kSOURCES_KEY] = fromStringList(tmpFiles); - array.append(tmp); - - return array; -} - -class LanguageData -{ -public: - bool operator==(const LanguageData& other) const; - - void SetDefines(const std::set<std::string>& defines); - - bool IsGenerated = false; - std::string Language; - std::string Flags; - std::vector<std::string> Defines; - std::vector<std::pair<std::string, bool>> IncludePathList; -}; - -bool LanguageData::operator==(const LanguageData& other) const -{ - return Language == other.Language && Defines == other.Defines && - Flags == other.Flags && IncludePathList == other.IncludePathList && - IsGenerated == other.IsGenerated; -} - -void LanguageData::SetDefines(const std::set<std::string>& defines) -{ - std::vector<std::string> result; - result.reserve(defines.size()); - for (std::string const& i : defines) { - result.push_back(i); - } - std::sort(result.begin(), result.end()); - Defines = std::move(result); -} - -namespace std { - -template <> -struct hash<LanguageData> -{ - std::size_t operator()(const LanguageData& in) const - { - using std::hash; - size_t result = - hash<std::string>()(in.Language) ^ hash<std::string>()(in.Flags); - for (auto const& i : in.IncludePathList) { - result = result ^ - (hash<std::string>()(i.first) ^ - (i.second ? std::numeric_limits<size_t>::max() : 0)); - } - for (auto const& i : in.Defines) { - result = result ^ hash<std::string>()(i); - } - result = - result ^ (in.IsGenerated ? std::numeric_limits<size_t>::max() : 0); - return result; - } -}; - -} // namespace std - -static Json::Value DumpSourceFileGroup(const LanguageData& data, - const std::vector<std::string>& files, - const std::string& baseDir) -{ - Json::Value result = Json::objectValue; - - if (!data.Language.empty()) { - result[kLANGUAGE_KEY] = data.Language; - if (!data.Flags.empty()) { - result[kCOMPILE_FLAGS_KEY] = data.Flags; - } - if (!data.IncludePathList.empty()) { - Json::Value includes = Json::arrayValue; - for (auto const& i : data.IncludePathList) { - Json::Value tmp = Json::objectValue; - tmp[kPATH_KEY] = i.first; - if (i.second) { - tmp[kIS_SYSTEM_KEY] = i.second; - } - includes.append(tmp); - } - result[kINCLUDE_PATH_KEY] = includes; - } - if (!data.Defines.empty()) { - result[kDEFINES_KEY] = fromStringList(data.Defines); - } - } - - result[kIS_GENERATED_KEY] = data.IsGenerated; - - Json::Value sourcesValue = Json::arrayValue; - for (auto const& i : files) { - const std::string relPath = cmSystemTools::RelativePath(baseDir, i); - sourcesValue.append(relPath.size() < i.size() ? relPath : i); - } - - result[kSOURCES_KEY] = sourcesValue; - return result; -} - -static Json::Value DumpSourceFilesList( - cmGeneratorTarget* target, const std::string& config, - const std::map<std::string, LanguageData>& languageDataMap) -{ - // Collect sourcefile groups: - - std::vector<cmSourceFile*> files; - target->GetSourceFiles(files, config); - - std::unordered_map<LanguageData, std::vector<std::string>> fileGroups; - for (cmSourceFile* file : files) { - LanguageData fileData; - fileData.Language = file->GetOrDetermineLanguage(); - if (!fileData.Language.empty()) { - const LanguageData& ld = languageDataMap.at(fileData.Language); - cmLocalGenerator* lg = target->GetLocalGenerator(); - cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target, - fileData.Language); - - std::string compileFlags = ld.Flags; - const std::string COMPILE_FLAGS("COMPILE_FLAGS"); - if (cmProp cflags = file->GetProperty(COMPILE_FLAGS)) { - lg->AppendFlags(compileFlags, - genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS)); - } - const std::string COMPILE_OPTIONS("COMPILE_OPTIONS"); - if (cmProp coptions = file->GetProperty(COMPILE_OPTIONS)) { - lg->AppendCompileOptions( - compileFlags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS)); - } - fileData.Flags = compileFlags; - - // Add include directories from source file properties. - std::vector<std::string> includes; - - const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES"); - if (cmProp cincludes = file->GetProperty(INCLUDE_DIRECTORIES)) { - const std::string& evaluatedIncludes = - genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES); - lg->AppendIncludeDirectories(includes, evaluatedIncludes, *file); - - for (const auto& include : includes) { - fileData.IncludePathList.emplace_back( - include, - target->IsSystemIncludeDirectory(include, config, - fileData.Language)); - } - } - - fileData.IncludePathList.insert(fileData.IncludePathList.end(), - ld.IncludePathList.begin(), - ld.IncludePathList.end()); - - const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS"); - std::set<std::string> defines; - if (cmProp defs = file->GetProperty(COMPILE_DEFINITIONS)) { - lg->AppendDefines( - defines, genexInterpreter.Evaluate(*defs, COMPILE_DEFINITIONS)); - } - - const std::string defPropName = - "COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(config); - if (cmProp config_defs = file->GetProperty(defPropName)) { - lg->AppendDefines( - defines, - genexInterpreter.Evaluate(*config_defs, COMPILE_DEFINITIONS)); - } - - defines.insert(ld.Defines.begin(), ld.Defines.end()); - - fileData.SetDefines(defines); - } - - fileData.IsGenerated = file->GetIsGenerated(); - std::vector<std::string>& groupFileList = fileGroups[fileData]; - groupFileList.push_back(file->ResolveFullPath()); - } - - const std::string& baseDir = target->Makefile->GetCurrentSourceDirectory(); - Json::Value result = Json::arrayValue; - for (auto const& it : fileGroups) { - Json::Value group = DumpSourceFileGroup(it.first, it.second, baseDir); - if (!group.isNull()) { - result.append(group); - } - } - - return result; -} - -static Json::Value DumpCTestInfo(cmLocalGenerator* lg, cmTest* testInfo, - const std::string& config) -{ - Json::Value result = Json::objectValue; - result[kCTEST_NAME] = testInfo->GetName(); - - // Concat command entries together. After the first should be the arguments - // for the command - std::string command; - for (auto const& cmd : testInfo->GetCommand()) { - command.append(cmd); - command.append(" "); - } - - // Remove any config specific variables from the output. - result[kCTEST_COMMAND] = - cmGeneratorExpression::Evaluate(command, lg, config); - - // Build up the list of properties that may have been specified - Json::Value properties = Json::arrayValue; - for (auto& prop : testInfo->GetProperties().GetList()) { - Json::Value entry = Json::objectValue; - entry[kKEY_KEY] = prop.first; - - // Remove config variables from the value too. - entry[kVALUE_KEY] = - cmGeneratorExpression::Evaluate(prop.second, lg, config); - properties.append(entry); - } - result[kPROPERTIES_KEY] = properties; - - return result; -} - -static void DumpMakefileTests(cmLocalGenerator* lg, const std::string& config, - Json::Value* result) -{ - auto mf = lg->GetMakefile(); - std::vector<cmTest*> tests; - mf->GetTests(config, tests); - for (auto test : tests) { - Json::Value tmp = DumpCTestInfo(lg, test, config); - if (!tmp.isNull()) { - result->append(tmp); - } - } -} - -static Json::Value DumpCTestProjectList(const cmake* cm, - std::string const& config) -{ - Json::Value result = Json::arrayValue; - - auto globalGen = cm->GetGlobalGenerator(); - - for (const auto& projectIt : globalGen->GetProjectMap()) { - Json::Value pObj = Json::objectValue; - pObj[kNAME_KEY] = projectIt.first; - - Json::Value tests = Json::arrayValue; - - // Gather tests for every generator - for (const auto& lg : projectIt.second) { - // Make sure they're generated. - lg->GenerateTestFiles(); - DumpMakefileTests(lg, config, &tests); - } - - pObj[kCTEST_INFO] = tests; - - result.append(pObj); - } - - return result; -} - -static Json::Value DumpCTestConfiguration(const cmake* cm, - const std::string& config) -{ - Json::Value result = Json::objectValue; - result[kNAME_KEY] = config; - - result[kPROJECTS_KEY] = DumpCTestProjectList(cm, config); - - return result; -} - -static Json::Value DumpCTestConfigurationsList(const cmake* cm) -{ - Json::Value result = Json::arrayValue; - - for (const std::string& c : getConfigurations(cm)) { - result.append(DumpCTestConfiguration(cm, c)); - } - - return result; -} - -Json::Value cmDumpCTestInfo(const cmake* cm) -{ - Json::Value result = Json::objectValue; - result[kCONFIGURATIONS_KEY] = DumpCTestConfigurationsList(cm); - return result; -} - -static Json::Value DumpTarget(cmGeneratorTarget* target, - const std::string& config) -{ - cmLocalGenerator* lg = target->GetLocalGenerator(); - - const cmStateEnums::TargetType type = target->GetType(); - const std::string typeName = cmState::GetTargetTypeName(type); - - Json::Value ttl = Json::arrayValue; - ttl.append("EXECUTABLE"); - ttl.append("STATIC_LIBRARY"); - ttl.append("SHARED_LIBRARY"); - ttl.append("MODULE_LIBRARY"); - ttl.append("OBJECT_LIBRARY"); - ttl.append("UTILITY"); - ttl.append("INTERFACE_LIBRARY"); - - if (!hasString(ttl, typeName) || target->IsImported()) { - return Json::Value(); - } - - Json::Value result = Json::objectValue; - result[kNAME_KEY] = target->GetName(); - result[kIS_GENERATOR_PROVIDED_KEY] = - target->Target->GetIsGeneratorProvided(); - result[kTYPE_KEY] = typeName; - result[kSOURCE_DIRECTORY_KEY] = lg->GetCurrentSourceDirectory(); - result[kBUILD_DIRECTORY_KEY] = lg->GetCurrentBinaryDirectory(); - - if (type == cmStateEnums::INTERFACE_LIBRARY) { - return result; - } - - result[kFULL_NAME_KEY] = target->GetFullName(config); - - if (target->Target->GetHaveInstallRule()) { - result[kHAS_INSTALL_RULE] = true; - - Json::Value installPaths = Json::arrayValue; - for (const auto& installGenerator : - target->Makefile->GetInstallGenerators()) { - auto installTargetGenerator = - dynamic_cast<cmInstallTargetGenerator*>(installGenerator.get()); - if (installTargetGenerator != nullptr && - installTargetGenerator->GetTarget()->Target == target->Target) { - auto dest = installTargetGenerator->GetDestination(config); - - std::string installPath; - if (!dest.empty() && cmSystemTools::FileIsFullPath(dest)) { - installPath = dest; - } else { - installPath = cmStrCat( - target->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX"), '/', - dest); - } - - installPaths.append(installPath); - } - } - - result[kINSTALL_PATHS] = installPaths; - } - - if (target->HaveWellDefinedOutputFiles()) { - Json::Value artifacts = Json::arrayValue; - artifacts.append( - target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact)); - if (target->HasImportLibrary(config)) { - artifacts.append( - target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact)); - } - if (target->IsDLLPlatform()) { - const cmGeneratorTarget::OutputInfo* output = - target->GetOutputInfo(config); - if (output && !output->PdbDir.empty()) { - artifacts.append(output->PdbDir + '/' + target->GetPDBName(config)); - } - } - result[kARTIFACTS_KEY] = artifacts; - - result[kLINKER_LANGUAGE_KEY] = target->GetLinkerLanguage(config); - - std::string linkLibs; - std::string linkFlags; - std::string linkLanguageFlags; - std::string frameworkPath; - std::string linkPath; - cmLinkLineComputer linkLineComputer(lg, - lg->GetStateSnapshot().GetDirectory()); - lg->GetTargetFlags(&linkLineComputer, config, linkLibs, linkLanguageFlags, - linkFlags, frameworkPath, linkPath, target); - - linkLibs = cmTrimWhitespace(linkLibs); - linkFlags = cmTrimWhitespace(linkFlags); - linkLanguageFlags = cmTrimWhitespace(linkLanguageFlags); - frameworkPath = cmTrimWhitespace(frameworkPath); - linkPath = cmTrimWhitespace(linkPath); - - if (!cmTrimWhitespace(linkLibs).empty()) { - result[kLINK_LIBRARIES_KEY] = linkLibs; - } - if (!cmTrimWhitespace(linkFlags).empty()) { - result[kLINK_FLAGS_KEY] = linkFlags; - } - if (!cmTrimWhitespace(linkLanguageFlags).empty()) { - result[kLINK_LANGUAGE_FLAGS_KEY] = linkLanguageFlags; - } - if (!frameworkPath.empty()) { - result[kFRAMEWORK_PATH_KEY] = frameworkPath; - } - if (!linkPath.empty()) { - result[kLINK_PATH_KEY] = linkPath; - } - const std::string sysroot = - lg->GetMakefile()->GetSafeDefinition("CMAKE_SYSROOT"); - if (!sysroot.empty()) { - result[kSYSROOT_KEY] = sysroot; - } - } - - std::set<std::string> languages; - target->GetLanguages(languages, config); - std::map<std::string, LanguageData> languageDataMap; - - for (std::string const& lang : languages) { - LanguageData& ld = languageDataMap[lang]; - ld.Language = lang; - lg->GetTargetCompileFlags(target, config, lang, ld.Flags); - std::set<std::string> defines; - lg->GetTargetDefines(target, config, lang, defines); - ld.SetDefines(defines); - std::vector<std::string> includePathList; - lg->GetIncludeDirectories(includePathList, target, lang, config); - for (std::string const& i : includePathList) { - ld.IncludePathList.emplace_back( - i, target->IsSystemIncludeDirectory(i, config, lang)); - } - } - - Json::Value sourceGroupsValue = - DumpSourceFilesList(target, config, languageDataMap); - if (!sourceGroupsValue.empty()) { - result[kFILE_GROUPS_KEY] = sourceGroupsValue; - } - - return result; -} - -static Json::Value DumpTargetsList( - const std::vector<cmLocalGenerator*>& generators, const std::string& config) -{ - Json::Value result = Json::arrayValue; - - std::vector<cmGeneratorTarget*> targetList; - for (auto const& lgIt : generators) { - cm::append(targetList, lgIt->GetGeneratorTargets()); - } - std::sort(targetList.begin(), targetList.end()); - - for (cmGeneratorTarget* target : targetList) { - Json::Value tmp = DumpTarget(target, config); - if (!tmp.isNull()) { - result.append(tmp); - } - } - - return result; -} - -static Json::Value DumpProjectList(const cmake* cm, std::string const& config) -{ - Json::Value result = Json::arrayValue; - - auto globalGen = cm->GetGlobalGenerator(); - - for (auto const& projectIt : globalGen->GetProjectMap()) { - Json::Value pObj = Json::objectValue; - pObj[kNAME_KEY] = projectIt.first; - - // All Projects must have at least one local generator - assert(!projectIt.second.empty()); - const cmLocalGenerator* lg = projectIt.second.at(0); - - // Project structure information: - const cmMakefile* mf = lg->GetMakefile(); - auto minVersion = mf->GetSafeDefinition("CMAKE_MINIMUM_REQUIRED_VERSION"); - pObj[kMINIMUM_CMAKE_VERSION] = minVersion; - pObj[kSOURCE_DIRECTORY_KEY] = mf->GetCurrentSourceDirectory(); - pObj[kBUILD_DIRECTORY_KEY] = mf->GetCurrentBinaryDirectory(); - pObj[kTARGETS_KEY] = DumpTargetsList(projectIt.second, config); - - // For a project-level install rule it might be defined in any of its - // associated generators. - bool hasInstallRule = false; - for (const auto generator : projectIt.second) { - for (const auto& installGen : - generator->GetMakefile()->GetInstallGenerators()) { - if (!dynamic_cast<cmInstallSubdirectoryGenerator*>(installGen.get())) { - hasInstallRule = true; - break; - } - } - - if (hasInstallRule) { - break; - } - } - - pObj[kHAS_INSTALL_RULE] = hasInstallRule; - - result.append(pObj); - } - - return result; -} - -static Json::Value DumpConfiguration(const cmake* cm, - const std::string& config) -{ - Json::Value result = Json::objectValue; - result[kNAME_KEY] = config; - - result[kPROJECTS_KEY] = DumpProjectList(cm, config); - - return result; -} - -static Json::Value DumpConfigurationsList(const cmake* cm) -{ - Json::Value result = Json::arrayValue; - - for (std::string const& c : getConfigurations(cm)) { - result.append(DumpConfiguration(cm, c)); - } - - return result; -} - -Json::Value cmDumpCodeModel(const cmake* cm) -{ - Json::Value result = Json::objectValue; - result[kCONFIGURATIONS_KEY] = DumpConfigurationsList(cm); - return result; -} diff --git a/Source/cmJsonObjects.h b/Source/cmJsonObjects.h deleted file mode 100644 index 80a4834..0000000 --- a/Source/cmJsonObjects.h +++ /dev/null @@ -1,24 +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 <string> -#include <vector> - -#include <cm3p/json/value.h> - -class cmake; -class cmGlobalGenerator; - -extern void cmGetCMakeInputs(const cmGlobalGenerator* gg, - const std::string& sourceDir, - const std::string& buildDir, - std::vector<std::string>* internalFiles, - std::vector<std::string>* explicitFiles, - std::vector<std::string>* tmpFiles); - -extern Json::Value cmDumpCodeModel(const cmake* cm); -extern Json::Value cmDumpCTestInfo(const cmake* cm); -extern Json::Value cmDumpCMakeInputs(const cmake* cm); diff --git a/Source/cmListFileCache.cxx b/Source/cmListFileCache.cxx index 70ef5af..3658d11 100644 --- a/Source/cmListFileCache.cxx +++ b/Source/cmListFileCache.cxx @@ -30,6 +30,7 @@ struct cmListFileParser bool ParseFunction(const char* name, long line); bool AddArgument(cmListFileLexer_Token* token, cmListFileArgument::Delimiter delim); + cm::optional<cmListFileContext> CheckNesting(); cmListFile* ListFile; cmListFileBacktrace Backtrace; cmMessenger* Messenger; @@ -158,6 +159,17 @@ bool cmListFileParser::Parse() return false; } } + + // Check if all functions are nested properly. + if (auto badNesting = this->CheckNesting()) { + this->Messenger->IssueMessage( + MessageType::FATAL_ERROR, + "Flow control statements are not properly nested.", + this->Backtrace.Push(*badNesting)); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + return true; } @@ -317,6 +329,112 @@ bool cmListFileParser::AddArgument(cmListFileLexer_Token* token, return true; } +namespace { +enum class NestingStateEnum +{ + If, + Else, + While, + Foreach, + Function, + Macro, +}; + +struct NestingState +{ + NestingStateEnum State; + cmListFileContext Context; +}; + +bool TopIs(std::vector<NestingState>& stack, NestingStateEnum state) +{ + return !stack.empty() && stack.back().State == state; +} +} + +cm::optional<cmListFileContext> cmListFileParser::CheckNesting() +{ + std::vector<NestingState> stack; + + for (auto const& func : this->ListFile->Functions) { + auto const& name = func.LowerCaseName(); + if (name == "if") { + stack.push_back({ + NestingStateEnum::If, + cmListFileContext::FromCommandContext(func, this->FileName), + }); + } else if (name == "elseif") { + if (!TopIs(stack, NestingStateEnum::If)) { + return cmListFileContext::FromCommandContext(func, this->FileName); + } + stack.back() = { + NestingStateEnum::If, + cmListFileContext::FromCommandContext(func, this->FileName), + }; + } else if (name == "else") { + if (!TopIs(stack, NestingStateEnum::If)) { + return cmListFileContext::FromCommandContext(func, this->FileName); + } + stack.back() = { + NestingStateEnum::Else, + cmListFileContext::FromCommandContext(func, this->FileName), + }; + } else if (name == "endif") { + if (!TopIs(stack, NestingStateEnum::If) && + !TopIs(stack, NestingStateEnum::Else)) { + return cmListFileContext::FromCommandContext(func, this->FileName); + } + stack.pop_back(); + } else if (name == "while") { + stack.push_back({ + NestingStateEnum::While, + cmListFileContext::FromCommandContext(func, this->FileName), + }); + } else if (name == "endwhile") { + if (!TopIs(stack, NestingStateEnum::While)) { + return cmListFileContext::FromCommandContext(func, this->FileName); + } + stack.pop_back(); + } else if (name == "foreach") { + stack.push_back({ + NestingStateEnum::Foreach, + cmListFileContext::FromCommandContext(func, this->FileName), + }); + } else if (name == "endforeach") { + if (!TopIs(stack, NestingStateEnum::Foreach)) { + return cmListFileContext::FromCommandContext(func, this->FileName); + } + stack.pop_back(); + } else if (name == "function") { + stack.push_back({ + NestingStateEnum::Function, + cmListFileContext::FromCommandContext(func, this->FileName), + }); + } else if (name == "endfunction") { + if (!TopIs(stack, NestingStateEnum::Function)) { + return cmListFileContext::FromCommandContext(func, this->FileName); + } + stack.pop_back(); + } else if (name == "macro") { + stack.push_back({ + NestingStateEnum::Macro, + cmListFileContext::FromCommandContext(func, this->FileName), + }); + } else if (name == "endmacro") { + if (!TopIs(stack, NestingStateEnum::Macro)) { + return cmListFileContext::FromCommandContext(func, this->FileName); + } + stack.pop_back(); + } + } + + if (!stack.empty()) { + return stack.back().Context; + } + + return cm::nullopt; +} + // We hold either the bottom scope of a directory or a call/file context. // Discriminate these cases via the parent pointer. struct cmListFileBacktrace::Entry diff --git a/Source/cmListFileCache.h b/Source/cmListFileCache.h index 727fc60..ed45c07 100644 --- a/Source/cmListFileCache.h +++ b/Source/cmListFileCache.h @@ -89,6 +89,14 @@ public: { } +#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L) + cmListFileContext(const cmListFileContext& /*other*/) = default; + cmListFileContext(cmListFileContext&& /*other*/) = default; + + cmListFileContext& operator=(const cmListFileContext& /*other*/) = default; + cmListFileContext& operator=(cmListFileContext&& /*other*/) = delete; +#endif + static cmListFileContext FromCommandContext( cmCommandContext const& lfcc, std::string const& fileName, cm::optional<std::string> deferId = {}) diff --git a/Source/cmLoadCommandCommand.cxx b/Source/cmLoadCommandCommand.cxx index 5790e16..adebe02 100644 --- a/Source/cmLoadCommandCommand.cxx +++ b/Source/cmLoadCommandCommand.cxx @@ -24,6 +24,7 @@ #include "cmCommand.h" #include "cmDynamicLoader.h" #include "cmExecutionStatus.h" +#include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmState.h" @@ -36,8 +37,6 @@ # include <malloc.h> /* for malloc/free on QNX */ #endif -class cmListFileBacktrace; - namespace { const char* LastName = nullptr; @@ -256,10 +255,12 @@ bool cmLoadCommandCommand(std::vector<std::string> const& args, // if the symbol is found call it to set the name on the // function blocker if (initFunction) { - status.GetMakefile().GetState()->AddScriptedCommand( + return status.GetMakefile().GetState()->AddScriptedCommand( args[0], - cmLegacyCommandWrapper(cm::make_unique<cmLoadedCommand>(initFunction))); - return true; + BT<cmState::Command>( + cmLegacyCommandWrapper(cm::make_unique<cmLoadedCommand>(initFunction)), + status.GetMakefile().GetBacktrace()), + status.GetMakefile()); } status.SetError("Attempt to load command failed. " "No init function found."); diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 2239192..b329e4b 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -3,6 +3,7 @@ #include "cmLocalGenerator.h" #include <algorithm> +#include <array> #include <cassert> #include <cstdio> #include <cstdlib> @@ -16,9 +17,11 @@ #include <cm/memory> #include <cm/string_view> #include <cmext/algorithm> +#include <cmext/string_view> #include "cmsys/RegularExpression.hxx" +#include "cmAlgorithms.h" #include "cmComputeLinkInformation.h" #include "cmCustomCommand.h" #include "cmCustomCommandGenerator.h" @@ -279,7 +282,7 @@ static void MoveSystemIncludesToEnd(std::vector<BT<std::string>>& includeDirs, }); } -void cmLocalGenerator::TraceDependencies() +void cmLocalGenerator::TraceDependencies() const { // Generate the rule files for each target. const auto& targets = this->GetGeneratorTargets(); @@ -823,16 +826,13 @@ cmStateSnapshot cmLocalGenerator::GetStateSnapshot() const return this->Makefile->GetStateSnapshot(); } -const char* cmLocalGenerator::GetRuleLauncher(cmGeneratorTarget* target, - const std::string& prop) +cmProp cmLocalGenerator::GetRuleLauncher(cmGeneratorTarget* target, + const std::string& prop) { - cmProp p; if (target) { - p = target->GetProperty(prop); - } else { - p = this->Makefile->GetProperty(prop); + return target->GetProperty(prop); } - return p ? p->c_str() : nullptr; + return this->Makefile->GetProperty(prop); } std::string cmLocalGenerator::ConvertToIncludeReference( @@ -1129,9 +1129,8 @@ cmTarget* cmLocalGenerator::AddUtilityCommand( detail::AddUtilityCommand( *this, this->DirectoryBacktrace, cmCommandOrigin::Generator, target, - this->Makefile->GetUtilityOutput(target), workingDir, byproducts, depends, - commandLines, escapeOldStyle, comment, uses_terminal, command_expand_lists, - job_pool, stdPipesUTF8); + workingDir, byproducts, depends, commandLines, escapeOldStyle, comment, + uses_terminal, command_expand_lists, job_pool, stdPipesUTF8); return target; } @@ -2498,8 +2497,10 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target) target->GetSourceFiles(sources, config); const std::string configUpper = cmSystemTools::UpperCase(config); + static const std::array<std::string, 4> langs = { { "C", "CXX", "OBJC", + "OBJCXX" } }; - for (const std::string& lang : { "C", "CXX", "OBJC", "OBJCXX" }) { + for (const std::string& lang : langs) { auto langSources = std::count_if( sources.begin(), sources.end(), [lang](cmSourceFile* sf) { return lang == sf->GetLanguage() && @@ -2633,14 +2634,16 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target) // Add pchHeader to source files, which will // be grouped as "Precompile Header File" auto pchHeader_sf = this->Makefile->GetOrCreateSource( - pchHeader, true, cmSourceFileLocationKind::Known); + pchHeader, false, cmSourceFileLocationKind::Known); std::string err; pchHeader_sf->ResolveFullPath(&err); - - // The pch file is generated, but mark it as not generated - // so that a clean operation will not remove it from disk - pchHeader_sf->SetProperty("GENERATED", "0"); - + if (!err.empty()) { + std::ostringstream msg; + msg << "Unable to resolve full path of PCH-header '" << pchHeader + << "' assigned to target " << target->GetName() + << ", although its path is supposed to be known!"; + this->IssueMessage(MessageType::FATAL_ERROR, msg.str()); + } target->AddSource(pchHeader); } } @@ -2769,8 +2772,15 @@ inline void RegisterUnitySources(cmGeneratorTarget* target, cmSourceFile* sf, inline void IncludeFileInUnitySources(cmGeneratedFileStream& unity_file, std::string const& sf_full_path, cmProp beforeInclude, - cmProp afterInclude) + cmProp afterInclude, cmProp uniqueIdName) { + + if (uniqueIdName && !uniqueIdName->empty()) { + unity_file << "#undef " << *uniqueIdName << "\n" + << "#define " << *uniqueIdName << " unity_" + << cmSystemTools::ComputeStringMD5(sf_full_path) << "\n"; + } + if (beforeInclude) { unity_file << *beforeInclude << "\n"; } @@ -2791,6 +2801,8 @@ std::vector<std::string> AddUnityFilesModeAuto( batchSize = filtered_sources.size(); } + cmProp uniqueIdName = target->GetProperty("UNITY_BUILD_UNIQUE_ID"); + std::vector<std::string> unity_files; for (size_t itemsLeft = filtered_sources.size(), chunk, batch = 0; itemsLeft > 0; itemsLeft -= chunk, ++batch) { @@ -2814,7 +2826,7 @@ std::vector<std::string> AddUnityFilesModeAuto( cmSourceFile* sf = filtered_sources[begin]; RegisterUnitySources(target, sf, filename); IncludeFileInUnitySources(file, sf->ResolveFullPath(), beforeInclude, - afterInclude); + afterInclude, uniqueIdName); } } cmSystemTools::MoveFileIfDifferent(filename_tmp, filename); @@ -2845,6 +2857,8 @@ std::vector<std::string> AddUnityFilesModeGroup( } } + cmProp uniqueIdName = target->GetProperty("UNITY_BUILD_UNIQUE_ID"); + for (auto const& item : explicit_mapping) { auto const& name = item.first; std::string filename = cmStrCat(filename_base, "unity_", name, @@ -2860,7 +2874,7 @@ std::vector<std::string> AddUnityFilesModeGroup( for (cmSourceFile* sf : item.second) { RegisterUnitySources(target, sf, filename); IncludeFileInUnitySources(file, sf->ResolveFullPath(), beforeInclude, - afterInclude); + afterInclude, uniqueIdName); } } cmSystemTools::MoveFileIfDifferent(filename_tmp, filename); @@ -3233,7 +3247,7 @@ std::string cmLocalGenerator::GetProjectName() const } std::string cmLocalGenerator::ConstructComment( - cmCustomCommandGenerator const& ccg, const char* default_comment) + cmCustomCommandGenerator const& ccg, const char* default_comment) const { // Check for a comment provided with the command. if (ccg.GetComment()) { @@ -3549,11 +3563,11 @@ std::string cmLocalGenerator::GetObjectFileNameWithoutTarget( // we don't end up having: // CMakeFiles/<target>.dir/CMakeFiles/<target>.dir/generated_source_file.obj cmProp unitySourceFile = source.GetProperty("UNITY_SOURCE_FILE"); - cmProp psExtension = source.GetProperty("PCH_EXTENSION"); + cmProp pchExtension = source.GetProperty("PCH_EXTENSION"); const bool isPchObject = objectName.find("cmake_pch") != std::string::npos; - if (unitySourceFile || psExtension || isPchObject) { - if (psExtension) { - customOutputExtension = psExtension->c_str(); + if (unitySourceFile || pchExtension || isPchObject) { + if (pchExtension) { + customOutputExtension = pchExtension->c_str(); } cmsys::RegularExpression var("(CMakeFiles/[^/]+.dir/)"); @@ -3791,7 +3805,7 @@ void cmLocalGenerator::GenerateAppleInfoPList(cmGeneratorTarget* target, cmLGInfoProp(mf, target, "MACOSX_BUNDLE_SHORT_VERSION_STRING"); cmLGInfoProp(mf, target, "MACOSX_BUNDLE_BUNDLE_VERSION"); cmLGInfoProp(mf, target, "MACOSX_BUNDLE_COPYRIGHT"); - mf->ConfigureFile(inFile, fname, false, false, false, true); + mf->ConfigureFile(inFile, fname, false, false, false); } void cmLocalGenerator::GenerateFrameworkInfoPList( @@ -3826,42 +3840,99 @@ void cmLocalGenerator::GenerateFrameworkInfoPList( cmLGInfoProp(mf, target, "MACOSX_FRAMEWORK_IDENTIFIER"); cmLGInfoProp(mf, target, "MACOSX_FRAMEWORK_SHORT_VERSION_STRING"); cmLGInfoProp(mf, target, "MACOSX_FRAMEWORK_BUNDLE_VERSION"); - mf->ConfigureFile(inFile, fname, false, false, false, true); + mf->ConfigureFile(inFile, fname, false, false, false); } namespace { +cm::string_view CustomOutputRoleKeyword(cmLocalGenerator::OutputRole role) +{ + return (role == cmLocalGenerator::OutputRole::Primary ? "OUTPUT"_s + : "BYPRODUCTS"_s); +} + void CreateGeneratedSource(cmLocalGenerator& lg, const std::string& output, + cmLocalGenerator::OutputRole role, cmCommandOrigin origin, const cmListFileBacktrace& lfbt) { - if (cmGeneratorExpression::Find(output) == std::string::npos) { - // Outputs without generator expressions from the project are already - // created and marked as generated. Do not mark them again, because - // other commands might have overwritten the property. - if (origin == cmCommandOrigin::Generator) { - lg.GetMakefile()->GetOrCreateGeneratedSource(output); - } - } else { + if (cmGeneratorExpression::Find(output) != std::string::npos) { lg.GetCMakeInstance()->IssueMessage( MessageType::FATAL_ERROR, "Generator expressions in custom command outputs are not implemented!", lfbt); + return; + } + + // Make sure the file will not be generated into the source + // directory during an out of source build. + if (!lg.GetMakefile()->CanIWriteThisFile(output)) { + lg.GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat(CustomOutputRoleKeyword(role), " path\n ", output, + "\nin a source directory as an output of custom command."), + lfbt); + return; + } + + // Make sure the output file name has no invalid characters. + std::string::size_type pos = output.find_first_of("#<>"); + if (pos != std::string::npos) { + lg.GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat(CustomOutputRoleKeyword(role), " containing a \"", output[pos], + "\" is not allowed."), + lfbt); + return; + } + + // Outputs without generator expressions from the project are already + // created and marked as generated. Do not mark them again, because + // other commands might have overwritten the property. + if (origin == cmCommandOrigin::Generator) { + lg.GetMakefile()->GetOrCreateGeneratedSource(output); } } -void CreateGeneratedSources(cmLocalGenerator& lg, - const std::vector<std::string>& outputs, - cmCommandOrigin origin, - const cmListFileBacktrace& lfbt) +std::string ComputeCustomCommandRuleFileName(cmLocalGenerator& lg, + cmListFileBacktrace const& bt, + std::string const& output) { - for (std::string const& o : outputs) { - CreateGeneratedSource(lg, o, origin, lfbt); + // If the output path has no generator expressions, use it directly. + if (cmGeneratorExpression::Find(output) == std::string::npos) { + return output; + } + + // The output path contains a generator expression, but we must choose + // a single source file path to which to attach the custom command. + // Use some heuristics to provie a nice-looking name when possible. + + // If the only genex is $<CONFIG>, replace that gracefully. + { + std::string simple = output; + cmSystemTools::ReplaceString(simple, "$<CONFIG>", "(CONFIG)"); + if (cmGeneratorExpression::Find(simple) == std::string::npos) { + return simple; + } } + + // If the genex evaluates to the same value in all configurations, use that. + { + std::vector<std::string> allConfigOutputs = + lg.ExpandCustomCommandOutputGenex(output, bt); + if (allConfigOutputs.size() == 1) { + return allConfigOutputs.front(); + } + } + + // Fall back to a deterministic unique name. + cmCryptoHash h(cmCryptoHash::AlgoSHA256); + return cmStrCat(lg.GetCurrentBinaryDirectory(), "/CMakeFiles/", + h.HashString(output).substr(0, 16)); } cmSourceFile* AddCustomCommand( cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, - const std::vector<std::string>& outputs, + cmCommandOrigin origin, const std::vector<std::string>& outputs, const std::vector<std::string>& byproducts, const std::vector<std::string>& depends, const std::string& main_dependency, const cmImplicitDependsList& implicit_depends, @@ -3898,7 +3969,8 @@ cmSourceFile* AddCustomCommand( cmGlobalGenerator* gg = lg.GetGlobalGenerator(); // Construct a rule file associated with the first output produced. - std::string outName = gg->GenerateRuleFile(outputs[0]); + std::string outName = gg->GenerateRuleFile( + ComputeCustomCommandRuleFileName(lg, lfbt, outputs[0])); // Check if the rule file already exists. file = mf->GetSource(outName, cmSourceFileLocationKind::Known); @@ -3941,10 +4013,38 @@ cmSourceFile* AddCustomCommand( cc->SetJobPool(job_pool); file->SetCustomCommand(std::move(cc)); - mf->AddSourceOutputs(file, outputs, byproducts); + lg.AddSourceOutputs(file, outputs, cmLocalGenerator::OutputRole::Primary, + lfbt, origin); + lg.AddSourceOutputs(file, byproducts, + cmLocalGenerator::OutputRole::Byproduct, lfbt, origin); } return file; } + +bool AnyOutputMatches(const std::string& name, + const std::vector<std::string>& outputs) +{ + for (std::string const& output : outputs) { + std::string::size_type pos = output.rfind(name); + // If the output matches exactly + if (pos != std::string::npos && pos == output.size() - name.size() && + (pos == 0 || output[pos - 1] == '/')) { + return true; + } + } + return false; +} + +bool AnyTargetCommandOutputMatches( + const std::string& name, const std::vector<cmCustomCommand>& commands) +{ + for (cmCustomCommand const& command : commands) { + if (AnyOutputMatches(name, command.GetByproducts())) { + return true; + } + } + return false; +} } namespace detail { @@ -3960,11 +4060,6 @@ void AddCustomCommandToTarget(cmLocalGenerator& lg, const std::string& job_pool, bool command_expand_lists, bool stdPipesUTF8) { - cmMakefile* mf = lg.GetMakefile(); - - // Always create the byproduct sources and mark them generated. - CreateGeneratedSources(lg, byproducts, origin, lfbt); - // Add the command to the appropriate build step for the target. std::vector<std::string> no_output; cmCustomCommand cc(no_output, byproducts, depends, commandLines, lfbt, @@ -3987,7 +4082,7 @@ void AddCustomCommandToTarget(cmLocalGenerator& lg, break; } - mf->AddTargetByproducts(target, byproducts); + lg.AddTargetByproducts(target, byproducts, lfbt, origin); } cmSourceFile* AddCustomCommandToOutput( @@ -4001,14 +4096,11 @@ cmSourceFile* AddCustomCommandToOutput( bool uses_terminal, bool command_expand_lists, const std::string& depfile, const std::string& job_pool, bool stdPipesUTF8) { - // Always create the output sources and mark them generated. - CreateGeneratedSources(lg, outputs, origin, lfbt); - CreateGeneratedSources(lg, byproducts, origin, lfbt); - - return AddCustomCommand( - lg, lfbt, outputs, byproducts, depends, main_dependency, implicit_depends, - commandLines, comment, workingDir, replace, escapeOldStyle, uses_terminal, - command_expand_lists, depfile, job_pool, stdPipesUTF8); + return AddCustomCommand(lg, lfbt, origin, outputs, byproducts, depends, + main_dependency, implicit_depends, commandLines, + comment, workingDir, replace, escapeOldStyle, + uses_terminal, command_expand_lists, depfile, + job_pool, stdPipesUTF8); } void AppendCustomCommandToOutput(cmLocalGenerator& lg, @@ -4019,7 +4111,22 @@ void AppendCustomCommandToOutput(cmLocalGenerator& lg, const cmCustomCommandLines& commandLines) { // Lookup an existing command. - if (cmSourceFile* sf = lg.GetMakefile()->GetSourceFileWithOutput(output)) { + cmSourceFile* sf = nullptr; + if (cmGeneratorExpression::Find(output) == std::string::npos) { + sf = lg.GetSourceFileWithOutput(output); + } else { + // This output path has a generator expression. Evaluate it to + // find the output for any configurations. + for (std::string const& out : + lg.ExpandCustomCommandOutputGenex(output, lfbt)) { + sf = lg.GetSourceFileWithOutput(out); + if (sf) { + break; + } + } + } + + if (sf) { if (cmCustomCommand* cc = sf->GetCustomCommand()) { cc->AppendCommands(commandLines); cc->AppendDepends(depends); @@ -4031,14 +4138,14 @@ void AppendCustomCommandToOutput(cmLocalGenerator& lg, // No existing command found. lg.GetCMakeInstance()->IssueMessage( MessageType::FATAL_ERROR, - cmStrCat("Attempt to append to output\n ", output, + cmStrCat("Attempt to APPEND to custom command with output\n ", output, "\nwhich is not already a custom command output."), lfbt); } void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, cmCommandOrigin origin, cmTarget* target, - const cmUtilityOutput& force, const char* workingDir, + const char* workingDir, const std::vector<std::string>& byproducts, const std::vector<std::string>& depends, const cmCustomCommandLines& commandLines, @@ -4046,29 +4153,27 @@ void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, bool uses_terminal, bool command_expand_lists, const std::string& job_pool, bool stdPipesUTF8) { - // Always create the byproduct sources and mark them generated. - CreateGeneratedSource(lg, force.Name, origin, lfbt); - CreateGeneratedSources(lg, byproducts, origin, lfbt); - // Use an empty comment to avoid generation of default comment. if (!comment) { comment = ""; } + // Create the generated symbolic output name of the utility target. + std::string output = + lg.CreateUtilityOutput(target->GetName(), byproducts, lfbt); + std::string no_main_dependency; cmImplicitDependsList no_implicit_depends; cmSourceFile* rule = AddCustomCommand( - lg, lfbt, { force.Name }, byproducts, depends, no_main_dependency, + lg, lfbt, origin, { output }, byproducts, depends, no_main_dependency, no_implicit_depends, commandLines, comment, workingDir, /*replace=*/false, escapeOldStyle, uses_terminal, command_expand_lists, /*depfile=*/"", job_pool, stdPipesUTF8); if (rule) { - lg.GetMakefile()->AddTargetByproducts(target, byproducts); + lg.AddTargetByproducts(target, byproducts, lfbt, origin); } - if (!force.NameCMP0049.empty()) { - target->AddSource(force.NameCMP0049); - } + target->AddSource(output); } std::vector<std::string> ComputeISPCObjectSuffixes(cmGeneratorTarget* target) @@ -4119,3 +4224,247 @@ std::vector<std::string> ComputeISPCExtraObjects( return computedObjects; } } + +cmSourcesWithOutput cmLocalGenerator::GetSourcesWithOutput( + const std::string& name) const +{ + // Linear search? Also see GetSourceFileWithOutput for detail. + if (!cmSystemTools::FileIsFullPath(name)) { + cmSourcesWithOutput sources; + sources.Target = this->LinearGetTargetWithOutput(name); + sources.Source = this->LinearGetSourceFileWithOutput( + name, cmSourceOutputKind::OutputOrByproduct, sources.SourceIsByproduct); + return sources; + } + // Otherwise we use an efficient lookup map. + auto o = this->OutputToSource.find(name); + if (o != this->OutputToSource.end()) { + return o->second.Sources; + } + return {}; +} + +cmSourceFile* cmLocalGenerator::GetSourceFileWithOutput( + const std::string& name, cmSourceOutputKind kind) const +{ + // If the queried path is not absolute we use the backward compatible + // linear-time search for an output with a matching suffix. + if (!cmSystemTools::FileIsFullPath(name)) { + bool byproduct = false; + return this->LinearGetSourceFileWithOutput(name, kind, byproduct); + } + // Otherwise we use an efficient lookup map. + auto o = this->OutputToSource.find(name); + if (o != this->OutputToSource.end() && + (!o->second.Sources.SourceIsByproduct || + kind == cmSourceOutputKind::OutputOrByproduct)) { + // Source file could also be null pointer for example if we found the + // byproduct of a utility target, a PRE_BUILD, PRE_LINK, or POST_BUILD + // command of a target, or a not yet created custom command. + return o->second.Sources.Source; + } + return nullptr; +} + +std::string cmLocalGenerator::CreateUtilityOutput( + std::string const& targetName, std::vector<std::string> const&, + cmListFileBacktrace const&) +{ + std::string force = + cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles/", targetName); + // The output is not actually created so mark it symbolic. + if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) { + sf->SetProperty("SYMBOLIC", "1"); + } else { + cmSystemTools::Error("Could not get source file entry for " + force); + } + return force; +} + +std::vector<cmCustomCommandGenerator> +cmLocalGenerator::MakeCustomCommandGenerators(cmCustomCommand const& cc, + std::string const& config) +{ + std::vector<cmCustomCommandGenerator> ccgs; + ccgs.emplace_back(cc, config, this); + return ccgs; +} + +std::vector<std::string> cmLocalGenerator::ExpandCustomCommandOutputPaths( + cmCompiledGeneratorExpression const& cge, std::string const& config) +{ + std::vector<std::string> paths = cmExpandedList(cge.Evaluate(this, config)); + for (std::string& p : paths) { + p = cmSystemTools::CollapseFullPath(p, this->GetCurrentBinaryDirectory()); + } + return paths; +} + +std::vector<std::string> cmLocalGenerator::ExpandCustomCommandOutputGenex( + std::string const& o, cmListFileBacktrace const& bt) +{ + std::vector<std::string> allConfigOutputs; + cmGeneratorExpression ge(bt); + std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(o); + std::vector<std::string> configs = + this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); + for (std::string const& config : configs) { + std::vector<std::string> configOutputs = + this->ExpandCustomCommandOutputPaths(*cge, config); + allConfigOutputs.reserve(allConfigOutputs.size() + configOutputs.size()); + std::move(configOutputs.begin(), configOutputs.end(), + std::back_inserter(allConfigOutputs)); + } + auto endUnique = + cmRemoveDuplicates(allConfigOutputs.begin(), allConfigOutputs.end()); + allConfigOutputs.erase(endUnique, allConfigOutputs.end()); + return allConfigOutputs; +} + +void cmLocalGenerator::AddTargetByproducts( + cmTarget* target, const std::vector<std::string>& byproducts, + cmListFileBacktrace const& bt, cmCommandOrigin origin) +{ + for (std::string const& o : byproducts) { + if (cmGeneratorExpression::Find(o) == std::string::npos) { + this->UpdateOutputToSourceMap(o, target, bt, origin); + continue; + } + + // This byproduct path has a generator expression. Evaluate it to + // register the byproducts for all configurations. + for (std::string const& b : this->ExpandCustomCommandOutputGenex(o, bt)) { + this->UpdateOutputToSourceMap(b, target, bt, cmCommandOrigin::Generator); + } + } +} + +void cmLocalGenerator::AddSourceOutputs( + cmSourceFile* source, const std::vector<std::string>& outputs, + OutputRole role, cmListFileBacktrace const& bt, cmCommandOrigin origin) +{ + for (std::string const& o : outputs) { + if (cmGeneratorExpression::Find(o) == std::string::npos) { + this->UpdateOutputToSourceMap(o, source, role, bt, origin); + continue; + } + + // This output path has a generator expression. Evaluate it to + // register the outputs for all configurations. + for (std::string const& out : + this->ExpandCustomCommandOutputGenex(o, bt)) { + this->UpdateOutputToSourceMap(out, source, role, bt, + cmCommandOrigin::Generator); + } + } +} + +void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& byproduct, + cmTarget* target, + cmListFileBacktrace const& bt, + cmCommandOrigin origin) +{ + SourceEntry entry; + entry.Sources.Target = target; + + auto pr = this->OutputToSource.emplace(byproduct, entry); + if (pr.second) { + CreateGeneratedSource(*this, byproduct, OutputRole::Byproduct, origin, bt); + } else { + SourceEntry& current = pr.first->second; + // Has the target already been set? + if (!current.Sources.Target) { + current.Sources.Target = target; + } else { + // Multiple custom commands/targets produce the same output (source file + // or target). See also comment in other UpdateOutputToSourceMap + // overload. + // + // TODO: Warn the user about this case. + } + } +} + +void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& output, + cmSourceFile* source, + OutputRole role, + cmListFileBacktrace const& bt, + cmCommandOrigin origin) +{ + SourceEntry entry; + entry.Sources.Source = source; + entry.Sources.SourceIsByproduct = role == OutputRole::Byproduct; + + auto pr = this->OutputToSource.emplace(output, entry); + if (pr.second) { + CreateGeneratedSource(*this, output, role, origin, bt); + } else { + SourceEntry& current = pr.first->second; + // Outputs take precedence over byproducts + if (!current.Sources.Source || + (current.Sources.SourceIsByproduct && role == OutputRole::Primary)) { + current.Sources.Source = source; + current.Sources.SourceIsByproduct = false; + } else { + // Multiple custom commands produce the same output but may + // be attached to a different source file (MAIN_DEPENDENCY). + // LinearGetSourceFileWithOutput would return the first one, + // so keep the mapping for the first one. + // + // TODO: Warn the user about this case. However, the VS 8 generator + // triggers it for separate generate.stamp rules in ZERO_CHECK and + // individual targets. + } + } +} + +cmTarget* cmLocalGenerator::LinearGetTargetWithOutput( + const std::string& name) const +{ + // We go through the ordered vector of targets to get reproducible results + // should multiple names match. + for (cmTarget* t : this->Makefile->GetOrderedTargets()) { + // Does the output of any command match the source file name? + if (AnyTargetCommandOutputMatches(name, t->GetPreBuildCommands())) { + return t; + } + if (AnyTargetCommandOutputMatches(name, t->GetPreLinkCommands())) { + return t; + } + if (AnyTargetCommandOutputMatches(name, t->GetPostBuildCommands())) { + return t; + } + } + return nullptr; +} + +cmSourceFile* cmLocalGenerator::LinearGetSourceFileWithOutput( + const std::string& name, cmSourceOutputKind kind, bool& byproduct) const +{ + // Outputs take precedence over byproducts. + byproduct = false; + cmSourceFile* fallback = nullptr; + + // Look through all the source files that have custom commands and see if the + // custom command has the passed source file as an output. + for (const auto& src : this->Makefile->GetSourceFiles()) { + // Does this source file have a custom command? + if (src->GetCustomCommand()) { + // Does the output of the custom command match the source file name? + if (AnyOutputMatches(name, src->GetCustomCommand()->GetOutputs())) { + // Return the first matching output. + return src.get(); + } + if (kind == cmSourceOutputKind::OutputOrByproduct) { + if (AnyOutputMatches(name, src->GetCustomCommand()->GetByproducts())) { + // Do not return the source yet as there might be a matching output. + fallback = src.get(); + } + } + } + } + + // Did we find a byproduct? + byproduct = fallback != nullptr; + return fallback; +} diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index 22d3599..91dd8ae 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -22,7 +22,9 @@ #include "cmProperty.h" #include "cmStateSnapshot.h" +class cmCompiledGeneratorExpression; class cmComputeLinkInformation; +class cmCustomCommand; class cmCustomCommandGenerator; class cmCustomCommandLines; class cmGeneratorTarget; @@ -36,6 +38,31 @@ class cmState; class cmTarget; class cmake; +/** Flag if byproducts shall also be considered. */ +enum class cmSourceOutputKind +{ + OutputOnly, + OutputOrByproduct +}; + +/** What scanner to use for dependencies lookup. */ +enum class cmDependencyScannerKind +{ + CMake, + Compiler +}; + +/** Target and source file which have a specific output. */ +struct cmSourcesWithOutput +{ + /** Target with byproduct. */ + cmTarget* Target = nullptr; + + /** Source file with output or byproduct. */ + cmSourceFile* Source = nullptr; + bool SourceIsByproduct = false; +}; + /** \class cmLocalGenerator * \brief Create required build files for a directory. * @@ -59,7 +86,7 @@ public: /** * Calls TraceVSDependencies() on all targets of this generator. */ - void TraceDependencies(); + void TraceDependencies() const; virtual void AddHelperCommands() {} @@ -337,6 +364,55 @@ public: bool command_expand_lists = false, const std::string& job_pool = "", bool stdPipesUTF8 = false); + virtual std::string CreateUtilityOutput( + std::string const& targetName, std::vector<std::string> const& byproducts, + cmListFileBacktrace const& bt); + + virtual std::vector<cmCustomCommandGenerator> MakeCustomCommandGenerators( + cmCustomCommand const& cc, std::string const& config); + + std::vector<std::string> ExpandCustomCommandOutputPaths( + cmCompiledGeneratorExpression const& cge, std::string const& config); + std::vector<std::string> ExpandCustomCommandOutputGenex( + std::string const& o, cmListFileBacktrace const& bt); + + /** + * Add target byproducts. + */ + void AddTargetByproducts(cmTarget* target, + const std::vector<std::string>& byproducts, + cmListFileBacktrace const& bt, + cmCommandOrigin origin); + + enum class OutputRole + { + Primary, + Byproduct, + }; + + /** + * Add source file outputs. + */ + void AddSourceOutputs(cmSourceFile* source, + std::vector<std::string> const& outputs, + OutputRole role, cmListFileBacktrace const& bt, + cmCommandOrigin origin); + + /** + * Return the target if the provided source name is a byproduct of a utility + * target or a PRE_BUILD, PRE_LINK, or POST_BUILD command. + * Return the source file which has the provided source name as output. + */ + cmSourcesWithOutput GetSourcesWithOutput(const std::string& name) const; + + /** + * Is there a source file that has the provided source name as an output? + * If so then return it. + */ + cmSourceFile* GetSourceFileWithOutput( + const std::string& name, + cmSourceOutputKind kind = cmSourceOutputKind::OutputOnly) const; + std::string GetProjectName() const; /** Compute the language used to compile the given source file. */ @@ -405,7 +481,7 @@ public: const std::string& fname); /** Construct a comment for a custom command. */ std::string ConstructComment(cmCustomCommandGenerator const& ccg, - const char* default_comment = ""); + const char* default_comment = "") const; // Compute object file names. std::string GetObjectFileNameWithoutTarget( const cmSourceFile& source, std::string const& dir_max, @@ -473,8 +549,7 @@ public: void CreateEvaluationFileOutputs(const std::string& config); void ProcessEvaluationFiles(std::vector<std::string>& generatedFiles); - const char* GetRuleLauncher(cmGeneratorTarget* target, - const std::string& prop); + cmProp GetRuleLauncher(cmGeneratorTarget* target, const std::string& prop); protected: //! put all the libraries for a target on into the given stream @@ -532,6 +607,36 @@ protected: bool BackwardsCompatibilityFinal; private: + /** + * See LinearGetSourceFileWithOutput for background information + */ + cmTarget* LinearGetTargetWithOutput(const std::string& name) const; + + /** + * Generalized old version of GetSourceFileWithOutput kept for + * backward-compatibility. It implements a linear search and supports + * relative file paths. It is used as a fall back by GetSourceFileWithOutput + * and GetSourcesWithOutput. + */ + cmSourceFile* LinearGetSourceFileWithOutput(const std::string& name, + cmSourceOutputKind kind, + bool& byproduct) const; + struct SourceEntry + { + cmSourcesWithOutput Sources; + }; + + // A map for fast output to input look up. + using OutputToSourceMap = std::unordered_map<std::string, SourceEntry>; + OutputToSourceMap OutputToSource; + + void UpdateOutputToSourceMap(std::string const& byproduct, cmTarget* target, + cmListFileBacktrace const& bt, + cmCommandOrigin origin); + void UpdateOutputToSourceMap(std::string const& output, cmSourceFile* source, + OutputRole role, cmListFileBacktrace const& bt, + cmCommandOrigin origin); + void AddSharedFlags(std::string& flags, const std::string& lang, bool shared); bool GetShouldUseOldFlags(bool shared, const std::string& lang) const; @@ -587,7 +692,7 @@ void AppendCustomCommandToOutput(cmLocalGenerator& lg, void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, cmCommandOrigin origin, cmTarget* target, - const cmUtilityOutput& force, const char* workingDir, + const char* workingDir, const std::vector<std::string>& byproducts, const std::vector<std::string>& depends, const cmCustomCommandLines& commandLines, diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx index ad782ee..5a747e5 100644 --- a/Source/cmLocalNinjaGenerator.cxx +++ b/Source/cmLocalNinjaGenerator.cxx @@ -10,6 +10,8 @@ #include <sstream> #include <utility> +#include <cmext/string_view> + #include "cmsys/FStream.hxx" #include "cmCryptoHash.h" @@ -22,7 +24,9 @@ #include "cmGlobalNinjaGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" +#include "cmMessageType.h" #include "cmNinjaTargetGenerator.h" +#include "cmPolicies.h" #include "cmProperty.h" #include "cmRulePlaceholderExpander.h" #include "cmSourceFile.h" @@ -553,9 +557,13 @@ void cmLocalNinjaGenerator::AppendCustomCommandLines( std::string launcher = this->MakeCustomLauncher(ccg); for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) { + std::string c = ccg.GetCommand(i); + if (c.empty()) { + continue; + } cmdLines.push_back(launcher + this->ConvertToOutputFormat( - ccg.GetCommand(i), + c, gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI : cmOutputConverter::SHELL)); @@ -565,71 +573,253 @@ void cmLocalNinjaGenerator::AppendCustomCommandLines( } void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement( - cmCustomCommand const* cc, const cmNinjaDeps& orderOnlyDeps, - const std::string& config) + cmCustomCommand const* cc, const std::set<cmGeneratorTarget*>& targets, + const std::string& fileConfig) { cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator(); - if (gg->SeenCustomCommand(cc, config)) { + if (gg->SeenCustomCommand(cc, fileConfig)) { return; } - cmCustomCommandGenerator ccg(*cc, config, this); + auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig); + for (cmCustomCommandGenerator const& ccg : ccgs) { + cmNinjaDeps orderOnlyDeps; - const std::vector<std::string>& outputs = ccg.GetOutputs(); - const std::vector<std::string>& byproducts = ccg.GetByproducts(); - - bool symbolic = false; - for (std::string const& output : outputs) { - if (cmSourceFile* sf = this->Makefile->GetSource(output)) { - if (sf->GetPropertyAsBool("SYMBOLIC")) { - symbolic = true; - break; + // A custom command may appear on multiple targets. However, some build + // systems exist where the target dependencies on some of the targets are + // overspecified, leading to a dependency cycle. If we assume all target + // dependencies are a superset of the true target dependencies for this + // custom command, we can take the set intersection of all target + // dependencies to obtain a correct dependency list. + // + // FIXME: This won't work in certain obscure scenarios involving indirect + // dependencies. + auto j = targets.begin(); + assert(j != targets.end()); + this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure( + *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1); + std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end()); + ++j; + + for (; j != targets.end(); ++j) { + std::vector<std::string> jDeps; + std::vector<std::string> depsIntersection; + this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure( + *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1); + std::sort(jDeps.begin(), jDeps.end()); + std::set_intersection(orderOnlyDeps.begin(), orderOnlyDeps.end(), + jDeps.begin(), jDeps.end(), + std::back_inserter(depsIntersection)); + orderOnlyDeps = depsIntersection; + } + + const std::vector<std::string>& outputs = ccg.GetOutputs(); + const std::vector<std::string>& byproducts = ccg.GetByproducts(); + + bool symbolic = false; + for (std::string const& output : outputs) { + if (cmSourceFile* sf = this->Makefile->GetSource(output)) { + if (sf->GetPropertyAsBool("SYMBOLIC")) { + symbolic = true; + break; + } + } + } + + cmNinjaDeps ninjaOutputs(outputs.size() + byproducts.size()); + std::transform(outputs.begin(), outputs.end(), ninjaOutputs.begin(), + gg->MapToNinjaPath()); + std::transform(byproducts.begin(), byproducts.end(), + ninjaOutputs.begin() + outputs.size(), + gg->MapToNinjaPath()); + + for (std::string const& ninjaOutput : ninjaOutputs) { + gg->SeenCustomCommandOutput(ninjaOutput); + } + + cmNinjaDeps ninjaDeps; + this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig); + + std::vector<std::string> cmdLines; + this->AppendCustomCommandLines(ccg, cmdLines); + + if (cmdLines.empty()) { + cmNinjaBuild build("phony"); + build.Comment = "Phony custom command for " + ninjaOutputs[0]; + build.Outputs = std::move(ninjaOutputs); + build.ExplicitDeps = std::move(ninjaDeps); + build.OrderOnlyDeps = orderOnlyDeps; + gg->WriteBuild(this->GetImplFileStream(fileConfig), build); + } else { + std::string customStep = cmSystemTools::GetFilenameName(ninjaOutputs[0]); + // Hash full path to make unique. + customStep += '-'; + cmCryptoHash hash(cmCryptoHash::AlgoSHA256); + customStep += hash.HashString(ninjaOutputs[0]).substr(0, 7); + + std::string depfile = cc->GetDepfile(); + if (!depfile.empty()) { + switch (this->GetPolicyStatus(cmPolicies::CMP0116)) { + case cmPolicies::WARN: + if (this->GetCurrentBinaryDirectory() != + this->GetBinaryDirectory() || + this->Makefile->PolicyOptionalWarningEnabled( + "CMAKE_POLICY_WARNING_CMP0116")) { + this->GetCMakeInstance()->IssueMessage( + MessageType::AUTHOR_WARNING, + cmPolicies::GetPolicyWarning(cmPolicies::CMP0116), + cc->GetBacktrace()); + } + CM_FALLTHROUGH; + case cmPolicies::OLD: + break; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::NEW: + cmSystemTools::MakeDirectory( + cmStrCat(this->GetBinaryDirectory(), "/CMakeFiles/d")); + depfile = ccg.GetInternalDepfile(); + break; + } } + + gg->WriteCustomCommandBuild( + this->BuildCommandLine(cmdLines, customStep), + this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0], + depfile, cc->GetJobPool(), cc->GetUsesTerminal(), + /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, fileConfig, + ninjaDeps, orderOnlyDeps); } } +} -#if 0 -# error TODO: Once CC in an ExternalProject target must provide the \ - file of each imported target that has an add_dependencies pointing \ - at us. How to know which ExternalProject step actually provides it? -#endif - cmNinjaDeps ninjaOutputs(outputs.size() + byproducts.size()); - std::transform(outputs.begin(), outputs.end(), ninjaOutputs.begin(), - gg->MapToNinjaPath()); - std::transform(byproducts.begin(), byproducts.end(), - ninjaOutputs.begin() + outputs.size(), gg->MapToNinjaPath()); +namespace { +bool HasUniqueByproducts(cmLocalGenerator& lg, + std::vector<std::string> const& byproducts, + cmListFileBacktrace const& bt) +{ + std::vector<std::string> configs = + lg.GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); + cmGeneratorExpression ge(bt); + for (std::string const& p : byproducts) { + if (cmGeneratorExpression::Find(p) == std::string::npos) { + return false; + } + std::set<std::string> seen; + std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p); + for (std::string const& config : configs) { + for (std::string const& b : + lg.ExpandCustomCommandOutputPaths(*cge, config)) { + if (!seen.insert(b).second) { + return false; + } + } + } + } + return true; +} - for (std::string const& ninjaOutput : ninjaOutputs) { - gg->SeenCustomCommandOutput(ninjaOutput); +bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs) +{ + std::set<std::string> allOutputs; + std::set<std::string> allByproducts; + for (cmCustomCommandGenerator const& ccg : ccgs) { + for (std::string const& output : ccg.GetOutputs()) { + if (!allOutputs.insert(output).second) { + return false; + } + } + for (std::string const& byproduct : ccg.GetByproducts()) { + if (!allByproducts.insert(byproduct).second) { + return false; + } + } + } + return true; +} +} + +std::string cmLocalNinjaGenerator::CreateUtilityOutput( + std::string const& targetName, std::vector<std::string> const& byproducts, + cmListFileBacktrace const& bt) +{ + // In Ninja Multi-Config, we can only produce cross-config utility + // commands if all byproducts are per-config. + if (!this->GetGlobalGenerator()->IsMultiConfig() || + !HasUniqueByproducts(*this, byproducts, bt)) { + return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts, + bt); + } + + std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(), + "/CMakeFiles/", targetName, '-'); + // The output is not actually created so mark it symbolic. + for (std::string const& config : + this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig)) { + std::string const force = cmStrCat(base, config); + if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) { + sf->SetProperty("SYMBOLIC", "1"); + } else { + cmSystemTools::Error("Could not get source file entry for " + force); + } } + this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName); + return cmStrCat(base, "$<CONFIG>"_s); +} + +std::vector<cmCustomCommandGenerator> +cmLocalNinjaGenerator::MakeCustomCommandGenerators( + cmCustomCommand const& cc, std::string const& fileConfig) +{ + cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator(); - cmNinjaDeps ninjaDeps; - this->AppendCustomCommandDeps(ccg, ninjaDeps, config); + bool transformDepfile = false; + switch (this->GetPolicyStatus(cmPolicies::CMP0116)) { + case cmPolicies::OLD: + case cmPolicies::WARN: + break; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::NEW: + transformDepfile = true; + break; + } - std::vector<std::string> cmdLines; - this->AppendCustomCommandLines(ccg, cmdLines); + // Start with the build graph's configuration. + std::vector<cmCustomCommandGenerator> ccgs; + ccgs.emplace_back(cc, fileConfig, this, transformDepfile); - if (cmdLines.empty()) { - cmNinjaBuild build("phony"); - build.Comment = "Phony custom command for " + ninjaOutputs[0]; - build.Outputs = std::move(ninjaOutputs); - build.ExplicitDeps = std::move(ninjaDeps); - build.OrderOnlyDeps = orderOnlyDeps; - gg->WriteBuild(this->GetImplFileStream(config), build); - } else { - std::string customStep = cmSystemTools::GetFilenameName(ninjaOutputs[0]); - // Hash full path to make unique. - customStep += '-'; - cmCryptoHash hash(cmCryptoHash::AlgoSHA256); - customStep += hash.HashString(ninjaOutputs[0]).substr(0, 7); + // Consider adding cross configurations. + if (!gg->EnableCrossConfigBuild()) { + return ccgs; + } + + // Outputs and byproducts must be expressed using generator expressions. + for (std::string const& output : cc.GetOutputs()) { + if (cmGeneratorExpression::Find(output) == std::string::npos) { + return ccgs; + } + } + for (std::string const& byproduct : cc.GetByproducts()) { + if (cmGeneratorExpression::Find(byproduct) == std::string::npos) { + return ccgs; + } + } - gg->WriteCustomCommandBuild( - this->BuildCommandLine(cmdLines, customStep), - this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0], - cc->GetDepfile(), cc->GetJobPool(), cc->GetUsesTerminal(), - /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, config, - ninjaDeps, orderOnlyDeps); + // Tentatively add the other cross configurations. + for (std::string const& config : gg->GetCrossConfigs(fileConfig)) { + if (fileConfig != config) { + ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config); + } + } + + // If outputs and byproducts are not unique to each configuration, + // drop the cross configurations. + if (!HasUniqueOutputs(ccgs)) { + ccgs.erase(ccgs.begin() + 1, ccgs.end()); } + + return ccgs; } void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc, @@ -645,42 +835,13 @@ void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc, } void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements( - const std::string& config) + const std::string& fileConfig) { for (cmCustomCommand const* customCommand : this->CustomCommands) { auto i = this->CustomCommandTargets.find(customCommand); assert(i != this->CustomCommandTargets.end()); - // A custom command may appear on multiple targets. However, some build - // systems exist where the target dependencies on some of the targets are - // overspecified, leading to a dependency cycle. If we assume all target - // dependencies are a superset of the true target dependencies for this - // custom command, we can take the set intersection of all target - // dependencies to obtain a correct dependency list. - // - // FIXME: This won't work in certain obscure scenarios involving indirect - // dependencies. - auto j = i->second.begin(); - assert(j != i->second.end()); - std::vector<std::string> ccTargetDeps; - this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure( - *j, ccTargetDeps, config); - std::sort(ccTargetDeps.begin(), ccTargetDeps.end()); - ++j; - - for (; j != i->second.end(); ++j) { - std::vector<std::string> jDeps; - std::vector<std::string> depsIntersection; - this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(*j, jDeps, - config); - std::sort(jDeps.begin(), jDeps.end()); - std::set_intersection(ccTargetDeps.begin(), ccTargetDeps.end(), - jDeps.begin(), jDeps.end(), - std::back_inserter(depsIntersection)); - ccTargetDeps = depsIntersection; - } - - this->WriteCustomCommandBuildStatement(i->first, ccTargetDeps, config); + this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig); } } diff --git a/Source/cmLocalNinjaGenerator.h b/Source/cmLocalNinjaGenerator.h index e81402c..87d5e53 100644 --- a/Source/cmLocalNinjaGenerator.h +++ b/Source/cmLocalNinjaGenerator.h @@ -10,6 +10,7 @@ #include <string> #include <vector> +#include "cmListFileCache.h" #include "cmLocalCommonGenerator.h" #include "cmNinjaTypes.h" #include "cmOutputConverter.h" @@ -70,6 +71,13 @@ public: const std::string& fileConfig, cmNinjaTargetDepends depends); + std::string CreateUtilityOutput(std::string const& targetName, + std::vector<std::string> const& byproducts, + cmListFileBacktrace const& bt) override; + + std::vector<cmCustomCommandGenerator> MakeCustomCommandGenerators( + cmCustomCommand const& cc, std::string const& config) override; + void AddCustomCommandTarget(cmCustomCommand const* cc, cmGeneratorTarget* target); void AppendCustomCommandLines(cmCustomCommandGenerator const& ccg, @@ -99,9 +107,9 @@ private: void WriteProcessedMakefile(std::ostream& os); void WritePools(std::ostream& os); - void WriteCustomCommandBuildStatement(cmCustomCommand const* cc, - const cmNinjaDeps& orderOnlyDeps, - const std::string& config); + void WriteCustomCommandBuildStatement( + cmCustomCommand const* cc, const std::set<cmGeneratorTarget*>& targets, + const std::string& config); void WriteCustomCommandBuildStatements(const std::string& config); diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx index c877cf8..358df9d 100644 --- a/Source/cmLocalUnixMakefileGenerator3.cxx +++ b/Source/cmLocalUnixMakefileGenerator3.cxx @@ -5,18 +5,23 @@ #include <algorithm> #include <cassert> #include <cstdio> +#include <functional> #include <sstream> #include <utility> #include <cm/memory> +#include <cm/string_view> #include <cm/vector> #include <cmext/algorithm> +#include <cmext/string_view> #include "cmsys/FStream.hxx" #include "cmsys/Terminal.h" +#include "cmCMakePath.h" #include "cmCustomCommand.h" // IWYU pragma: keep #include "cmCustomCommandGenerator.h" +#include "cmDependsCompiler.h" #include "cmFileTimeCache.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" @@ -50,8 +55,9 @@ # include "cmDependsJava.h" #endif +namespace { // Helper function used below. -static std::string cmSplitExtension(std::string const& in, std::string& base) +std::string cmSplitExtension(std::string const& in, std::string& base) { std::string ext; std::string::size_type dot_pos = in.rfind('.'); @@ -65,6 +71,43 @@ static std::string cmSplitExtension(std::string const& in, std::string& base) return ext; } +// Helper predicate for removing absolute paths that don't point to the +// source or binary directory. It is used when CMAKE_DEPENDS_IN_PROJECT_ONLY +// is set ON, to only consider in-project dependencies during the build. +class NotInProjectDir +{ +public: + // Constructor with the source and binary directory's path + NotInProjectDir(cm::string_view sourceDir, cm::string_view binaryDir) + : SourceDir(sourceDir) + , BinaryDir(binaryDir) + { + } + + // Operator evaluating the predicate + bool operator()(const std::string& p) const + { + auto path = cmCMakePath(p).Normal(); + + // Keep all relative paths: + if (path.IsRelative()) { + return false; + } + + // If it's an absolute path, check if it starts with the source + // directory: + return !(cmCMakePath(SourceDir).IsPrefix(path) || + cmCMakePath(BinaryDir).IsPrefix(path)); + } + +private: + // The path to the source directory + cm::string_view SourceDir; + // The path to the binary directory + cm::string_view BinaryDir; +}; +} + cmLocalUnixMakefileGenerator3::cmLocalUnixMakefileGenerator3( cmGlobalGenerator* gg, cmMakefile* mf) : cmLocalCommonGenerator(gg, mf, mf->GetCurrentBinaryDirectory()) @@ -552,8 +595,10 @@ void cmLocalUnixMakefileGenerator3::WriteMakeRule( } } - // Write the list of commands. - os << cmWrap("\t", commands, "", "\n") << "\n"; + if (!commands.empty()) { + // Write the list of commands. + os << cmWrap("\t", commands, "", "\n") << "\n"; + } if (symbolic && !this->IsWatcomWMake()) { os << ".PHONY : " << tgt << "\n"; } @@ -960,7 +1005,7 @@ void cmLocalUnixMakefileGenerator3::AppendCustomCommand( std::string launcher; // Short-circuit if there is no launcher. - const char* val = this->GetRuleLauncher(target, "RULE_LAUNCH_CUSTOM"); + cmProp val = this->GetRuleLauncher(target, "RULE_LAUNCH_CUSTOM"); if (cmNonempty(val)) { // Expand rule variables referenced in the given launcher command. cmRulePlaceholderExpander::RuleVariables vars; @@ -980,7 +1025,7 @@ void cmLocalUnixMakefileGenerator3::AppendCustomCommand( } vars.Output = output.c_str(); - launcher = val; + launcher = *val; rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars); if (!launcher.empty()) { launcher += " "; @@ -1298,91 +1343,153 @@ bool cmLocalUnixMakefileGenerator3::UpdateDependencies( cmSystemTools::Error("Target DependInfo.cmake file not found"); } + bool status = true; + // Check if any multiple output pairs have a missing file. this->CheckMultipleOutputs(verbose); std::string const targetDir = cmSystemTools::GetFilenamePath(tgtInfo); - std::string const internalDependFile = targetDir + "/depend.internal"; - std::string const dependFile = targetDir + "/depend.make"; - - // If the target DependInfo.cmake file has changed since the last - // time dependencies were scanned then force rescanning. This may - // happen when a new source file is added and CMake regenerates the - // project but no other sources were touched. - bool needRescanDependInfo = false; - cmFileTimeCache* ftc = - this->GlobalGenerator->GetCMakeInstance()->GetFileTimeCache(); - { - int result; - if (!ftc->Compare(internalDependFile, tgtInfo, &result) || result < 0) { - if (verbose) { - cmSystemTools::Stdout(cmStrCat("Dependee \"", tgtInfo, - "\" is newer than depender \"", - internalDependFile, "\".\n")); + if (!this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES").empty()) { + // dependencies are managed by CMake itself + + std::string const internalDependFile = targetDir + "/depend.internal"; + std::string const dependFile = targetDir + "/depend.make"; + + // If the target DependInfo.cmake file has changed since the last + // time dependencies were scanned then force rescanning. This may + // happen when a new source file is added and CMake regenerates the + // project but no other sources were touched. + bool needRescanDependInfo = false; + cmFileTimeCache* ftc = + this->GlobalGenerator->GetCMakeInstance()->GetFileTimeCache(); + { + int result; + if (!ftc->Compare(internalDependFile, tgtInfo, &result) || result < 0) { + if (verbose) { + cmSystemTools::Stdout(cmStrCat("Dependee \"", tgtInfo, + "\" is newer than depender \"", + internalDependFile, "\".\n")); + } + needRescanDependInfo = true; } - needRescanDependInfo = true; } - } - // If the directory information is newer than depend.internal, include dirs - // may have changed. In this case discard all old dependencies. - bool needRescanDirInfo = false; - { - std::string dirInfoFile = - cmStrCat(this->GetCurrentBinaryDirectory(), - "/CMakeFiles/CMakeDirectoryInformation.cmake"); - int result; - if (!ftc->Compare(internalDependFile, dirInfoFile, &result) || - result < 0) { - if (verbose) { - cmSystemTools::Stdout(cmStrCat("Dependee \"", dirInfoFile, - "\" is newer than depender \"", - internalDependFile, "\".\n")); + // If the directory information is newer than depend.internal, include + // dirs may have changed. In this case discard all old dependencies. + bool needRescanDirInfo = false; + { + std::string dirInfoFile = + cmStrCat(this->GetCurrentBinaryDirectory(), + "/CMakeFiles/CMakeDirectoryInformation.cmake"); + int result; + if (!ftc->Compare(internalDependFile, dirInfoFile, &result) || + result < 0) { + if (verbose) { + cmSystemTools::Stdout(cmStrCat("Dependee \"", dirInfoFile, + "\" is newer than depender \"", + internalDependFile, "\".\n")); + } + needRescanDirInfo = true; } - needRescanDirInfo = true; + } + + // Check the implicit dependencies to see if they are up to date. + // The build.make file may have explicit dependencies for the object + // files but these will not affect the scanning process so they need + // not be considered. + cmDepends::DependencyMap validDependencies; + bool needRescanDependencies = false; + if (!needRescanDirInfo) { + cmDependsC checker; + checker.SetVerbose(verbose); + checker.SetFileTimeCache(ftc); + // cmDependsC::Check() fills the vector validDependencies() with the + // dependencies for those files where they are still valid, i.e. + // neither the files themselves nor any files they depend on have + // changed. We don't do that if the CMakeDirectoryInformation.cmake + // file has changed, because then potentially all dependencies have + // changed. This information is given later on to cmDependsC, which + // then only rescans the files where it did not get valid dependencies + // via this dependency vector. This means that in the normal case, when + // only few or one file have been edited, then also only this one file + // is actually scanned again, instead of all files for this target. + needRescanDependencies = + !checker.Check(dependFile, internalDependFile, validDependencies); + } + + if (needRescanDependInfo || needRescanDirInfo || needRescanDependencies) { + // The dependencies must be regenerated. + std::string targetName = cmSystemTools::GetFilenameName(targetDir); + targetName = targetName.substr(0, targetName.length() - 4); + std::string message = + cmStrCat("Scanning dependencies of target ", targetName); + cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta | + cmsysTerminal_Color_ForegroundBold, + message.c_str(), true, color); + + status = this->ScanDependencies(targetDir, dependFile, + internalDependFile, validDependencies); } } - // Check the implicit dependencies to see if they are up to date. - // The build.make file may have explicit dependencies for the object - // files but these will not affect the scanning process so they need - // not be considered. - cmDepends::DependencyMap validDependencies; - bool needRescanDependencies = false; - if (!needRescanDirInfo) { - cmDependsC checker; - checker.SetVerbose(verbose); - checker.SetFileTimeCache(ftc); - // cmDependsC::Check() fills the vector validDependencies() with the - // dependencies for those files where they are still valid, i.e. neither - // the files themselves nor any files they depend on have changed. - // We don't do that if the CMakeDirectoryInformation.cmake file has - // changed, because then potentially all dependencies have changed. - // This information is given later on to cmDependsC, which then only - // rescans the files where it did not get valid dependencies via this - // dependency vector. This means that in the normal case, when only - // few or one file have been edited, then also only this one file is - // actually scanned again, instead of all files for this target. - needRescanDependencies = - !checker.Check(dependFile, internalDependFile, validDependencies); - } - - if (needRescanDependInfo || needRescanDirInfo || needRescanDependencies) { - // The dependencies must be regenerated. - std::string targetName = cmSystemTools::GetFilenameName(targetDir); - targetName = targetName.substr(0, targetName.length() - 4); - std::string message = - cmStrCat("Scanning dependencies of target ", targetName); - cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta | - cmsysTerminal_Color_ForegroundBold, - message.c_str(), true, color); - - return this->ScanDependencies(targetDir, dependFile, internalDependFile, - validDependencies); + auto depends = + this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES"); + if (!depends.empty()) { + // dependencies are managed by compiler + auto depFiles = cmExpandedList(depends, true); + std::string const internalDepFile = + targetDir + "/compiler_depend.internal"; + std::string const depFile = targetDir + "/compiler_depend.make"; + cmDepends::DependencyMap dependencies; + cmDependsCompiler depsManager; + bool projectOnly = cmIsOn( + this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_IN_PROJECT_ONLY")); + + depsManager.SetVerbose(verbose); + depsManager.SetLocalGenerator(this); + + if (!depsManager.CheckDependencies( + internalDepFile, depFiles, dependencies, + projectOnly ? NotInProjectDir(this->GetSourceDirectory(), + this->GetBinaryDirectory()) + : std::function<bool(const std::string&)>())) { + // regenerate dependencies files + std::string targetName = + cmCMakePath(targetDir).GetFileName().RemoveExtension().GenericString(); + auto message = cmStrCat( + "Consolidate compiler generated dependencies of target ", targetName); + cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta | + cmsysTerminal_Color_ForegroundBold, + message.c_str(), true, color); + + // Open the make depends file. This should be copy-if-different + // because the make tool may try to reload it needlessly otherwise. + cmGeneratedFileStream ruleFileStream( + depFile, false, this->GlobalGenerator->GetMakefileEncoding()); + ruleFileStream.SetCopyIfDifferent(true); + if (!ruleFileStream) { + return false; + } + + // Open the cmake dependency tracking file. This should not be + // copy-if-different because dependencies are re-scanned when it is + // older than the DependInfo.cmake. + cmGeneratedFileStream internalRuleFileStream( + internalDepFile, false, this->GlobalGenerator->GetMakefileEncoding()); + if (!internalRuleFileStream) { + return false; + } + + this->WriteDisclaimer(ruleFileStream); + this->WriteDisclaimer(internalRuleFileStream); + + depsManager.WriteDependencies(dependencies, ruleFileStream, + internalRuleFileStream); + } } // The dependencies are already up-to-date. - return true; + return status; } bool cmLocalUnixMakefileGenerator3::ScanDependencies( @@ -1721,178 +1828,207 @@ void cmLocalUnixMakefileGenerator3::ClearDependencies(cmMakefile* mf, cmDepends clearer; clearer.SetVerbose(verbose); for (std::string const& file : files) { - std::string dir = cmSystemTools::GetFilenamePath(file); + auto snapshot = mf->GetState()->CreateBaseSnapshot(); + cmMakefile lmf(mf->GetGlobalGenerator(), snapshot); + lmf.ReadListFile(file); - // Clear the implicit dependency makefile. - std::string dependFile = dir + "/depend.make"; - clearer.Clear(dependFile); + if (!lmf.GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES").empty()) { + std::string dir = cmSystemTools::GetFilenamePath(file); - // Remove the internal dependency check file to force - // regeneration. - std::string internalDependFile = dir + "/depend.internal"; - cmSystemTools::RemoveFile(internalDependFile); - } -} - -namespace { -// Helper predicate for removing absolute paths that don't point to the -// source or binary directory. It is used when CMAKE_DEPENDS_IN_PROJECT_ONLY -// is set ON, to only consider in-project dependencies during the build. -class NotInProjectDir -{ -public: - // Constructor with the source and binary directory's path - NotInProjectDir(std::string sourceDir, std::string binaryDir) - : SourceDir(std::move(sourceDir)) - , BinaryDir(std::move(binaryDir)) - { - } + // Clear the implicit dependency makefile. + std::string dependFile = dir + "/depend.make"; + clearer.Clear(dependFile); - // Operator evaluating the predicate - bool operator()(const std::string& path) const - { - // Keep all relative paths: - if (!cmSystemTools::FileIsFullPath(path)) { - return false; + // Remove the internal dependency check file to force + // regeneration. + std::string internalDependFile = dir + "/depend.internal"; + cmSystemTools::RemoveFile(internalDependFile); } - // If it's an absolute path, check if it starts with the source - // directory: - return ( - !(IsInDirectory(SourceDir, path) || IsInDirectory(BinaryDir, path))); - } -private: - // Helper function used by the predicate - static bool IsInDirectory(const std::string& baseDir, - const std::string& testDir) - { - // First check if the test directory "starts with" the base directory: - if (!cmHasPrefix(testDir, baseDir)) { - return false; + auto depsFiles = lmf.GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES"); + if (!depsFiles.empty()) { + auto dir = cmCMakePath(file).GetParentPath(); + // Clear the implicit dependency makefile. + auto depFile = cmCMakePath(dir).Append("compiler_depend.make"); + clearer.Clear(depFile.GenericString()); + + // Remove the internal dependency check file + auto internalDepFile = + cmCMakePath(dir).Append("compiler_depend.internal"); + cmSystemTools::RemoveFile(internalDepFile.GenericString()); + + // Touch timestamp file to force dependencies regeneration + auto DepTimestamp = cmCMakePath(dir).Append("compiler_depend.ts"); + cmSystemTools::Touch(DepTimestamp.GenericString(), true); + + // clear the dependencies files generated by the compiler + std::vector<std::string> dependencies = cmExpandedList(depsFiles); + cmDependsCompiler depsManager; + depsManager.SetVerbose(verbose); + depsManager.ClearDependencies(dependencies); } - // If it does, then check that it's either the same string, or that the - // next character is a slash: - return ((testDir.size() == baseDir.size()) || - (testDir[baseDir.size()] == '/')); } - - // The path to the source directory - std::string SourceDir; - // The path to the binary directory - std::string BinaryDir; -}; } void cmLocalUnixMakefileGenerator3::WriteDependLanguageInfo( std::ostream& cmakefileStream, cmGeneratorTarget* target) { - ImplicitDependLanguageMap const& implicitLangs = - this->GetImplicitDepends(target); + // To enable dependencies filtering + cmakefileStream << "\n" + << "# Consider dependencies only in project.\n" + << "set(CMAKE_DEPENDS_IN_PROJECT_ONLY " + << (cmIsOn(this->Makefile->GetSafeDefinition( + "CMAKE_DEPENDS_IN_PROJECT_ONLY")) + ? "ON" + : "OFF") + << ")\n\n"; + + auto const& implicitLangs = + this->GetImplicitDepends(target, cmDependencyScannerKind::CMake); // list the languages - cmakefileStream - << "# The set of languages for which implicit dependencies are needed:\n"; + cmakefileStream << "# The set of languages for which implicit " + "dependencies are needed:\n"; cmakefileStream << "set(CMAKE_DEPENDS_LANGUAGES\n"; for (auto const& implicitLang : implicitLangs) { cmakefileStream << " \"" << implicitLang.first << "\"\n"; } cmakefileStream << " )\n"; - // now list the files for each language - cmakefileStream - << "# The set of files for implicit dependencies of each language:\n"; - for (auto const& implicitLang : implicitLangs) { - cmakefileStream << "set(CMAKE_DEPENDS_CHECK_" << implicitLang.first - << "\n"; - ImplicitDependFileMap const& implicitPairs = implicitLang.second; - - // for each file pair - for (auto const& implicitPair : implicitPairs) { - for (auto const& di : implicitPair.second) { - cmakefileStream << " \"" << di << "\" "; - cmakefileStream << "\"" << implicitPair.first << "\"\n"; + if (!implicitLangs.empty()) { + // now list the files for each language + cmakefileStream + << "# The set of files for implicit dependencies of each language:\n"; + for (auto const& implicitLang : implicitLangs) { + const auto& lang = implicitLang.first; + + cmakefileStream << "set(CMAKE_DEPENDS_CHECK_" << lang << "\n"; + auto const& implicitPairs = implicitLang.second; + + // for each file pair + for (auto const& implicitPair : implicitPairs) { + for (auto const& di : implicitPair.second) { + cmakefileStream << " \"" << di << "\" "; + cmakefileStream << "\"" << implicitPair.first << "\"\n"; + } } - } - cmakefileStream << " )\n"; - - // Tell the dependency scanner what compiler is used. - std::string cidVar = - cmStrCat("CMAKE_", implicitLang.first, "_COMPILER_ID"); - cmProp cid = this->Makefile->GetDefinition(cidVar); - if (cmNonempty(cid)) { - cmakefileStream << "set(CMAKE_" << implicitLang.first - << "_COMPILER_ID \"" << *cid << "\")\n"; - } + cmakefileStream << " )\n"; - if (implicitLang.first == "Fortran") { - std::string smodSep = - this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP"); - std::string smodExt = - this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT"); - cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_SEP \"" << smodSep - << "\")\n"; - cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_EXT \"" << smodExt - << "\")\n"; - } + // Tell the dependency scanner what compiler is used. + std::string cidVar = cmStrCat("CMAKE_", lang, "_COMPILER_ID"); + cmProp cid = this->Makefile->GetDefinition(cidVar); + if (cmNonempty(cid)) { + cmakefileStream << "set(CMAKE_" << lang << "_COMPILER_ID \"" << *cid + << "\")\n"; + } - // Build a list of preprocessor definitions for the target. - std::set<std::string> defines; - this->GetTargetDefines(target, this->GetConfigName(), implicitLang.first, - defines); - if (!defines.empty()) { - /* clang-format off */ + if (lang == "Fortran") { + std::string smodSep = + this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP"); + std::string smodExt = + this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT"); + cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_SEP \"" << smodSep + << "\")\n"; + cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_EXT \"" << smodExt + << "\")\n"; + } + + // Build a list of preprocessor definitions for the target. + std::set<std::string> defines; + this->GetTargetDefines(target, this->GetConfigName(), lang, defines); + if (!defines.empty()) { + /* clang-format off */ cmakefileStream << "\n" << "# Preprocessor definitions for this target.\n" - << "set(CMAKE_TARGET_DEFINITIONS_" << implicitLang.first << "\n"; - /* clang-format on */ - for (std::string const& define : defines) { - cmakefileStream << " " << cmOutputConverter::EscapeForCMake(define) - << "\n"; + << "set(CMAKE_TARGET_DEFINITIONS_" << lang << "\n"; + /* clang-format on */ + for (std::string const& define : defines) { + cmakefileStream << " " << cmOutputConverter::EscapeForCMake(define) + << "\n"; + } + cmakefileStream << " )\n"; + } + + // Target-specific include directories: + cmakefileStream << "\n" + << "# The include file search paths:\n"; + cmakefileStream << "set(CMAKE_" << lang << "_TARGET_INCLUDE_PATH\n"; + std::vector<std::string> includes; + + this->GetIncludeDirectories(includes, target, lang, + this->GetConfigName()); + std::string const& binaryDir = this->GetState()->GetBinaryDirectory(); + if (this->Makefile->IsOn("CMAKE_DEPENDS_IN_PROJECT_ONLY")) { + std::string const& sourceDir = this->GetState()->GetSourceDirectory(); + cm::erase_if(includes, ::NotInProjectDir(sourceDir, binaryDir)); + } + for (std::string const& include : includes) { + cmakefileStream << " \"" + << this->MaybeConvertToRelativePath(binaryDir, include) + << "\"\n"; } cmakefileStream << " )\n"; } - // Target-specific include directories: - cmakefileStream << "\n" - << "# The include file search paths:\n"; - cmakefileStream << "set(CMAKE_" << implicitLang.first - << "_TARGET_INCLUDE_PATH\n"; - std::vector<std::string> includes; - - this->GetIncludeDirectories(includes, target, implicitLang.first, - this->GetConfigName()); - std::string binaryDir = this->GetState()->GetBinaryDirectory(); - if (this->Makefile->IsOn("CMAKE_DEPENDS_IN_PROJECT_ONLY")) { - std::string const& sourceDir = this->GetState()->GetSourceDirectory(); - cm::erase_if(includes, ::NotInProjectDir(sourceDir, binaryDir)); + // Store include transform rule properties. Write the directory + // rules first because they may be overridden by later target rules. + std::vector<std::string> transformRules; + if (cmProp xform = + this->Makefile->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { + cmExpandList(*xform, transformRules); } - for (std::string const& include : includes) { - cmakefileStream << " \"" - << this->MaybeConvertToRelativePath(binaryDir, include) - << "\"\n"; + if (cmProp xform = + target->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { + cmExpandList(*xform, transformRules); + } + if (!transformRules.empty()) { + cmakefileStream << "\nset(CMAKE_INCLUDE_TRANSFORMS\n"; + for (std::string const& tr : transformRules) { + cmakefileStream << " " << cmOutputConverter::EscapeForCMake(tr) + << "\n"; + } + cmakefileStream << " )\n"; } - cmakefileStream << " )\n"; } - // Store include transform rule properties. Write the directory - // rules first because they may be overridden by later target rules. - std::vector<std::string> transformRules; - if (cmProp xform = - this->Makefile->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { - cmExpandList(*xform, transformRules); - } - if (cmProp xform = - target->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { - cmExpandList(*xform, transformRules); - } - if (!transformRules.empty()) { - cmakefileStream << "set(CMAKE_INCLUDE_TRANSFORMS\n"; - for (std::string const& tr : transformRules) { - cmakefileStream << " " << cmOutputConverter::EscapeForCMake(tr) << "\n"; + auto const& compilerLangs = + this->GetImplicitDepends(target, cmDependencyScannerKind::Compiler); + + // list the dependency files managed by the compiler + cmakefileStream << "\n# The set of dependency files which are needed:\n"; + cmakefileStream << "set(CMAKE_DEPENDS_DEPENDENCY_FILES\n"; + for (auto const& compilerLang : compilerLangs) { + auto const& compilerPairs = compilerLang.second; + if (compilerLang.first == "CUSTOM"_s) { + for (auto const& compilerPair : compilerPairs) { + for (auto const& src : compilerPair.second) { + cmakefileStream << R"( "" ")" + << this->MaybeConvertToRelativePath( + this->GetBinaryDirectory(), compilerPair.first) + << R"(" "custom" ")" + << this->MaybeConvertToRelativePath( + this->GetBinaryDirectory(), src) + << "\"\n"; + } + } + } else { + auto depFormat = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_", compilerLang.first, "_DEPFILE_FORMAT")); + for (auto const& compilerPair : compilerPairs) { + for (auto const& src : compilerPair.second) { + cmakefileStream << " \"" << src << "\" \"" + << this->MaybeConvertToRelativePath( + this->GetBinaryDirectory(), compilerPair.first) + << "\" \"" << depFormat << "\" \"" + << this->MaybeConvertToRelativePath( + this->GetBinaryDirectory(), compilerPair.first) + << ".d\"\n"; + } + } } - cmakefileStream << " )\n"; } + cmakefileStream << " )\n"; } void cmLocalUnixMakefileGenerator3::WriteDisclaimer(std::ostream& os) @@ -2049,16 +2185,18 @@ std::string cmLocalUnixMakefileGenerator3::GetTargetDirectory( } cmLocalUnixMakefileGenerator3::ImplicitDependLanguageMap const& -cmLocalUnixMakefileGenerator3::GetImplicitDepends(const cmGeneratorTarget* tgt) +cmLocalUnixMakefileGenerator3::GetImplicitDepends( + const cmGeneratorTarget* tgt, cmDependencyScannerKind scanner) { - return this->ImplicitDepends[tgt->GetName()]; + return this->ImplicitDepends[tgt->GetName()][scanner]; } void cmLocalUnixMakefileGenerator3::AddImplicitDepends( const cmGeneratorTarget* tgt, const std::string& lang, - const std::string& obj, const std::string& src) + const std::string& obj, const std::string& src, + cmDependencyScannerKind scanner) { - this->ImplicitDepends[tgt->GetName()][lang][obj].push_back(src); + this->ImplicitDepends[tgt->GetName()][scanner][lang][obj].push_back(src); } void cmLocalUnixMakefileGenerator3::CreateCDCommand( diff --git a/Source/cmLocalUnixMakefileGenerator3.h b/Source/cmLocalUnixMakefileGenerator3.h index 8286d67..14dd0ba 100644 --- a/Source/cmLocalUnixMakefileGenerator3.h +++ b/Source/cmLocalUnixMakefileGenerator3.h @@ -13,6 +13,7 @@ #include "cmDepends.h" #include "cmLocalCommonGenerator.h" +#include "cmLocalGenerator.h" class cmCustomCommand; class cmCustomCommandGenerator; @@ -152,23 +153,21 @@ public: // File pairs for implicit dependency scanning. The key of the map // is the depender and the value is the explicit dependee. - struct ImplicitDependFileMap : public cmDepends::DependencyMap - { - }; - struct ImplicitDependLanguageMap - : public std::map<std::string, ImplicitDependFileMap> - { - }; - struct ImplicitDependTargetMap - : public std::map<std::string, ImplicitDependLanguageMap> - { - }; + using ImplicitDependFileMap = cmDepends::DependencyMap; + using ImplicitDependLanguageMap = + std::map<std::string, ImplicitDependFileMap>; + using ImplicitDependScannerMap = + std::map<cmDependencyScannerKind, ImplicitDependLanguageMap>; + using ImplicitDependTargetMap = + std::map<std::string, ImplicitDependScannerMap>; ImplicitDependLanguageMap const& GetImplicitDepends( - cmGeneratorTarget const* tgt); + cmGeneratorTarget const* tgt, + cmDependencyScannerKind scanner = cmDependencyScannerKind::CMake); - void AddImplicitDepends(cmGeneratorTarget const* tgt, - const std::string& lang, const std::string& obj, - const std::string& src); + void AddImplicitDepends( + cmGeneratorTarget const* tgt, const std::string& lang, + const std::string& obj, const std::string& src, + cmDependencyScannerKind scanner = cmDependencyScannerKind::CMake); // write the target rules for the local Makefile into the stream void WriteLocalAllRules(std::ostream& ruleFileStream); @@ -178,11 +177,11 @@ public: /** Get whether to create rules to generate preprocessed and assembly sources. This could be converted to a variable lookup later. */ - bool GetCreatePreprocessedSourceRules() + bool GetCreatePreprocessedSourceRules() const { return !this->SkipPreprocessedSourceRules; } - bool GetCreateAssemblySourceRules() + bool GetCreateAssemblySourceRules() const { return !this->SkipAssemblySourceRules; } diff --git a/Source/cmMacroCommand.cxx b/Source/cmMacroCommand.cxx index 98f88c1..8c4b2a7 100644 --- a/Source/cmMacroCommand.cxx +++ b/Source/cmMacroCommand.cxx @@ -171,8 +171,11 @@ bool cmMacroFunctionBlocker::Replay(std::vector<cmListFileFunction> functions, f.Functions = std::move(functions); f.FilePath = this->GetStartingContext().FilePath; mf.RecordPolicies(f.Policies); - mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f)); - return true; + return mf.GetState()->AddScriptedCommand( + this->Args[0], + BT<cmState::Command>(std::move(f), + mf.GetBacktrace().Push(this->GetStartingContext())), + mf); } } diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 0e4f888..9d37d61 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -29,15 +29,12 @@ #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" -#include "cm_sys_stat.h" - #include "cmCommandArgumentParserHelper.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmExecutionStatus.h" #include "cmExpandedCommandArgument.h" // IWYU pragma: keep #include "cmExportBuildFileGenerator.h" -#include "cmFSPermissions.h" #include "cmFileLockPool.h" #include "cmFunctionBlocker.h" #include "cmGeneratedFileStream.h" @@ -72,8 +69,6 @@ class cmMessenger; -using namespace cmFSPermissions; - cmDirectoryId::cmDirectoryId(std::string s) : String(std::move(s)) { @@ -870,13 +865,14 @@ void cmMakefile::EnforceDirectoryLevelRules() const void cmMakefile::AddEvaluationFile( const std::string& inputFile, const std::string& targetName, std::unique_ptr<cmCompiledGeneratorExpression> outputName, - std::unique_ptr<cmCompiledGeneratorExpression> condition, + std::unique_ptr<cmCompiledGeneratorExpression> condition, mode_t permissions, bool inputIsContent) { this->EvaluationFiles.push_back( cm::make_unique<cmGeneratorExpressionEvaluationFile>( inputFile, targetName, std::move(outputName), std::move(condition), - inputIsContent, this->GetPolicyStatus(cmPolicies::CMP0070))); + inputIsContent, permissions, + this->GetPolicyStatus(cmPolicies::CMP0070))); } const std::vector<std::unique_ptr<cmGeneratorExpressionEvaluationFile>>& @@ -939,8 +935,6 @@ void cmMakefile::DoGenerate(cmLocalGenerator& lg) action.Value(lg, action.Backtrace); } this->GeneratorActionsInvoked = true; - this->DelayedOutputFiles.clear(); - this->DelayedOutputFilesHaveGenex = false; // go through all configured files and see which ones still exist. // we don't want cmake to re-run if a configured file is created and deleted @@ -1104,7 +1098,7 @@ cmTarget* cmMakefile::AddCustomCommandToTarget( } // Always create the byproduct sources and mark them generated. - this->CreateGeneratedByproducts(byproducts); + this->CreateGeneratedOutputs(byproducts); // Strings could be moved into the callback function with C++14. cm::optional<std::string> commentStr = MakeOptionalString(comment); @@ -1163,7 +1157,7 @@ void cmMakefile::AddCustomCommandToOutput( // Always create the output sources and mark them generated. this->CreateGeneratedOutputs(outputs); - this->CreateGeneratedByproducts(byproducts); + this->CreateGeneratedOutputs(byproducts); // Strings could be moved into the callback function with C++14. cm::optional<std::string> commentStr = MakeOptionalString(comment); @@ -1247,16 +1241,11 @@ void cmMakefile::AddCustomCommandOldStyle( } } -bool cmMakefile::AppendCustomCommandToOutput( +void cmMakefile::AppendCustomCommandToOutput( const std::string& output, const std::vector<std::string>& depends, const cmImplicitDependsList& implicit_depends, const cmCustomCommandLines& commandLines) { - // Check as good as we can if there will be a command for this output. - if (!this->MightHaveCustomCommand(output)) { - return false; - } - // Validate custom commands. if (this->ValidateCustomCommand(commandLines)) { // Dispatch command creation to allow generator expressions in outputs. @@ -1267,29 +1256,6 @@ bool cmMakefile::AppendCustomCommandToOutput( implicit_depends, commandLines); }); } - - return true; -} - -cmUtilityOutput cmMakefile::GetUtilityOutput(cmTarget* target) -{ - std::string force = cmStrCat(this->GetCurrentBinaryDirectory(), - "/CMakeFiles/", target->GetName()); - std::string forceCMP0049 = target->GetSourceCMP0049(force); - { - cmSourceFile* sf = nullptr; - if (!forceCMP0049.empty()) { - sf = this->GetOrCreateSource(forceCMP0049, false, - cmSourceFileLocationKind::Known); - } - // The output is not actually created so mark it symbolic. - if (sf) { - sf->SetProperty("SYMBOLIC", "1"); - } else { - cmSystemTools::Error("Could not get source file entry for " + force); - } - } - return { std::move(force), std::move(forceCMP0049) }; } cmTarget* cmMakefile::AddUtilityCommand( @@ -1308,12 +1274,8 @@ cmTarget* cmMakefile::AddUtilityCommand( return target; } - // Get the output name of the utility target and mark it generated. - cmUtilityOutput force = this->GetUtilityOutput(target); - this->GetOrCreateGeneratedSource(force.Name); - // Always create the byproduct sources and mark them generated. - this->CreateGeneratedByproducts(byproducts); + this->CreateGeneratedOutputs(byproducts); // Strings could be moved into the callback function with C++14. cm::optional<std::string> commentStr = MakeOptionalString(comment); @@ -1324,8 +1286,8 @@ cmTarget* cmMakefile::AddUtilityCommand( [=](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt) { BacktraceGuard guard(this->Backtrace, lfbt); detail::AddUtilityCommand(lg, lfbt, cmCommandOrigin::Project, target, - force, GetCStrOrNull(workingStr), byproducts, - depends, commandLines, escapeOldStyle, + GetCStrOrNull(workingStr), byproducts, depends, + commandLines, escapeOldStyle, GetCStrOrNull(commentStr), uses_terminal, command_expand_lists, job_pool, stdPipesUTF8); }); @@ -1496,15 +1458,14 @@ void cmMakefile::InitializeFromParent(cmMakefile* parent) // Include transform property. There is no per-config version. { const char* prop = "IMPLICIT_DEPENDS_INCLUDE_TRANSFORM"; - cmProp p = parent->GetProperty(prop); - this->SetProperty(prop, cmToCStr(p)); + this->SetProperty(prop, cmToCStr(parent->GetProperty(prop))); } // compile definitions property and per-config versions cmPolicies::PolicyStatus polSt = this->GetPolicyStatus(cmPolicies::CMP0043); if (polSt == cmPolicies::WARN || polSt == cmPolicies::OLD) { - cmProp p = parent->GetProperty("COMPILE_DEFINITIONS"); - this->SetProperty("COMPILE_DEFINITIONS", cmToCStr(p)); + this->SetProperty("COMPILE_DEFINITIONS", + cmToCStr(parent->GetProperty("COMPILE_DEFINITIONS"))); std::vector<std::string> configs = this->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig); for (std::string const& config : configs) { @@ -1516,12 +1477,11 @@ void cmMakefile::InitializeFromParent(cmMakefile* parent) } // labels - cmProp p = parent->GetProperty("LABELS"); - this->SetProperty("LABELS", cmToCStr(p)); + this->SetProperty("LABELS", cmToCStr(parent->GetProperty("LABELS"))); // link libraries - p = parent->GetProperty("LINK_LIBRARIES"); - this->SetProperty("LINK_LIBRARIES", cmToCStr(p)); + this->SetProperty("LINK_LIBRARIES", + cmToCStr(parent->GetProperty("LINK_LIBRARIES"))); // the initial project name this->StateSnapshot.SetProjectName(parent->StateSnapshot.GetProjectName()); @@ -2038,7 +1998,7 @@ void cmMakefile::RemoveDefinition(const std::string& name) #endif } -void cmMakefile::RemoveCacheDefinition(const std::string& name) +void cmMakefile::RemoveCacheDefinition(const std::string& name) const { this->GetState()->RemoveCacheEntry(name); } @@ -2154,213 +2114,6 @@ cmTarget* cmMakefile::AddNewUtilityTarget(const std::string& utilityName, } namespace { -bool AnyOutputMatches(const std::string& name, - const std::vector<std::string>& outputs) -{ - for (std::string const& output : outputs) { - std::string::size_type pos = output.rfind(name); - // If the output matches exactly - if (pos != std::string::npos && pos == output.size() - name.size() && - (pos == 0 || output[pos - 1] == '/')) { - return true; - } - } - return false; -} - -bool AnyTargetCommandOutputMatches( - const std::string& name, const std::vector<cmCustomCommand>& commands) -{ - for (cmCustomCommand const& command : commands) { - if (AnyOutputMatches(name, command.GetByproducts())) { - return true; - } - } - return false; -} -} - -cmTarget* cmMakefile::LinearGetTargetWithOutput(const std::string& name) const -{ - // We go through the ordered vector of targets to get reproducible results - // should multiple names match. - for (cmTarget* t : this->OrderedTargets) { - // Does the output of any command match the source file name? - if (AnyTargetCommandOutputMatches(name, t->GetPreBuildCommands())) { - return t; - } - if (AnyTargetCommandOutputMatches(name, t->GetPreLinkCommands())) { - return t; - } - if (AnyTargetCommandOutputMatches(name, t->GetPostBuildCommands())) { - return t; - } - } - return nullptr; -} - -cmSourceFile* cmMakefile::LinearGetSourceFileWithOutput( - const std::string& name, cmSourceOutputKind kind, bool& byproduct) const -{ - // Outputs take precedence over byproducts. - byproduct = false; - cmSourceFile* fallback = nullptr; - - // Look through all the source files that have custom commands and see if the - // custom command has the passed source file as an output. - for (const auto& src : this->SourceFiles) { - // Does this source file have a custom command? - if (src->GetCustomCommand()) { - // Does the output of the custom command match the source file name? - if (AnyOutputMatches(name, src->GetCustomCommand()->GetOutputs())) { - // Return the first matching output. - return src.get(); - } - if (kind == cmSourceOutputKind::OutputOrByproduct) { - if (AnyOutputMatches(name, src->GetCustomCommand()->GetByproducts())) { - // Do not return the source yet as there might be a matching output. - fallback = src.get(); - } - } - } - } - - // Did we find a byproduct? - byproduct = fallback != nullptr; - return fallback; -} - -cmSourcesWithOutput cmMakefile::GetSourcesWithOutput( - const std::string& name) const -{ - // Linear search? Also see GetSourceFileWithOutput for detail. - if (!cmSystemTools::FileIsFullPath(name)) { - cmSourcesWithOutput sources; - sources.Target = this->LinearGetTargetWithOutput(name); - sources.Source = this->LinearGetSourceFileWithOutput( - name, cmSourceOutputKind::OutputOrByproduct, sources.SourceIsByproduct); - return sources; - } - // Otherwise we use an efficient lookup map. - auto o = this->OutputToSource.find(name); - if (o != this->OutputToSource.end()) { - return o->second.Sources; - } - return {}; -} - -cmSourceFile* cmMakefile::GetSourceFileWithOutput( - const std::string& name, cmSourceOutputKind kind) const -{ - // If the queried path is not absolute we use the backward compatible - // linear-time search for an output with a matching suffix. - if (!cmSystemTools::FileIsFullPath(name)) { - bool byproduct = false; - return this->LinearGetSourceFileWithOutput(name, kind, byproduct); - } - // Otherwise we use an efficient lookup map. - auto o = this->OutputToSource.find(name); - if (o != this->OutputToSource.end() && - (!o->second.Sources.SourceIsByproduct || - kind == cmSourceOutputKind::OutputOrByproduct)) { - // Source file could also be null pointer for example if we found the - // byproduct of a utility target, a PRE_BUILD, PRE_LINK, or POST_BUILD - // command of a target, or a not yet created custom command. - return o->second.Sources.Source; - } - return nullptr; -} - -bool cmMakefile::MightHaveCustomCommand(const std::string& name) const -{ - if (this->DelayedOutputFilesHaveGenex || - cmGeneratorExpression::Find(name) != std::string::npos) { - // Could be more restrictive, but for now we assume that there could always - // be a match when generator expressions are involved. - return true; - } - // Also see LinearGetSourceFileWithOutput. - if (!cmSystemTools::FileIsFullPath(name)) { - return AnyOutputMatches(name, this->DelayedOutputFiles); - } - // Otherwise we use an efficient lookup map. - auto o = this->OutputToSource.find(name); - if (o != this->OutputToSource.end()) { - return o->second.SourceMightBeOutput; - } - return false; -} - -void cmMakefile::AddTargetByproducts( - cmTarget* target, const std::vector<std::string>& byproducts) -{ - for (std::string const& o : byproducts) { - this->UpdateOutputToSourceMap(o, target); - } -} - -void cmMakefile::AddSourceOutputs(cmSourceFile* source, - const std::vector<std::string>& outputs, - const std::vector<std::string>& byproducts) -{ - for (std::string const& o : outputs) { - this->UpdateOutputToSourceMap(o, source, false); - } - for (std::string const& o : byproducts) { - this->UpdateOutputToSourceMap(o, source, true); - } -} - -void cmMakefile::UpdateOutputToSourceMap(std::string const& byproduct, - cmTarget* target) -{ - SourceEntry entry; - entry.Sources.Target = target; - - auto pr = this->OutputToSource.emplace(byproduct, entry); - if (!pr.second) { - SourceEntry& current = pr.first->second; - // Has the target already been set? - if (!current.Sources.Target) { - current.Sources.Target = target; - } else { - // Multiple custom commands/targets produce the same output (source file - // or target). See also comment in other UpdateOutputToSourceMap - // overload. - // - // TODO: Warn the user about this case. - } - } -} - -void cmMakefile::UpdateOutputToSourceMap(std::string const& output, - cmSourceFile* source, bool byproduct) -{ - SourceEntry entry; - entry.Sources.Source = source; - entry.Sources.SourceIsByproduct = byproduct; - entry.SourceMightBeOutput = !byproduct; - - auto pr = this->OutputToSource.emplace(output, entry); - if (!pr.second) { - SourceEntry& current = pr.first->second; - // Outputs take precedence over byproducts - if (!current.Sources.Source || - (current.Sources.SourceIsByproduct && !byproduct)) { - current.Sources.Source = source; - current.Sources.SourceIsByproduct = false; - current.SourceMightBeOutput = true; - } else { - // Multiple custom commands produce the same output but may - // be attached to a different source file (MAIN_DEPENDENCY). - // LinearGetSourceFileWithOutput would return the first one, - // so keep the mapping for the first one. - // - // TODO: Warn the user about this case. However, the VS 8 generator - // triggers it for separate generate.stamp rules in ZERO_CHECK and - // individual targets. - } - } } #if !defined(CMAKE_BOOTSTRAP) @@ -3055,7 +2808,7 @@ void cmMakefile::SetRecursionDepth(int recursionDepth) this->RecursionDepth = recursionDepth; } -std::string cmMakefile::NewDeferId() +std::string cmMakefile::NewDeferId() const { return this->GetGlobalGenerator()->NewDeferId(); } @@ -3629,11 +3382,7 @@ cmSourceFile* cmMakefile::CreateSource(const std::string& sourceName, bool generated, cmSourceFileLocationKind kind) { - auto sf = cm::make_unique<cmSourceFile>(this, sourceName, kind); - if (generated) { - sf->SetProperty("GENERATED", "1"); - } - + auto sf = cm::make_unique<cmSourceFile>(this, sourceName, generated, kind); auto name = this->GetCMakeInstance()->StripExtension(sf->GetLocation().GetName()); #if defined(_WIN32) || defined(__APPLE__) @@ -3665,7 +3414,7 @@ cmSourceFile* cmMakefile::GetOrCreateGeneratedSource( { cmSourceFile* sf = this->GetOrCreateSource(sourceName, true, cmSourceFileLocationKind::Known); - sf->SetProperty("GENERATED", "1"); + sf->MarkAsGenerated(); // In case we did not create the source file. return sf; } @@ -3675,38 +3424,10 @@ void cmMakefile::CreateGeneratedOutputs( for (std::string const& o : outputs) { if (cmGeneratorExpression::Find(o) == std::string::npos) { this->GetOrCreateGeneratedSource(o); - this->AddDelayedOutput(o); - } else { - this->DelayedOutputFilesHaveGenex = true; } } } -void cmMakefile::CreateGeneratedByproducts( - const std::vector<std::string>& byproducts) -{ - for (std::string const& o : byproducts) { - if (cmGeneratorExpression::Find(o) == std::string::npos) { - this->GetOrCreateGeneratedSource(o); - } - } -} - -void cmMakefile::AddDelayedOutput(std::string const& output) -{ - // Note that this vector might contain the output names in a different order - // than in source file iteration order. - this->DelayedOutputFiles.push_back(output); - - SourceEntry entry; - entry.SourceMightBeOutput = true; - - auto pr = this->OutputToSource.emplace(output, entry); - if (!pr.second) { - pr.first->second.SourceMightBeOutput = true; - } -} - void cmMakefile::AddTargetObject(std::string const& tgtName, std::string const& objFile) { @@ -4094,8 +3815,7 @@ void cmMakefile::ConfigureString(const std::string& input, std::string& output, int cmMakefile::ConfigureFile(const std::string& infile, const std::string& outfile, bool copyonly, bool atOnly, bool escapeQuotes, - bool use_source_permissions, - cmNewLineStyle newLine) + mode_t permissions, cmNewLineStyle newLine) { int res = 1; if (!this->CanIWriteThisFile(outfile)) { @@ -4117,12 +3837,8 @@ int cmMakefile::ConfigureFile(const std::string& infile, // output files that now don't exist. this->AddCMakeOutputFile(soutfile); - mode_t perm = 0; - if (!use_source_permissions) { - perm = perm | mode_owner_read | mode_owner_write | mode_group_read | - mode_world_read; - } else { - cmSystemTools::GetPermissions(sinfile, perm); + if (permissions == 0) { + cmSystemTools::GetPermissions(sinfile, permissions); } std::string::size_type pos = soutfile.rfind('/'); @@ -4137,7 +3853,7 @@ int cmMakefile::ConfigureFile(const std::string& infile, cmSystemTools::GetLastSystemError()); return 0; } - if (!cmSystemTools::SetPermissions(soutfile, perm)) { + if (!cmSystemTools::SetPermissions(soutfile, permissions)) { this->IssueMessage(MessageType::FATAL_ERROR, cmSystemTools::GetLastSystemError()); return 0; @@ -4194,7 +3910,7 @@ int cmMakefile::ConfigureFile(const std::string& infile, cmSystemTools::GetLastSystemError()); res = 0; } else { - if (!cmSystemTools::SetPermissions(soutfile, perm)) { + if (!cmSystemTools::SetPermissions(soutfile, permissions)) { this->IssueMessage(MessageType::FATAL_ERROR, cmSystemTools::GetLastSystemError()); res = 0; @@ -4283,7 +3999,7 @@ cmTest* cmMakefile::GetTest(const std::string& testName) const } void cmMakefile::GetTests(const std::string& config, - std::vector<cmTest*>& tests) + std::vector<cmTest*>& tests) const { for (const auto& generator : this->GetTestGenerators()) { if (generator->TestsForConfig(config)) { @@ -4633,7 +4349,7 @@ cmPolicies::PolicyStatus cmMakefile::GetPolicyStatus(cmPolicies::PolicyID id, return this->StateSnapshot.GetPolicy(id, parent_scope); } -bool cmMakefile::PolicyOptionalWarningEnabled(std::string const& var) +bool cmMakefile::PolicyOptionalWarningEnabled(std::string const& var) const { // Check for an explicit CMAKE_POLICY_WARNING_CMP<NNNN> setting. if (cmProp val = this->GetDefinition(var)) { @@ -4670,7 +4386,7 @@ bool cmMakefile::SetPolicy(cmPolicies::PolicyID id, // Deprecate old policies, especially those that require a lot // of code to maintain the old behavior. - if (status == cmPolicies::OLD && id <= cmPolicies::CMP0072 && + if (status == cmPolicies::OLD && id <= cmPolicies::CMP0075 && !(this->GetCMakeInstance()->GetIsInTryCompile() && ( // Policies set by cmCoreTryCompile::TryCompileCode. @@ -4738,7 +4454,7 @@ bool cmMakefile::HasCMP0054AlreadyBeenReported( return !this->CMP0054ReportedIds.insert(context).second; } -void cmMakefile::RecordPolicies(cmPolicies::PolicyMap& pm) +void cmMakefile::RecordPolicies(cmPolicies::PolicyMap& pm) const { /* Record the setting of every policy. */ using PolicyID = cmPolicies::PolicyID; diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index c7940fb..1617793 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -20,6 +20,8 @@ #include "cmsys/RegularExpression.hxx" +#include "cm_sys_stat.h" + #include "cmAlgorithms.h" #include "cmCustomCommandTypes.h" #include "cmListFileCache.h" @@ -59,24 +61,6 @@ class cmTestGenerator; class cmVariableWatch; class cmake; -/** Flag if byproducts shall also be considered. */ -enum class cmSourceOutputKind -{ - OutputOnly, - OutputOrByproduct -}; - -/** Target and source file which have a specific output. */ -struct cmSourcesWithOutput -{ - /** Target with byproduct. */ - cmTarget* Target = nullptr; - - /** Source file with output or byproduct. */ - cmSourceFile* Source = nullptr; - bool SourceIsByproduct = false; -}; - /** A type-safe wrapper for a string representing a directory id. */ class cmDirectoryId { @@ -225,25 +209,12 @@ public: const std::string& source, const cmCustomCommandLines& commandLines, const char* comment); - bool AppendCustomCommandToOutput( + void AppendCustomCommandToOutput( const std::string& output, const std::vector<std::string>& depends, const cmImplicitDependsList& implicit_depends, const cmCustomCommandLines& commandLines); /** - * Add target byproducts. - */ - void AddTargetByproducts(cmTarget* target, - const std::vector<std::string>& byproducts); - - /** - * Add source file outputs. - */ - void AddSourceOutputs(cmSourceFile* source, - const std::vector<std::string>& outputs, - const std::vector<std::string>& byproducts); - - /** * Add a define flag to the build. */ void AddDefineFlag(std::string const& definition); @@ -272,11 +243,6 @@ public: bool excludeFromAll = false); /** - * Return the utility target output source file name and the CMP0049 name. - */ - cmUtilityOutput GetUtilityOutput(cmTarget* target); - - /** * Dispatch adding a utility to the build. A utility target is a command * that is run every time the target is built. */ @@ -335,7 +301,7 @@ public: */ void RemoveDefinition(const std::string& name); //! Remove a definition from the cache. - void RemoveCacheDefinition(const std::string& name); + void RemoveCacheDefinition(const std::string& name) const; /** * Specify the name of the project for this build. @@ -376,7 +342,7 @@ public: bool parent_scope = false) const; bool SetPolicyVersion(std::string const& version_min, std::string const& version_max); - void RecordPolicies(cmPolicies::PolicyMap& pm); + void RecordPolicies(cmPolicies::PolicyMap& pm) const; //@} /** Helper class to push and pop policies automatically. */ @@ -430,8 +396,7 @@ public: } const char* GetIncludeRegularExpression() const { - cmProp p = this->GetProperty("INCLUDE_REGULAR_EXPRESSION"); - return p ? p->c_str() : nullptr; + return cmToCStr(this->GetProperty("INCLUDE_REGULAR_EXPRESSION")); } /** @@ -690,8 +655,7 @@ public: */ int ConfigureFile(const std::string& infile, const std::string& outfile, bool copyonly, bool atOnly, bool escapeQuotes, - bool use_source_permissions, - cmNewLineStyle = cmNewLineStyle()); + mode_t permissions = 0, cmNewLineStyle = cmNewLineStyle()); /** * Print a command's invocation @@ -753,20 +717,10 @@ public: return this->SourceFiles; } - /** - * Return the target if the provided source name is a byproduct of a utility - * target or a PRE_BUILD, PRE_LINK, or POST_BUILD command. - * Return the source file which has the provided source name as output. - */ - cmSourcesWithOutput GetSourcesWithOutput(const std::string& name) const; - - /** - * Is there a source file that has the provided source name as an output? - * If so then return it. - */ - cmSourceFile* GetSourceFileWithOutput( - const std::string& name, - cmSourceOutputKind kind = cmSourceOutputKind::OutputOnly) const; + std::vector<cmTarget*> const& GetOrderedTargets() const + { + return this->OrderedTargets; + } //! Add a new cmTest to the list of tests for this makefile. cmTest* CreateTest(const std::string& testName); @@ -779,7 +733,7 @@ public: /** * Get all tests that run under the given configuration. */ - void GetTests(const std::string& config, std::vector<cmTest*>& tests); + void GetTests(const std::string& config, std::vector<cmTest*>& tests) const; /** * Return a location of a file in cmake or custom modules directory @@ -926,7 +880,7 @@ public: return this->SystemIncludeDirectories; } - bool PolicyOptionalWarningEnabled(std::string const& var); + bool PolicyOptionalWarningEnabled(std::string const& var) const; void PushLoopBlock(); void PopLoopBlock(); @@ -945,7 +899,7 @@ public: const std::string& inputFile, const std::string& targetName, std::unique_ptr<cmCompiledGeneratorExpression> outputName, std::unique_ptr<cmCompiledGeneratorExpression> condition, - bool inputIsContent); + mode_t permissions, bool inputIsContent); const std::vector<std::unique_ptr<cmGeneratorExpressionEvaluationFile>>& GetEvaluationFiles() const; @@ -967,7 +921,7 @@ public: int GetRecursionDepth() const; void SetRecursionDepth(int recursionDepth); - std::string NewDeferId(); + std::string NewDeferId() const; bool DeferCall(std::string id, std::string fileName, cmListFileFunction lff); bool DeferCancelCall(std::string const& id); cm::optional<std::string> DeferGetCallIds() const; @@ -983,8 +937,7 @@ protected: mutable cmTargetMap Targets; std::map<std::string, std::string> AliasTargets; - using TargetsVec = std::vector<cmTarget*>; - TargetsVec OrderedTargets; + std::vector<cmTarget*> OrderedTargets; std::vector<std::unique_ptr<cmSourceFile>> SourceFiles; @@ -1129,48 +1082,9 @@ private: bool ValidateCustomCommand(const cmCustomCommandLines& commandLines) const; void CreateGeneratedOutputs(const std::vector<std::string>& outputs); - void CreateGeneratedByproducts(const std::vector<std::string>& byproducts); std::vector<BT<GeneratorAction>> GeneratorActions; bool GeneratorActionsInvoked = false; - bool DelayedOutputFilesHaveGenex = false; - std::vector<std::string> DelayedOutputFiles; - - void AddDelayedOutput(std::string const& output); - - /** - * See LinearGetSourceFileWithOutput for background information - */ - cmTarget* LinearGetTargetWithOutput(const std::string& name) const; - - /** - * Generalized old version of GetSourceFileWithOutput kept for - * backward-compatibility. It implements a linear search and supports - * relative file paths. It is used as a fall back by GetSourceFileWithOutput - * and GetSourcesWithOutput. - */ - cmSourceFile* LinearGetSourceFileWithOutput(const std::string& name, - cmSourceOutputKind kind, - bool& byproduct) const; - - struct SourceEntry - { - cmSourcesWithOutput Sources; - bool SourceMightBeOutput = false; - }; - - // A map for fast output to input look up. - using OutputToSourceMap = std::unordered_map<std::string, SourceEntry>; - OutputToSourceMap OutputToSource; - - void UpdateOutputToSourceMap(std::string const& byproduct, cmTarget* target); - void UpdateOutputToSourceMap(std::string const& output, cmSourceFile* source, - bool byproduct); - - /** - * Return if the provided source file might have a custom command. - */ - bool MightHaveCustomCommand(const std::string& name) const; bool CheckSystemVars; bool CheckCMP0000; diff --git a/Source/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx index 871878c..1750e37 100644 --- a/Source/cmMakefileExecutableTargetGenerator.cxx +++ b/Source/cmMakefileExecutableTargetGenerator.cxx @@ -21,6 +21,7 @@ #include "cmMakefile.h" #include "cmOSXBundleGenerator.h" #include "cmOutputConverter.h" +#include "cmProperty.h" #include "cmRulePlaceholderExpander.h" #include "cmState.h" #include "cmStateDirectory.h" @@ -232,10 +233,10 @@ void cmMakefileExecutableTargetGenerator::WriteNvidiaDeviceExecutableRule( std::string launcher; - const char* val = this->LocalGenerator->GetRuleLauncher( - this->GeneratorTarget, "RULE_LAUNCH_LINK"); + cmProp val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget, + "RULE_LAUNCH_LINK"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); } std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander( @@ -591,10 +592,10 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink) std::string launcher; - const char* val = this->LocalGenerator->GetRuleLauncher( - this->GeneratorTarget, "RULE_LAUNCH_LINK"); + cmProp val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget, + "RULE_LAUNCH_LINK"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); } std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander( diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx index b32ea6a..ce64e2c 100644 --- a/Source/cmMakefileLibraryTargetGenerator.cxx +++ b/Source/cmMakefileLibraryTargetGenerator.cxx @@ -21,6 +21,7 @@ #include "cmMakefile.h" #include "cmOSXBundleGenerator.h" #include "cmOutputConverter.h" +#include "cmProperty.h" #include "cmRulePlaceholderExpander.h" #include "cmState.h" #include "cmStateDirectory.h" @@ -366,10 +367,10 @@ void cmMakefileLibraryTargetGenerator::WriteNvidiaDeviceLibraryRules( vars.TargetCompilePDB = targetOutPathCompilePDB.c_str(); std::string launcher; - const char* val = this->LocalGenerator->GetRuleLauncher( - this->GeneratorTarget, "RULE_LAUNCH_LINK"); + cmProp val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget, + "RULE_LAUNCH_LINK"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); } std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander( @@ -816,10 +817,10 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules( vars.LanguageCompileFlags = langFlags.c_str(); std::string launcher; - const char* val = this->LocalGenerator->GetRuleLauncher( - this->GeneratorTarget, "RULE_LAUNCH_LINK"); + cmProp val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget, + "RULE_LAUNCH_LINK"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); } std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander( diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index c6d6c99..70a0393 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -12,7 +12,9 @@ #include <utility> #include <cm/memory> +#include <cm/string_view> #include <cmext/algorithm> +#include <cmext/string_view> #include "cmComputeLinkInformation.h" #include "cmCustomCommand.h" @@ -23,6 +25,7 @@ #include "cmGlobalUnixMakefileGenerator3.h" #include "cmLinkLineComputer.h" // IWYU pragma: keep #include "cmLocalCommonGenerator.h" +#include "cmLocalGenerator.h" #include "cmLocalUnixMakefileGenerator3.h" #include "cmMakefile.h" #include "cmMakefileExecutableTargetGenerator.h" @@ -325,7 +328,45 @@ void cmMakefileTargetGenerator::WriteCommonCodeRules() << cmSystemTools::ConvertToOutputPath( this->LocalGenerator->MaybeConvertToRelativePath( this->LocalGenerator->GetBinaryDirectory(), dependFileNameFull)) - << "\n\n"; + << "\n"; + + std::string depsUseCompiler = "CMAKE_DEPENDS_USE_COMPILER"; + if (!this->Makefile->IsDefinitionSet(depsUseCompiler) || + this->Makefile->IsOn(depsUseCompiler)) { + std::string compilerDependFile = + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.make"); + *this->BuildFileStream + << "# Include any dependencies generated by the " + "compiler for this target.\n" + << this->GlobalGenerator->IncludeDirective << " " << root + << cmSystemTools::ConvertToOutputPath( + this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), compilerDependFile)) + << "\n\n"; + + if (!cmSystemTools::FileExists(compilerDependFile)) { + // Write an empty dependency file. + cmGeneratedFileStream depFileStream( + compilerDependFile, false, + this->GlobalGenerator->GetMakefileEncoding()); + depFileStream << "# Empty compiler generated dependencies file for " + << this->GeneratorTarget->GetName() << ".\n" + << "# This may be replaced when dependencies are built.\n"; + } + + std::string compilerDependTimestamp = + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts"); + if (!cmSystemTools::FileExists(compilerDependTimestamp)) { + // Write a dependency timestamp file. + cmGeneratedFileStream depFileStream( + compilerDependTimestamp, false, + this->GlobalGenerator->GetMakefileEncoding()); + depFileStream << "# CMAKE generated file: DO NOT EDIT!\n" + << "# Timestamp file for compiler generated dependencies " + "management for " + << this->GeneratorTarget->GetName() << ".\n"; + } + } if (!this->NoRuleMessages) { // Include the progress variables for the target. @@ -472,6 +513,14 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( return; } + // Use compiler to generate dependencies, if supported. + bool compilerGenerateDeps = + this->GlobalGenerator->SupportsCompilerDependencies() && + cmIsOn(this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "_DEPENDS_USE_COMPILER"))); + auto scanner = compilerGenerateDeps ? cmDependencyScannerKind::Compiler + : cmDependencyScannerKind::CMake; + // Get the full path name of the object file. std::string const& objectName = this->GeneratorTarget->GetObjectName(&source); @@ -511,7 +560,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( std::string srcFullPath = cmSystemTools::CollapseFullPath(source.GetFullPath()); this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang, - objFullPath, srcFullPath); + objFullPath, srcFullPath, scanner); this->LocalGenerator->AppendRuleDepend(depends, this->FlagFileNameFull.c_str()); @@ -553,8 +602,8 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( depends.push_back( this->GeneratorTarget->GetPchFile(config, lang, arch)); } - this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang, - objFullPath, pchHeader); + this->LocalGenerator->AddImplicitDepends( + this->GeneratorTarget, lang, objFullPath, pchHeader, scanner); } } @@ -573,6 +622,10 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( // Build the set of compiler flags. std::string flags; + // Explicitly add the explicit language flag before any other flag + // so user flags can override it. + this->GeneratorTarget->AddExplicitLanguageFlags(flags, source); + // Add language-specific flags. std::string langFlags = cmStrCat("$(", lang, "_FLAGS", filterArch, ")"); this->LocalGenerator->AppendFlags(flags, langFlags); @@ -693,7 +746,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( source.GetFullPath(), cmOutputConverter::SHELL); // Construct the build message. - std::vector<std::string> no_commands; + std::vector<std::string> no_depends; std::vector<std::string> commands; // add in a progress call if needed @@ -787,13 +840,33 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( "$(" + lang + "_INCLUDES)"); vars.Includes = includesString.c_str(); + std::string dependencyTarget; + std::string shellDependencyFile; + std::string dependencyTimestamp; + if (compilerGenerateDeps) { + dependencyTarget = this->LocalGenerator->EscapeForShell( + this->LocalGenerator->ConvertToMakefilePath( + this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), relativeObj))); + vars.DependencyTarget = dependencyTarget.c_str(); + + auto depFile = cmStrCat(obj, ".d"); + shellDependencyFile = this->LocalGenerator->ConvertToOutputFormat( + depFile, cmOutputConverter::SHELL); + vars.DependencyFile = shellDependencyFile.c_str(); + + dependencyTimestamp = this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts")); + } + // At the moment, it is assumed that C, C++, Fortran, and CUDA have both // assembly and preprocessor capabilities. The same is true for the // ability to export compile commands bool lang_has_preprocessor = ((lang == "C") || (lang == "CXX") || (lang == "OBJC") || (lang == "OBJCXX") || (lang == "Fortran") || (lang == "CUDA") || - lang == "ISPC"); + lang == "ISPC" || lang == "ASM"); bool const lang_has_assembly = lang_has_preprocessor; bool const lang_can_export_cmds = lang_has_preprocessor; @@ -875,15 +948,21 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( } // Maybe insert an include-what-you-use runner. - if (!compileCommands.empty() && (lang == "C" || lang == "CXX")) { - std::string const iwyu_prop = lang + "_INCLUDE_WHAT_YOU_USE"; - cmProp iwyu = this->GeneratorTarget->GetProperty(iwyu_prop); + if (!compileCommands.empty() && + (lang == "C" || lang == "CXX" || lang == "OBJC" || lang == "OBJCXX")) { std::string const tidy_prop = lang + "_CLANG_TIDY"; cmProp tidy = this->GeneratorTarget->GetProperty(tidy_prop); - std::string const cpplint_prop = lang + "_CPPLINT"; - cmProp cpplint = this->GeneratorTarget->GetProperty(cpplint_prop); - std::string const cppcheck_prop = lang + "_CPPCHECK"; - cmProp cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop); + cmProp iwyu = nullptr; + cmProp cpplint = nullptr; + cmProp cppcheck = nullptr; + if (lang == "C" || lang == "CXX") { + std::string const iwyu_prop = lang + "_INCLUDE_WHAT_YOU_USE"; + iwyu = this->GeneratorTarget->GetProperty(iwyu_prop); + std::string const cpplint_prop = lang + "_CPPLINT"; + cpplint = this->GeneratorTarget->GetProperty(cpplint_prop); + std::string const cppcheck_prop = lang + "_CPPCHECK"; + cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop); + } if (cmNonempty(iwyu) || cmNonempty(tidy) || cmNonempty(cpplint) || cmNonempty(cppcheck)) { std::string run_iwyu = "$(CMAKE_COMMAND) -E __run_co_compile"; @@ -945,10 +1024,57 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( std::string launcher; { - const char* val = this->LocalGenerator->GetRuleLauncher( + cmProp val = this->LocalGenerator->GetRuleLauncher( this->GeneratorTarget, "RULE_LAUNCH_COMPILE"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); + } + } + + std::string flagsWithDeps(flags); + + if (compilerGenerateDeps) { + // Injects dependency computation + auto depFlags = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_DEPFILE_FLAGS_", lang)); + + if (!depFlags.empty()) { + // Add dependency flags + rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator, + depFlags, vars); + flagsWithDeps.append(1, ' '); + flagsWithDeps.append(depFlags); + } + vars.Flags = flagsWithDeps.c_str(); + + const auto& extraCommands = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_", lang, "_DEPENDS_EXTRA_COMMANDS")); + if (!extraCommands.empty()) { + auto commandList = cmExpandedList(extraCommands); + compileCommands.insert(compileCommands.end(), commandList.cbegin(), + commandList.cend()); + } + + const auto& depFormat = this->Makefile->GetRequiredDefinition( + cmStrCat("CMAKE_", lang, "_DEPFILE_FORMAT")); + + if (depFormat == "msvc"_s) { + // compiler must be launched through a wrapper to pick-up dependencies + std::string depFilter = + "$(CMAKE_COMMAND) -E cmake_cl_compile_depends "; + depFilter += cmStrCat("--dep-file=", shellDependencyFile); + depFilter += + cmStrCat(" --working-dir=", + this->LocalGenerator->ConvertToOutputFormat( + this->LocalGenerator->GetCurrentBinaryDirectory(), + cmOutputConverter::SHELL)); + const auto& prefix = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_", lang, "_CL_SHOWINCLUDES_PREFIX")); + depFilter += cmStrCat(" --filter-prefix=", + this->LocalGenerator->ConvertToOutputFormat( + prefix, cmOutputConverter::SHELL)); + depFilter += " -- "; + compileCommands.front().insert(0, depFilter); } } @@ -977,8 +1103,8 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( cmExpandList(evaluated_outputs, outputs); } } - if (!ispcHeaderRelative - .empty()) { // can't move ispcHeader as vars is using it + if (!ispcHeaderRelative.empty()) { + // can't move ispcHeader as vars is using it outputs.emplace_back(ispcHeaderRelative); } @@ -986,10 +1112,19 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( this->CleanFiles.insert(outputs.begin() + 1, outputs.end()); } + if (compilerGenerateDeps) { + depends.push_back(dependencyTimestamp); + } + // Write the rule. this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends, commands); + if (compilerGenerateDeps) { + // set back flags without dependency generation + vars.Flags = flags.c_str(); + } + bool do_preprocess_rules = lang_has_preprocessor && this->LocalGenerator->GetCreatePreprocessedSourceRules(); bool do_assembly_rules = @@ -1386,10 +1521,10 @@ void cmMakefileTargetGenerator::WriteDeviceLinkRule( std::string registerFileCmd; - // The generated register file contains macros that when expanded register - // the device routines. Because the routines are the same for all - // architectures the register file will be the same too. Thus generate it - // only on the first invocation to reduce overhead. + // The generated register file contains macros that when expanded + // register the device routines. Because the routines are the same for + // all architectures the register file will be the same too. Thus + // generate it only on the first invocation to reduce overhead. if (fatbinaryDepends.size() == 1) { std::string registerFileRel = this->LocalGenerator->MaybeConvertToRelativePath( @@ -1424,7 +1559,8 @@ void cmMakefileTargetGenerator::WriteDeviceLinkRule( fatbinaryOutputRel, fatbinaryDepends, { fatbinaryCommand }, false); - // Compile the stub that registers the kernels and contains the fatbinaries. + // Compile the stub that registers the kernels and contains the + // fatbinaries. cmRulePlaceholderExpander::RuleVariables vars; vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str(); vars.CMTargetType = @@ -1481,6 +1617,16 @@ void cmMakefileTargetGenerator::GenerateCustomRuleFile( std::vector<std::string> depends; this->LocalGenerator->AppendCustomDepend(depends, ccg); + if (!ccg.GetCC().GetDepfile().empty()) { + // Add dependency over timestamp file for dependencies management + auto dependTimestamp = cmSystemTools::ConvertToOutputPath( + this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts"))); + + depends.push_back(dependTimestamp); + } + // Write the rule. const std::vector<std::string>& outputs = ccg.GetOutputs(); bool symbolic = this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, @@ -1517,6 +1663,15 @@ void cmMakefileTargetGenerator::GenerateCustomRuleFile( objFullPath, srcFullPath); } + // Setup implicit depend for depfile if any + if (!ccg.GetCC().GetDepfile().empty()) { + std::string objFullPath = cmSystemTools::CollapseFullPath( + outputs[0], this->LocalGenerator->GetCurrentBinaryDirectory()); + this->LocalGenerator->AddImplicitDepends( + this->GeneratorTarget, "CUSTOM", objFullPath, ccg.GetFullDepfile(), + cmDependencyScannerKind::Compiler); + } + this->CustomCommandOutputs.insert(outputs.begin(), outputs.end()); } @@ -1542,12 +1697,7 @@ void cmMakefileTargetGenerator::WriteObjectsVariable( << this->GeneratorTarget->GetName() << "\n" << variableName << " ="; std::string object; - std::string lineContinue; - if (cmProp p = this->Makefile->GetDefinition("CMAKE_MAKE_LINE_CONTINUE")) { - lineContinue = *p; - } else { - lineContinue = "\\"; - } + const auto& lineContinue = this->GlobalGenerator->LineContinueDirective; cmProp pchExtension = this->Makefile->GetDefinition("CMAKE_PCH_EXTENSION"); @@ -1555,7 +1705,7 @@ void cmMakefileTargetGenerator::WriteObjectsVariable( if (cmSystemTools::StringEndsWith(obj, cmToCStr(pchExtension))) { continue; } - *this->BuildFileStream << " " << lineContinue << "\n"; + *this->BuildFileStream << " " << lineContinue; *this->BuildFileStream << cmLocalUnixMakefileGenerator3::ConvertToQuotedOutputPath( obj, useWatcomQuote); @@ -1578,7 +1728,7 @@ void cmMakefileTargetGenerator::WriteObjectsVariable( for (std::string const& obj : this->ExternalObjects) { object = this->LocalGenerator->MaybeConvertToRelativePath(currentBinDir, obj); - *this->BuildFileStream << " " << lineContinue << "\n"; + *this->BuildFileStream << " " << lineContinue; *this->BuildFileStream << cmLocalUnixMakefileGenerator3::ConvertToQuotedOutputPath( obj, useWatcomQuote); @@ -1842,9 +1992,9 @@ bool cmMakefileTargetGenerator::CheckUseResponseFileForObjects( if (size_t const limit = cmSystemTools::CalculateCommandLineLengthLimit()) { // Compute the total length of our list of object files with room // for argument separation and quoting. This does not convert paths - // relative to CMAKE_CURRENT_BINARY_DIR like the final list will be, so the - // actual list will likely be much shorter than this. However, in the - // worst case all objects will remain as absolute paths. + // relative to CMAKE_CURRENT_BINARY_DIR like the final list will be, so + // the actual list will likely be much shorter than this. However, in + // the worst case all objects will remain as absolute paths. size_t length = 0; for (std::string const& obj : this->Objects) { length += obj.size() + 3; diff --git a/Source/cmMakefileUtilityTargetGenerator.cxx b/Source/cmMakefileUtilityTargetGenerator.cxx index 6c18e48..a885b17 100644 --- a/Source/cmMakefileUtilityTargetGenerator.cxx +++ b/Source/cmMakefileUtilityTargetGenerator.cxx @@ -15,6 +15,7 @@ #include "cmLocalUnixMakefileGenerator3.h" #include "cmMakefile.h" #include "cmOSXBundleGenerator.h" +#include "cmStringAlgorithms.h" #include "cmSystemTools.h" cmMakefileUtilityTargetGenerator::cmMakefileUtilityTargetGenerator( @@ -36,10 +37,42 @@ void cmMakefileUtilityTargetGenerator::WriteRuleFiles() *this->BuildFileStream << "# Utility rule file for " << this->GeneratorTarget->GetName() << ".\n\n"; + const char* root = (this->Makefile->IsOn("CMAKE_MAKE_INCLUDE_FROM_ROOT") + ? "$(CMAKE_BINARY_DIR)/" + : ""); + + // Include the dependencies for the target. + std::string dependFile = + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.make"); + *this->BuildFileStream + << "# Include any custom commands dependencies for this target.\n" + << this->GlobalGenerator->IncludeDirective << " " << root + << cmSystemTools::ConvertToOutputPath( + this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), dependFile)) + << "\n\n"; + if (!cmSystemTools::FileExists(dependFile)) { + // Write an empty dependency file. + cmGeneratedFileStream depFileStream( + dependFile, false, this->GlobalGenerator->GetMakefileEncoding()); + depFileStream << "# Empty custom commands generated dependencies file for " + << this->GeneratorTarget->GetName() << ".\n" + << "# This may be replaced when dependencies are built.\n"; + } + + std::string dependTimestamp = + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts"); + if (!cmSystemTools::FileExists(dependTimestamp)) { + // Write a dependency timestamp file. + cmGeneratedFileStream depFileStream( + dependTimestamp, false, this->GlobalGenerator->GetMakefileEncoding()); + depFileStream << "# CMAKE generated file: DO NOT EDIT!\n" + << "# Timestamp file for custom commands dependencies " + "management for " + << this->GeneratorTarget->GetName() << ".\n"; + } + if (!this->NoRuleMessages) { - const char* root = (this->Makefile->IsOn("CMAKE_MAKE_INCLUDE_FROM_ROOT") - ? "$(CMAKE_BINARY_DIR)/" - : ""); // Include the progress variables for the target. *this->BuildFileStream << "# Include the progress variables for this target.\n" diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index ccb959b..a5b9466 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -266,10 +266,10 @@ void cmNinjaNormalTargetGenerator::WriteNvidiaDeviceLinkRule( vars.LanguageCompileFlags = "$LANGUAGE_COMPILE_FLAGS"; std::string launcher; - const char* val = this->GetLocalGenerator()->GetRuleLauncher( + cmProp val = this->GetLocalGenerator()->GetRuleLauncher( this->GetGeneratorTarget(), "RULE_LAUNCH_LINK"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); } std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander( @@ -452,10 +452,10 @@ void cmNinjaNormalTargetGenerator::WriteLinkRule(bool useResponseFile, } std::string launcher; - const char* val = this->GetLocalGenerator()->GetRuleLauncher( + cmProp val = this->GetLocalGenerator()->GetRuleLauncher( this->GetGeneratorTarget(), "RULE_LAUNCH_LINK"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); } std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander( diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index d41cbd2..9075563 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -12,7 +12,9 @@ #include <utility> #include <cm/memory> +#include <cm/string_view> #include <cmext/algorithm> +#include <cmext/string_view> #include <cm3p/json/value.h> #include <cm3p/json/writer.h> @@ -105,7 +107,7 @@ std::string cmNinjaTargetGenerator::LanguageCompilerRule( '_', config); } -std::string cmNinjaTargetGenerator::LanguagePreprocessRule( +std::string cmNinjaTargetGenerator::LanguagePreprocessAndScanRule( std::string const& lang, const std::string& config) const { return cmStrCat( @@ -114,7 +116,7 @@ std::string cmNinjaTargetGenerator::LanguagePreprocessRule( '_', config); } -std::string cmNinjaTargetGenerator::LanguageDependencyRule( +std::string cmNinjaTargetGenerator::LanguageScanRule( std::string const& lang, const std::string& config) const { return cmStrCat( @@ -129,14 +131,7 @@ bool cmNinjaTargetGenerator::NeedExplicitPreprocessing( return lang == "Fortran"; } -bool cmNinjaTargetGenerator::UsePreprocessedSource( - std::string const& lang) const -{ - return lang == "Fortran"; -} - -bool cmNinjaTargetGenerator::CompilePreprocessedSourceWithDefines( - std::string const& lang) const +bool cmNinjaTargetGenerator::CompileWithDefines(std::string const& lang) const { return this->Makefile->IsOn( cmStrCat("CMAKE_", lang, "_COMPILE_WITH_DEFINES")); @@ -190,7 +185,15 @@ std::string cmNinjaTargetGenerator::ComputeFlagsForObject( } } - std::string flags = this->GetFlags(language, config, filterArch); + std::string flags; + // Explicitly add the explicit language flag before any other flag + // so user flags can override it. + this->GeneratorTarget->AddExplicitLanguageFlags(flags, *source); + + if (!flags.empty()) { + flags += " "; + } + flags += this->GetFlags(language, config, filterArch); // Add Fortran format flags. if (language == "Fortran") { @@ -252,32 +255,6 @@ void cmNinjaTargetGenerator::AddIncludeFlags(std::string& languageFlags, this->LocalGenerator->AppendFlags(languageFlags, includeFlags); } -bool cmNinjaTargetGenerator::NeedDepTypeMSVC(const std::string& lang) const -{ - std::string const& deptype = this->GetMakefile()->GetSafeDefinition( - cmStrCat("CMAKE_NINJA_DEPTYPE_", lang)); - if (deptype == "msvc") { - return true; - } - if (deptype == "intel") { - // Ninja does not really define "intel", but we use it to switch based - // on whether this environment supports "gcc" or "msvc" deptype. - if (!this->GetGlobalGenerator()->SupportsMultilineDepfile()) { - // This ninja version is too old to support the Intel depfile format. - // Fall back to msvc deptype. - return true; - } - if ((this->Makefile->GetHomeDirectory().find(' ') != std::string::npos) || - (this->Makefile->GetHomeOutputDirectory().find(' ') != - std::string::npos)) { - // The Intel compiler does not properly escape spaces in a depfile. - // Fall back to msvc deptype. - return true; - } - } - return false; -} - // TODO: Refactor with // void cmMakefileTargetGenerator::WriteTargetLanguageFlags(). std::string cmNinjaTargetGenerator::ComputeDefines(cmSourceFile const* source, @@ -527,82 +504,61 @@ namespace { // Create the command to run the dependency scanner std::string GetScanCommand(const std::string& cmakeCmd, const std::string& tdi, const std::string& lang, const std::string& ppFile, - bool needDyndep, const std::string& ddiFile) + const std::string& ddiFile) { - std::string ccmd = - cmStrCat(cmakeCmd, " -E cmake_ninja_depends --tdi=", tdi, " --lang=", lang, - " --pp=", ppFile, " --dep=$DEP_FILE"); - if (needDyndep) { - ccmd = cmStrCat(ccmd, " --obj=$OBJ_FILE --ddi=", ddiFile); - } - return ccmd; + return cmStrCat(cmakeCmd, " -E cmake_ninja_depends --tdi=", tdi, + " --lang=", lang, " --src=$in", " --pp=", ppFile, + " --dep=$DEP_FILE --obj=$OBJ_FILE --ddi=", ddiFile); } -// Helper function to create dependency scanning rule, with optional -// explicit preprocessing step if preprocessCommand is non-empty -cmNinjaRule GetPreprocessScanRule( - const std::string& ruleName, cmRulePlaceholderExpander::RuleVariables& vars, +// Helper function to create dependency scanning rule that may or may +// not perform explicit preprocessing too. +cmNinjaRule GetScanRule( + const std::string& ruleName, + cmRulePlaceholderExpander::RuleVariables const& vars, const std::string& responseFlag, const std::string& flags, - const std::string& launcher, cmRulePlaceholderExpander* const rulePlaceholderExpander, - std::string scanCommand, cmLocalNinjaGenerator* generator, - const std::string& preprocessCommand = "") + cmLocalNinjaGenerator* generator, std::vector<std::string> scanCmds) { cmNinjaRule rule(ruleName); - // Explicit preprocessing always uses a depfile. + // Scanning always uses a depfile for preprocessor dependencies. rule.DepType = ""; // no deps= for multiple outputs rule.DepFile = "$DEP_FILE"; - cmRulePlaceholderExpander::RuleVariables ppVars; - ppVars.CMTargetName = vars.CMTargetName; - ppVars.CMTargetType = vars.CMTargetType; - ppVars.Language = vars.Language; - ppVars.Object = "$out"; // for RULE_LAUNCH_COMPILE - ppVars.PreprocessedSource = "$out"; - ppVars.DependencyFile = rule.DepFile.c_str(); - - // Preprocessing uses the original source, compilation uses - // preprocessed output or original source - ppVars.Source = vars.Source; - vars.Source = "$in"; - - // Copy preprocessor definitions to the preprocessor rule. - ppVars.Defines = vars.Defines; + cmRulePlaceholderExpander::RuleVariables scanVars; + scanVars.CMTargetName = vars.CMTargetName; + scanVars.CMTargetType = vars.CMTargetType; + scanVars.Language = vars.Language; + scanVars.Object = "$out"; // for RULE_LAUNCH_COMPILE + scanVars.PreprocessedSource = "$out"; + scanVars.DependencyFile = rule.DepFile.c_str(); + scanVars.DependencyTarget = "$out"; - // Copy include directories to the preprocessor rule. The Fortran - // compilation rule still needs them for the INCLUDE directive. - ppVars.Includes = vars.Includes; + // Scanning needs the same preprocessor settings as direct compilation would. + scanVars.Source = vars.Source; + scanVars.Defines = vars.Defines; + scanVars.Includes = vars.Includes; - // Preprocessing and compilation use the same flags. - std::string ppFlags = flags; + // Scanning needs the compilation flags too. + std::string scanFlags = flags; // If using a response file, move defines, includes, and flags into it. if (!responseFlag.empty()) { rule.RspFile = "$RSP_FILE"; rule.RspContent = - cmStrCat(' ', ppVars.Defines, ' ', ppVars.Includes, ' ', ppFlags); - ppFlags = cmStrCat(responseFlag, rule.RspFile); - ppVars.Defines = ""; - ppVars.Includes = ""; + cmStrCat(' ', scanVars.Defines, ' ', scanVars.Includes, ' ', scanFlags); + scanFlags = cmStrCat(responseFlag, rule.RspFile); + scanVars.Defines = ""; + scanVars.Includes = ""; } - ppVars.Flags = ppFlags.c_str(); - - // Rule for preprocessing source file. - std::vector<std::string> ppCmds; + scanVars.Flags = scanFlags.c_str(); - if (!preprocessCommand.empty()) { - // Lookup the explicit preprocessing rule. - cmExpandList(preprocessCommand, ppCmds); - for (std::string& i : ppCmds) { - i = cmStrCat(launcher, i); - rulePlaceholderExpander->ExpandRuleVariables(generator, i, ppVars); - } + // Rule for scanning a source file. + for (std::string& scanCmd : scanCmds) { + rulePlaceholderExpander->ExpandRuleVariables(generator, scanCmd, scanVars); } - - // Run CMake dependency scanner on either preprocessed output or source file - ppCmds.emplace_back(std::move(scanCommand)); - rule.Command = generator->BuildCommandLine(ppCmds); + rule.Command = generator->BuildCommandLine(scanCmds); return rule; } @@ -628,11 +584,9 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, cmMakefile* mf = this->GetMakefile(); - // For some cases we do an explicit preprocessor invocation. - bool const explicitPP = this->NeedExplicitPreprocessing(lang); - bool const compilePPWithDefines = this->UsePreprocessedSource(lang) && - this->CompilePreprocessedSourceWithDefines(lang); + // For some cases we scan to dynamically discover dependencies. bool const needDyndep = this->NeedDyndep(lang); + bool const compilationPreprocesses = !this->NeedExplicitPreprocessing(lang); std::string flags = "$FLAGS"; @@ -655,56 +609,68 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, cmLocalGenerator::SHELL); std::string launcher; - const char* val = this->GetLocalGenerator()->GetRuleLauncher( + cmProp val = this->GetLocalGenerator()->GetRuleLauncher( this->GetGeneratorTarget(), "RULE_LAUNCH_COMPILE"); if (cmNonempty(val)) { - launcher = cmStrCat(val, ' '); + launcher = cmStrCat(*val, ' '); } std::string const cmakeCmd = this->GetLocalGenerator()->ConvertToOutputFormat( cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL); - if (explicitPP) { - // Combined preprocessing and dependency scanning - const auto ppScanCommand = GetScanCommand( - cmakeCmd, tdi, lang, "$out", needDyndep, "$DYNDEP_INTERMEDIATE_FILE"); - const auto ppVar = cmStrCat("CMAKE_", lang, "_PREPROCESS_SOURCE"); - - auto ppRule = GetPreprocessScanRule( - this->LanguagePreprocessRule(lang, config), vars, responseFlag, flags, - launcher, rulePlaceholderExpander.get(), ppScanCommand, - this->GetLocalGenerator(), mf->GetRequiredDefinition(ppVar)); + if (needDyndep) { + // Rule to scan dependencies of sources that need preprocessing. + { + std::vector<std::string> scanCommands; + std::string const& scanRuleName = + this->LanguagePreprocessAndScanRule(lang, config); + std::string const& ppCommmand = mf->GetRequiredDefinition( + cmStrCat("CMAKE_", lang, "_PREPROCESS_SOURCE")); + cmExpandList(ppCommmand, scanCommands); + for (std::string& i : scanCommands) { + i = cmStrCat(launcher, i); + } + scanCommands.emplace_back(GetScanCommand(cmakeCmd, tdi, lang, "$out", + "$DYNDEP_INTERMEDIATE_FILE")); - // Write the rule for preprocessing file of the given language. - ppRule.Comment = cmStrCat("Rule for preprocessing ", lang, " files."); - ppRule.Description = cmStrCat("Building ", lang, " preprocessed $out"); + auto scanRule = GetScanRule( + scanRuleName, vars, responseFlag, flags, rulePlaceholderExpander.get(), + this->GetLocalGenerator(), std::move(scanCommands)); - this->GetGlobalGenerator()->AddRule(ppRule); + scanRule.Comment = + cmStrCat("Rule for generating ", lang, " dependencies."); + scanRule.Description = cmStrCat("Building ", lang, " preprocessed $out"); - if (!compilePPWithDefines) { - // Remove preprocessor definitions from compilation step - vars.Defines = ""; + this->GetGlobalGenerator()->AddRule(scanRule); } - // Just dependency scanning for files that have preprocessing turned off - const auto scanCommand = - GetScanCommand(cmakeCmd, tdi, lang, "$in", needDyndep, "$out"); + { + // Compilation will not preprocess, so it does not need the defines + // unless the compiler wants them for some other purpose. + if (!this->CompileWithDefines(lang)) { + vars.Defines = ""; + } - auto scanRule = GetPreprocessScanRule( - this->LanguageDependencyRule(lang, config), vars, "", flags, launcher, - rulePlaceholderExpander.get(), scanCommand, this->GetLocalGenerator()); + // Rule to scan dependencies of sources that do not need preprocessing. + std::string const& scanRuleName = this->LanguageScanRule(lang, config); + std::vector<std::string> scanCommands; + scanCommands.emplace_back( + GetScanCommand(cmakeCmd, tdi, lang, "$in", "$out")); - // Write the rule for generating dependencies for the given language. - scanRule.Comment = cmStrCat("Rule for generating ", lang, - " dependencies on non-preprocessed files."); - scanRule.Description = - cmStrCat("Generating ", lang, " dependencies for $in"); + auto scanRule = GetScanRule( + scanRuleName, vars, "", flags, rulePlaceholderExpander.get(), + this->GetLocalGenerator(), std::move(scanCommands)); - this->GetGlobalGenerator()->AddRule(scanRule); - } + // Write the rule for generating dependencies for the given language. + scanRule.Comment = cmStrCat("Rule for generating ", lang, + " dependencies on non-preprocessed files."); + scanRule.Description = + cmStrCat("Generating ", lang, " dependencies for $in"); + + this->GetGlobalGenerator()->AddRule(scanRule); + } - if (needDyndep) { // Write the rule for ninja dyndep file generation. cmNinjaRule rule(this->LanguageDyndepRule(lang, config)); // Command line length is almost always limited -> use response file for @@ -743,12 +709,8 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, // Tell ninja dependency format so all deps can be loaded into a database std::string cldeps; - if (explicitPP) { - // The explicit preprocessing step will handle dependency scanning. - } else if (this->NeedDepTypeMSVC(lang)) { - rule.DepType = "msvc"; - rule.DepFile.clear(); - flags += " /showIncludes"; + if (!compilationPreprocesses) { + // The compiler will not do preprocessing, so it has no such dependencies. } else if (mf->IsOn(cmStrCat("CMAKE_NINJA_CMCLDEPS_", lang))) { // For the MS resource compiler we need cmcldeps, but skip dependencies // for source-file try_compile cases because they are always fresh. @@ -764,16 +726,23 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, "\" \"", cl, "\" "); } } else { - rule.DepType = "gcc"; - rule.DepFile = "$DEP_FILE"; + const auto& depType = this->GetMakefile()->GetSafeDefinition( + cmStrCat("CMAKE_", lang, "_DEPFILE_FORMAT")); + if (depType == "msvc"_s) { + rule.DepType = "msvc"; + rule.DepFile.clear(); + } else { + rule.DepType = "gcc"; + rule.DepFile = "$DEP_FILE"; + } + vars.DependencyFile = rule.DepFile.c_str(); + vars.DependencyTarget = "$out"; + const std::string flagsName = cmStrCat("CMAKE_DEPFILE_FLAGS_", lang); std::string depfileFlags = mf->GetSafeDefinition(flagsName); if (!depfileFlags.empty()) { - cmSystemTools::ReplaceString(depfileFlags, "<DEPFILE>", "$DEP_FILE"); - cmSystemTools::ReplaceString(depfileFlags, "<OBJECT>", "$out"); - cmSystemTools::ReplaceString( - depfileFlags, "<CMAKE_C_COMPILER>", - cmToCStr(mf->GetDefinition("CMAKE_C_COMPILER"))); + rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), + depfileFlags, vars); flags += cmStrCat(' ', depfileFlags); } } @@ -815,15 +784,21 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, } // Maybe insert an include-what-you-use runner. - if (!compileCmds.empty() && (lang == "C" || lang == "CXX")) { - std::string const iwyu_prop = cmStrCat(lang, "_INCLUDE_WHAT_YOU_USE"); - cmProp iwyu = this->GeneratorTarget->GetProperty(iwyu_prop); + if (!compileCmds.empty() && + (lang == "C" || lang == "CXX" || lang == "OBJC" || lang == "OBJCXX")) { std::string const tidy_prop = cmStrCat(lang, "_CLANG_TIDY"); cmProp tidy = this->GeneratorTarget->GetProperty(tidy_prop); - std::string const cpplint_prop = cmStrCat(lang, "_CPPLINT"); - cmProp cpplint = this->GeneratorTarget->GetProperty(cpplint_prop); - std::string const cppcheck_prop = cmStrCat(lang, "_CPPCHECK"); - cmProp cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop); + cmProp iwyu = nullptr; + cmProp cpplint = nullptr; + cmProp cppcheck = nullptr; + if (lang == "C" || lang == "CXX") { + std::string const iwyu_prop = cmStrCat(lang, "_INCLUDE_WHAT_YOU_USE"); + iwyu = this->GeneratorTarget->GetProperty(iwyu_prop); + std::string const cpplint_prop = cmStrCat(lang, "_CPPLINT"); + cpplint = this->GeneratorTarget->GetProperty(cpplint_prop); + std::string const cppcheck_prop = cmStrCat(lang, "_CPPCHECK"); + cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop); + } if (cmNonempty(iwyu) || cmNonempty(tidy) || cmNonempty(cpplint) || cmNonempty(cppcheck)) { std::string run_iwyu = cmStrCat(cmakeCmd, " -E __run_co_compile"); @@ -887,6 +862,14 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, compileCmds.front().insert(0, cldeps); } + const auto& extraCommands = this->GetMakefile()->GetSafeDefinition( + cmStrCat("CMAKE_", lang, "_DEPENDS_EXTRA_COMMANDS")); + if (!extraCommands.empty()) { + auto commandList = cmExpandedList(extraCommands); + compileCmds.insert(compileCmds.end(), commandList.cbegin(), + commandList.cend()); + } + for (std::string& i : compileCmds) { i = cmStrCat(launcher, i); rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), i, @@ -1062,78 +1045,81 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements( } namespace { -cmNinjaBuild GetPreprocessOrScanBuild( - const std::string& ruleName, const std::string& ppFileName, bool compilePP, - bool compilePPWithDefines, cmNinjaBuild& objBuild, cmNinjaVars& vars, - const std::string& depFileName, bool needDyndep, - const std::string& objectFileName) +cmNinjaBuild GetScanBuildStatement(const std::string& ruleName, + const std::string& ppFileName, + bool compilePP, bool compilePPWithDefines, + cmNinjaBuild& objBuild, cmNinjaVars& vars, + const std::string& objectFileName, + cmLocalGenerator* lg) { - // Explicit preprocessing and dependency - cmNinjaBuild ppBuild(ruleName); + cmNinjaBuild scanBuild(ruleName); if (!ppFileName.empty()) { - ppBuild.Outputs.push_back(ppFileName); - ppBuild.RspFile = cmStrCat(ppFileName, ".rsp"); + scanBuild.RspFile = cmStrCat(ppFileName, ".rsp"); } else { - ppBuild.RspFile = "$out.rsp"; + scanBuild.RspFile = "$out.rsp"; } if (compilePP) { - // Move compilation dependencies to the preprocessing build statement. - std::swap(ppBuild.ExplicitDeps, objBuild.ExplicitDeps); - std::swap(ppBuild.ImplicitDeps, objBuild.ImplicitDeps); - std::swap(ppBuild.OrderOnlyDeps, objBuild.OrderOnlyDeps); - std::swap(ppBuild.Variables["IN_ABS"], vars["IN_ABS"]); + // Move compilation dependencies to the scan/preprocessing build statement. + std::swap(scanBuild.ExplicitDeps, objBuild.ExplicitDeps); + std::swap(scanBuild.ImplicitDeps, objBuild.ImplicitDeps); + std::swap(scanBuild.OrderOnlyDeps, objBuild.OrderOnlyDeps); + std::swap(scanBuild.Variables["IN_ABS"], vars["IN_ABS"]); // The actual compilation will now use the preprocessed source. objBuild.ExplicitDeps.push_back(ppFileName); } else { - // Copy compilation dependencies to the preprocessing build statement. - ppBuild.ExplicitDeps = objBuild.ExplicitDeps; - ppBuild.ImplicitDeps = objBuild.ImplicitDeps; - ppBuild.OrderOnlyDeps = objBuild.OrderOnlyDeps; - ppBuild.Variables["IN_ABS"] = vars["IN_ABS"]; + // Copy compilation dependencies to the scan/preprocessing build statement. + scanBuild.ExplicitDeps = objBuild.ExplicitDeps; + scanBuild.ImplicitDeps = objBuild.ImplicitDeps; + scanBuild.OrderOnlyDeps = objBuild.OrderOnlyDeps; + scanBuild.Variables["IN_ABS"] = vars["IN_ABS"]; } - // Preprocessing and compilation generally use the same flags. - ppBuild.Variables["FLAGS"] = vars["FLAGS"]; + // Scanning and compilation generally use the same flags. + scanBuild.Variables["FLAGS"] = vars["FLAGS"]; if (compilePP && !compilePPWithDefines) { - // Move preprocessor definitions to the preprocessor build statement. - std::swap(ppBuild.Variables["DEFINES"], vars["DEFINES"]); + // Move preprocessor definitions to the scan/preprocessor build statement. + std::swap(scanBuild.Variables["DEFINES"], vars["DEFINES"]); } else { - // Copy preprocessor definitions to the preprocessor build statement. - ppBuild.Variables["DEFINES"] = vars["DEFINES"]; + // Copy preprocessor definitions to the scan/preprocessor build statement. + scanBuild.Variables["DEFINES"] = vars["DEFINES"]; } // Copy include directories to the preprocessor build statement. The // Fortran compilation build statement still needs them for the INCLUDE // directive. - ppBuild.Variables["INCLUDES"] = vars["INCLUDES"]; + scanBuild.Variables["INCLUDES"] = vars["INCLUDES"]; + + // Tell dependency scanner the object file that will result from + // compiling the source. + scanBuild.Variables["OBJ_FILE"] = objectFileName; + + // Tell dependency scanner where to store dyndep intermediate results. + std::string const& ddiFile = cmStrCat(objectFileName, ".ddi"); + scanBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFile; + + // Outputs of the scan/preprocessor build statement. + if (!ppFileName.empty()) { + scanBuild.Outputs.push_back(ppFileName); + scanBuild.ImplicitOuts.push_back(ddiFile); + } else { + scanBuild.Outputs.push_back(ddiFile); + } - // Explicit preprocessing always uses a depfile. - ppBuild.Variables["DEP_FILE"] = depFileName; + // Scanning always uses a depfile for preprocessor dependencies. + std::string const& depFileName = cmStrCat(scanBuild.Outputs.front(), ".d"); + scanBuild.Variables["DEP_FILE"] = + lg->ConvertToOutputFormat(depFileName, cmOutputConverter::SHELL); if (compilePP) { // The actual compilation does not need a depfile because it // depends on the already-preprocessed source. vars.erase("DEP_FILE"); } - if (needDyndep) { - // Tell dependency scanner the object file that will result from - // compiling the source. - ppBuild.Variables["OBJ_FILE"] = objectFileName; - - // Tell dependency scanner where to store dyndep intermediate results. - std::string const ddiFile = cmStrCat(objectFileName, ".ddi"); - if (ppFileName.empty()) { - ppBuild.Outputs.push_back(ddiFile); - } else { - ppBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFile; - ppBuild.ImplicitOuts.push_back(ddiFile); - } - } - return ppBuild; + return scanBuild; } } @@ -1170,7 +1156,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( vars["DEFINES"] = this->ComputeDefines(source, language, config); vars["INCLUDES"] = this->ComputeIncludes(source, language, config); - if (!this->NeedDepTypeMSVC(language)) { + if (this->GetMakefile()->GetSafeDefinition( + cmStrCat("CMAKE_", language, "_DEPFILE_FORMAT")) != "msvc"_s) { bool replaceExt(false); if (!language.empty()) { std::string repVar = @@ -1270,13 +1257,12 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( sourceFileName, objBuild.OrderOnlyDeps); } - // For some cases we need to generate a ninja dyndep file. + // For some cases we scan to dynamically discover dependencies. bool const needDyndep = this->NeedDyndep(language); + bool const compilationPreprocesses = + !this->NeedExplicitPreprocessing(language); - // For some cases we do an explicit preprocessor invocation. - bool const explicitPP = this->NeedExplicitPreprocessing(language); - if (explicitPP) { - + if (needDyndep) { // If source/target has preprocessing turned off, we still need to // generate an explicit dependency step const auto srcpp = source->GetSafeProperty("Fortran_PREPROCESS"); @@ -1288,27 +1274,24 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( preprocess = cmOutputConverter::GetFortranPreprocess(tgtpp); } - bool const compilePP = this->UsePreprocessedSource(language) && + bool const compilePP = !compilationPreprocesses && (preprocess != cmOutputConverter::FortranPreprocess::NotNeeded); bool const compilePPWithDefines = - compilePP && this->CompilePreprocessedSourceWithDefines(language); - - std::string const ppFileName = compilePP - ? this->ConvertToNinjaPath(this->GetPreprocessedFilePath(source, config)) - : ""; + compilePP && this->CompileWithDefines(language); - std::string const buildName = compilePP - ? this->LanguagePreprocessRule(language, config) - : this->LanguageDependencyRule(language, config); - - const auto depExtension = compilePP ? ".pp.d" : ".d"; - const std::string depFileName = - this->GetLocalGenerator()->ConvertToOutputFormat( - cmStrCat(objectFileName, depExtension), cmOutputConverter::SHELL); + std::string scanRuleName; + std::string ppFileName; + if (compilePP) { + scanRuleName = this->LanguagePreprocessAndScanRule(language, config); + ppFileName = this->ConvertToNinjaPath( + this->GetPreprocessedFilePath(source, config)); + } else { + scanRuleName = this->LanguageScanRule(language, config); + } - cmNinjaBuild ppBuild = GetPreprocessOrScanBuild( - buildName, ppFileName, compilePP, compilePPWithDefines, objBuild, vars, - depFileName, needDyndep, objectFileName); + cmNinjaBuild ppBuild = GetScanBuildStatement( + scanRuleName, ppFileName, compilePP, compilePPWithDefines, objBuild, + vars, objectFileName, this->LocalGenerator); if (compilePP) { // In case compilation requires flags that are incompatible with @@ -1330,7 +1313,7 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( vars["INCLUDES"] = cmStrCat(sourceDirectoryFlag, ' ', vars["INCLUDES"]); } - if (firstForConfig && needDyndep) { + if (firstForConfig) { std::string const ddiFile = cmStrCat(objectFileName, ".ddi"); this->Configs[config].DDIFiles[language].push_back(ddiFile); } @@ -1340,8 +1323,7 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), ppBuild, commandLineLengthLimit); - } - if (needDyndep) { + std::string const dyndep = this->GetDyndepFilePath(language, config); objBuild.OrderOnlyDeps.push_back(dyndep); vars["dyndep"] = dyndep; diff --git a/Source/cmNinjaTargetGenerator.h b/Source/cmNinjaTargetGenerator.h index a27c9b4..83a4342 100644 --- a/Source/cmNinjaTargetGenerator.h +++ b/Source/cmNinjaTargetGenerator.h @@ -42,8 +42,6 @@ public: std::string GetTargetName() const; - bool NeedDepTypeMSVC(const std::string& lang) const; - protected: bool SetMsvcTargetPdbVariable(cmNinjaVars&, const std::string& config) const; @@ -67,16 +65,15 @@ protected: std::string LanguageCompilerRule(const std::string& lang, const std::string& config) const; - std::string LanguagePreprocessRule(std::string const& lang, - const std::string& config) const; - std::string LanguageDependencyRule(std::string const& lang, - const std::string& config) const; - bool NeedExplicitPreprocessing(std::string const& lang) const; + std::string LanguagePreprocessAndScanRule(std::string const& lang, + const std::string& config) const; + std::string LanguageScanRule(std::string const& lang, + const std::string& config) const; std::string LanguageDyndepRule(std::string const& lang, const std::string& config) const; bool NeedDyndep(std::string const& lang) const; - bool UsePreprocessedSource(std::string const& lang) const; - bool CompilePreprocessedSourceWithDefines(std::string const& lang) const; + bool NeedExplicitPreprocessing(std::string const& lang) const; + bool CompileWithDefines(std::string const& lang) const; std::string OrderDependsTargetForTarget(const std::string& config); diff --git a/Source/cmNinjaUtilityTargetGenerator.cxx b/Source/cmNinjaUtilityTargetGenerator.cxx index ad1d5f1..0b62e16 100644 --- a/Source/cmNinjaUtilityTargetGenerator.cxx +++ b/Source/cmNinjaUtilityTargetGenerator.cxx @@ -5,6 +5,7 @@ #include <algorithm> #include <array> #include <iterator> +#include <set> #include <string> #include <utility> #include <vector> @@ -34,13 +35,30 @@ cmNinjaUtilityTargetGenerator::~cmNinjaUtilityTargetGenerator() = default; void cmNinjaUtilityTargetGenerator::Generate(const std::string& config) { + for (auto const& fileConfig : this->GetConfigNames()) { + if (!this->GetGlobalGenerator() + ->GetCrossConfigs(fileConfig) + .count(config)) { + continue; + } + if (fileConfig != config && + this->GetGeneratorTarget()->GetType() == cmStateEnums::GLOBAL_TARGET) { + continue; + } + this->WriteUtilBuildStatements(config, fileConfig); + } +} + +void cmNinjaUtilityTargetGenerator::WriteUtilBuildStatements( + std::string const& config, std::string const& fileConfig) +{ cmGlobalNinjaGenerator* gg = this->GetGlobalGenerator(); cmLocalNinjaGenerator* lg = this->GetLocalGenerator(); cmGeneratorTarget* genTarget = this->GetGeneratorTarget(); std::string configDir; if (genTarget->Target->IsPerConfig()) { - configDir = gg->ConfigDirectory(config); + configDir = gg->ConfigDirectory(fileConfig); } std::string utilCommandName = cmStrCat(lg->GetCurrentBinaryDirectory(), "/CMakeFiles", configDir, "/", @@ -60,8 +78,8 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config) for (std::vector<cmCustomCommand> const* cmdList : cmdLists) { for (cmCustomCommand const& ci : *cmdList) { - cmCustomCommandGenerator ccg(ci, config, lg); - lg->AppendCustomCommandDeps(ccg, deps, config); + cmCustomCommandGenerator ccg(ci, fileConfig, lg); + lg->AppendCustomCommandDeps(ccg, deps, fileConfig); lg->AppendCustomCommandLines(ccg, commands); std::vector<std::string> const& ccByproducts = ccg.GetByproducts(); std::transform(ccByproducts.begin(), ccByproducts.end(), @@ -103,13 +121,19 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config) std::copy(util_outputs.begin(), util_outputs.end(), std::back_inserter(gg->GetByproductsForCleanTarget())); } - lg->AppendTargetDepends(genTarget, deps, config, config, + // TODO: Does this need an output config? + // Does this need to go in impl-<config>.ninja? + lg->AppendTargetDepends(genTarget, deps, config, fileConfig, DependOnTargetArtifact); if (commands.empty()) { phonyBuild.Comment = "Utility command for " + this->GetTargetName(); phonyBuild.ExplicitDeps = std::move(deps); - gg->WriteBuild(this->GetCommonFileStream(), phonyBuild); + if (genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) { + gg->WriteBuild(this->GetImplFileStream(fileConfig), phonyBuild); + } else { + gg->WriteBuild(this->GetCommonFileStream(), phonyBuild); + } } else { std::string command = lg->BuildCommandLine(commands, "utility", this->GeneratorTarget); @@ -145,15 +169,22 @@ void cmNinjaUtilityTargetGenerator::Generate(const std::string& config) std::string ccConfig; if (genTarget->Target->IsPerConfig() && genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) { - ccConfig = config; + ccConfig = fileConfig; + } + if (config == fileConfig || + gg->GetPerConfigUtilityTargets().count(genTarget->GetName())) { + gg->WriteCustomCommandBuild( + command, desc, "Utility command for " + this->GetTargetName(), + /*depfile*/ "", /*job_pool*/ "", uses_terminal, + /*restat*/ true, util_outputs, ccConfig, deps); } - gg->WriteCustomCommandBuild(command, desc, - "Utility command for " + this->GetTargetName(), - /*depfile*/ "", /*job_pool*/ "", uses_terminal, - /*restat*/ true, util_outputs, ccConfig, deps); phonyBuild.ExplicitDeps.push_back(utilCommandName); - gg->WriteBuild(this->GetCommonFileStream(), phonyBuild); + if (genTarget->GetType() != cmStateEnums::GLOBAL_TARGET) { + gg->WriteBuild(this->GetImplFileStream(fileConfig), phonyBuild); + } else { + gg->WriteBuild(this->GetCommonFileStream(), phonyBuild); + } } // Find ADDITIONAL_CLEAN_FILES diff --git a/Source/cmNinjaUtilityTargetGenerator.h b/Source/cmNinjaUtilityTargetGenerator.h index 24b47f8..dbd3797 100644 --- a/Source/cmNinjaUtilityTargetGenerator.h +++ b/Source/cmNinjaUtilityTargetGenerator.h @@ -17,4 +17,8 @@ public: ~cmNinjaUtilityTargetGenerator() override; void Generate(const std::string& config) override; + +private: + void WriteUtilBuildStatements(std::string const& config, + std::string const& fileConfig); }; diff --git a/Source/cmPipeConnection.cxx b/Source/cmPipeConnection.cxx deleted file mode 100644 index 1eede13..0000000 --- a/Source/cmPipeConnection.cxx +++ /dev/null @@ -1,71 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmPipeConnection.h" - -#include <utility> - -#include "cmServer.h" - -cmPipeConnection::cmPipeConnection(std::string name, - cmConnectionBufferStrategy* bufferStrategy) - : cmEventBasedConnection(bufferStrategy) - , PipeName(std::move(name)) -{ -} - -void cmPipeConnection::Connect(uv_stream_t* server) -{ - if (this->WriteStream.get()) { - // Accept and close all pipes but the first: - cm::uv_pipe_ptr rejectPipe; - - rejectPipe.init(*this->Server->GetLoop(), 0); - uv_accept(server, rejectPipe); - - return; - } - - cm::uv_pipe_ptr ClientPipe; - ClientPipe.init(*this->Server->GetLoop(), 0, - static_cast<cmEventBasedConnection*>(this)); - - if (uv_accept(server, ClientPipe) != 0) { - return; - } - - uv_read_start(ClientPipe, on_alloc_buffer, on_read); - WriteStream = std::move(ClientPipe); - Server->OnConnected(this); -} - -bool cmPipeConnection::OnServeStart(std::string* errorMessage) -{ - this->ServerPipe.init(*this->Server->GetLoop(), 0, - static_cast<cmEventBasedConnection*>(this)); - - int r; - if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) { - *errorMessage = std::string("Internal Error with ") + this->PipeName + - ": " + uv_err_name(r); - return false; - } - - if ((r = uv_listen(this->ServerPipe, 1, on_new_connection)) != 0) { - *errorMessage = std::string("Internal Error listening on ") + - this->PipeName + ": " + uv_err_name(r); - return false; - } - - return cmConnection::OnServeStart(errorMessage); -} - -bool cmPipeConnection::OnConnectionShuttingDown() -{ - if (this->WriteStream.get()) { - this->WriteStream->data = nullptr; - } - - this->ServerPipe.reset(); - - return cmEventBasedConnection::OnConnectionShuttingDown(); -} diff --git a/Source/cmPipeConnection.h b/Source/cmPipeConnection.h deleted file mode 100644 index 1215716..0000000 --- a/Source/cmPipeConnection.h +++ /dev/null @@ -1,29 +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 <string> - -#include <cm3p/uv.h> - -#include "cmConnection.h" -#include "cmUVHandlePtr.h" - -class cmPipeConnection : public cmEventBasedConnection -{ -public: - cmPipeConnection(std::string name, - cmConnectionBufferStrategy* bufferStrategy = nullptr); - - bool OnServeStart(std::string* pString) override; - - bool OnConnectionShuttingDown() override; - - void Connect(uv_stream_t* server) override; - -private: - const std::string PipeName; - cm::uv_pipe_ptr ServerPipe; -}; diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index 18ce9c3..2194b0f 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -340,6 +340,25 @@ class cmMakefile; 3, 19, 0, cmPolicies::WARN) \ SELECT(POLICY, CMP0114, \ "ExternalProject step targets fully adopt their steps.", 3, 19, 0, \ + cmPolicies::WARN) \ + SELECT(POLICY, CMP0115, "Source file extensions must be explicit.", 3, 20, \ + 0, cmPolicies::WARN) \ + SELECT(POLICY, CMP0116, \ + "Ninja generators transform DEPFILEs from add_custom_command().", 3, \ + 20, 0, cmPolicies::WARN) \ + SELECT(POLICY, CMP0117, \ + "MSVC RTTI flag /GR is not added to CMAKE_CXX_FLAGS by default.", 3, \ + 20, 0, cmPolicies::WARN) \ + SELECT( \ + POLICY, CMP0118, \ + "The GENERATED source file property is now visible in all directories.", \ + 3, 20, 0, cmPolicies::WARN) \ + SELECT(POLICY, CMP0119, \ + "LANGUAGE source file property explicitly compiles as specified " \ + "language.", \ + 3, 20, 0, cmPolicies::WARN) \ + SELECT(POLICY, CMP0120, \ + "The WriteCompilerDetectionHeader module is removed.", 3, 20, 0, \ cmPolicies::WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) @@ -375,7 +394,8 @@ class cmMakefile; F(CMP0105) \ F(CMP0108) \ F(CMP0112) \ - F(CMP0113) + F(CMP0113) \ + F(CMP0119) /** \class cmPolicies * \brief Handles changes in CMake behavior and policies diff --git a/Source/cmProjectCommand.cxx b/Source/cmProjectCommand.cxx index 0cfba63..ed32de9 100644 --- a/Source/cmProjectCommand.cxx +++ b/Source/cmProjectCommand.cxx @@ -358,6 +358,17 @@ static bool IncludeByVariable(cmExecutionStatus& status, return true; } + 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(*include); if (readit) { return true; @@ -367,7 +378,7 @@ static bool IncludeByVariable(cmExecutionStatus& status, return true; } - status.SetError(cmStrCat("could not find file:\n ", *include)); + status.SetError(cmStrCat("could not load requested file:\n ", *include)); return false; } diff --git a/Source/cmQTWrapCPPCommand.cxx b/Source/cmQTWrapCPPCommand.cxx index de462db..e058176 100644 --- a/Source/cmQTWrapCPPCommand.cxx +++ b/Source/cmQTWrapCPPCommand.cxx @@ -40,8 +40,7 @@ bool cmQTWrapCPPCommand(std::vector<std::string> const& args, cmStrCat(mf.GetCurrentBinaryDirectory(), "/moc_", srcName, ".cxx"); cmSourceFile* sf = mf.GetOrCreateSource(newName, true); if (curr) { - cmProp p = curr->GetProperty("ABSTRACT"); - sf->SetProperty("ABSTRACT", cmToCStr(p)); + sf->SetProperty("ABSTRACT", cmToCStr(curr->GetProperty("ABSTRACT"))); } // Compute the name of the header from which to generate the file. diff --git a/Source/cmQtAutoGen.h b/Source/cmQtAutoGen.h index cf90417..2db1b84 100644 --- a/Source/cmQtAutoGen.h +++ b/Source/cmQtAutoGen.h @@ -29,13 +29,13 @@ public: { } - bool operator>(IntegerVersion const version) + bool operator>(IntegerVersion const version) const { return (this->Major > version.Major) || ((this->Major == version.Major) && (this->Minor > version.Minor)); } - bool operator>=(IntegerVersion const version) + bool operator>=(IntegerVersion const version) const { return (this->Major > version.Major) || ((this->Major == version.Major) && (this->Minor >= version.Minor)); diff --git a/Source/cmQtAutoGenGlobalInitializer.cxx b/Source/cmQtAutoGenGlobalInitializer.cxx index fac2bbf..ff6fcd0 100644 --- a/Source/cmQtAutoGenGlobalInitializer.cxx +++ b/Source/cmQtAutoGenGlobalInitializer.cxx @@ -2,6 +2,7 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmQtAutoGenGlobalInitializer.h" +#include <set> #include <utility> #include <cm/memory> @@ -91,6 +92,12 @@ cmQtAutoGenGlobalInitializer::cmQtAutoGenGlobalInitializer( // Don't process target continue; } + std::set<std::string> const& languages = + target->GetAllConfigCompileLanguages(); + if (languages.count("CSharp")) { + // Don't process target if it's a CSharp target + continue; + } bool const moc = target->GetPropertyAsBool(kw().AUTOMOC); bool const uic = target->GetPropertyAsBool(kw().AUTOUIC); diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx index 3b62e9c..1f74578 100644 --- a/Source/cmQtAutoGenInitializer.cxx +++ b/Source/cmQtAutoGenInitializer.cxx @@ -17,6 +17,7 @@ #include <cm/iterator> #include <cm/memory> #include <cmext/algorithm> +#include <cmext/string_view> #include <cm3p/json/value.h> #include <cm3p/json/writer.h> @@ -29,6 +30,7 @@ #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" +#include "cmGlobalNinjaGenerator.h" #include "cmLinkItem.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" @@ -563,8 +565,22 @@ bool cmQtAutoGenInitializer::InitCustomTargets() bool cmQtAutoGenInitializer::InitMoc() { // Mocs compilation file - this->Moc.CompilationFile = - cmStrCat(this->Dir.Build, "/mocs_compilation.cpp"); + if (this->GlobalGen->IsXcode()) { + // XXX(xcode-per-cfg-src): Drop this Xcode-specific code path + // when the Xcode generator supports per-config sources. + this->Moc.CompilationFile.Default = + cmStrCat(this->Dir.Build, "/mocs_compilation.cpp"); + this->Moc.CompilationFileGenex = this->Moc.CompilationFile.Default; + } else { + ConfigFileNames(this->Moc.CompilationFile, + cmStrCat(this->Dir.Build, "/mocs_compilation"), ".cpp"); + if (this->MultiConfig) { + this->Moc.CompilationFileGenex = + cmStrCat(this->Dir.Build, "/mocs_compilation_$<CONFIG>.cpp"_s); + } else { + this->Moc.CompilationFileGenex = this->Moc.CompilationFile.Default; + } + } // Moc predefs if (this->GenTarget->GetPropertyAsBool("AUTOMOC_COMPILER_PREDEFINES") && @@ -730,10 +746,14 @@ bool cmQtAutoGenInitializer::InitScanFiles() auto const& kw = this->GlobalInitializer->kw(); auto makeMUFile = [this, &kw](cmSourceFile* sf, std::string const& fullPath, + std::vector<size_t> const& configs, bool muIt) -> MUFileHandle { MUFileHandle muf = cm::make_unique<MUFile>(); muf->FullPath = fullPath; muf->SF = sf; + if (!configs.empty() && configs.size() != this->ConfigsList.size()) { + muf->Configs = configs; + } muf->Generated = sf->GetIsGenerated(); bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN); muf->SkipMoc = this->Moc.Enabled && @@ -772,42 +792,37 @@ bool cmQtAutoGenInitializer::InitScanFiles() // Scan through target files { // Scan through target files - std::vector<cmSourceFile*> srcFiles; - this->GenTarget->GetConfigCommonSourceFiles(srcFiles); - for (cmSourceFile* sf : srcFiles) { - // sf->GetExtension() is only valid after sf->ResolveFullPath() ... - // Since we're iterating over source files that might be not in the - // target we need to check for path errors (not existing files). - std::string pathError; - std::string const& fullPath = sf->ResolveFullPath(&pathError); - if (!pathError.empty() || fullPath.empty()) { - continue; - } + for (cmGeneratorTarget::AllConfigSource const& acs : + this->GenTarget->GetAllConfigSources()) { + std::string const& fullPath = acs.Source->GetFullPath(); std::string const& extLower = - cmSystemTools::LowerCase(sf->GetExtension()); + cmSystemTools::LowerCase(acs.Source->GetExtension()); // Register files that will be scanned by moc or uic if (this->MocOrUicEnabled()) { if (cm->IsAHeaderExtension(extLower)) { - addMUHeader(makeMUFile(sf, fullPath, true), extLower); + addMUHeader(makeMUFile(acs.Source, fullPath, acs.Configs, true), + extLower); } else if (cm->IsACLikeSourceExtension(extLower)) { - addMUSource(makeMUFile(sf, fullPath, true)); + addMUSource(makeMUFile(acs.Source, fullPath, acs.Configs, true)); } } // Register rcc enabled files if (this->Rcc.Enabled) { - if ((extLower == kw.qrc) && !sf->GetPropertyAsBool(kw.SKIP_AUTOGEN) && - !sf->GetPropertyAsBool(kw.SKIP_AUTORCC)) { + if ((extLower == kw.qrc) && + !acs.Source->GetPropertyAsBool(kw.SKIP_AUTOGEN) && + !acs.Source->GetPropertyAsBool(kw.SKIP_AUTORCC)) { // Register qrc file Qrc qrc; qrc.QrcFile = fullPath; qrc.QrcName = cmSystemTools::GetFilenameWithoutLastExtension(qrc.QrcFile); - qrc.Generated = sf->GetIsGenerated(); + qrc.Generated = acs.Source->GetIsGenerated(); // RCC options { - std::string const opts = sf->GetSafeProperty(kw.AUTORCC_OPTIONS); + std::string const& opts = + acs.Source->GetSafeProperty(kw.AUTORCC_OPTIONS); if (!opts.empty()) { cmExpandList(opts, qrc.Options); } @@ -817,7 +832,7 @@ bool cmQtAutoGenInitializer::InitScanFiles() } } } - // cmGeneratorTarget::GetConfigCommonSourceFiles computes the target's + // cmGeneratorTarget::GetAllConfigSources computes the target's // sources meta data cache. Clear it so that OBJECT library targets that // are AUTOGEN initialized after this target get their added // mocs_compilation.cpp source acknowledged by this target. @@ -861,7 +876,7 @@ bool cmQtAutoGenInitializer::InitScanFiles() } if (sf != nullptr) { - auto eMuf = makeMUFile(sf, fullPath, true); + auto eMuf = makeMUFile(sf, fullPath, muf.Configs, true); // Only process moc/uic when the parent is processed as well if (!muf.MocIt) { eMuf->MocIt = false; @@ -896,14 +911,14 @@ bool cmQtAutoGenInitializer::InitScanFiles() if (cm->IsAHeaderExtension(extLower)) { if (!cm::contains(this->AutogenTarget.Headers, sf.get())) { - auto muf = makeMUFile(sf.get(), fullPath, false); + auto muf = makeMUFile(sf.get(), fullPath, {}, false); if (muf->SkipMoc || muf->SkipUic) { addMUHeader(std::move(muf), extLower); } } } else if (cm->IsACLikeSourceExtension(extLower)) { if (!cm::contains(this->AutogenTarget.Sources, sf.get())) { - auto muf = makeMUFile(sf.get(), fullPath, false); + auto muf = makeMUFile(sf.get(), fullPath, {}, false); if (muf->SkipMoc || muf->SkipUic) { addMUSource(std::move(muf)); } @@ -1066,10 +1081,10 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() this->Makefile->AddCMakeOutputFile(this->AutogenTarget.InfoFile); // Files provided by the autogen target - std::vector<std::string> autogenProvides; + std::vector<std::string> autogenByproducts; if (this->Moc.Enabled) { this->AddGeneratedSource(this->Moc.CompilationFile, this->Moc, true); - autogenProvides.push_back(this->Moc.CompilationFile); + autogenByproducts.push_back(this->Moc.CompilationFileGenex); } // Compose target comment @@ -1090,8 +1105,8 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() } // Compose command lines - // TODO: Refactor autogen to output a per-config mocs_compilation.cpp instead - // of fiddling with the include directories + // FIXME: Take advantage of our per-config mocs_compilation_$<CONFIG>.cpp + // instead of fiddling with the include directories std::vector<std::string> configs; this->GlobalGen->GetQtAutoGenConfigs(configs); bool stdPipesUTF8 = true; @@ -1137,7 +1152,7 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() // PRE_BUILD does not support file dependencies! const std::vector<std::string> no_output; const std::vector<std::string> no_deps; - cmCustomCommand cc(no_output, autogenProvides, no_deps, commandLines, + cmCustomCommand cc(no_output, autogenByproducts, no_deps, commandLines, this->Makefile->GetBacktrace(), autogenComment.c_str(), this->Dir.Work.c_str(), stdPipesUTF8); cc.SetEscapeOldStyle(false); @@ -1237,11 +1252,23 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() const std::string outputFile = cmStrCat(this->Dir.Build, "/", timestampFileName); this->AutogenTarget.DepFile = cmStrCat(this->Dir.Build, "/deps"); - auto relativeBinaryDir = cmSystemTools::RelativePath( - this->LocalGen->GetBinaryDirectory(), - this->LocalGen->GetCurrentBinaryDirectory()); - if (!relativeBinaryDir.empty()) { - relativeBinaryDir = cmStrCat(relativeBinaryDir, "/"); + std::string relativeBinaryDir; + if (dynamic_cast<cmGlobalNinjaGenerator*>(this->GlobalGen)) { + switch (this->LocalGen->GetPolicyStatus(cmPolicies::CMP0116)) { + case cmPolicies::OLD: + case cmPolicies::WARN: + relativeBinaryDir = cmSystemTools::RelativePath( + this->LocalGen->GetBinaryDirectory(), + this->LocalGen->GetCurrentBinaryDirectory()); + if (!relativeBinaryDir.empty()) { + relativeBinaryDir = cmStrCat(relativeBinaryDir, "/"); + } + break; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::NEW: + break; + } } this->AutogenTarget.DepFileRuleName = cmStrCat(relativeBinaryDir, this->GenTarget->GetName(), "_autogen/", @@ -1270,7 +1297,7 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() // Create autogen target cmTarget* autogenTarget = this->LocalGen->AddUtilityCommand( this->AutogenTarget.Name, true, this->Dir.Work.c_str(), - /*byproducts=*/autogenProvides, + /*byproducts=*/autogenByproducts, /*depends=*/dependencies, commandLines, false, autogenComment.c_str()); // Create autogen generator target this->LocalGen->AddGeneratorTarget( @@ -1520,18 +1547,31 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo() info.SetArray("CMAKE_LIST_FILES", this->Makefile->GetListFiles()); info.SetArray("HEADER_EXTENSIONS", this->Makefile->GetCMakeInstance()->GetHeaderExtensions()); + auto cfgArray = [this](std::vector<size_t> const& configs) -> Json::Value { + Json::Value value; + if (!configs.empty()) { + value = Json::arrayValue; + for (size_t ci : configs) { + value.append(this->ConfigsList[ci]); + } + } + return value; + }; + info.SetArrayArray("HEADERS", headers, + [this, &cfgArray](Json::Value& jval, MUFile const* muf) { + jval.resize(4u); + jval[0u] = muf->FullPath; + jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm', + muf->UicIt ? 'U' : 'u'); + jval[2u] = cfgArray(muf->Configs); + jval[3u] = this->GetMocBuildPath(*muf); + }); info.SetArrayArray( - "HEADERS", headers, [this](Json::Value& jval, MUFile const* muf) { + "SOURCES", sources, [&cfgArray](Json::Value& jval, MUFile const* muf) { jval.resize(3u); jval[0u] = muf->FullPath; jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm', muf->UicIt ? 'U' : 'u'); - jval[2u] = this->GetMocBuildPath(*muf); - }); - info.SetArrayArray( - "SOURCES", sources, [](Json::Value& jval, MUFile const* muf) { - jval.resize(2u); - jval[0u] = muf->FullPath; - jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm', muf->UicIt ? 'U' : 'u'); + jval[2u] = cfgArray(muf->Configs); }); // Write moc settings @@ -1550,7 +1590,7 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo() jval[0u] = pair.first; jval[1u] = pair.second; }); - info.Set("MOC_COMPILATION_FILE", this->Moc.CompilationFile); + info.SetConfig("MOC_COMPILATION_FILE", this->Moc.CompilationFile); info.SetArray("MOC_PREDEFS_CMD", this->Moc.PredefsCmd); info.SetConfig("MOC_PREDEFS_FILE", this->Moc.PredefsFile); } @@ -1624,7 +1664,7 @@ cmSourceFile* cmQtAutoGenInitializer::RegisterGeneratedSource( std::string const& filename) { cmSourceFile* gFile = this->Makefile->GetOrCreateSource(filename, true); - gFile->SetProperty("GENERATED", "1"); + gFile->MarkAsGenerated(); gFile->SetProperty("SKIP_AUTOGEN", "1"); return gFile; } @@ -1643,6 +1683,28 @@ cmSourceFile* cmQtAutoGenInitializer::AddGeneratedSource( return gFile; } +void cmQtAutoGenInitializer::AddGeneratedSource(ConfigString const& filename, + GenVarsT const& genVars, + bool prepend) +{ + // XXX(xcode-per-cfg-src): Drop the Xcode-specific part of the condition + // when the Xcode generator supports per-config sources. + if (!this->MultiConfig || this->GlobalGen->IsXcode()) { + this->AddGeneratedSource(filename.Default, genVars, prepend); + return; + } + for (auto const& cfg : this->ConfigsList) { + std::string const& filenameCfg = filename.Config.at(cfg); + // Register source at makefile + this->RegisterGeneratedSource(filenameCfg); + // Add source file to target for this configuration. + this->GenTarget->AddSource( + cmStrCat("$<$<CONFIG:"_s, cfg, ">:"_s, filenameCfg, ">"_s), prepend); + // Add source file to source group + this->AddToSourceGroup(filenameCfg, genVars.GenNameUpper); + } +} + void cmQtAutoGenInitializer::AddToSourceGroup(std::string const& fileName, cm::string_view genNameUpper) { diff --git a/Source/cmQtAutoGenInitializer.h b/Source/cmQtAutoGenInitializer.h index 3ab303a..e0e66f1 100644 --- a/Source/cmQtAutoGenInitializer.h +++ b/Source/cmQtAutoGenInitializer.h @@ -4,6 +4,7 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include <cstddef> #include <memory> #include <set> #include <string> @@ -70,6 +71,7 @@ public: { std::string FullPath; cmSourceFile* SF = nullptr; + std::vector<size_t> Configs; bool Generated = false; bool SkipMoc = false; bool SkipUic = false; @@ -132,6 +134,8 @@ private: cmSourceFile* AddGeneratedSource(std::string const& filename, GenVarsT const& genVars, bool prepend = false); + void AddGeneratedSource(ConfigString const& filename, + GenVarsT const& genVars, bool prepend = false); void AddToSourceGroup(std::string const& fileName, cm::string_view genNameUpper); void AddCleanFile(std::string const& fileName); @@ -207,7 +211,8 @@ private: bool RelaxedMode = false; bool PathPrefix = false; - std::string CompilationFile; + ConfigString CompilationFile; + std::string CompilationFileGenex; // Compiler implicit pre defines std::vector<std::string> PredefsCmd; ConfigString PredefsFile; diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx index 9cb172b..c9d4268 100644 --- a/Source/cmQtAutoMocUic.cxx +++ b/Source/cmQtAutoMocUic.cxx @@ -15,6 +15,7 @@ #include <vector> #include <cm/memory> +#include <cm/optional> #include <cm/string_view> #include <cmext/algorithm> @@ -26,7 +27,6 @@ #include "cmCryptoHash.h" #include "cmFileTime.h" #include "cmGccDepfileReader.h" -#include "cmGccDepfileReaderTypes.h" #include "cmGeneratedFileStream.h" #include "cmQtAutoGen.h" #include "cmQtAutoGenerator.h" @@ -2452,17 +2452,20 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info) Json::Value const& entry = val[ii]; if (testEntry(entry.isArray(), "JSON value is not an array.") || - testEntry(entry.size() == 3, "JSON array size invalid.")) { + testEntry(entry.size() == 4, "JSON array size invalid.")) { return false; } Json::Value const& entryName = entry[0u]; Json::Value const& entryFlags = entry[1u]; - Json::Value const& entryBuild = entry[2u]; + Json::Value const& entryConfigs = entry[2u]; + Json::Value const& entryBuild = entry[3u]; if (testEntry(entryName.isString(), "JSON value for name is not a string.") || testEntry(entryFlags.isString(), "JSON value for flags is not a string.") || + testEntry(entryConfigs.isNull() || entryConfigs.isArray(), + "JSON value for configs is not null or array.") || testEntry(entryBuild.isString(), "JSON value for build path is not a string.")) { return false; @@ -2475,6 +2478,22 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info) return false; } + if (entryConfigs.isArray()) { + bool configFound = false; + Json::ArrayIndex const configArraySize = entryConfigs.size(); + for (Json::ArrayIndex ci = 0; ci != configArraySize; ++ci) { + Json::Value const& config = entryConfigs[ci]; + if (testEntry(config.isString(), + "JSON value in config array is not a string.")) { + return false; + } + configFound = configFound || config.asString() == this->InfoConfig(); + } + if (!configFound) { + continue; + } + } + cmFileTime fileTime; if (!fileTime.Load(name)) { return info.LogError(cmStrCat( @@ -2515,16 +2534,19 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info) Json::Value const& entry = val[ii]; if (testEntry(entry.isArray(), "JSON value is not an array.") || - testEntry(entry.size() == 2, "JSON array size invalid.")) { + testEntry(entry.size() == 3, "JSON array size invalid.")) { return false; } Json::Value const& entryName = entry[0u]; Json::Value const& entryFlags = entry[1u]; + Json::Value const& entryConfigs = entry[2u]; if (testEntry(entryName.isString(), "JSON value for name is not a string.") || testEntry(entryFlags.isString(), - "JSON value for flags is not a string.")) { + "JSON value for flags is not a string.") || + testEntry(entryConfigs.isNull() || entryConfigs.isArray(), + "JSON value for configs is not null or array.")) { return false; } @@ -2534,6 +2556,22 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info) return false; } + if (entryConfigs.isArray()) { + bool configFound = false; + Json::ArrayIndex const configArraySize = entryConfigs.size(); + for (Json::ArrayIndex ci = 0; ci != configArraySize; ++ci) { + Json::Value const& config = entryConfigs[ci]; + if (testEntry(config.isString(), + "JSON value in config array is not a string.")) { + return false; + } + configFound = configFound || config.asString() == this->InfoConfig(); + } + if (!configFound) { + continue; + } + } + cmFileTime fileTime; if (!fileTime.Load(name)) { return info.LogError(cmStrCat( @@ -2841,14 +2879,14 @@ bool cmQtAutoMocUicT::CreateDirectories() std::vector<std::string> cmQtAutoMocUicT::dependenciesFromDepFile( const char* filePath) { - cmGccDepfileContent content = cmReadGccDepfile(filePath); - if (content.empty()) { + auto const content = cmReadGccDepfile(filePath); + if (!content || content->empty()) { return {}; } // Moc outputs a depfile with exactly one rule. // Discard the rule and return the dependencies. - return content.front().paths; + return content->front().paths; } void cmQtAutoMocUicT::Abort(bool error) diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx index f5f9c67..5363fef 100644 --- a/Source/cmRulePlaceholderExpander.cxx +++ b/Source/cmRulePlaceholderExpander.cxx @@ -3,7 +3,6 @@ #include "cmRulePlaceholderExpander.h" #include <cctype> -#include <cstring> #include <utility> #include "cmOutputConverter.h" @@ -20,11 +19,6 @@ cmRulePlaceholderExpander::cmRulePlaceholderExpander( { } -cmRulePlaceholderExpander::RuleVariables::RuleVariables() -{ - memset(this, 0, sizeof(*this)); -} - std::string cmRulePlaceholderExpander::ExpandRuleVariable( cmOutputConverter* outputConverter, std::string const& variable, const RuleVariables& replaceValues) @@ -141,6 +135,11 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable( return replaceValues.DependencyFile; } } + if (replaceValues.DependencyTarget) { + if (variable == "DEP_TARGET") { + return replaceValues.DependencyTarget; + } + } if (replaceValues.Fatbinary) { if (variable == "FATBINARY") { return replaceValues.Fatbinary; diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h index c8d107d..710f8a6 100644 --- a/Source/cmRulePlaceholderExpander.h +++ b/Source/cmRulePlaceholderExpander.h @@ -27,45 +27,45 @@ public: // ExpandRuleVariables struct RuleVariables { - RuleVariables(); - const char* CMTargetName; - const char* CMTargetType; - const char* TargetPDB; - const char* TargetCompilePDB; - const char* TargetVersionMajor; - const char* TargetVersionMinor; - const char* Language; - const char* AIXExports; - const char* Objects; - const char* Target; - const char* LinkLibraries; - const char* Source; - const char* AssemblySource; - const char* PreprocessedSource; - const char* Output; - const char* Object; - const char* ObjectDir; - const char* ObjectFileDir; - const char* Flags; - const char* ObjectsQuoted; - const char* SONameFlag; - const char* TargetSOName; - const char* TargetInstallNameDir; - const char* LinkFlags; - const char* Manifests; - const char* LanguageCompileFlags; - const char* Defines; - const char* Includes; - const char* DependencyFile; - const char* FilterPrefix; - const char* SwiftLibraryName; - const char* SwiftModule; - const char* SwiftModuleName; - const char* SwiftOutputFileMap; - const char* SwiftSources; - const char* ISPCHeader; - const char* Fatbinary; - const char* RegisterFile; + const char* CMTargetName = nullptr; + const char* CMTargetType = nullptr; + const char* TargetPDB = nullptr; + const char* TargetCompilePDB = nullptr; + const char* TargetVersionMajor = nullptr; + const char* TargetVersionMinor = nullptr; + const char* Language = nullptr; + const char* AIXExports = nullptr; + const char* Objects = nullptr; + const char* Target = nullptr; + const char* LinkLibraries = nullptr; + const char* Source = nullptr; + const char* AssemblySource = nullptr; + const char* PreprocessedSource = nullptr; + const char* Output = nullptr; + const char* Object = nullptr; + const char* ObjectDir = nullptr; + const char* ObjectFileDir = nullptr; + const char* Flags = nullptr; + const char* ObjectsQuoted = nullptr; + const char* SONameFlag = nullptr; + const char* TargetSOName = nullptr; + const char* TargetInstallNameDir = nullptr; + const char* LinkFlags = nullptr; + const char* Manifests = nullptr; + const char* LanguageCompileFlags = nullptr; + const char* Defines = nullptr; + const char* Includes = nullptr; + const char* DependencyFile = nullptr; + const char* DependencyTarget = nullptr; + const char* FilterPrefix = nullptr; + const char* SwiftLibraryName = nullptr; + const char* SwiftModule = nullptr; + const char* SwiftModuleName = nullptr; + const char* SwiftOutputFileMap = nullptr; + const char* SwiftSources = nullptr; + const char* ISPCHeader = nullptr; + const char* Fatbinary = nullptr; + const char* RegisterFile = nullptr; }; // Expand rule variables in CMake of the type found in language rules diff --git a/Source/cmScanDepFormat.cxx b/Source/cmScanDepFormat.cxx new file mode 100644 index 0000000..40bf4c9 --- /dev/null +++ b/Source/cmScanDepFormat.cxx @@ -0,0 +1,267 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmScanDepFormat.h" + +#include <cctype> +#include <cstdio> + +#include <cm3p/json/reader.h> +#include <cm3p/json/value.h> +#include <cm3p/json/writer.h> + +#include "cmsys/FStream.hxx" + +#include "cmGeneratedFileStream.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" + +static bool ParseFilename(Json::Value const& val, std::string& result) +{ + if (val.isString()) { + result = val.asString(); + } else { + return false; + } + + return true; +} + +static Json::Value EncodeFilename(std::string const& path) +{ + std::string data; + data.reserve(path.size()); + + for (auto const& byte : path) { + if (std::iscntrl(byte)) { + // Control characters. + data.append("\\u"); + char buf[5]; + std::snprintf(buf, sizeof(buf), "%04x", byte); + data.append(buf); + } else if (byte == '"' || byte == '\\') { + // Special JSON characters. + data.push_back('\\'); + data.push_back(byte); + } else { + // Other data. + data.push_back(byte); + } + } + + return data; +} + +#define PARSE_BLOB(val, res) \ + do { \ + if (!ParseFilename(val, res)) { \ + cmSystemTools::Error( \ + cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp, \ + ": invalid blob")); \ + return false; \ + } \ + } while (0) + +#define PARSE_FILENAME(val, res) \ + do { \ + if (!ParseFilename(val, res)) { \ + cmSystemTools::Error( \ + cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp, \ + ": invalid filename")); \ + return false; \ + } \ + \ + if (!cmSystemTools::FileIsFullPath(res)) { \ + res = cmStrCat(work_directory, '/', res); \ + } \ + } while (0) + +bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* info) +{ + Json::Value ppio; + Json::Value const& ppi = ppio; + cmsys::ifstream ppf(arg_pp.c_str(), std::ios::in | std::ios::binary); + { + Json::Reader reader; + if (!reader.parse(ppf, ppio, false)) { + cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ", + arg_pp, + reader.getFormattedErrorMessages())); + return false; + } + } + + Json::Value const& version = ppi["version"]; + if (version.asUInt() != 0) { + cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ", + arg_pp, ": version ", version.asString())); + return false; + } + + Json::Value const& rules = ppi["rules"]; + if (rules.isArray()) { + if (rules.size() != 1) { + cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ", + arg_pp, ": expected 1 source entry")); + return false; + } + + for (auto const& rule : rules) { + Json::Value const& workdir = rule["work-directory"]; + if (!workdir.isString()) { + cmSystemTools::Error( + cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp, + ": work-directory is not a string")); + return false; + } + std::string work_directory; + PARSE_BLOB(workdir, work_directory); + + Json::Value const& depends = rule["depends"]; + if (depends.isArray()) { + std::string depend_filename; + for (auto const& depend : depends) { + PARSE_FILENAME(depend, depend_filename); + info->Includes.push_back(depend_filename); + } + } + + if (rule.isMember("future-compile")) { + Json::Value const& future_compile = rule["future-compile"]; + + if (future_compile.isMember("outputs")) { + Json::Value const& outputs = future_compile["outputs"]; + if (outputs.isArray()) { + if (outputs.empty()) { + cmSystemTools::Error( + cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp, + ": expected at least one 1 output")); + return false; + } + + PARSE_FILENAME(outputs[0], info->PrimaryOutput); + } + } + + if (future_compile.isMember("provides")) { + Json::Value const& provides = future_compile["provides"]; + if (provides.isArray()) { + for (auto const& provide : provides) { + cmSourceReqInfo provide_info; + + Json::Value const& logical_name = provide["logical-name"]; + PARSE_BLOB(logical_name, provide_info.LogicalName); + + if (provide.isMember("compiled-module-path")) { + Json::Value const& compiled_module_path = + provide["compiled-module-path"]; + PARSE_FILENAME(compiled_module_path, + provide_info.CompiledModulePath); + } else { + provide_info.CompiledModulePath = + cmStrCat(provide_info.LogicalName, ".mod"); + } + + info->Provides.push_back(provide_info); + } + } + } + + if (future_compile.isMember("requires")) { + Json::Value const& reqs = future_compile["requires"]; + if (reqs.isArray()) { + for (auto const& require : reqs) { + cmSourceReqInfo require_info; + + Json::Value const& logical_name = require["logical-name"]; + PARSE_BLOB(logical_name, require_info.LogicalName); + + if (require.isMember("compiled-module-path")) { + Json::Value const& compiled_module_path = + require["compiled-module-path"]; + PARSE_FILENAME(compiled_module_path, + require_info.CompiledModulePath); + } + + info->Requires.push_back(require_info); + } + } + } + } + } + } + + return true; +} + +bool cmScanDepFormat_P1689_Write(std::string const& path, + std::string const& input, + cmSourceInfo const& info) +{ + Json::Value ddi(Json::objectValue); + ddi["version"] = 0; + ddi["revision"] = 0; + + Json::Value& rules = ddi["rules"] = Json::arrayValue; + + Json::Value rule(Json::objectValue); + rule["work-directory"] = + EncodeFilename(cmSystemTools::GetCurrentWorkingDirectory()); + Json::Value& inputs = rule["inputs"] = Json::arrayValue; + inputs.append(EncodeFilename(input)); + + Json::Value& rule_outputs = rule["outputs"] = Json::arrayValue; + rule_outputs.append(EncodeFilename(path)); + + Json::Value& depends = rule["depends"] = Json::arrayValue; + for (auto const& include : info.Includes) { + depends.append(EncodeFilename(include)); + } + + Json::Value& future_compile = rule["future-compile"] = Json::objectValue; + + Json::Value& outputs = future_compile["outputs"] = Json::arrayValue; + outputs.append(info.PrimaryOutput); + + Json::Value& provides = future_compile["provides"] = Json::arrayValue; + for (auto const& provide : info.Provides) { + Json::Value provide_obj(Json::objectValue); + auto const encoded = EncodeFilename(provide.LogicalName); + provide_obj["logical-name"] = encoded; + if (provide.CompiledModulePath.empty()) { + provide_obj["compiled-module-path"] = encoded; + } else { + provide_obj["compiled-module-path"] = + EncodeFilename(provide.CompiledModulePath); + } + + // TODO: Source file tracking. See below. + + provides.append(provide_obj); + } + + Json::Value& reqs = future_compile["requires"] = Json::arrayValue; + for (auto const& require : info.Requires) { + Json::Value require_obj(Json::objectValue); + auto const encoded = EncodeFilename(require.LogicalName); + require_obj["logical-name"] = encoded; + if (require.CompiledModulePath.empty()) { + require_obj["compiled-module-path"] = encoded; + } else { + require_obj["compiled-module-path"] = + EncodeFilename(require.CompiledModulePath); + } + + // TODO: Source filename inclusion. Requires collating with the provides + // filenames (as a sanity check if available on both sides). + + reqs.append(require_obj); + } + + rules.append(rule); + + cmGeneratedFileStream ddif(path); + ddif << ddi; + + return !!ddif; +} diff --git a/Source/cmScanDepFormat.h b/Source/cmScanDepFormat.h new file mode 100644 index 0000000..1ad0ecf --- /dev/null +++ b/Source/cmScanDepFormat.h @@ -0,0 +1,30 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <string> +#include <vector> + +struct cmSourceReqInfo +{ + std::string LogicalName; + std::string CompiledModulePath; +}; + +struct cmSourceInfo +{ + std::string PrimaryOutput; + + // Set of provided and required modules. + std::vector<cmSourceReqInfo> Provides; + std::vector<cmSourceReqInfo> Requires; + + // Set of files included in the translation unit. + std::vector<std::string> Includes; +}; + +bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, + cmSourceInfo* info); +bool cmScanDepFormat_P1689_Write(std::string const& path, + std::string const& input, + cmSourceInfo const& info); diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx deleted file mode 100644 index 7f97406..0000000 --- a/Source/cmServer.cxx +++ /dev/null @@ -1,570 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmServer.h" - -#include <algorithm> -#include <cassert> -#include <csignal> -#include <cstdint> -#include <iostream> -#include <mutex> -#include <utility> - -#include <cm/memory> -#include <cm/shared_mutex> - -#include <cm3p/json/reader.h> -#include <cm3p/json/writer.h> - -#include "cmsys/FStream.hxx" - -#include "cmConnection.h" -#include "cmFileMonitor.h" -#include "cmJsonObjectDictionary.h" -#include "cmServerDictionary.h" -#include "cmServerProtocol.h" -#include "cmSystemTools.h" -#include "cmake.h" - -void on_signal(uv_signal_t* signal, int signum) -{ - auto conn = static_cast<cmServerBase*>(signal->data); - conn->OnSignal(signum); -} - -static void on_walk_to_shutdown(uv_handle_t* handle, void* arg) -{ - (void)arg; - assert(uv_is_closing(handle)); - if (!uv_is_closing(handle)) { - uv_close(handle, &cmEventBasedConnection::on_close); - } -} - -class cmServer::DebugInfo -{ -public: - DebugInfo() - : StartTime(uv_hrtime()) - { - } - - bool PrintStatistics = false; - - std::string OutputFile; - uint64_t StartTime; -}; - -cmServer::cmServer(cmConnection* conn, bool supportExperimental) - : cmServerBase(conn) - , SupportExperimental(supportExperimental) -{ - // Register supported protocols: - this->RegisterProtocol(cm::make_unique<cmServerProtocol1>()); -} - -cmServer::~cmServer() -{ - Close(); -} - -void cmServer::ProcessRequest(cmConnection* connection, - const std::string& input) -{ - Json::Reader reader; - Json::Value value; - if (!reader.parse(input, value)) { - this->WriteParseError(connection, "Failed to parse JSON input."); - return; - } - - std::unique_ptr<DebugInfo> debug; - Json::Value debugValue = value["debug"]; - if (!debugValue.isNull()) { - debug = cm::make_unique<DebugInfo>(); - debug->OutputFile = debugValue["dumpToFile"].asString(); - debug->PrintStatistics = debugValue["showStats"].asBool(); - } - - const cmServerRequest request(this, connection, value[kTYPE_KEY].asString(), - value[kCOOKIE_KEY].asString(), value); - - if (request.Type.empty()) { - cmServerResponse response(request); - response.SetError("No type given in request."); - this->WriteResponse(connection, response, nullptr); - return; - } - - cmSystemTools::SetMessageCallback( - [&request](const std::string& msg, const char* title) { - reportMessage(msg, title, request); - }); - - if (this->Protocol) { - this->Protocol->CMakeInstance()->SetProgressCallback( - [&request](const std::string& msg, float prog) { - reportProgress(msg, prog, request); - }); - this->WriteResponse(connection, this->Protocol->Process(request), - debug.get()); - } else { - this->WriteResponse(connection, this->SetProtocolVersion(request), - debug.get()); - } -} - -void cmServer::RegisterProtocol(std::unique_ptr<cmServerProtocol> protocol) -{ - if (protocol->IsExperimental() && !this->SupportExperimental) { - protocol.reset(); - return; - } - auto version = protocol->ProtocolVersion(); - assert(version.first >= 0); - assert(version.second >= 0); - auto it = std::find_if( - this->SupportedProtocols.begin(), this->SupportedProtocols.end(), - [version](const std::unique_ptr<cmServerProtocol>& p) { - return p->ProtocolVersion() == version; - }); - if (it == this->SupportedProtocols.end()) { - this->SupportedProtocols.push_back(std::move(protocol)); - } -} - -void cmServer::PrintHello(cmConnection* connection) const -{ - Json::Value hello = Json::objectValue; - hello[kTYPE_KEY] = "hello"; - - Json::Value& protocolVersions = hello[kSUPPORTED_PROTOCOL_VERSIONS] = - Json::arrayValue; - - for (auto const& proto : this->SupportedProtocols) { - auto version = proto->ProtocolVersion(); - Json::Value tmp = Json::objectValue; - tmp[kMAJOR_KEY] = version.first; - tmp[kMINOR_KEY] = version.second; - if (proto->IsExperimental()) { - tmp[kIS_EXPERIMENTAL_KEY] = true; - } - protocolVersions.append(tmp); - } - - this->WriteJsonObject(connection, hello, nullptr); -} - -void cmServer::reportProgress(const std::string& msg, float progress, - const cmServerRequest& request) -{ - if (progress < 0.0f || progress > 1.0f) { - request.ReportMessage(msg, ""); - } else { - request.ReportProgress(0, static_cast<int>(progress * 1000), 1000, msg); - } -} - -void cmServer::reportMessage(const std::string& msg, const char* title, - const cmServerRequest& request) -{ - std::string titleString; - if (title) { - titleString = title; - } - request.ReportMessage(msg, titleString); -} - -cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request) -{ - if (request.Type != kHANDSHAKE_TYPE) { - return request.ReportError("Waiting for type \"" + kHANDSHAKE_TYPE + - "\"."); - } - - Json::Value requestedProtocolVersion = request.Data[kPROTOCOL_VERSION_KEY]; - if (requestedProtocolVersion.isNull()) { - return request.ReportError("\"" + kPROTOCOL_VERSION_KEY + - "\" is required for \"" + kHANDSHAKE_TYPE + - "\"."); - } - - if (!requestedProtocolVersion.isObject()) { - return request.ReportError("\"" + kPROTOCOL_VERSION_KEY + - "\" must be a JSON object."); - } - - Json::Value majorValue = requestedProtocolVersion[kMAJOR_KEY]; - if (!majorValue.isInt()) { - return request.ReportError("\"" + kMAJOR_KEY + - "\" must be set and an integer."); - } - - Json::Value minorValue = requestedProtocolVersion[kMINOR_KEY]; - if (!minorValue.isNull() && !minorValue.isInt()) { - return request.ReportError("\"" + kMINOR_KEY + - "\" must be unset or an integer."); - } - - const int major = majorValue.asInt(); - const int minor = minorValue.isNull() ? -1 : minorValue.asInt(); - if (major < 0) { - return request.ReportError("\"" + kMAJOR_KEY + "\" must be >= 0."); - } - if (!minorValue.isNull() && minor < 0) { - return request.ReportError("\"" + kMINOR_KEY + - "\" must be >= 0 when set."); - } - - this->Protocol = - cmServer::FindMatchingProtocol(this->SupportedProtocols, major, minor); - if (!this->Protocol) { - return request.ReportError("Protocol version not supported."); - } - - std::string errorMessage; - if (!this->Protocol->Activate(this, request, &errorMessage)) { - this->Protocol = nullptr; - return request.ReportError("Failed to activate protocol version: " + - errorMessage); - } - return request.Reply(Json::objectValue); -} - -bool cmServer::Serve(std::string* errorMessage) -{ - if (this->SupportedProtocols.empty()) { - *errorMessage = - "No protocol versions defined. Maybe you need --experimental?"; - return false; - } - assert(!this->Protocol); - - return cmServerBase::Serve(errorMessage); -} - -cmFileMonitor* cmServer::FileMonitor() const -{ - return fileMonitor.get(); -} - -void cmServer::WriteJsonObject(const Json::Value& jsonValue, - const DebugInfo* debug) const -{ - cm::shared_lock<cm::shared_mutex> lock(ConnectionsMutex); - for (auto& connection : this->Connections) { - WriteJsonObject(connection.get(), jsonValue, debug); - } -} - -void cmServer::WriteJsonObject(cmConnection* connection, - const Json::Value& jsonValue, - const DebugInfo* debug) const -{ - Json::FastWriter writer; - - auto beforeJson = uv_hrtime(); - std::string result = writer.write(jsonValue); - - if (debug) { - Json::Value copy = jsonValue; - if (debug->PrintStatistics) { - Json::Value stats = Json::objectValue; - auto endTime = uv_hrtime(); - - stats["jsonSerialization"] = double(endTime - beforeJson) / 1000000.0; - stats["totalTime"] = double(endTime - debug->StartTime) / 1000000.0; - stats["size"] = static_cast<int>(result.size()); - if (!debug->OutputFile.empty()) { - stats["dumpFile"] = debug->OutputFile; - } - - copy["zzzDebug"] = stats; - - result = writer.write(copy); // Update result to include debug info - } - - if (!debug->OutputFile.empty()) { - cmsys::ofstream myfile(debug->OutputFile.c_str()); - myfile << result; - } - } - - connection->WriteData(result); -} - -cmServerProtocol* cmServer::FindMatchingProtocol( - const std::vector<std::unique_ptr<cmServerProtocol>>& protocols, int major, - int minor) -{ - cmServerProtocol* bestMatch = nullptr; - for (const auto& protocol : protocols) { - auto version = protocol->ProtocolVersion(); - if (major != version.first) { - continue; - } - if (minor == version.second) { - return protocol.get(); - } - if (!bestMatch || bestMatch->ProtocolVersion().second < version.second) { - bestMatch = protocol.get(); - } - } - return minor < 0 ? bestMatch : nullptr; -} - -void cmServer::WriteProgress(const cmServerRequest& request, int min, - int current, int max, - const std::string& message) const -{ - assert(min <= current && current <= max); - assert(message.length() != 0); - - Json::Value obj = Json::objectValue; - obj[kTYPE_KEY] = kPROGRESS_TYPE; - obj[kREPLY_TO_KEY] = request.Type; - obj[kCOOKIE_KEY] = request.Cookie; - obj[kPROGRESS_MESSAGE_KEY] = message; - obj[kPROGRESS_MINIMUM_KEY] = min; - obj[kPROGRESS_MAXIMUM_KEY] = max; - obj[kPROGRESS_CURRENT_KEY] = current; - - this->WriteJsonObject(request.Connection, obj, nullptr); -} - -void cmServer::WriteMessage(const cmServerRequest& request, - const std::string& message, - const std::string& title) const -{ - if (message.empty()) { - return; - } - - Json::Value obj = Json::objectValue; - obj[kTYPE_KEY] = kMESSAGE_TYPE; - obj[kREPLY_TO_KEY] = request.Type; - obj[kCOOKIE_KEY] = request.Cookie; - obj[kMESSAGE_KEY] = message; - if (!title.empty()) { - obj[kTITLE_KEY] = title; - } - - WriteJsonObject(request.Connection, obj, nullptr); -} - -void cmServer::WriteParseError(cmConnection* connection, - const std::string& message) const -{ - Json::Value obj = Json::objectValue; - obj[kTYPE_KEY] = kERROR_TYPE; - obj[kERROR_MESSAGE_KEY] = message; - obj[kREPLY_TO_KEY] = ""; - obj[kCOOKIE_KEY] = ""; - - this->WriteJsonObject(connection, obj, nullptr); -} - -void cmServer::WriteSignal(const std::string& name, - const Json::Value& data) const -{ - assert(data.isObject()); - Json::Value obj = data; - obj[kTYPE_KEY] = kSIGNAL_TYPE; - obj[kREPLY_TO_KEY] = ""; - obj[kCOOKIE_KEY] = ""; - obj[kNAME_KEY] = name; - - WriteJsonObject(obj, nullptr); -} - -void cmServer::WriteResponse(cmConnection* connection, - const cmServerResponse& response, - const DebugInfo* debug) const -{ - assert(response.IsComplete()); - - Json::Value obj = response.Data(); - obj[kCOOKIE_KEY] = response.Cookie; - obj[kTYPE_KEY] = response.IsError() ? kERROR_TYPE : kREPLY_TYPE; - obj[kREPLY_TO_KEY] = response.Type; - if (response.IsError()) { - obj[kERROR_MESSAGE_KEY] = response.ErrorMessage(); - } - - this->WriteJsonObject(connection, obj, debug); -} - -void cmServer::OnConnected(cmConnection* connection) -{ - PrintHello(connection); -} - -void cmServer::OnServeStart() -{ - cmServerBase::OnServeStart(); - fileMonitor = std::make_shared<cmFileMonitor>(GetLoop()); -} - -void cmServer::StartShutDown() -{ - if (fileMonitor) { - fileMonitor->StopMonitoring(); - fileMonitor.reset(); - } - cmServerBase::StartShutDown(); -} - -static void __start_thread(void* arg) -{ - auto server = static_cast<cmServerBase*>(arg); - std::string error; - bool success = server->Serve(&error); - if (!success || !error.empty()) { - std::cerr << "Error during serve: " << error << std::endl; - } -} - -bool cmServerBase::StartServeThread() -{ - ServeThreadRunning = true; - uv_thread_create(&ServeThread, __start_thread, this); - return true; -} - -static void __shutdownThread(uv_async_t* arg) -{ - auto server = static_cast<cmServerBase*>(arg->data); - server->StartShutDown(); -} - -bool cmServerBase::Serve(std::string* errorMessage) -{ -#ifndef NDEBUG - uv_thread_t blank_thread_t = {}; - assert(uv_thread_equal(&blank_thread_t, &ServeThreadId)); - ServeThreadId = uv_thread_self(); -#endif - - errorMessage->clear(); - - ShutdownSignal.init(Loop, __shutdownThread, this); - - SIGINTHandler.init(Loop, this); - SIGHUPHandler.init(Loop, this); - - SIGINTHandler.start(&on_signal, SIGINT); - SIGHUPHandler.start(&on_signal, SIGHUP); - - OnServeStart(); - - { - cm::shared_lock<cm::shared_mutex> lock(ConnectionsMutex); - for (auto& connection : Connections) { - if (!connection->OnServeStart(errorMessage)) { - return false; - } - } - } - - if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) { - // It is important we don't ever let the event loop exit with open handles - // at best this is a memory leak, but it can also introduce race conditions - // which can hang the program. - assert(false && "Event loop stopped in unclean state."); - - *errorMessage = "Internal Error: Event loop stopped in unclean state."; - return false; - } - - return true; -} - -void cmServerBase::OnConnected(cmConnection*) -{ -} - -void cmServerBase::OnServeStart() -{ -} - -void cmServerBase::StartShutDown() -{ - ShutdownSignal.reset(); - SIGINTHandler.reset(); - SIGHUPHandler.reset(); - - { - std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex); - for (auto& connection : Connections) { - connection->OnConnectionShuttingDown(); - } - Connections.clear(); - } - - uv_walk(&Loop, on_walk_to_shutdown, nullptr); -} - -bool cmServerBase::OnSignal(int signum) -{ - (void)signum; - StartShutDown(); - return true; -} - -cmServerBase::cmServerBase(cmConnection* connection) -{ - auto err = uv_loop_init(&Loop); - (void)err; - Loop.data = this; - assert(err == 0); - - AddNewConnection(connection); -} - -void cmServerBase::Close() -{ - if (Loop.data) { - if (ServeThreadRunning) { - this->ShutdownSignal.send(); - uv_thread_join(&ServeThread); - } - - uv_loop_close(&Loop); - Loop.data = nullptr; - } -} -cmServerBase::~cmServerBase() -{ - Close(); -} - -void cmServerBase::AddNewConnection(cmConnection* ownedConnection) -{ - { - std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex); - Connections.emplace_back(ownedConnection); - } - ownedConnection->SetServer(this); -} - -uv_loop_t* cmServerBase::GetLoop() -{ - return &Loop; -} - -void cmServerBase::OnDisconnect(cmConnection* pConnection) -{ - auto pred = [pConnection](const std::unique_ptr<cmConnection>& m) { - return m.get() == pConnection; - }; - { - std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex); - Connections.erase( - std::remove_if(Connections.begin(), Connections.end(), pred), - Connections.end()); - } - - if (Connections.empty()) { - this->ShutdownSignal.send(); - } -} diff --git a/Source/cmServer.h b/Source/cmServer.h deleted file mode 100644 index 9543329..0000000 --- a/Source/cmServer.h +++ /dev/null @@ -1,162 +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 <memory> -#include <string> -#include <vector> - -#include <cm/shared_mutex> - -#include <cm3p/json/value.h> -#include <cm3p/uv.h> - -#include "cmUVHandlePtr.h" - -class cmConnection; -class cmFileMonitor; -class cmServerProtocol; -class cmServerRequest; -class cmServerResponse; - -/*** - * This essentially hold and manages a libuv event queue and responds to - * messages - * on any of its connections. - */ -class cmServerBase -{ -public: - cmServerBase(cmConnection* connection); - virtual ~cmServerBase(); - - virtual void AddNewConnection(cmConnection* ownedConnection); - - /*** - * The main override responsible for tailoring behavior towards - * whatever the given server is supposed to do - * - * This should almost always be called by the given connections - * directly. - * - * @param connection The connection the request was received on - * @param request The actual request - */ - virtual void ProcessRequest(cmConnection* connection, - const std::string& request) = 0; - virtual void OnConnected(cmConnection* connection); - - /*** - * Start a dedicated thread. If this is used to start the server, it will - * join on the - * servers dtor. - */ - virtual bool StartServeThread(); - virtual bool Serve(std::string* errorMessage); - - virtual void OnServeStart(); - virtual void StartShutDown(); - - virtual bool OnSignal(int signum); - uv_loop_t* GetLoop(); - void Close(); - void OnDisconnect(cmConnection* pConnection); - -protected: - mutable cm::shared_mutex ConnectionsMutex; - std::vector<std::unique_ptr<cmConnection>> Connections; - - bool ServeThreadRunning = false; - uv_thread_t ServeThread; - cm::uv_async_ptr ShutdownSignal; -#ifndef NDEBUG -public: - // When the server starts it will mark down it's current thread ID, - // which is useful in other contexts to just assert that operations - // are performed on that same thread. - uv_thread_t ServeThreadId = {}; - -protected: -#endif - - uv_loop_t Loop; - - cm::uv_signal_ptr SIGINTHandler; - cm::uv_signal_ptr SIGHUPHandler; -}; - -class cmServer : public cmServerBase -{ -public: - class DebugInfo; - - cmServer(cmConnection* conn, bool supportExperimental); - ~cmServer() override; - - cmServer(cmServer const&) = delete; - cmServer& operator=(cmServer const&) = delete; - - bool Serve(std::string* errorMessage) override; - - cmFileMonitor* FileMonitor() const; - -private: - void RegisterProtocol(std::unique_ptr<cmServerProtocol> protocol); - - // Callbacks from cmServerConnection: - - void ProcessRequest(cmConnection* connection, - const std::string& request) override; - std::shared_ptr<cmFileMonitor> fileMonitor; - -public: - void OnServeStart() override; - - void StartShutDown() override; - -public: - void OnConnected(cmConnection* connection) override; - -private: - static void reportProgress(const std::string& msg, float progress, - const cmServerRequest& request); - static void reportMessage(const std::string& msg, const char* title, - const cmServerRequest& request); - - // Handle requests: - cmServerResponse SetProtocolVersion(const cmServerRequest& request); - - void PrintHello(cmConnection* connection) const; - - // Write responses: - void WriteProgress(const cmServerRequest& request, int min, int current, - int max, const std::string& message) const; - void WriteMessage(const cmServerRequest& request, const std::string& message, - const std::string& title) const; - void WriteResponse(cmConnection* connection, - const cmServerResponse& response, - const DebugInfo* debug) const; - void WriteParseError(cmConnection* connection, - const std::string& message) const; - void WriteSignal(const std::string& name, const Json::Value& obj) const; - - void WriteJsonObject(Json::Value const& jsonValue, - const DebugInfo* debug) const; - - void WriteJsonObject(cmConnection* connection, Json::Value const& jsonValue, - const DebugInfo* debug) const; - - static cmServerProtocol* FindMatchingProtocol( - const std::vector<std::unique_ptr<cmServerProtocol>>& protocols, int major, - int minor); - - const bool SupportExperimental; - - cmServerProtocol* Protocol = nullptr; - std::vector<std::unique_ptr<cmServerProtocol>> SupportedProtocols; - - friend class cmServerProtocol; - friend class cmServerRequest; -}; diff --git a/Source/cmServerConnection.cxx b/Source/cmServerConnection.cxx deleted file mode 100644 index b4f41a0..0000000 --- a/Source/cmServerConnection.cxx +++ /dev/null @@ -1,165 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmConfigure.h" - -#include "cmServerConnection.h" - -#include <cm3p/uv.h> - -#include "cmServer.h" -#include "cmServerDictionary.h" - -#ifdef _WIN32 -# include "io.h" -#else -# include <unistd.h> -#endif -#include <cassert> -#include <utility> - -cmStdIoConnection::cmStdIoConnection( - cmConnectionBufferStrategy* bufferStrategy) - : cmEventBasedConnection(bufferStrategy) -{ -} - -cm::uv_stream_ptr cmStdIoConnection::SetupStream(int file_id) -{ - switch (uv_guess_handle(file_id)) { - case UV_TTY: { - cm::uv_tty_ptr tty; - tty.init(*this->Server->GetLoop(), file_id, file_id == 0, - static_cast<cmEventBasedConnection*>(this)); - uv_tty_set_mode(tty, UV_TTY_MODE_NORMAL); - return { std::move(tty) }; - } - case UV_FILE: - if (file_id == 0) { - return nullptr; - } - // Intentional fallthrough; stdin can _not_ be treated as a named - // pipe, however stdout can be. - CM_FALLTHROUGH; - case UV_NAMED_PIPE: { - cm::uv_pipe_ptr pipe; - pipe.init(*this->Server->GetLoop(), 0, - static_cast<cmEventBasedConnection*>(this)); - uv_pipe_open(pipe, file_id); - return { std::move(pipe) }; - } - default: - assert(false && "Unable to determine stream type"); - return nullptr; - } -} - -void cmStdIoConnection::SetServer(cmServerBase* s) -{ - cmConnection::SetServer(s); - if (!s) { - return; - } - - this->ReadStream = SetupStream(0); - this->WriteStream = SetupStream(1); -} - -void shutdown_connection(uv_prepare_t* prepare) -{ - cmStdIoConnection* connection = - static_cast<cmStdIoConnection*>(prepare->data); - - if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(prepare))) { - uv_close(reinterpret_cast<uv_handle_t*>(prepare), - &cmEventBasedConnection::on_close_delete<uv_prepare_t>); - } - connection->OnDisconnect(0); -} - -bool cmStdIoConnection::OnServeStart(std::string* pString) -{ - Server->OnConnected(this); - if (this->ReadStream.get()) { - uv_read_start(this->ReadStream, on_alloc_buffer, on_read); - } else if (uv_guess_handle(0) == UV_FILE) { - char buffer[1024]; - while (auto len = read(0, buffer, sizeof(buffer))) { - ReadData(std::string(buffer, buffer + len)); - } - - // We can't start the disconnect from here, add a prepare hook to do that - // for us - auto prepare = new uv_prepare_t(); - prepare->data = this; - uv_prepare_init(Server->GetLoop(), prepare); - uv_prepare_start(prepare, shutdown_connection); - } - return cmConnection::OnServeStart(pString); -} - -bool cmStdIoConnection::OnConnectionShuttingDown() -{ - if (ReadStream.get()) { - uv_read_stop(ReadStream); - ReadStream->data = nullptr; - } - - this->ReadStream.reset(); - - cmEventBasedConnection::OnConnectionShuttingDown(); - - return true; -} - -cmServerPipeConnection::cmServerPipeConnection(const std::string& name) - : cmPipeConnection(name, new cmServerBufferStrategy) -{ -} - -cmServerStdIoConnection::cmServerStdIoConnection() - : cmStdIoConnection(new cmServerBufferStrategy) -{ -} - -cmConnectionBufferStrategy::~cmConnectionBufferStrategy() = default; - -void cmConnectionBufferStrategy::clear() -{ -} - -std::string cmServerBufferStrategy::BufferOutMessage( - const std::string& rawBuffer) const -{ - return std::string("\n") + kSTART_MAGIC + std::string("\n") + rawBuffer + - kEND_MAGIC + std::string("\n"); -} - -std::string cmServerBufferStrategy::BufferMessage(std::string& RawReadBuffer) -{ - for (;;) { - auto needle = RawReadBuffer.find('\n'); - - if (needle == std::string::npos) { - return ""; - } - std::string line = RawReadBuffer.substr(0, needle); - const auto ls = line.size(); - if (ls > 1 && line.at(ls - 1) == '\r') { - line.erase(ls - 1, 1); - } - RawReadBuffer.erase(RawReadBuffer.begin(), - RawReadBuffer.begin() + static_cast<long>(needle) + 1); - if (line == kSTART_MAGIC) { - RequestBuffer.clear(); - continue; - } - if (line == kEND_MAGIC) { - std::string rtn; - rtn.swap(this->RequestBuffer); - return rtn; - } - - this->RequestBuffer += line; - this->RequestBuffer += "\n"; - } -} diff --git a/Source/cmServerConnection.h b/Source/cmServerConnection.h deleted file mode 100644 index a70edb4..0000000 --- a/Source/cmServerConnection.h +++ /dev/null @@ -1,67 +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 <string> - -#include "cmConnection.h" -#include "cmPipeConnection.h" -#include "cmUVHandlePtr.h" - -class cmServerBase; - -/*** - * This connection buffer strategy accepts messages in the form of - * [== "CMake Server" ==[ -{ - ... some JSON message ... -} -]== "CMake Server" ==] - * and only passes on the core json; it discards the envelope. - */ -class cmServerBufferStrategy : public cmConnectionBufferStrategy -{ -public: - std::string BufferMessage(std::string& rawBuffer) override; - std::string BufferOutMessage(const std::string& rawBuffer) const override; - -private: - std::string RequestBuffer; -}; - -/*** - * Generic connection over std io interfaces -- tty - */ -class cmStdIoConnection : public cmEventBasedConnection -{ -public: - cmStdIoConnection(cmConnectionBufferStrategy* bufferStrategy); - - void SetServer(cmServerBase* s) override; - - bool OnConnectionShuttingDown() override; - - bool OnServeStart(std::string* pString) override; - -private: - cm::uv_stream_ptr SetupStream(int file_id); - cm::uv_stream_ptr ReadStream; -}; - -/*** - * These specific connections use the cmake server - * buffering strategy. - */ -class cmServerStdIoConnection : public cmStdIoConnection -{ -public: - cmServerStdIoConnection(); -}; - -class cmServerPipeConnection : public cmPipeConnection -{ -public: - cmServerPipeConnection(const std::string& name); -}; diff --git a/Source/cmServerDictionary.h b/Source/cmServerDictionary.h deleted file mode 100644 index 961e4b7..0000000 --- a/Source/cmServerDictionary.h +++ /dev/null @@ -1,67 +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 <string> - -// Vocabulary: - -static const std::string kDIRTY_SIGNAL = "dirty"; -static const std::string kFILE_CHANGE_SIGNAL = "fileChange"; - -static const std::string kCACHE_TYPE = "cache"; -static const std::string kCMAKE_INPUTS_TYPE = "cmakeInputs"; -static const std::string kCODE_MODEL_TYPE = "codemodel"; -static const std::string kCOMPUTE_TYPE = "compute"; -static const std::string kCONFIGURE_TYPE = "configure"; -static const std::string kERROR_TYPE = "error"; -static const std::string kFILESYSTEM_WATCHERS_TYPE = "fileSystemWatchers"; -static const std::string kGLOBAL_SETTINGS_TYPE = "globalSettings"; -static const std::string kHANDSHAKE_TYPE = "handshake"; -static const std::string kMESSAGE_TYPE = "message"; -static const std::string kPROGRESS_TYPE = "progress"; -static const std::string kREPLY_TYPE = "reply"; -static const std::string kSET_GLOBAL_SETTINGS_TYPE = "setGlobalSettings"; -static const std::string kSIGNAL_TYPE = "signal"; -static const std::string kCTEST_INFO_TYPE = "ctestInfo"; - -static const std::string kBUILD_FILES_KEY = "buildFiles"; -static const std::string kCACHE_ARGUMENTS_KEY = "cacheArguments"; -static const std::string kCACHE_KEY = "cache"; -static const std::string kCAPABILITIES_KEY = "capabilities"; -static const std::string kCHECK_SYSTEM_VARS_KEY = "checkSystemVars"; -static const std::string kCMAKE_ROOT_DIRECTORY_KEY = "cmakeRootDirectory"; -static const std::string kCOOKIE_KEY = "cookie"; -static const std::string kDEBUG_OUTPUT_KEY = "debugOutput"; -static const std::string kERROR_MESSAGE_KEY = "errorMessage"; -static const std::string kEXTRA_GENERATOR_KEY = "extraGenerator"; -static const std::string kGENERATOR_KEY = "generator"; -static const std::string kIS_EXPERIMENTAL_KEY = "isExperimental"; -static const std::string kKEYS_KEY = "keys"; -static const std::string kMAJOR_KEY = "major"; -static const std::string kMESSAGE_KEY = "message"; -static const std::string kMINOR_KEY = "minor"; -static const std::string kPLATFORM_KEY = "platform"; -static const std::string kPROGRESS_CURRENT_KEY = "progressCurrent"; -static const std::string kPROGRESS_MAXIMUM_KEY = "progressMaximum"; -static const std::string kPROGRESS_MESSAGE_KEY = "progressMessage"; -static const std::string kPROGRESS_MINIMUM_KEY = "progressMinimum"; -static const std::string kPROTOCOL_VERSION_KEY = "protocolVersion"; -static const std::string kREPLY_TO_KEY = "inReplyTo"; -static const std::string kSUPPORTED_PROTOCOL_VERSIONS = - "supportedProtocolVersions"; -static const std::string kTITLE_KEY = "title"; -static const std::string kTOOLSET_KEY = "toolset"; -static const std::string kTRACE_EXPAND_KEY = "traceExpand"; -static const std::string kTRACE_KEY = "trace"; -static const std::string kWARN_UNINITIALIZED_KEY = "warnUninitialized"; -static const std::string kWARN_UNUSED_CLI_KEY = "warnUnusedCli"; -static const std::string kWARN_UNUSED_KEY = "warnUnused"; -static const std::string kWATCHED_DIRECTORIES_KEY = "watchedDirectories"; -static const std::string kWATCHED_FILES_KEY = "watchedFiles"; - -static const std::string kSTART_MAGIC = "[== \"CMake Server\" ==["; -static const std::string kEND_MAGIC = "]== \"CMake Server\" ==]"; - -static const std::string kRENAME_PROPERTY_VALUE = "rename"; -static const std::string kCHANGE_PROPERTY_VALUE = "change"; diff --git a/Source/cmServerProtocol.cxx b/Source/cmServerProtocol.cxx deleted file mode 100644 index e586fd9..0000000 --- a/Source/cmServerProtocol.cxx +++ /dev/null @@ -1,760 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmServerProtocol.h" - -#include <algorithm> -#include <cassert> -#include <functional> -#include <string> -#include <utility> -#include <vector> - -#include <cm/memory> -#include <cmext/algorithm> - -#include <cm3p/uv.h> - -#include "cmExternalMakefileProjectGenerator.h" -#include "cmFileMonitor.h" -#include "cmGlobalGenerator.h" -#include "cmJsonObjectDictionary.h" -#include "cmJsonObjects.h" -#include "cmMessageType.h" -#include "cmProperty.h" -#include "cmServer.h" -#include "cmServerDictionary.h" -#include "cmState.h" -#include "cmSystemTools.h" -#include "cmake.h" - -// Get rid of some windows macros: -#undef max - -namespace { - -std::vector<std::string> toStringList(const Json::Value& in) -{ - std::vector<std::string> result; - for (auto const& it : in) { - result.push_back(it.asString()); - } - return result; -} - -} // namespace - -cmServerRequest::cmServerRequest(cmServer* server, cmConnection* connection, - std::string t, std::string c, Json::Value d) - : Type(std::move(t)) - , Cookie(std::move(c)) - , Data(std::move(d)) - , Connection(connection) - , m_Server(server) -{ -} - -void cmServerRequest::ReportProgress(int min, int current, int max, - const std::string& message) const -{ - this->m_Server->WriteProgress(*this, min, current, max, message); -} - -void cmServerRequest::ReportMessage(const std::string& message, - const std::string& title) const -{ - m_Server->WriteMessage(*this, message, title); -} - -cmServerResponse cmServerRequest::Reply(const Json::Value& data) const -{ - cmServerResponse response(*this); - response.SetData(data); - return response; -} - -cmServerResponse cmServerRequest::ReportError(const std::string& message) const -{ - cmServerResponse response(*this); - response.SetError(message); - return response; -} - -cmServerResponse::cmServerResponse(const cmServerRequest& request) - : Type(request.Type) - , Cookie(request.Cookie) -{ -} - -void cmServerResponse::SetData(const Json::Value& data) -{ - assert(this->m_Payload == PAYLOAD_UNKNOWN); - if (!data[kCOOKIE_KEY].isNull() || !data[kTYPE_KEY].isNull()) { - this->SetError("Response contains cookie or type field."); - return; - } - this->m_Payload = PAYLOAD_DATA; - this->m_Data = data; -} - -void cmServerResponse::SetError(const std::string& message) -{ - assert(this->m_Payload == PAYLOAD_UNKNOWN); - this->m_Payload = PAYLOAD_ERROR; - this->m_ErrorMessage = message; -} - -bool cmServerResponse::IsComplete() const -{ - return this->m_Payload != PAYLOAD_UNKNOWN; -} - -bool cmServerResponse::IsError() const -{ - assert(this->m_Payload != PAYLOAD_UNKNOWN); - return this->m_Payload == PAYLOAD_ERROR; -} - -std::string cmServerResponse::ErrorMessage() const -{ - if (this->m_Payload == PAYLOAD_ERROR) { - return this->m_ErrorMessage; - } - return std::string(); -} - -Json::Value cmServerResponse::Data() const -{ - assert(this->m_Payload != PAYLOAD_UNKNOWN); - return this->m_Data; -} - -bool cmServerProtocol::Activate(cmServer* server, - const cmServerRequest& request, - std::string* errorMessage) -{ - assert(server); - this->m_Server = server; - this->m_CMakeInstance = - cm::make_unique<cmake>(cmake::RoleProject, cmState::Project); - this->m_WarnUnused = false; - const bool result = this->DoActivate(request, errorMessage); - if (!result) { - this->m_CMakeInstance = nullptr; - } - return result; -} - -cmFileMonitor* cmServerProtocol::FileMonitor() const -{ - return this->m_Server ? this->m_Server->FileMonitor() : nullptr; -} - -void cmServerProtocol::SendSignal(const std::string& name, - const Json::Value& data) const -{ - if (this->m_Server) { - this->m_Server->WriteSignal(name, data); - } -} - -cmake* cmServerProtocol::CMakeInstance() const -{ - return this->m_CMakeInstance.get(); -} - -bool cmServerProtocol::DoActivate(const cmServerRequest& /*request*/, - std::string* /*errorMessage*/) -{ - return true; -} - -std::pair<int, int> cmServerProtocol1::ProtocolVersion() const -{ - return { 1, 2 }; -} - -static void setErrorMessage(std::string* errorMessage, const std::string& text) -{ - if (errorMessage) { - *errorMessage = text; - } -} - -static bool getOrTestHomeDirectory(cmState* state, std::string& value, - std::string* errorMessage) -{ - const std::string cachedValue = - *state->GetCacheEntryValue("CMAKE_HOME_DIRECTORY"); - if (value.empty()) { - value = cachedValue; - return true; - } - const std::string suffix = "/CMakeLists.txt"; - const std::string cachedValueCML = cachedValue + suffix; - const std::string valueCML = value + suffix; - if (!cmSystemTools::SameFile(valueCML, cachedValueCML)) { - setErrorMessage(errorMessage, - std::string("\"CMAKE_HOME_DIRECTORY\" is set but " - "incompatible with configured " - "source directory value.")); - return false; - } - return true; -} - -static bool getOrTestValue(cmState* state, const std::string& key, - std::string& value, - const std::string& keyDescription, - std::string* errorMessage) -{ - const std::string cachedValue = state->GetSafeCacheEntryValue(key); - if (value.empty()) { - value = cachedValue; - } - if (!cachedValue.empty() && cachedValue != value) { - setErrorMessage(errorMessage, - std::string("\"") + key + - "\" is set but incompatible with configured " + - keyDescription + " value."); - return false; - } - return true; -} - -bool cmServerProtocol1::DoActivate(const cmServerRequest& request, - std::string* errorMessage) -{ - std::string sourceDirectory = request.Data[kSOURCE_DIRECTORY_KEY].asString(); - std::string buildDirectory = request.Data[kBUILD_DIRECTORY_KEY].asString(); - std::string generator = request.Data[kGENERATOR_KEY].asString(); - std::string extraGenerator = request.Data[kEXTRA_GENERATOR_KEY].asString(); - std::string toolset = request.Data[kTOOLSET_KEY].asString(); - std::string platform = request.Data[kPLATFORM_KEY].asString(); - - // normalize source and build directory - if (!sourceDirectory.empty()) { - sourceDirectory = cmSystemTools::CollapseFullPath(sourceDirectory); - cmSystemTools::ConvertToUnixSlashes(sourceDirectory); - } - if (!buildDirectory.empty()) { - buildDirectory = cmSystemTools::CollapseFullPath(buildDirectory); - cmSystemTools::ConvertToUnixSlashes(buildDirectory); - } - - if (buildDirectory.empty()) { - setErrorMessage(errorMessage, - std::string("\"") + kBUILD_DIRECTORY_KEY + - "\" is missing."); - return false; - } - - cmake* cm = CMakeInstance(); - if (cmSystemTools::PathExists(buildDirectory)) { - if (!cmSystemTools::FileIsDirectory(buildDirectory)) { - setErrorMessage(errorMessage, - std::string("\"") + kBUILD_DIRECTORY_KEY + - "\" exists but is not a directory."); - return false; - } - - const std::string cachePath = cmake::FindCacheFile(buildDirectory); - if (cm->LoadCache(cachePath)) { - cmState* state = cm->GetState(); - - // Check generator: - if (!getOrTestValue(state, "CMAKE_GENERATOR", generator, "generator", - errorMessage)) { - return false; - } - - // check extra generator: - if (!getOrTestValue(state, "CMAKE_EXTRA_GENERATOR", extraGenerator, - "extra generator", errorMessage)) { - return false; - } - - // check sourcedir: - if (!getOrTestHomeDirectory(state, sourceDirectory, errorMessage)) { - return false; - } - - // check toolset: - if (!getOrTestValue(state, "CMAKE_GENERATOR_TOOLSET", toolset, "toolset", - errorMessage)) { - return false; - } - - // check platform: - if (!getOrTestValue(state, "CMAKE_GENERATOR_PLATFORM", platform, - "platform", errorMessage)) { - return false; - } - } - } - - if (sourceDirectory.empty()) { - setErrorMessage(errorMessage, - std::string("\"") + kSOURCE_DIRECTORY_KEY + - "\" is unset but required."); - return false; - } - if (!cmSystemTools::FileIsDirectory(sourceDirectory)) { - setErrorMessage(errorMessage, - std::string("\"") + kSOURCE_DIRECTORY_KEY + - "\" is not a directory."); - return false; - } - if (generator.empty()) { - setErrorMessage(errorMessage, - std::string("\"") + kGENERATOR_KEY + - "\" is unset but required."); - return false; - } - - std::vector<cmake::GeneratorInfo> generators; - cm->GetRegisteredGenerators(generators); - auto baseIt = std::find_if(generators.begin(), generators.end(), - [&generator](const cmake::GeneratorInfo& info) { - return info.name == generator; - }); - if (baseIt == generators.end()) { - setErrorMessage(errorMessage, - std::string("Generator \"") + generator + - "\" not supported."); - return false; - } - auto extraIt = std::find_if( - generators.begin(), generators.end(), - [&generator, &extraGenerator](const cmake::GeneratorInfo& info) { - return info.baseName == generator && info.extraName == extraGenerator; - }); - if (extraIt == generators.end()) { - setErrorMessage(errorMessage, - std::string("The combination of generator \"" + generator + - "\" and extra generator \"" + extraGenerator + - "\" is not supported.")); - return false; - } - if (!extraIt->supportsToolset && !toolset.empty()) { - setErrorMessage(errorMessage, - std::string("Toolset was provided but is not supported by " - "the requested generator.")); - return false; - } - if (!extraIt->supportsPlatform && !platform.empty()) { - setErrorMessage(errorMessage, - std::string("Platform was provided but is not supported " - "by the requested generator.")); - return false; - } - - this->GeneratorInfo = - GeneratorInformation(generator, extraGenerator, toolset, platform, - sourceDirectory, buildDirectory); - - this->m_State = STATE_ACTIVE; - return true; -} - -void cmServerProtocol1::HandleCMakeFileChanges(const std::string& path, - int event, int status) -{ - assert(status == 0); - static_cast<void>(status); - - if (!m_isDirty) { - m_isDirty = true; - SendSignal(kDIRTY_SIGNAL, Json::objectValue); - } - Json::Value obj = Json::objectValue; - obj[kPATH_KEY] = path; - Json::Value properties = Json::arrayValue; - if (event & UV_RENAME) { - properties.append(kRENAME_PROPERTY_VALUE); - } - if (event & UV_CHANGE) { - properties.append(kCHANGE_PROPERTY_VALUE); - } - - obj[kPROPERTIES_KEY] = properties; - SendSignal(kFILE_CHANGE_SIGNAL, obj); -} - -cmServerResponse cmServerProtocol1::Process(const cmServerRequest& request) -{ - assert(this->m_State >= STATE_ACTIVE); - - if (request.Type == kCACHE_TYPE) { - return this->ProcessCache(request); - } - if (request.Type == kCMAKE_INPUTS_TYPE) { - return this->ProcessCMakeInputs(request); - } - if (request.Type == kCODE_MODEL_TYPE) { - return this->ProcessCodeModel(request); - } - if (request.Type == kCOMPUTE_TYPE) { - return this->ProcessCompute(request); - } - if (request.Type == kCONFIGURE_TYPE) { - return this->ProcessConfigure(request); - } - if (request.Type == kFILESYSTEM_WATCHERS_TYPE) { - return this->ProcessFileSystemWatchers(request); - } - if (request.Type == kGLOBAL_SETTINGS_TYPE) { - return this->ProcessGlobalSettings(request); - } - if (request.Type == kSET_GLOBAL_SETTINGS_TYPE) { - return this->ProcessSetGlobalSettings(request); - } - if (request.Type == kCTEST_INFO_TYPE) { - return this->ProcessCTests(request); - } - - return request.ReportError("Unknown command!"); -} - -bool cmServerProtocol1::IsExperimental() const -{ - return true; -} - -cmServerResponse cmServerProtocol1::ProcessCache( - const cmServerRequest& request) -{ - cmState* state = this->CMakeInstance()->GetState(); - - Json::Value result = Json::objectValue; - - std::vector<std::string> allKeys = state->GetCacheEntryKeys(); - - Json::Value list = Json::arrayValue; - std::vector<std::string> keys = toStringList(request.Data[kKEYS_KEY]); - if (keys.empty()) { - keys = allKeys; - } else { - for (auto const& i : keys) { - if (!cm::contains(allKeys, i)) { - return request.ReportError("Key \"" + i + "\" not found in cache."); - } - } - } - std::sort(keys.begin(), keys.end()); - for (auto const& key : keys) { - Json::Value entry = Json::objectValue; - entry[kKEY_KEY] = key; - entry[kTYPE_KEY] = - cmState::CacheEntryTypeToString(state->GetCacheEntryType(key)); - entry[kVALUE_KEY] = *state->GetCacheEntryValue(key); - - Json::Value props = Json::objectValue; - bool haveProperties = false; - for (auto const& prop : state->GetCacheEntryPropertyList(key)) { - haveProperties = true; - props[prop] = *state->GetCacheEntryProperty(key, prop); - } - if (haveProperties) { - entry[kPROPERTIES_KEY] = props; - } - - list.append(entry); - } - - result[kCACHE_KEY] = list; - return request.Reply(result); -} - -cmServerResponse cmServerProtocol1::ProcessCMakeInputs( - const cmServerRequest& request) -{ - if (this->m_State < STATE_CONFIGURED) { - return request.ReportError("This instance was not yet configured."); - } - - const cmake* cm = this->CMakeInstance(); - const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot(); - const std::string& sourceDir = cm->GetHomeDirectory(); - - Json::Value result = Json::objectValue; - result[kSOURCE_DIRECTORY_KEY] = sourceDir; - result[kCMAKE_ROOT_DIRECTORY_KEY] = cmakeRootDir; - result[kBUILD_FILES_KEY] = cmDumpCMakeInputs(cm); - return request.Reply(result); -} - -cmServerResponse cmServerProtocol1::ProcessCodeModel( - const cmServerRequest& request) -{ - if (this->m_State != STATE_COMPUTED) { - return request.ReportError("No build system was generated yet."); - } - - return request.Reply(cmDumpCodeModel(this->CMakeInstance())); -} - -cmServerResponse cmServerProtocol1::ProcessCompute( - const cmServerRequest& request) -{ - if (this->m_State > STATE_CONFIGURED) { - return request.ReportError("This build system was already generated."); - } - if (this->m_State < STATE_CONFIGURED) { - return request.ReportError("This project was not configured yet."); - } - - cmake* cm = this->CMakeInstance(); - int ret = cm->Generate(); - - if (ret < 0) { - return request.ReportError("Failed to compute build system."); - } - m_State = STATE_COMPUTED; - return request.Reply(Json::Value()); -} - -cmServerResponse cmServerProtocol1::ProcessConfigure( - const cmServerRequest& request) -{ - if (this->m_State == STATE_INACTIVE) { - return request.ReportError("This instance is inactive."); - } - - FileMonitor()->StopMonitoring(); - - std::string errorMessage; - cmake* cm = this->CMakeInstance(); - this->GeneratorInfo.SetupGenerator(cm, &errorMessage); - if (!errorMessage.empty()) { - return request.ReportError(errorMessage); - } - - // Make sure the types of cacheArguments matches (if given): - std::vector<std::string> cacheArgs = { "unused" }; - bool cacheArgumentsError = false; - const Json::Value passedArgs = request.Data[kCACHE_ARGUMENTS_KEY]; - if (!passedArgs.isNull()) { - if (passedArgs.isString()) { - cacheArgs.push_back(passedArgs.asString()); - } else if (passedArgs.isArray()) { - for (auto const& arg : passedArgs) { - if (!arg.isString()) { - cacheArgumentsError = true; - break; - } - cacheArgs.push_back(arg.asString()); - } - } else { - cacheArgumentsError = true; - } - } - if (cacheArgumentsError) { - request.ReportError( - "cacheArguments must be unset, a string or an array of strings."); - } - - std::string sourceDir = cm->GetHomeDirectory(); - const std::string buildDir = cm->GetHomeOutputDirectory(); - - cmGlobalGenerator* gg = cm->GetGlobalGenerator(); - - if (buildDir.empty()) { - return request.ReportError("No build directory set via Handshake."); - } - - if (cm->LoadCache(buildDir)) { - // build directory has been set up before - cmProp cachedSourceDir = - cm->GetState()->GetInitializedCacheValue("CMAKE_HOME_DIRECTORY"); - if (!cachedSourceDir) { - return request.ReportError("No CMAKE_HOME_DIRECTORY found in cache."); - } - if (sourceDir.empty()) { - sourceDir = *cachedSourceDir; - cm->SetHomeDirectory(sourceDir); - } - - cmProp cachedGenerator = - cm->GetState()->GetInitializedCacheValue("CMAKE_GENERATOR"); - if (cachedGenerator) { - if (gg && gg->GetName() != *cachedGenerator) { - return request.ReportError("Configured generator does not match with " - "CMAKE_GENERATOR found in cache."); - } - } - } else { - // build directory has not been set up before - if (sourceDir.empty()) { - return request.ReportError("No sourceDirectory set via " - "setGlobalSettings and no cache found in " - "buildDirectory."); - } - } - - cmSystemTools::ResetErrorOccuredFlag(); // Reset error state - - if (cm->AddCMakePaths() != 1) { - return request.ReportError("Failed to set CMake paths."); - } - - if (!cm->SetCacheArgs(cacheArgs)) { - return request.ReportError("cacheArguments could not be set."); - } - - int ret = cm->Configure(); - cm->IssueMessage( - MessageType::DEPRECATION_WARNING, - "The 'cmake-server(7)' is deprecated. " - "Please port clients to use the 'cmake-file-api(7)' instead."); - if (ret < 0) { - return request.ReportError("Configuration failed."); - } - - std::vector<std::string> toWatchList; - cmGetCMakeInputs(gg, std::string(), buildDir, nullptr, &toWatchList, - nullptr); - - FileMonitor()->MonitorPaths(toWatchList, - [this](const std::string& p, int e, int s) { - this->HandleCMakeFileChanges(p, e, s); - }); - - m_State = STATE_CONFIGURED; - m_isDirty = false; - return request.Reply(Json::Value()); -} - -cmServerResponse cmServerProtocol1::ProcessGlobalSettings( - const cmServerRequest& request) -{ - cmake* cm = this->CMakeInstance(); - Json::Value obj = Json::objectValue; - - // Capabilities information: - obj[kCAPABILITIES_KEY] = cm->ReportCapabilitiesJson(); - - obj[kDEBUG_OUTPUT_KEY] = cm->GetDebugOutput(); - obj[kTRACE_KEY] = cm->GetTrace(); - obj[kTRACE_EXPAND_KEY] = cm->GetTraceExpand(); - obj[kWARN_UNINITIALIZED_KEY] = cm->GetWarnUninitialized(); - obj[kWARN_UNUSED_KEY] = m_WarnUnused; - obj[kWARN_UNUSED_CLI_KEY] = cm->GetWarnUnusedCli(); - obj[kCHECK_SYSTEM_VARS_KEY] = cm->GetCheckSystemVars(); - - obj[kSOURCE_DIRECTORY_KEY] = this->GeneratorInfo.SourceDirectory; - obj[kBUILD_DIRECTORY_KEY] = this->GeneratorInfo.BuildDirectory; - - // Currently used generator: - obj[kGENERATOR_KEY] = this->GeneratorInfo.GeneratorName; - obj[kEXTRA_GENERATOR_KEY] = this->GeneratorInfo.ExtraGeneratorName; - - return request.Reply(obj); -} - -static void setBool(const cmServerRequest& request, const std::string& key, - std::function<void(bool)> const& setter) -{ - if (request.Data[key].isNull()) { - return; - } - setter(request.Data[key].asBool()); -} - -cmServerResponse cmServerProtocol1::ProcessSetGlobalSettings( - const cmServerRequest& request) -{ - const std::vector<std::string> boolValues = { - kDEBUG_OUTPUT_KEY, kTRACE_KEY, kTRACE_EXPAND_KEY, - kWARN_UNINITIALIZED_KEY, kWARN_UNUSED_KEY, kWARN_UNUSED_CLI_KEY, - kCHECK_SYSTEM_VARS_KEY - }; - for (std::string const& i : boolValues) { - if (!request.Data[i].isNull() && !request.Data[i].isBool()) { - return request.ReportError("\"" + i + - "\" must be unset or a bool value."); - } - } - - cmake* cm = this->CMakeInstance(); - - setBool(request, kDEBUG_OUTPUT_KEY, - [cm](bool e) { cm->SetDebugOutputOn(e); }); - setBool(request, kTRACE_KEY, [cm](bool e) { cm->SetTrace(e); }); - setBool(request, kTRACE_EXPAND_KEY, [cm](bool e) { cm->SetTraceExpand(e); }); - setBool(request, kWARN_UNINITIALIZED_KEY, - [cm](bool e) { cm->SetWarnUninitialized(e); }); - setBool(request, kWARN_UNUSED_KEY, [this](bool e) { m_WarnUnused = e; }); - setBool(request, kWARN_UNUSED_CLI_KEY, - [cm](bool e) { cm->SetWarnUnusedCli(e); }); - setBool(request, kCHECK_SYSTEM_VARS_KEY, - [cm](bool e) { cm->SetCheckSystemVars(e); }); - - return request.Reply(Json::Value()); -} - -cmServerResponse cmServerProtocol1::ProcessFileSystemWatchers( - const cmServerRequest& request) -{ - const cmFileMonitor* const fm = FileMonitor(); - Json::Value result = Json::objectValue; - Json::Value files = Json::arrayValue; - for (auto const& f : fm->WatchedFiles()) { - files.append(f); - } - Json::Value directories = Json::arrayValue; - for (auto const& d : fm->WatchedDirectories()) { - directories.append(d); - } - result[kWATCHED_FILES_KEY] = files; - result[kWATCHED_DIRECTORIES_KEY] = directories; - - return request.Reply(result); -} - -cmServerResponse cmServerProtocol1::ProcessCTests( - const cmServerRequest& request) -{ - if (this->m_State < STATE_COMPUTED) { - return request.ReportError("This instance was not yet computed."); - } - - return request.Reply(cmDumpCTestInfo(this->CMakeInstance())); -} - -cmServerProtocol1::GeneratorInformation::GeneratorInformation( - std::string generatorName, std::string extraGeneratorName, - std::string toolset, std::string platform, std::string sourceDirectory, - std::string buildDirectory) - : GeneratorName(std::move(generatorName)) - , ExtraGeneratorName(std::move(extraGeneratorName)) - , Toolset(std::move(toolset)) - , Platform(std::move(platform)) - , SourceDirectory(std::move(sourceDirectory)) - , BuildDirectory(std::move(buildDirectory)) -{ -} - -void cmServerProtocol1::GeneratorInformation::SetupGenerator( - cmake* cm, std::string* errorMessage) -{ - const std::string fullGeneratorName = - cmExternalMakefileProjectGenerator::CreateFullGeneratorName( - GeneratorName, ExtraGeneratorName); - - cm->SetHomeDirectory(SourceDirectory); - cm->SetHomeOutputDirectory(BuildDirectory); - - auto gg = cm->CreateGlobalGenerator(fullGeneratorName); - if (!gg) { - setErrorMessage( - errorMessage, - std::string("Could not set up the requested combination of \"") + - kGENERATOR_KEY + "\" and \"" + kEXTRA_GENERATOR_KEY + "\""); - return; - } - - cm->SetGlobalGenerator(std::move(gg)); - - cm->SetGeneratorToolset(Toolset); - cm->SetGeneratorPlatform(Platform); -} diff --git a/Source/cmServerProtocol.h b/Source/cmServerProtocol.h deleted file mode 100644 index 6009e23..0000000 --- a/Source/cmServerProtocol.h +++ /dev/null @@ -1,162 +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 <memory> -#include <string> -#include <utility> - -#include <cm3p/json/value.h> - -#include "cmake.h" - -class cmConnection; -class cmFileMonitor; -class cmServer; -class cmServerRequest; - -class cmServerResponse -{ -public: - explicit cmServerResponse(const cmServerRequest& request); - - void SetData(const Json::Value& data); - void SetError(const std::string& message); - - bool IsComplete() const; - bool IsError() const; - std::string ErrorMessage() const; - Json::Value Data() const; - - const std::string Type; - const std::string Cookie; - -private: - enum PayLoad - { - PAYLOAD_UNKNOWN, - PAYLOAD_ERROR, - PAYLOAD_DATA - }; - PayLoad m_Payload = PAYLOAD_UNKNOWN; - std::string m_ErrorMessage; - Json::Value m_Data; -}; - -class cmServerRequest -{ -public: - cmServerResponse Reply(const Json::Value& data) const; - cmServerResponse ReportError(const std::string& message) const; - - const std::string Type; - const std::string Cookie; - const Json::Value Data; - cmConnection* Connection; - -private: - cmServerRequest(cmServer* server, cmConnection* connection, std::string t, - std::string c, Json::Value d); - - void ReportProgress(int min, int current, int max, - const std::string& message) const; - void ReportMessage(const std::string& message, - const std::string& title) const; - - cmServer* m_Server; - - friend class cmServer; -}; - -class cmServerProtocol -{ -public: - cmServerProtocol() = default; - virtual ~cmServerProtocol() = default; - - cmServerProtocol(cmServerProtocol const&) = delete; - cmServerProtocol& operator=(cmServerProtocol const&) = delete; - - virtual std::pair<int, int> ProtocolVersion() const = 0; - virtual bool IsExperimental() const = 0; - virtual cmServerResponse Process(const cmServerRequest& request) = 0; - - bool Activate(cmServer* server, const cmServerRequest& request, - std::string* errorMessage); - - cmFileMonitor* FileMonitor() const; - void SendSignal(const std::string& name, const Json::Value& data) const; - -protected: - cmake* CMakeInstance() const; - // Implement protocol specific activation tasks here. Called from Activate(). - virtual bool DoActivate(const cmServerRequest& request, - std::string* errorMessage); - bool m_WarnUnused = false; // storage for legacy option - -private: - std::unique_ptr<cmake> m_CMakeInstance; - cmServer* m_Server = nullptr; // not owned! - - friend class cmServer; -}; - -class cmServerProtocol1 : public cmServerProtocol -{ -public: - std::pair<int, int> ProtocolVersion() const override; - bool IsExperimental() const override; - cmServerResponse Process(const cmServerRequest& request) override; - -private: - bool DoActivate(const cmServerRequest& request, - std::string* errorMessage) override; - - void HandleCMakeFileChanges(const std::string& path, int event, int status); - - // Handle requests: - cmServerResponse ProcessCache(const cmServerRequest& request); - cmServerResponse ProcessCMakeInputs(const cmServerRequest& request); - cmServerResponse ProcessCodeModel(const cmServerRequest& request); - cmServerResponse ProcessCompute(const cmServerRequest& request); - cmServerResponse ProcessConfigure(const cmServerRequest& request); - cmServerResponse ProcessGlobalSettings(const cmServerRequest& request); - cmServerResponse ProcessSetGlobalSettings(const cmServerRequest& request); - cmServerResponse ProcessFileSystemWatchers(const cmServerRequest& request); - cmServerResponse ProcessCTests(const cmServerRequest& request); - - enum State - { - STATE_INACTIVE, - STATE_ACTIVE, - STATE_CONFIGURED, - STATE_COMPUTED - }; - State m_State = STATE_INACTIVE; - - bool m_isDirty = false; - - struct GeneratorInformation - { - public: - GeneratorInformation() = default; - GeneratorInformation(std::string generatorName, - std::string extraGeneratorName, std::string toolset, - std::string platform, std::string sourceDirectory, - std::string buildDirectory); - - void SetupGenerator(cmake* cm, std::string* errorMessage); - - std::string GeneratorName; - std::string ExtraGeneratorName; - std::string Toolset; - std::string Platform; - - std::string SourceDirectory; - std::string BuildDirectory; - }; - - GeneratorInformation GeneratorInfo; -}; diff --git a/Source/cmSetPropertyCommand.cxx b/Source/cmSetPropertyCommand.cxx index df6a38a..970564d 100644 --- a/Source/cmSetPropertyCommand.cxx +++ b/Source/cmSetPropertyCommand.cxx @@ -10,9 +10,12 @@ #include "cmGlobalGenerator.h" #include "cmInstalledFile.h" #include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmPolicies.h" #include "cmProperty.h" #include "cmRange.h" #include "cmSourceFile.h" +#include "cmSourceFileLocation.h" #include "cmState.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" @@ -157,7 +160,7 @@ bool HandleSourceFileDirectoryScopeValidation( return true; } -bool HandleAndValidateSourceFileDirectortoryScopes( +bool HandleAndValidateSourceFileDirectoryScopes( cmExecutionStatus& status, bool source_file_directory_option_enabled, bool source_file_target_option_enabled, std::vector<std::string>& source_file_directories, @@ -216,8 +219,92 @@ void MakeSourceFilePathsAbsoluteIfNeeded( source_files_absolute_paths.push_back(absolute_file_path); } } + +bool HandleAndValidateSourceFilePropertyGENERATED( + cmSourceFile* sf, std::string const& propertyValue, PropertyOp op) +{ + auto& mf = *sf->GetLocation().GetMakefile(); + auto policyStatus = mf.GetPolicyStatus(cmPolicies::CMP0118); + + const bool policyWARN = policyStatus == cmPolicies::WARN; + const bool policyNEW = policyStatus != cmPolicies::OLD && !policyWARN; + + if (policyWARN) { + if (!cmIsOn(propertyValue) && !cmIsOff(propertyValue)) { + mf.IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0118), + "\nAttempt to set property 'GENERATED' with the following " + "non-boolean value (which will be interpreted as \"0\"):\n", + propertyValue, + "\nThat exact value will not be retrievable. A value of " + "\"0\" will be returned instead.\n" + "This will be an error under policy CMP0118.\n")); + } + if (cmIsOff(propertyValue)) { + mf.IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0118), + "\nUnsetting property 'GENERATED' will not be allowed under " + "policy CMP0118!\n")); + } + if (op == PropertyOp::Append || op == PropertyOp::AppendAsString) { + mf.IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0118), + "\nAppending to property 'GENERATED' will not be allowed " + "under policy CMP0118!\n")); + } + } else if (policyNEW) { + if (!cmIsOn(propertyValue) && !cmIsOff(propertyValue)) { + mf.IssueMessage( + MessageType::AUTHOR_ERROR, + cmStrCat( + "Policy CMP0118 is set to NEW and the following non-boolean value " + "given for property 'GENERATED' is therefore not allowed:\n", + propertyValue, "\nReplace it with a boolean value!\n")); + return true; + } + if (cmIsOff(propertyValue)) { + mf.IssueMessage( + MessageType::AUTHOR_ERROR, + "Unsetting the 'GENERATED' property is not allowed under CMP0118!\n"); + return true; + } + if (op == PropertyOp::Append || op == PropertyOp::AppendAsString) { + mf.IssueMessage(MessageType::AUTHOR_ERROR, + "Policy CMP0118 is set to NEW and appending to the " + "'GENERATED' property is therefore not allowed. Only " + "setting it to \"1\" is allowed!\n"); + return true; + } + } + + // Set property. + if (!policyNEW) { + // Do it the traditional way. + switch (op) { + case PropertyOp::Append: + sf->AppendProperty("GENERATED", propertyValue, false); + break; + case PropertyOp::AppendAsString: + sf->AppendProperty("GENERATED", propertyValue, true); + break; + case PropertyOp::Remove: + sf->SetProperty("GENERATED", nullptr); + break; + case PropertyOp::Set: + sf->SetProperty("GENERATED", propertyValue.c_str()); + break; + } + } else { + sf->MarkAsGenerated(); + } + return true; } +} // END: namespace SetPropertyCommand + bool cmSetPropertyCommand(std::vector<std::string> const& args, cmExecutionStatus& status) { @@ -324,7 +411,7 @@ bool cmSetPropertyCommand(std::vector<std::string> const& args, std::vector<cmMakefile*> source_file_directory_makefiles; bool file_scopes_handled = - SetPropertyCommand::HandleAndValidateSourceFileDirectortoryScopes( + SetPropertyCommand::HandleAndValidateSourceFileDirectoryScopes( status, source_file_directory_option_enabled, source_file_target_option_enabled, source_file_directories, source_file_target_directories, source_file_directory_makefiles); @@ -367,7 +454,7 @@ bool cmSetPropertyCommand(std::vector<std::string> const& args, return true; } -namespace { +namespace /* anonymous */ { bool HandleGlobalMode(cmExecutionStatus& status, const std::set<std::string>& names, const std::string& propertyName, @@ -525,6 +612,18 @@ bool HandleSource(cmSourceFile* sf, const std::string& propertyName, const std::string& propertyValue, bool appendAsString, bool appendMode, bool remove) { + // Special validation and handling of GENERATED flag? + if (propertyName == "GENERATED") { + SetPropertyCommand::PropertyOp op = (remove) + ? SetPropertyCommand::PropertyOp::Remove + : (appendAsString) + ? SetPropertyCommand::PropertyOp::AppendAsString + : (appendMode) ? SetPropertyCommand::PropertyOp::Append + : SetPropertyCommand::PropertyOp::Set; + return SetPropertyCommand::HandleAndValidateSourceFilePropertyGENERATED( + sf, propertyValue, op); + } + // Set or append the property. if (appendMode) { sf->AppendProperty(propertyName, propertyValue, appendAsString); diff --git a/Source/cmSetPropertyCommand.h b/Source/cmSetPropertyCommand.h index 89fdd9a..05c4873 100644 --- a/Source/cmSetPropertyCommand.h +++ b/Source/cmSetPropertyCommand.h @@ -9,6 +9,7 @@ class cmMakefile; class cmExecutionStatus; +class cmSourceFile; bool cmSetPropertyCommand(std::vector<std::string> const& args, cmExecutionStatus& status); @@ -25,7 +26,7 @@ bool HandleSourceFileDirectoryScopeValidation( std::vector<std::string>& source_file_directories, std::vector<std::string>& source_file_target_directories); -bool HandleAndValidateSourceFileDirectortoryScopes( +bool HandleAndValidateSourceFileDirectoryScopes( cmExecutionStatus& status, bool source_directories_option_encountered, bool source_target_directories_option_encountered, std::vector<std::string>& source_directories, @@ -39,4 +40,16 @@ void MakeSourceFilePathsAbsoluteIfNeeded( std::vector<std::string>& source_files_absolute_paths, std::vector<std::string>::const_iterator files_it_begin, std::vector<std::string>::const_iterator files_it_end, bool needed); + +enum class PropertyOp +{ + Remove, + Set, + Append, + AppendAsString +}; + +bool HandleAndValidateSourceFilePropertyGENERATED( + cmSourceFile* sf, std::string const& propertyValue, + PropertyOp op = PropertyOp::Set); } diff --git a/Source/cmSetSourceFilesPropertiesCommand.cxx b/Source/cmSetSourceFilesPropertiesCommand.cxx index c1b0c28..742aa96 100644 --- a/Source/cmSetSourceFilesPropertiesCommand.cxx +++ b/Source/cmSetSourceFilesPropertiesCommand.cxx @@ -7,6 +7,7 @@ #include <cm/string_view> #include <cmext/algorithm> +#include <cmext/string_view> #include "cmExecutionStatus.h" #include "cmMakefile.h" @@ -82,7 +83,7 @@ bool cmSetSourceFilesPropertiesCommand(std::vector<std::string> const& args, const auto props_begin = options_it; bool file_scopes_handled = - SetPropertyCommand::HandleAndValidateSourceFileDirectortoryScopes( + SetPropertyCommand::HandleAndValidateSourceFileDirectoryScopes( status, source_file_directory_option_enabled, source_file_target_option_enabled, source_file_directories, source_file_target_directories, source_file_directory_makefiles); @@ -167,7 +168,13 @@ static bool RunCommandForScope( if (cmSourceFile* sf = mf->GetOrCreateSource(sfname)) { // loop through the props and set them for (auto k = propertyPairs.begin(); k != propertyPairs.end(); k += 2) { - sf->SetProperty(*k, (k + 1)->c_str()); + // Special handling for GENERATED property? + if (*k == "GENERATED"_s) { + SetPropertyCommand::HandleAndValidateSourceFilePropertyGENERATED( + sf, *(k + 1)); + } else { + sf->SetProperty(*k, (k + 1)->c_str()); + } } } } diff --git a/Source/cmSourceFile.cxx b/Source/cmSourceFile.cxx index ef44a57..9d9a7c3 100644 --- a/Source/cmSourceFile.cxx +++ b/Source/cmSourceFile.cxx @@ -8,6 +8,7 @@ #include "cmListFileCache.h" #include "cmMakefile.h" #include "cmMessageType.h" +#include "cmPolicies.h" #include "cmProperty.h" #include "cmState.h" #include "cmStringAlgorithms.h" @@ -15,9 +16,12 @@ #include "cmake.h" cmSourceFile::cmSourceFile(cmMakefile* mf, const std::string& name, - cmSourceFileLocationKind kind) - : Location(mf, name, kind) + bool generated, cmSourceFileLocationKind kind) + : Location(mf, name, (!generated) ? kind : cmSourceFileLocationKind::Known) { + if (generated) { + this->MarkAsGenerated(); + } } std::string const& cmSourceFile::GetExtension() const @@ -25,6 +29,8 @@ std::string const& cmSourceFile::GetExtension() const return this->Extension; } +const std::string propTRUE = "1"; +const std::string propFALSE = "0"; const std::string cmSourceFile::propLANGUAGE = "LANGUAGE"; const std::string cmSourceFile::propLOCATION = "LOCATION"; const std::string cmSourceFile::propGENERATED = "GENERATED"; @@ -54,16 +60,14 @@ std::string const& cmSourceFile::GetOrDetermineLanguage() } // Perform computation needed to get the language if necessary. - if (this->FullPath.empty() && this->Language.empty()) { - // If a known extension is given or a known full path is given - // then trust that the current extension is sufficient to - // determine the language. This will fail only if the user - // specifies a full path to the source but leaves off the - // extension, which is kind of weird. - if (this->Location.ExtensionIsAmbiguous() && + if (this->Language.empty()) { + // If a known extension is given or a known full path is given then trust + // that the current extension is sufficient to determine the language. This + // will fail only if the user specifies a full path to the source but + // leaves off the extension, which is kind of weird. + if (this->FullPath.empty() && this->Location.ExtensionIsAmbiguous() && this->Location.DirectoryIsAmbiguous()) { - // Finalize the file location to get the extension and set the - // language. + // Finalize the file location to get the extension and set the language. this->ResolveFullPath(); } else { // Use the known extension to get the language if possible. @@ -93,10 +97,11 @@ cmSourceFileLocation const& cmSourceFile::GetLocation() const return this->Location; } -std::string const& cmSourceFile::ResolveFullPath(std::string* error) +std::string const& cmSourceFile::ResolveFullPath(std::string* error, + std::string* cmp0115Warning) { if (this->FullPath.empty()) { - if (this->FindFullPath(error)) { + if (this->FindFullPath(error, cmp0115Warning)) { this->CheckExtension(); } } @@ -108,14 +113,18 @@ std::string const& cmSourceFile::GetFullPath() const return this->FullPath; } -bool cmSourceFile::FindFullPath(std::string* error) +bool cmSourceFile::FindFullPath(std::string* error, + std::string* cmp0115Warning) { // If the file is generated compute the location without checking on disk. - if (this->GetIsGenerated()) { + // Note: We also check for a locally set GENERATED property, because + // it might have been set before policy CMP0118 was set to NEW. + if (this->GetIsGenerated(CheckScope::GlobalAndLocal)) { // The file is either already a full path or is relative to the // build directory for the target. this->Location.DirectoryUseBinary(); this->FullPath = this->Location.GetFullPath(); + this->FindFullPathFailed = false; return true; } @@ -131,9 +140,11 @@ bool cmSourceFile::FindFullPath(std::string* error) // List of extension lists std::vector<std::string> exts = makefile->GetCMakeInstance()->GetAllExtensions(); + auto cmp0115 = makefile->GetPolicyStatus(cmPolicies::CMP0115); // Tries to find the file in a given directory - auto findInDir = [this, &exts, &lPath](std::string const& dir) -> bool { + auto findInDir = [this, &exts, &lPath, cmp0115, cmp0115Warning, + makefile](std::string const& dir) -> bool { // Compute full path std::string const fullPath = cmSystemTools::CollapseFullPath(lPath, dir); // Try full path @@ -141,13 +152,29 @@ bool cmSourceFile::FindFullPath(std::string* error) this->FullPath = fullPath; return true; } - // Try full path with extension - for (std::string const& ext : exts) { - if (!ext.empty()) { - std::string extPath = cmStrCat(fullPath, '.', ext); - if (cmSystemTools::FileExists(extPath)) { - this->FullPath = extPath; - return true; + // This has to be an if statement due to a bug in Oracle Developer Studio. + // See https://community.oracle.com/tech/developers/discussion/4476246/ + // for details. + if (cmp0115 == cmPolicies::OLD || cmp0115 == cmPolicies::WARN) { + // Try full path with extension + for (std::string const& ext : exts) { + if (!ext.empty()) { + std::string extPath = cmStrCat(fullPath, '.', ext); + if (cmSystemTools::FileExists(extPath)) { + this->FullPath = extPath; + if (cmp0115 == cmPolicies::WARN) { + std::string warning = + cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0115), + "\nFile:\n ", extPath); + if (cmp0115Warning) { + *cmp0115Warning = std::move(warning); + } else { + makefile->GetCMakeInstance()->IssueMessage( + MessageType::AUTHOR_WARNING, warning); + } + } + return true; + } } } } @@ -168,11 +195,19 @@ bool cmSourceFile::FindFullPath(std::string* error) } // Compose error - std::string err = - cmStrCat("Cannot find source file:\n ", lPath, "\nTried extensions"); - for (std::string const& ext : exts) { - err += " ."; - err += ext; + std::string err = cmStrCat("Cannot find source file:\n ", lPath); + switch (cmp0115) { + case cmPolicies::OLD: + case cmPolicies::WARN: + err = cmStrCat(err, "\nTried extensions"); + for (auto const& ext : exts) { + err = cmStrCat(err, " .", ext); + } + break; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::NEW: + break; } if (error != nullptr) { *error = std::move(err); @@ -246,11 +281,6 @@ void cmSourceFile::SetProperty(const std::string& prop, const char* value) } else { this->Properties.SetProperty(prop, value); } - - // Update IsGenerated flag - if (prop == propGENERATED) { - this->IsGenerated = cmIsOn(value); - } } void cmSourceFile::AppendProperty(const std::string& prop, @@ -274,14 +304,9 @@ void cmSourceFile::AppendProperty(const std::string& prop, } else { this->Properties.AppendProperty(prop, value, asString); } - - // Update IsGenerated flag - if (prop == propGENERATED) { - this->IsGenerated = this->GetPropertyAsBool(propGENERATED); - } } -const char* cmSourceFile::GetPropertyForUser(const std::string& prop) +cmProp cmSourceFile::GetPropertyForUser(const std::string& prop) { // This method is a consequence of design history and backwards // compatibility. GetProperty is (and should be) a const method. @@ -305,13 +330,27 @@ const char* cmSourceFile::GetPropertyForUser(const std::string& prop) // Similarly, LANGUAGE can be determined by the file extension // if it is requested by the user. if (prop == propLANGUAGE) { - // The c_str pointer is valid until `this->Language` is modified. - return this->GetOrDetermineLanguage().c_str(); + // The pointer is valid until `this->Language` is modified. + return &this->GetOrDetermineLanguage(); + } + + // Special handling for GENERATED property. + if (prop == propGENERATED) { + // We need to check policy CMP0118 in order to determine if we need to + // possibly consider the value of a locally set GENERATED property, too. + auto policyStatus = + this->Location.GetMakefile()->GetPolicyStatus(cmPolicies::CMP0118); + if (this->GetIsGenerated( + (policyStatus == cmPolicies::WARN || policyStatus == cmPolicies::OLD) + ? CheckScope::GlobalAndLocal + : CheckScope::Global)) { + return &propTRUE; + } + return &propFALSE; } // Perform the normal property lookup. - cmProp p = this->GetProperty(prop); - return p ? p->c_str() : nullptr; + return this->GetProperty(prop); } cmProp cmSourceFile::GetProperty(const std::string& prop) const @@ -369,13 +408,15 @@ cmProp cmSourceFile::GetProperty(const std::string& prop) const return retVal; } -const char* cmSourceFile::GetSafeProperty(const std::string& prop) const +const std::string& cmSourceFile::GetSafeProperty(const std::string& prop) const { cmProp ret = this->GetProperty(prop); - if (!ret) { - return ""; + if (ret) { + return *ret; } - return ret->c_str(); + + static std::string const s_empty; + return s_empty; } bool cmSourceFile::GetPropertyAsBool(const std::string& prop) const @@ -383,11 +424,29 @@ bool cmSourceFile::GetPropertyAsBool(const std::string& prop) const return cmIsOn(this->GetProperty(prop)); } +void cmSourceFile::MarkAsGenerated() +{ + this->IsGenerated = true; + auto& mf = *this->Location.GetMakefile(); + mf.GetGlobalGenerator()->MarkAsGeneratedFile(this->ResolveFullPath()); +} + +bool cmSourceFile::GetIsGenerated(CheckScope checkScope) const +{ + if (this->IsGenerated) { + // Globally marked as generated! + return true; + } + if (checkScope == CheckScope::GlobalAndLocal) { + // Check locally stored properties. + return this->GetPropertyAsBool(propGENERATED); + } + return false; +} + void cmSourceFile::SetProperties(cmPropertyMap properties) { this->Properties = std::move(properties); - - this->IsGenerated = this->GetPropertyAsBool(propGENERATED); } cmCustomCommand* cmSourceFile::GetCustomCommand() const diff --git a/Source/cmSourceFile.h b/Source/cmSourceFile.h index 39ea8e3..94b5cc8 100644 --- a/Source/cmSourceFile.h +++ b/Source/cmSourceFile.h @@ -20,18 +20,18 @@ class cmMakefile; /** \class cmSourceFile * \brief Represent a class loaded from a makefile. * - * cmSourceFile is represents a class loaded from - * a makefile. + * cmSourceFile represents a class loaded from a makefile. */ class cmSourceFile { public: /** - * Construct with the makefile storing the source and the initial - * name referencing it. + * Construct with the makefile storing the source and the initial name + * referencing it. If it shall be marked as generated, this source file's + * kind is assumed to be known, regardless of the given value. */ cmSourceFile( - cmMakefile* mf, const std::string& name, + cmMakefile* mf, const std::string& name, bool generated, cmSourceFileLocationKind kind = cmSourceFileLocationKind::Ambiguous); /** @@ -47,16 +47,36 @@ public: //! Might return a nullptr if the property is not set or invalid cmProp GetProperty(const std::string& prop) const; //! Always returns a valid pointer - const char* GetSafeProperty(const std::string& prop) const; + const std::string& GetSafeProperty(const std::string& prop) const; bool GetPropertyAsBool(const std::string& prop) const; /** Implement getting a property when called from a CMake language command like get_property or get_source_file_property. */ - const char* GetPropertyForUser(const std::string& prop); + cmProp GetPropertyForUser(const std::string& prop); - //! Checks is the GENERATED property is set and true - /// @return Equivalent to GetPropertyAsBool("GENERATED") - bool GetIsGenerated() const { return this->IsGenerated; } + /// Marks this file as generated + /** + * This stores this file's path in the global table for all generated source + * files. + */ + void MarkAsGenerated(); + enum class CheckScope + { + Global, + GlobalAndLocal + }; + /// Determines if this source file is marked as generated. + /** + * This will check if this file's path is stored in the global table of all + * generated source files. If that is not the case and checkScope is set to + * GlobalAndLocal the value of the possibly existing local GENERATED property + * is returned instead. + * @param checkScope Determines if alternatively for backwards-compatibility + * a local GENERATED property should be considered, too. + * @return true if this source file is marked as generated, otherwise false. + */ + bool GetIsGenerated( + CheckScope checkScope = CheckScope::GlobalAndLocal) const; const std::vector<BT<std::string>>& GetCompileOptions() const { @@ -77,7 +97,8 @@ public: * Resolves the full path to the file. Attempts to locate the file on disk * and finalizes its location. */ - std::string const& ResolveFullPath(std::string* error = nullptr); + std::string const& ResolveFullPath(std::string* error = nullptr, + std::string* cmp0115Warning = nullptr); /** * The resolved full path to the file. The returned file name might be empty @@ -138,7 +159,7 @@ private: bool FindFullPathFailed = false; bool IsGenerated = false; - bool FindFullPath(std::string* error); + bool FindFullPath(std::string* error, std::string* cmp0115Warning); void CheckExtension(); void CheckLanguage(std::string const& ext); diff --git a/Source/cmSourceFileLocation.cxx b/Source/cmSourceFileLocation.cxx index 222bafa..921eb0e 100644 --- a/Source/cmSourceFileLocation.cxx +++ b/Source/cmSourceFileLocation.cxx @@ -33,8 +33,7 @@ cmSourceFileLocation::cmSourceFileLocation(cmMakefile const* mf, this->AmbiguousExtension = true; this->Directory = cmSystemTools::GetFilenamePath(name); if (cmSystemTools::FileIsFullPath(this->Directory)) { - this->Directory = cmSystemTools::CollapseFullPath( - this->Directory, mf->GetHomeOutputDirectory()); + this->Directory = cmSystemTools::CollapseFullPath(this->Directory); } this->Name = cmSystemTools::GetFilenameName(name); if (kind == cmSourceFileLocationKind::Known) { diff --git a/Source/cmStandardLevelResolver.cxx b/Source/cmStandardLevelResolver.cxx index 8672f61..5d8ccf1 100644 --- a/Source/cmStandardLevelResolver.cxx +++ b/Source/cmStandardLevelResolver.cxx @@ -311,19 +311,19 @@ std::unordered_map<std::string, StanardLevelComputer> StandardComputerMapping = std::vector<std::string>{ "90", "99", "11" } } }, { "CXX", StanardLevelComputer{ - "CXX", std::vector<int>{ 98, 11, 14, 17, 20 }, - std::vector<std::string>{ "98", "11", "14", "17", "20" } } }, + "CXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 }, + std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } }, { "CUDA", StanardLevelComputer{ - "CUDA", std::vector<int>{ 03, 11, 14, 17, 20 }, - std::vector<std::string>{ "03", "11", "14", "17", "20" } } }, + "CUDA", std::vector<int>{ 03, 11, 14, 17, 20, 23 }, + std::vector<std::string>{ "03", "11", "14", "17", "20", "23" } } }, { "OBJC", StanardLevelComputer{ "OBJC", std::vector<int>{ 90, 99, 11 }, std::vector<std::string>{ "90", "99", "11" } } }, { "OBJCXX", StanardLevelComputer{ - "OBJCXX", std::vector<int>{ 98, 11, 14, 17, 20 }, - std::vector<std::string>{ "98", "11", "14", "17", "20" } } }, + "OBJCXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 }, + std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } }, }; } diff --git a/Source/cmState.cxx b/Source/cmState.cxx index d268e62..3692a01 100644 --- a/Source/cmState.cxx +++ b/Source/cmState.cxx @@ -441,6 +441,19 @@ void cmState::AddBuiltinCommand(std::string const& name, }); } +void cmState::AddFlowControlCommand(std::string const& name, Command command) +{ + this->FlowControlCommands.insert(name); + this->AddBuiltinCommand(name, std::move(command)); +} + +void cmState::AddFlowControlCommand(std::string const& name, + BuiltinCommand command) +{ + this->FlowControlCommands.insert(name); + this->AddBuiltinCommand(name, command); +} + void cmState::AddDisallowedCommand(std::string const& name, BuiltinCommand command, cmPolicies::PolicyID policy, @@ -470,7 +483,7 @@ void cmState::AddDisallowedCommand(std::string const& name, void cmState::AddUnexpectedCommand(std::string const& name, const char* error) { - this->AddBuiltinCommand( + this->AddFlowControlCommand( name, [name, error](std::vector<cmListFileArgument> const&, cmExecutionStatus& status) -> bool { @@ -485,16 +498,28 @@ void cmState::AddUnexpectedCommand(std::string const& name, const char* error) }); } -void cmState::AddScriptedCommand(std::string const& name, Command command) +bool cmState::AddScriptedCommand(std::string const& name, BT<Command> command, + cmMakefile& mf) { std::string sName = cmSystemTools::LowerCase(name); + if (this->FlowControlCommands.count(sName)) { + mf.GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Built-in flow control command \"", sName, + "\" cannot be overridden."), + command.Backtrace); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + // if the command already exists, give a new name to the old command. if (Command oldCmd = this->GetCommandByExactName(sName)) { this->ScriptedCommands["_" + sName] = oldCmd; } - this->ScriptedCommands[sName] = std::move(command); + this->ScriptedCommands[sName] = std::move(command.Value); + return true; } cmState::Command cmState::GetCommand(std::string const& name) const diff --git a/Source/cmState.h b/Source/cmState.h index e4c9eb5..4e41156 100644 --- a/Source/cmState.h +++ b/Source/cmState.h @@ -9,6 +9,7 @@ #include <set> #include <string> #include <unordered_map> +#include <unordered_set> #include <vector> #include "cmDefinitions.h" @@ -24,6 +25,7 @@ class cmCacheManager; class cmCommand; class cmGlobVerificationManager; +class cmMakefile; class cmStateSnapshot; class cmMessenger; class cmExecutionStatus; @@ -159,10 +161,13 @@ public: std::unique_ptr<cmCommand> command); void AddBuiltinCommand(std::string const& name, Command command); void AddBuiltinCommand(std::string const& name, BuiltinCommand command); + void AddFlowControlCommand(std::string const& name, Command command); + void AddFlowControlCommand(std::string const& name, BuiltinCommand command); void AddDisallowedCommand(std::string const& name, BuiltinCommand command, cmPolicies::PolicyID policy, const char* message); void AddUnexpectedCommand(std::string const& name, const char* error); - void AddScriptedCommand(std::string const& name, Command command); + bool AddScriptedCommand(std::string const& name, BT<Command> command, + cmMakefile& mf); void RemoveBuiltinCommand(std::string const& name); void RemoveUserDefinedCommands(); std::vector<std::string> GetCommandNames() const; @@ -225,6 +230,7 @@ private: std::vector<std::string> EnabledLanguages; std::unordered_map<std::string, Command> BuiltinCommands; std::unordered_map<std::string, Command> ScriptedCommands; + std::unordered_set<std::string> FlowControlCommands; cmPropertyMap GlobalProperties; std::unique_ptr<cmCacheManager> CacheManager; std::unique_ptr<cmGlobVerificationManager> GlobVerificationManager; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index bda2b30..017ab10 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -213,7 +213,7 @@ public: bool CheckImportedLibName(std::string const& prop, std::string const& value) const; - std::string ProcessSourceItemCMP0049(const std::string& s); + std::string ProcessSourceItemCMP0049(const std::string& s) const; }; namespace { @@ -373,11 +373,14 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type, initProp("ISPC_INSTRUCTION_SETS"); initProp("LINK_SEARCH_START_STATIC"); initProp("LINK_SEARCH_END_STATIC"); + initProp("OBJC_CLANG_TIDY"); + initProp("OBJCXX_CLANG_TIDY"); initProp("Swift_LANGUAGE_VERSION"); initProp("Swift_MODULE_DIRECTORY"); initProp("VS_JUST_MY_CODE_DEBUGGING"); initProp("DISABLE_PRECOMPILE_HEADERS"); initProp("UNITY_BUILD"); + initProp("UNITY_BUILD_UNIQUE_ID"); initProp("OPTIMIZE_DEPENDENCIES"); initPropValue("UNITY_BUILD_BATCH_SIZE", "8"); initPropValue("UNITY_BUILD_MODE", "BATCH"); @@ -624,6 +627,11 @@ void cmTarget::AddUtility(std::string const& name, bool cross, cmMakefile* mf) { name, cross }, mf ? mf->GetBacktrace() : cmListFileBacktrace())); } +void cmTarget::AddUtility(BT<std::pair<std::string, bool>> util) +{ + impl->Utilities.emplace(std::move(util)); +} + std::set<BT<std::pair<std::string, bool>>> const& cmTarget::GetUtilities() const { @@ -741,7 +749,8 @@ void cmTarget::AddSources(std::vector<std::string> const& srcs) } } -std::string cmTargetInternals::ProcessSourceItemCMP0049(const std::string& s) +std::string cmTargetInternals::ProcessSourceItemCMP0049( + const std::string& s) const { std::string src = s; @@ -792,7 +801,7 @@ struct CreateLocation { } - cmSourceFileLocation operator()(const std::string& filename) + cmSourceFileLocation operator()(const std::string& filename) const { return cmSourceFileLocation(this->Makefile, filename); } @@ -858,7 +867,7 @@ cmSourceFile* cmTarget::AddSource(const std::string& src, bool before) cmSourceFileLocationKind::Known); } -void cmTarget::ClearDependencyInformation(cmMakefile& mf) +void cmTarget::ClearDependencyInformation(cmMakefile& mf) const { std::string depname = cmStrCat(this->GetName(), "_LIB_DEPENDS"); mf.RemoveCacheDefinition(depname); diff --git a/Source/cmTarget.h b/Source/cmTarget.h index d8f66bc..27f0c59 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -114,7 +114,7 @@ public: LinkLibraryVectorType const& GetOriginalLinkLibraries() const; //! Clear the dependency information recorded for this target, if any. - void ClearDependencyInformation(cmMakefile& mf); + void ClearDependencyInformation(cmMakefile& mf) const; void AddLinkLibrary(cmMakefile& mf, std::string const& lib, cmTargetLinkLibraryType llt); @@ -164,6 +164,7 @@ public: */ void AddUtility(std::string const& name, bool cross, cmMakefile* mf = nullptr); + void AddUtility(BT<std::pair<std::string, bool>> util); //! Get the utilities used by this target std::set<BT<std::pair<std::string, bool>>> const& GetUtilities() const; diff --git a/Source/cmTargetIncludeDirectoriesCommand.cxx b/Source/cmTargetIncludeDirectoriesCommand.cxx index 35635b9..bf4cc09 100644 --- a/Source/cmTargetIncludeDirectoriesCommand.cxx +++ b/Source/cmTargetIncludeDirectoriesCommand.cxx @@ -101,5 +101,6 @@ bool cmTargetIncludeDirectoriesCommand(std::vector<std::string> const& args, args, "INCLUDE_DIRECTORIES", TargetIncludeDirectoriesImpl::ArgumentFlags( TargetIncludeDirectoriesImpl::PROCESS_BEFORE | + TargetIncludeDirectoriesImpl::PROCESS_AFTER | TargetIncludeDirectoriesImpl::PROCESS_SYSTEM)); } diff --git a/Source/cmTargetPropCommandBase.cxx b/Source/cmTargetPropCommandBase.cxx index 9e30136..e41714a 100644 --- a/Source/cmTargetPropCommandBase.cxx +++ b/Source/cmTargetPropCommandBase.cxx @@ -45,15 +45,26 @@ bool cmTargetPropCommandBase::HandleArguments( this->HandleMissingTarget(args[0]); return false; } - if ((this->Target->GetType() != cmStateEnums::EXECUTABLE) && - (this->Target->GetType() != cmStateEnums::STATIC_LIBRARY) && - (this->Target->GetType() != cmStateEnums::SHARED_LIBRARY) && - (this->Target->GetType() != cmStateEnums::MODULE_LIBRARY) && - (this->Target->GetType() != cmStateEnums::OBJECT_LIBRARY) && - (this->Target->GetType() != cmStateEnums::INTERFACE_LIBRARY) && - (this->Target->GetType() != cmStateEnums::UNKNOWN_LIBRARY)) { - this->SetError("called with non-compilable target type"); - return false; + const bool isRegularTarget = + (this->Target->GetType() == cmStateEnums::EXECUTABLE) || + (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY) || + (this->Target->GetType() == cmStateEnums::SHARED_LIBRARY) || + (this->Target->GetType() == cmStateEnums::MODULE_LIBRARY) || + (this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) || + (this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY) || + (this->Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY); + const bool isCustomTarget = this->Target->GetType() == cmStateEnums::UTILITY; + + if (prop == "SOURCES") { + if (!isRegularTarget && !isCustomTarget) { + this->SetError("called with non-compilable target type"); + return false; + } + } else { + if (!isRegularTarget) { + this->SetError("called with non-compilable target type"); + return false; + } } bool system = false; @@ -76,6 +87,13 @@ bool cmTargetPropCommandBase::HandleArguments( } prepend = true; ++argIndex; + } else if ((flags & PROCESS_AFTER) && args[argIndex] == "AFTER") { + if (args.size() < 3) { + this->SetError("called with incorrect number of arguments"); + return false; + } + prepend = false; + ++argIndex; } if ((flags & PROCESS_REUSE_FROM) && args[argIndex] == "REUSE_FROM") { @@ -131,6 +149,11 @@ bool cmTargetPropCommandBase::ProcessContentArgs( this->SetError("may only set INTERFACE properties on IMPORTED targets"); return false; } + if (this->Target->GetType() == cmStateEnums::UTILITY && + scope != "PRIVATE") { + this->SetError("may only set PRIVATE properties on custom targets"); + return false; + } } return this->PopulateTargetProperies(scope, content, prepend, system); } diff --git a/Source/cmTargetPropCommandBase.h b/Source/cmTargetPropCommandBase.h index 50ac1aa..fc24fe8 100644 --- a/Source/cmTargetPropCommandBase.h +++ b/Source/cmTargetPropCommandBase.h @@ -23,8 +23,9 @@ public: { NO_FLAGS = 0x0, PROCESS_BEFORE = 0x1, - PROCESS_SYSTEM = 0x2, - PROCESS_REUSE_FROM = 0x3 + PROCESS_AFTER = 0x2, + PROCESS_SYSTEM = 0x3, + PROCESS_REUSE_FROM = 0x4 }; bool HandleArguments(std::vector<std::string> const& args, diff --git a/Source/cmTransformDepfile.cxx b/Source/cmTransformDepfile.cxx new file mode 100644 index 0000000..b91e1ce --- /dev/null +++ b/Source/cmTransformDepfile.cxx @@ -0,0 +1,101 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmTransformDepfile.h" + +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include <cm/optional> + +#include "cmsys/FStream.hxx" + +#include "cmGccDepfileReader.h" +#include "cmGccDepfileReaderTypes.h" +#include "cmSystemTools.h" + +namespace { +void WriteFilenameGcc(cmsys::ofstream& fout, const std::string& filename) +{ + for (auto c : filename) { + switch (c) { + case ' ': + fout << "\\ "; + break; + case '\\': + fout << "\\\\"; + break; + default: + fout << c; + break; + } + } +} + +void WriteGccDepfile(cmsys::ofstream& fout, const cmGccDepfileContent& content) +{ + for (auto const& dep : content) { + bool first = true; + for (auto const& rule : dep.rules) { + if (!first) { + fout << " \\\n "; + } + first = false; + WriteFilenameGcc(fout, rule); + } + fout << ':'; + for (auto const& path : dep.paths) { + fout << " \\\n "; + WriteFilenameGcc(fout, path); + } + fout << '\n'; + } +} + +void WriteVsTlog(cmsys::ofstream& fout, const cmGccDepfileContent& content) +{ + for (auto const& dep : content) { + fout << '^'; + bool first = true; + for (auto const& rule : dep.rules) { + if (!first) { + fout << '|'; + } + first = false; + fout << cmSystemTools::ConvertToOutputPath(rule); + } + fout << "\r\n"; + for (auto const& path : dep.paths) { + fout << cmSystemTools::ConvertToOutputPath(path) << "\r\n"; + } + } +} +} + +bool cmTransformDepfile(cmDepfileFormat format, const std::string& prefix, + const std::string& infile, const std::string& outfile) +{ + cmGccDepfileContent content; + if (cmSystemTools::FileExists(infile)) { + auto result = cmReadGccDepfile(infile.c_str(), prefix); + if (!result) { + return false; + } + content = *std::move(result); + } + + cmsys::ofstream fout(outfile.c_str()); + if (!fout) { + return false; + } + switch (format) { + case cmDepfileFormat::GccDepfile: + WriteGccDepfile(fout, content); + break; + case cmDepfileFormat::VsTlog: + WriteVsTlog(fout, content); + break; + } + return true; +} diff --git a/Source/cmTransformDepfile.h b/Source/cmTransformDepfile.h new file mode 100644 index 0000000..792c1aa --- /dev/null +++ b/Source/cmTransformDepfile.h @@ -0,0 +1,14 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <string> + +enum class cmDepfileFormat +{ + GccDepfile, + VsTlog, +}; + +bool cmTransformDepfile(cmDepfileFormat format, const std::string& prefix, + const std::string& infile, const std::string& outfile); diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx index 4eb3b7f..9c41504 100644 --- a/Source/cmVisualStudio10TargetGenerator.cxx +++ b/Source/cmVisualStudio10TargetGenerator.cxx @@ -676,7 +676,7 @@ void cmVisualStudio10TargetGenerator::Generate() cmStrCat(this->DefaultArtifactDir, "\\nasm.props"); ConvertToWindowsSlash(propsLocal); this->Makefile->ConfigureFile(propsTemplate, propsLocal, false, true, - true, true); + true); Elem(e1, "Import").Attribute("Project", propsLocal); } } @@ -1020,9 +1020,9 @@ void cmVisualStudio10TargetGenerator::WriteEmbeddedResourceGroup(Elem& e0) cm::string_view tagName = cm::string_view(p).substr(propNamePrefix.length()); if (!tagName.empty()) { - const std::string& value = *props.GetPropertyValue(p); - if (!value.empty()) { - e2.Element(tagName, value); + cmProp value = props.GetPropertyValue(p); + if (cmNonempty(value)) { + e2.Element(tagName, *value); } } } @@ -2402,27 +2402,28 @@ void cmVisualStudio10TargetGenerator::OutputSourceSpecificFlags( configDefines += *ccdefs; } - // Add precompile headers compile options. - std::string customAndPchOptions = options; + // We have pch state in the following situation: + // 1. We have SKIP_PRECOMPILE_HEADERS == true + // 2. We are creating the pre-compiled header + // 3. We are a different language than the linker language AND pch is + // enabled const std::string pchSource = this->GeneratorTarget->GetPchSource(config, lang); - if (!pchSource.empty() && !sf.GetProperty("SKIP_PRECOMPILE_HEADERS")) { - std::string pchOptions; - if (sf.GetFullPath() == pchSource) { - pchOptions = - this->GeneratorTarget->GetPchCreateCompileOptions(config, lang); - } else { - pchOptions = - this->GeneratorTarget->GetPchUseCompileOptions(config, lang); - } - customAndPchOptions = cmStrCat(customAndPchOptions, ';', pchOptions); - } + const bool skipPCH = + pchSource.empty() || sf.GetPropertyAsBool("SKIP_PRECOMPILE_HEADERS"); + const bool makePCH = (sf.GetFullPath() == pchSource); + const bool useSharedPCH = + !skipPCH && (lang == this->GeneratorTarget->GetLinkerLanguage(config)); + const bool useDifferentLangPCH = + !skipPCH && (lang != this->GeneratorTarget->GetLinkerLanguage(config)); + const bool needsPCHFlags = + (makePCH || useSharedPCH || useDifferentLangPCH); // if we have flags or defines for this config then // use them if (!flags.empty() || !options.empty() || !configDefines.empty() || - !includes.empty() || compileAs || noWinRT || - !customAndPchOptions.empty()) { + !includes.empty() || compileAs || noWinRT || !options.empty() || + needsPCHFlags) { cmGlobalVisualStudio10Generator* gg = this->GlobalGenerator; cmIDEFlagTable const* flagtable = nullptr; const std::string& srclang = source->GetLanguage(); @@ -2455,15 +2456,35 @@ void cmVisualStudio10TargetGenerator::OutputSourceSpecificFlags( } else { clOptions.Parse(flags); } - if (!customAndPchOptions.empty()) { + + if (needsPCHFlags) { + // Add precompile headers compile options. + std::string expandedOptions; + std::string pchOptions; + if (makePCH) { + pchOptions = + this->GeneratorTarget->GetPchCreateCompileOptions(config, lang); + } else if (useSharedPCH) { + std::string pchHeader = + this->GeneratorTarget->GetPchHeader(config, lang); + clOptions.AddFlag("ForcedIncludeFiles", pchHeader); + } else if (useDifferentLangPCH) { + pchOptions = + this->GeneratorTarget->GetPchUseCompileOptions(config, lang); + } + this->LocalGenerator->AppendCompileOptions(expandedOptions, + pchOptions); + clOptions.Parse(expandedOptions); + } + + if (!options.empty()) { std::string expandedOptions; if (configDependentOptions) { this->LocalGenerator->AppendCompileOptions( expandedOptions, - genexInterpreter.Evaluate(customAndPchOptions, "COMPILE_OPTIONS")); + genexInterpreter.Evaluate(options, "COMPILE_OPTIONS")); } else { - this->LocalGenerator->AppendCompileOptions(expandedOptions, - customAndPchOptions); + this->LocalGenerator->AppendCompileOptions(expandedOptions, options); } clOptions.Parse(expandedOptions); } @@ -2786,6 +2807,13 @@ bool cmVisualStudio10TargetGenerator::ComputeClOptions( this->GeneratorTarget->GetPchHeader(configName, linkLanguage); if (this->MSTools && vcxproj == this->ProjectType && pchHeader.empty()) { clOptions.AddFlag("PrecompiledHeader", "NotUsing"); + } else if (this->MSTools && vcxproj == this->ProjectType && + !pchHeader.empty()) { + clOptions.AddFlag("PrecompiledHeader", "Use"); + clOptions.AddFlag("PrecompiledHeaderFile", pchHeader); + std::string pchFile = + this->GeneratorTarget->GetPchFile(configName, linkLanguage); + clOptions.AddFlag("PrecompiledHeaderOutputFile", pchFile); } // Get preprocessor definitions for this directory. @@ -4183,8 +4211,7 @@ void cmVisualStudio10TargetGenerator::WriteProjectReferences(Elem& e0) cmLocalGenerator* lg = dt->GetLocalGenerator(); std::string name = dt->GetName(); std::string path; - cmProp p = dt->GetProperty("EXTERNAL_MSPROJECT"); - if (p) { + if (cmProp p = dt->GetProperty("EXTERNAL_MSPROJECT")) { path = *p; } else { path = cmStrCat(lg->GetCurrentBinaryDirectory(), '/', dt->GetName(), @@ -4972,9 +4999,9 @@ void cmVisualStudio10TargetGenerator::GetCSharpSourceProperties( if (cmHasPrefix(p, propNamePrefix)) { std::string tagName = p.substr(propNamePrefix.length()); if (!tagName.empty()) { - const std::string& val = *props.GetPropertyValue(p); - if (!val.empty()) { - tags[tagName] = val; + cmProp val = props.GetPropertyValue(p); + if (cmNonempty(val)) { + tags[tagName] = *val; } else { tags.erase(tagName); } diff --git a/Source/cmXCodeObject.h b/Source/cmXCodeObject.h index 78d4727..ab7f99e 100644 --- a/Source/cmXCodeObject.h +++ b/Source/cmXCodeObject.h @@ -81,6 +81,13 @@ public: void SetObject(cmXCodeObject* value) { this->Object = value; } cmXCodeObject* GetObject() { return this->Object; } void AddObject(cmXCodeObject* value) { this->List.push_back(value); } + size_t GetObjectCount() { return this->List.size(); } + void InsertObject(size_t position, cmXCodeObject* value) + { + if (position < GetObjectCount()) { + this->List.insert(this->List.begin() + position, value); + } + } void PrependObject(cmXCodeObject* value) { this->List.insert(this->List.begin(), value); diff --git a/Source/cmake.cxx b/Source/cmake.cxx index e655634..1691037 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -132,6 +132,131 @@ namespace { using JsonValueMapType = std::unordered_map<std::string, Json::Value>; #endif +struct CommandArgument +{ + enum struct Values + { + Zero, + One, + Two, + }; + + std::string InvalidSyntaxMessage; + std::string InvalidValueMessage; + std::string Name; + CommandArgument::Values Type; + std::function<bool(std::string const& value, cmake* state)> StoreCall; + + template <typename FunctionType> + CommandArgument(std::string n, CommandArgument::Values t, + FunctionType&& func) + : InvalidSyntaxMessage(cmStrCat("Invalid syntax used with ", n)) + , InvalidValueMessage(cmStrCat("Invalid value used with ", n)) + , Name(std::move(n)) + , Type(t) + , StoreCall(std::forward<FunctionType>(func)) + { + } + + template <typename FunctionType> + CommandArgument(std::string n, std::string failedMsg, + CommandArgument::Values t, FunctionType&& func) + : InvalidSyntaxMessage(cmStrCat("Invalid syntax used with ", n)) + , InvalidValueMessage(std::move(failedMsg)) + , Name(std::move(n)) + , Type(t) + , StoreCall(std::forward<FunctionType>(func)) + { + } + + bool matches(std::string const& input) const + { + return cmHasPrefix(input, this->Name); + } + + template <typename T> + bool parse(std::string const& input, T& index, + std::vector<std::string> const& allArgs, cmake* state) const + { + enum struct ParseMode + { + Valid, + Invalid, + SyntaxError, + ValueError + }; + ParseMode parseState = ParseMode::Valid; + + // argument is the next parameter + if (this->Type == CommandArgument::Values::Zero) { + if (input.size() == this->Name.size()) { + parseState = this->StoreCall(input, state) ? ParseMode::Valid + : ParseMode::Invalid; + } else { + parseState = ParseMode::SyntaxError; + } + + } else if (this->Type == CommandArgument::Values::One) { + if (input.size() == this->Name.size()) { + ++index; + if (index >= allArgs.size() || allArgs[index][0] == '-') { + parseState = ParseMode::ValueError; + } else { + parseState = this->StoreCall(allArgs[index], state) + ? ParseMode::Valid + : ParseMode::Invalid; + } + } else { + // parse the string to get the value + auto possible_value = cm::string_view(input).substr(this->Name.size()); + if (possible_value.empty()) { + parseState = ParseMode::SyntaxError; + parseState = ParseMode::ValueError; + } else if (possible_value[0] == '=') { + possible_value.remove_prefix(1); + if (possible_value.empty()) { + parseState = ParseMode::ValueError; + } else { + parseState = this->StoreCall(std::string(possible_value), state) + ? ParseMode::Valid + : ParseMode::Invalid; + } + } + if (parseState == ParseMode::Valid) { + parseState = this->StoreCall(std::string(possible_value), state) + ? ParseMode::Valid + : ParseMode::Invalid; + } + } + } else if (this->Type == CommandArgument::Values::Two) { + if (input.size() == this->Name.size()) { + if (index + 2 >= allArgs.size() || allArgs[index + 1][0] == '-' || + allArgs[index + 2][0] == '-') { + parseState = ParseMode::ValueError; + } else { + index += 2; + parseState = + this->StoreCall(cmStrCat(allArgs[index - 1], ";", allArgs[index]), + state) + ? ParseMode::Valid + : ParseMode::Invalid; + } + } + } + + if (parseState == ParseMode::SyntaxError) { + cmSystemTools::Error(this->InvalidSyntaxMessage); + } else if (parseState == ParseMode::ValueError) { + cmSystemTools::Error(this->InvalidValueMessage); + } + return (parseState == ParseMode::Valid); + } +}; + +auto IgnoreAndTrueLambda = [](std::string const&, cmake*) -> bool { + return true; +}; + } // namespace static bool cmakeCheckStampFile(const std::string& stampName); @@ -263,7 +388,7 @@ Json::Value cmake::ReportCapabilitiesJson() const } obj["generators"] = generators; obj["fileApi"] = cmFileAPI::ReportCapabilities(); - obj["serverMode"] = true; + obj["serverMode"] = false; return obj; } @@ -386,152 +511,145 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args) { auto findPackageMode = false; auto seenScriptOption = false; - for (unsigned int i = 1; i < args.size(); ++i) { - std::string const& arg = args[i]; - if (cmHasLiteralPrefix(arg, "-D")) { - std::string entry = arg.substr(2); - if (entry.empty()) { - ++i; - if (i < args.size()) { - entry = args[i]; - } else { - cmSystemTools::Error("-D must be followed with VAR=VALUE."); - return false; - } - } - std::string var; - std::string value; - cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED; - if (cmState::ParseCacheEntry(entry, var, value, type)) { + + auto DefineLambda = [](std::string const& entry, cmake* state) -> bool { + std::string var; + std::string value; + cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED; + if (cmState::ParseCacheEntry(entry, var, value, type)) { #ifndef CMAKE_BOOTSTRAP - this->UnprocessedPresetVariables.erase(var); + state->UnprocessedPresetVariables.erase(var); #endif - this->ProcessCacheArg(var, value, type); - } else { - cmSystemTools::Error("Parse error in command line argument: " + arg + - "\n" + "Should be: VAR:type=value\n"); - return false; - } - } else if (cmHasLiteralPrefix(arg, "-W")) { - std::string entry = arg.substr(2); - if (entry.empty()) { - ++i; - if (i < args.size()) { - entry = args[i]; - } else { - cmSystemTools::Error("-W must be followed with [no-]<name>."); - return false; - } - } + state->ProcessCacheArg(var, value, type); + } else { + cmSystemTools::Error(cmStrCat("Parse error in command line argument: ", + entry, "\n Should be: VAR:type=value\n")); + return false; + } + return true; + }; - std::string name; - bool foundNo = false; - bool foundError = false; - unsigned int nameStartPosition = 0; + auto WarningLambda = [](cm::string_view entry, cmake* state) -> bool { + bool foundNo = false; + bool foundError = false; - if (entry.find("no-", nameStartPosition) == 0) { - foundNo = true; - nameStartPosition += 3; - } + if (cmHasLiteralPrefix(entry, "no-")) { + foundNo = true; + entry.remove_prefix(3); + } - if (entry.find("error=", nameStartPosition) == 0) { - foundError = true; - nameStartPosition += 6; - } + if (cmHasLiteralPrefix(entry, "error=")) { + foundError = true; + entry.remove_prefix(6); + } - name = entry.substr(nameStartPosition); - if (name.empty()) { - cmSystemTools::Error("No warning name provided."); - return false; - } + if (entry.empty()) { + cmSystemTools::Error("No warning name provided."); + return false; + } - if (!foundNo && !foundError) { - // -W<name> - this->DiagLevels[name] = std::max(this->DiagLevels[name], DIAG_WARN); - } else if (foundNo && !foundError) { - // -Wno<name> - this->DiagLevels[name] = DIAG_IGNORE; - } else if (!foundNo && foundError) { - // -Werror=<name> - this->DiagLevels[name] = DIAG_ERROR; - } else { - // -Wno-error=<name> - this->DiagLevels[name] = std::min(this->DiagLevels[name], DIAG_WARN); - } - } else if (cmHasLiteralPrefix(arg, "-U")) { - std::string entryPattern = arg.substr(2); - if (entryPattern.empty()) { - ++i; - if (i < args.size()) { - entryPattern = args[i]; - } else { - cmSystemTools::Error("-U must be followed with VAR."); - return false; - } + std::string const name = std::string(entry); + if (!foundNo && !foundError) { + // -W<name> + state->DiagLevels[name] = std::max(state->DiagLevels[name], DIAG_WARN); + } else if (foundNo && !foundError) { + // -Wno<name> + state->DiagLevels[name] = DIAG_IGNORE; + } else if (!foundNo && foundError) { + // -Werror=<name> + state->DiagLevels[name] = DIAG_ERROR; + } else { + // -Wno-error=<name> + // This can downgrade an error to a warning, but should not enable + // or disable a warning in the first place. + auto dli = state->DiagLevels.find(name); + if (dli != state->DiagLevels.end()) { + dli->second = std::min(dli->second, DIAG_WARN); } - cmsys::RegularExpression regex( - cmsys::Glob::PatternToRegex(entryPattern, true, true)); - // go through all cache entries and collect the vars which will be - // removed - std::vector<std::string> entriesToDelete; - std::vector<std::string> cacheKeys = this->State->GetCacheEntryKeys(); - for (std::string const& ck : cacheKeys) { - cmStateEnums::CacheEntryType t = this->State->GetCacheEntryType(ck); - if (t != cmStateEnums::STATIC) { - if (regex.find(ck)) { - entriesToDelete.push_back(ck); - } + } + return true; + }; + + auto UnSetLambda = [](std::string const& entryPattern, + cmake* state) -> bool { + cmsys::RegularExpression regex( + cmsys::Glob::PatternToRegex(entryPattern, true, true)); + // go through all cache entries and collect the vars which will be + // removed + std::vector<std::string> entriesToDelete; + std::vector<std::string> cacheKeys = state->State->GetCacheEntryKeys(); + for (std::string const& ck : cacheKeys) { + cmStateEnums::CacheEntryType t = state->State->GetCacheEntryType(ck); + if (t != cmStateEnums::STATIC) { + if (regex.find(ck)) { + entriesToDelete.push_back(ck); } } + } - // now remove them from the cache - for (std::string const& currentEntry : entriesToDelete) { + // now remove them from the cache + for (std::string const& currentEntry : entriesToDelete) { #ifndef CMAKE_BOOTSTRAP - this->UnprocessedPresetVariables.erase(currentEntry); + state->UnprocessedPresetVariables.erase(currentEntry); #endif - this->State->RemoveCacheEntry(currentEntry); - } - } else if (cmHasLiteralPrefix(arg, "-C")) { - std::string path = arg.substr(2); - if (path.empty()) { - ++i; - if (i < args.size()) { - path = args[i]; - } else { - cmSystemTools::Error("-C must be followed by a file name."); - return false; - } - } - cmSystemTools::Stdout("loading initial cache file " + path + "\n"); - // Resolve script path specified on command line relative to $PWD. - path = cmSystemTools::CollapseFullPath(path); - this->ReadListFile(args, path); - } else if (cmHasLiteralPrefix(arg, "-P")) { - i++; - if (i >= args.size()) { - cmSystemTools::Error("-P must be followed by a file name."); - return false; - } - std::string path = args[i]; - if (path.empty()) { - cmSystemTools::Error("No cmake script provided."); - return false; - } - // Register fake project commands that hint misuse in script mode. - GetProjectCommandsInScriptMode(this->GetState()); - // Documented behaviour of CMAKE{,_CURRENT}_{SOURCE,BINARY}_DIR is to be - // set to $PWD for -P mode. - this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory()); - this->SetHomeOutputDirectory( - cmSystemTools::GetCurrentWorkingDirectory()); - this->ReadListFile(args, path); - seenScriptOption = true; - } else if (arg == "--" && seenScriptOption) { + state->State->RemoveCacheEntry(currentEntry); + } + return true; + }; + + auto ScriptLambda = [&](std::string const& path, cmake* state) -> bool { + // Register fake project commands that hint misuse in script mode. + GetProjectCommandsInScriptMode(state->GetState()); + // Documented behaviour of CMAKE{,_CURRENT}_{SOURCE,BINARY}_DIR is to be + // set to $PWD for -P mode. + state->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory()); + state->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory()); + state->ReadListFile(args, path); + seenScriptOption = true; + return true; + }; + + std::vector<CommandArgument> arguments = { + CommandArgument{ "-D", "-D must be followed with VAR=VALUE.", + CommandArgument::Values::One, DefineLambda }, + CommandArgument{ "-W", "-W must be followed with [no-]<name>.", + CommandArgument::Values::One, WarningLambda }, + CommandArgument{ "-U", "-U must be followed with VAR.", + CommandArgument::Values::One, UnSetLambda }, + CommandArgument{ "-C", "-C must be followed by a file name.", + CommandArgument::Values::One, + [&](std::string const& value, cmake* state) -> bool { + cmSystemTools::Stdout("loading initial cache file " + + value + "\n"); + // Resolve script path specified on command line + // relative to $PWD. + auto path = cmSystemTools::CollapseFullPath(value); + state->ReadListFile(args, path); + return true; + } }, + CommandArgument{ "-P", "-P must be followed by a file name.", + CommandArgument::Values::One, ScriptLambda }, + CommandArgument{ "--find-package", CommandArgument::Values::Zero, + [&](std::string const&, cmake*) -> bool { + findPackageMode = true; + return true; + } }, + }; + for (decltype(args.size()) i = 1; i < args.size(); ++i) { + std::string const& arg = args[i]; + + if (arg == "--" && seenScriptOption) { // Stop processing CMake args and avoid possible errors // when arbitrary args are given to CMake script. break; - } else if (cmHasLiteralPrefix(arg, "--find-package")) { - findPackageMode = true; + } + for (auto const& m : arguments) { + if (m.matches(arg)) { + const bool parsedCorrectly = m.parse(arg, i, args, this); + if (!parsedCorrectly) { + return false; + } + } } } @@ -730,255 +848,341 @@ void cmake::SetArgs(const std::vector<std::string>& args) bool haveToolset = false; bool havePlatform = false; bool haveBArg = false; + bool scriptMode = false; + std::string possibleUnknownArg; #if !defined(CMAKE_BOOTSTRAP) std::string profilingFormat; std::string profilingOutput; std::string presetName; bool listPresets = false; #endif - for (unsigned int i = 1; i < args.size(); ++i) { - std::string const& arg = args[i]; - if (cmHasLiteralPrefix(arg, "-H") || cmHasLiteralPrefix(arg, "-S")) { - std::string path = arg.substr(2); - if (path.empty()) { - ++i; - if (i >= args.size()) { - cmSystemTools::Error("No source directory specified for -S"); - return; - } - path = args[i]; - if (path[0] == '-') { - cmSystemTools::Error("No source directory specified for -S"); - return; - } - } - path = cmSystemTools::CollapseFullPath(path); - cmSystemTools::ConvertToUnixSlashes(path); - this->SetHomeDirectory(path); - // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165 - // NOLINTNEXTLINE(bugprone-branch-clone) - } else if (cmHasLiteralPrefix(arg, "-O")) { - // There is no local generate anymore. Ignore -O option. - } else if (cmHasLiteralPrefix(arg, "-B")) { - std::string path = arg.substr(2); - if (path.empty()) { - ++i; - if (i >= args.size()) { - cmSystemTools::Error("No build directory specified for -B"); - return; - } - path = args[i]; - if (path[0] == '-') { - cmSystemTools::Error("No build directory specified for -B"); - return; - } - } + auto SourceArgLambda = [](std::string const& value, cmake* state) -> bool { + std::string path = cmSystemTools::CollapseFullPath(value); + cmSystemTools::ConvertToUnixSlashes(path); + state->SetHomeDirectory(path); + return true; + }; - path = cmSystemTools::CollapseFullPath(path); - cmSystemTools::ConvertToUnixSlashes(path); - this->SetHomeOutputDirectory(path); - haveBArg = true; - } else if ((i < args.size() - 2) && - cmHasLiteralPrefix(arg, "--check-build-system")) { - this->CheckBuildSystemArgument = args[++i]; - this->ClearBuildSystem = (atoi(args[++i].c_str()) > 0); - } else if ((i < args.size() - 1) && - cmHasLiteralPrefix(arg, "--check-stamp-file")) { - this->CheckStampFile = args[++i]; - } else if ((i < args.size() - 1) && - cmHasLiteralPrefix(arg, "--check-stamp-list")) { - this->CheckStampList = args[++i]; - } else if (arg == "--regenerate-during-build"_s) { - this->RegenerateDuringBuild = true; + auto BuildArgLambda = [&](std::string const& value, cmake* state) -> bool { + std::string path = cmSystemTools::CollapseFullPath(value); + cmSystemTools::ConvertToUnixSlashes(path); + state->SetHomeOutputDirectory(path); + haveBArg = true; + return true; + }; + + auto PlatformLambda = [&](std::string const& value, cmake* state) -> bool { + if (havePlatform) { + cmSystemTools::Error("Multiple -A options not allowed"); + return false; } -#if defined(CMAKE_HAVE_VS_GENERATORS) - else if ((i < args.size() - 1) && - cmHasLiteralPrefix(arg, "--vs-solution-file")) { - this->VSSolutionFile = args[++i]; + state->SetGeneratorPlatform(value); + havePlatform = true; + return true; + }; + + auto ToolsetLamda = [&](std::string const& value, cmake* state) -> bool { + if (haveToolset) { + cmSystemTools::Error("Multiple -T options not allowed"); + return false; } + state->SetGeneratorToolset(value); + haveToolset = true; + return true; + }; + + std::vector<CommandArgument> arguments = { + CommandArgument{ "-S", "No source directory specified for -S", + CommandArgument::Values::One, SourceArgLambda }, + CommandArgument{ "-H", "No source directory specified for -H", + CommandArgument::Values::One, SourceArgLambda }, + CommandArgument{ "-O", CommandArgument::Values::Zero, + IgnoreAndTrueLambda }, + CommandArgument{ "-B", "No build directory specified for -B", + CommandArgument::Values::One, BuildArgLambda }, + CommandArgument{ "-P", "-P must be followed by a file name.", + CommandArgument::Values::One, + [&](std::string const&, cmake*) -> bool { + scriptMode = true; + return true; + } }, + CommandArgument{ "-D", "-D must be followed with VAR=VALUE.", + CommandArgument::Values::One, IgnoreAndTrueLambda }, + CommandArgument{ "-C", "-C must be followed by a file name.", + CommandArgument::Values::One, IgnoreAndTrueLambda }, + CommandArgument{ "-U", "-U must be followed with VAR.", + CommandArgument::Values::One, IgnoreAndTrueLambda }, + CommandArgument{ "-W", "-W must be followed with [no-]<name>.", + CommandArgument::Values::One, IgnoreAndTrueLambda }, + CommandArgument{ "-A", "No platform specified for -A", + CommandArgument::Values::One, PlatformLambda }, + CommandArgument{ "-T", "No toolset specified for -T", + CommandArgument::Values::One, ToolsetLamda }, + + CommandArgument{ "--check-build-system", CommandArgument::Values::Two, + [](std::string const& value, cmake* state) -> bool { + std::vector<std::string> values = cmExpandedList(value); + state->CheckBuildSystemArgument = values[0]; + state->ClearBuildSystem = (atoi(values[1].c_str()) > 0); + return true; + } }, + CommandArgument{ "--check-stamp-file", CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + state->CheckStampFile = value; + return true; + } }, + CommandArgument{ "--check-stamp-list", CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + state->CheckStampList = value; + return true; + } }, + CommandArgument{ "--regenerate-during-build", + CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + state->RegenerateDuringBuild = true; + return true; + } }, + + CommandArgument{ "--find-package", CommandArgument::Values::Zero, + IgnoreAndTrueLambda }, + + CommandArgument{ "--graphviz", "No file specified for --graphviz", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + std::string path = + cmSystemTools::CollapseFullPath(value); + cmSystemTools::ConvertToUnixSlashes(path); + state->GraphVizFile = path; + return true; + } }, + + CommandArgument{ "--debug-trycompile", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout << "debug trycompile on\n"; + state->DebugTryCompileOn(); + return true; + } }, + CommandArgument{ "--debug-output", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout << "Running with debug output on.\n"; + state->SetDebugOutputOn(true); + return true; + } }, + + CommandArgument{ "--log-level", "Invalid level specified for --log-level", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + const auto logLevel = StringToLogLevel(value); + if (logLevel == LogLevel::LOG_UNDEFINED) { + cmSystemTools::Error( + "Invalid level specified for --log-level"); + return false; + } + state->SetLogLevel(logLevel); + state->LogLevelWasSetViaCLI = true; + return true; + } }, + // This is supported for backward compatibility. This option only + // appeared in the 3.15.x release series and was renamed to + // --log-level in 3.16.0 + CommandArgument{ "--loglevel", "Invalid level specified for --loglevel", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + const auto logLevel = StringToLogLevel(value); + if (logLevel == LogLevel::LOG_UNDEFINED) { + cmSystemTools::Error( + "Invalid level specified for --loglevel"); + return false; + } + state->SetLogLevel(logLevel); + state->LogLevelWasSetViaCLI = true; + return true; + } }, + + CommandArgument{ "--log-context", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + state->SetShowLogContext(true); + return true; + } }, + CommandArgument{ + "--debug-find", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout << "Running with debug output on for the `find` commands.\n"; + state->SetDebugFindOutputOn(true); + return true; + } }, + CommandArgument{ "--trace-expand", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout << "Running with expanded trace output on.\n"; + state->SetTrace(true); + state->SetTraceExpand(true); + return true; + } }, + CommandArgument{ "--trace-format", CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + state->SetTrace(true); + const auto traceFormat = StringToTraceFormat(value); + if (traceFormat == TraceFormat::TRACE_UNDEFINED) { + cmSystemTools::Error( + "Invalid format specified for --trace-format. " + "Valid formats are human, json-v1."); + return false; + } + state->SetTraceFormat(traceFormat); + return true; + } }, + CommandArgument{ "--trace-source", CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + std::string file(value); + cmSystemTools::ConvertToUnixSlashes(file); + state->AddTraceSource(file); + state->SetTrace(true); + return true; + } }, + CommandArgument{ "--trace-redirect", CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + std::string file(value); + cmSystemTools::ConvertToUnixSlashes(file); + state->SetTraceFile(file); + state->SetTrace(true); + return true; + } }, + CommandArgument{ "--trace", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout << "Running with trace output on.\n"; + state->SetTrace(true); + state->SetTraceExpand(false); + return true; + } }, + CommandArgument{ "--warn-uninitialized", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout << "Warn about uninitialized values.\n"; + state->SetWarnUninitialized(true); + return true; + } }, + CommandArgument{ "--warn-unused-vars", CommandArgument::Values::Zero, + IgnoreAndTrueLambda }, // Option was removed. + CommandArgument{ "--no-warn-unused-cli", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout + << "Not searching for unused variables given on the " + << "command line.\n"; + state->SetWarnUnusedCli(false); + return true; + } }, + CommandArgument{ + "--check-system-vars", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { + std::cout << "Also check system files when warning about unused and " + << "uninitialized variables.\n"; + state->SetCheckSystemVars(true); + return true; + } } + }; + +#if defined(CMAKE_HAVE_VS_GENERATORS) + arguments.emplace_back("--vs-solution-file", CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + state->VSSolutionFile = value; + return true; + }); #endif - else if (cmHasLiteralPrefix(arg, "-D") || cmHasLiteralPrefix(arg, "-U") || - cmHasLiteralPrefix(arg, "-C")) { - // skip for now - // in case '-[DUC] argval' var' is given, also skip the next - // in case '-[DUC]argval' is given, don't skip the next - if (arg.size() == 2) { - ++i; - } - // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165 - // NOLINTNEXTLINE(bugprone-branch-clone) - } else if (cmHasLiteralPrefix(arg, "-P")) { - // skip for now - i++; - } else if (cmHasLiteralPrefix(arg, "--find-package")) { - // skip for now - i++; - } else if (cmHasLiteralPrefix(arg, "-W")) { - // skip for now - } else if (cmHasLiteralPrefix(arg, "--graphviz=")) { - std::string path = arg.substr(strlen("--graphviz=")); - path = cmSystemTools::CollapseFullPath(path); - cmSystemTools::ConvertToUnixSlashes(path); - this->GraphVizFile = path; - if (this->GraphVizFile.empty()) { - cmSystemTools::Error("No file specified for --graphviz"); - return; - } - } else if (cmHasLiteralPrefix(arg, "--debug-trycompile")) { - std::cout << "debug trycompile on\n"; - this->DebugTryCompileOn(); - } else if (cmHasLiteralPrefix(arg, "--debug-output")) { - std::cout << "Running with debug output on.\n"; - this->SetDebugOutputOn(true); - } else if (cmHasLiteralPrefix(arg, "--log-level=")) { - const auto logLevel = - StringToLogLevel(arg.substr(sizeof("--log-level=") - 1)); - if (logLevel == LogLevel::LOG_UNDEFINED) { - cmSystemTools::Error("Invalid level specified for --log-level"); - return; - } - this->SetLogLevel(logLevel); - this->LogLevelWasSetViaCLI = true; - } else if (cmHasLiteralPrefix(arg, "--loglevel=")) { - // This is supported for backward compatibility. This option only - // appeared in the 3.15.x release series and was renamed to - // --log-level in 3.16.0 - const auto logLevel = - StringToLogLevel(arg.substr(sizeof("--loglevel=") - 1)); - if (logLevel == LogLevel::LOG_UNDEFINED) { - cmSystemTools::Error("Invalid level specified for --loglevel"); - return; - } - this->SetLogLevel(logLevel); - this->LogLevelWasSetViaCLI = true; - } else if (arg == "--log-context"_s) { - this->SetShowLogContext(true); - } else if (cmHasLiteralPrefix(arg, "--debug-find")) { - std::cout << "Running with debug output on for the `find` commands.\n"; - this->SetDebugFindOutputOn(true); - } else if (cmHasLiteralPrefix(arg, "--trace-expand")) { - std::cout << "Running with expanded trace output on.\n"; - this->SetTrace(true); - this->SetTraceExpand(true); - } else if (cmHasLiteralPrefix(arg, "--trace-format=")) { - this->SetTrace(true); - const auto traceFormat = - StringToTraceFormat(arg.substr(strlen("--trace-format="))); - if (traceFormat == TraceFormat::TRACE_UNDEFINED) { - cmSystemTools::Error("Invalid format specified for --trace-format. " - "Valid formats are human, json-v1."); - return; - } - this->SetTraceFormat(traceFormat); - } else if (cmHasLiteralPrefix(arg, "--trace-source=")) { - std::string file = arg.substr(strlen("--trace-source=")); - cmSystemTools::ConvertToUnixSlashes(file); - this->AddTraceSource(file); - this->SetTrace(true); - } else if (cmHasLiteralPrefix(arg, "--trace-redirect=")) { - std::string file = arg.substr(strlen("--trace-redirect=")); - cmSystemTools::ConvertToUnixSlashes(file); - this->SetTraceFile(file); - this->SetTrace(true); - } else if (cmHasLiteralPrefix(arg, "--trace")) { - std::cout << "Running with trace output on.\n"; - this->SetTrace(true); - this->SetTraceExpand(false); - } else if (cmHasLiteralPrefix(arg, "--warn-uninitialized")) { - std::cout << "Warn about uninitialized values.\n"; - this->SetWarnUninitialized(true); - } else if (cmHasLiteralPrefix(arg, "--warn-unused-vars")) { - // Option was removed. - } else if (cmHasLiteralPrefix(arg, "--no-warn-unused-cli")) { - std::cout << "Not searching for unused variables given on the " - << "command line.\n"; - this->SetWarnUnusedCli(false); - } else if (cmHasLiteralPrefix(arg, "--check-system-vars")) { - std::cout << "Also check system files when warning about unused and " - << "uninitialized variables.\n"; - this->SetCheckSystemVars(true); - } else if (cmHasLiteralPrefix(arg, "-A")) { - std::string value = arg.substr(2); - if (value.empty()) { - ++i; - if (i >= args.size()) { - cmSystemTools::Error("No platform specified for -A"); - return; - } - value = args[i]; - } - if (havePlatform) { - cmSystemTools::Error("Multiple -A options not allowed"); - return; - } - this->SetGeneratorPlatform(value); - havePlatform = true; - } else if (cmHasLiteralPrefix(arg, "-T")) { - std::string value = arg.substr(2); - if (value.empty()) { - ++i; - if (i >= args.size()) { - cmSystemTools::Error("No toolset specified for -T"); - return; - } - value = args[i]; - } - if (haveToolset) { - cmSystemTools::Error("Multiple -T options not allowed"); - return; - } - this->SetGeneratorToolset(value); - haveToolset = true; - } else if (cmHasLiteralPrefix(arg, "-G")) { - std::string value = arg.substr(2); - if (value.empty()) { - ++i; - if (i >= args.size()) { - cmSystemTools::Error("No generator specified for -G"); - this->PrintGeneratorList(); - return; - } - value = args[i]; - } - if (!this->CreateAndSetGlobalGenerator(value, true)) { - return; - } + #if !defined(CMAKE_BOOTSTRAP) - } else if (cmHasLiteralPrefix(arg, "--profiling-format=")) { - profilingFormat = arg.substr(strlen("--profiling-format=")); - if (profilingFormat.empty()) { - cmSystemTools::Error("No format specified for --profiling-format"); - } - } else if (cmHasLiteralPrefix(arg, "--profiling-output=")) { - profilingOutput = arg.substr(strlen("--profiling-output=")); - profilingOutput = cmSystemTools::CollapseFullPath(profilingOutput); + arguments.emplace_back("--profiling-format", + "No format specified for --profiling-format", + CommandArgument::Values::One, + [&](std::string const& value, cmake*) -> bool { + profilingFormat = value; + return true; + }); + arguments.emplace_back( + "--profiling-output", "No path specified for --profiling-output", + CommandArgument::Values::One, + [&](std::string const& value, cmake*) -> bool { + profilingOutput = cmSystemTools::CollapseFullPath(value); cmSystemTools::ConvertToUnixSlashes(profilingOutput); - if (profilingOutput.empty()) { - cmSystemTools::Error("No path specified for --profiling-output"); + return true; + }); + arguments.emplace_back("--preset", "No preset specified for --preset", + CommandArgument::Values::One, + [&](std::string const& value, cmake*) -> bool { + presetName = value; + return true; + }); + arguments.emplace_back("--list-presets", CommandArgument::Values::Zero, + [&](std::string const&, cmake*) -> bool { + listPresets = true; + return true; + }); + +#endif + + bool badGeneratorName = false; + CommandArgument generatorCommand( + "-G", "No generator specified for -G", CommandArgument::Values::One, + [&](std::string const& value, cmake* state) -> bool { + bool valid = state->CreateAndSetGlobalGenerator(value, true); + badGeneratorName = !valid; + return valid; + }); + + for (decltype(args.size()) i = 1; i < args.size(); ++i) { + // iterate each argument + std::string const& arg = args[i]; + + // Generator flag has special handling for when to print help + // so it becomes the exception + if (generatorCommand.matches(arg)) { + bool parsed = generatorCommand.parse(arg, i, args, this); + if (!parsed && !badGeneratorName) { + this->PrintGeneratorList(); + return; } - } else if (cmHasLiteralPrefix(arg, "--preset=")) { - presetName = arg.substr(strlen("--preset=")); - if (presetName.empty()) { - cmSystemTools::Error("No preset specified for --preset"); + continue; + } + + bool matched = false; + bool parsedCorrectly = true; // needs to be true so we can ignore + // arguments so as -E + for (auto const& m : arguments) { + if (m.matches(arg)) { + matched = true; + parsedCorrectly = m.parse(arg, i, args, this); + break; } - } else if (cmHasLiteralPrefix(arg, "--list-presets")) { - listPresets = true; -#endif } - // no option assume it is the path to the source or an existing build - else { + + // We have an issue where arguments to a "-P" script mode + // can be provided before the "-P" argument. This means + // that we need to lazily check this argument after checking + // all args. + // Additionally it can't be the source/binary tree location + if (!parsedCorrectly) { + cmSystemTools::Error("Run 'cmake --help' for all supported options."); + exit(1); + } else if (!matched && cmHasLiteralPrefix(arg, "-")) { + possibleUnknownArg = arg; + } else if (!matched) { this->SetDirectoriesFromFile(arg); } - // Empty instance, platform and toolset if only a generator is specified - if (this->GlobalGenerator) { - this->GeneratorInstance = ""; - if (!this->GeneratorPlatformSet) { - this->GeneratorPlatform = ""; - } - if (!this->GeneratorToolsetSet) { - this->GeneratorToolset = ""; - } + } + + if (!possibleUnknownArg.empty() && !scriptMode) { + cmSystemTools::Error(cmStrCat("Unknown argument ", possibleUnknownArg)); + cmSystemTools::Error("Run 'cmake --help' for all supported options."); + exit(1); + } + + // Empty instance, platform and toolset if only a generator is specified + if (this->GlobalGenerator) { + this->GeneratorInstance = ""; + if (!this->GeneratorPlatformSet) { + this->GeneratorPlatform = ""; + } + if (!this->GeneratorToolsetSet) { + this->GeneratorToolset = ""; } } @@ -1951,7 +2155,7 @@ int cmake::ActualConfigure() } } - auto& mf = this->GlobalGenerator->GetMakefiles()[0]; + const auto& mf = this->GlobalGenerator->GetMakefiles()[0]; if (mf->IsOn("CTEST_USE_LAUNCHERS") && !this->State->GetGlobalProperty("RULE_LAUNCH_COMPILE")) { cmSystemTools::Error( @@ -2115,7 +2319,7 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure) #endif // Add any cache args if (!this->SetCacheArgs(args)) { - cmSystemTools::Error("Problem processing arguments. Aborting.\n"); + cmSystemTools::Error("Run 'cmake --help' for all supported options."); return -1; } #ifndef CMAKE_BOOTSTRAP @@ -2293,12 +2497,12 @@ cmProp cmake::GetCacheDefinition(const std::string& name) const return this->State->GetInitializedCacheValue(name); } -void cmake::AddScriptingCommands() +void cmake::AddScriptingCommands() const { GetScriptingCommands(this->GetState()); } -void cmake::AddProjectCommands() +void cmake::AddProjectCommands() const { GetProjectCommands(this->GetState()); } @@ -2573,8 +2777,7 @@ int cmake::CheckBuildSystem() if (this->ClearBuildSystem) { // Get the generator used for this build system. - const char* genName = - cmToCStr(mf.GetDefinition("CMAKE_DEPENDS_GENERATOR")); + std::string genName = mf.GetSafeDefinition("CMAKE_DEPENDS_GENERATOR"); if (!cmNonempty(genName)) { genName = "Unix Makefiles"; } diff --git a/Source/cmake.h b/Source/cmake.h index 1ecf2c2..d936f28 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -411,7 +411,7 @@ public: WorkingMode GetWorkingMode() { return this->CurrentWorkingMode; } //! Debug the try compile stuff by not deleting the files - bool GetDebugTryCompile() { return this->DebugTryCompile; } + bool GetDebugTryCompile() const { return this->DebugTryCompile; } void DebugTryCompileOn() { this->DebugTryCompile = true; } /** @@ -456,11 +456,11 @@ public: void SetShowLogContext(bool b) { this->LogContext = b; } //! Do we want debug output during the cmake run. - bool GetDebugOutput() { return this->DebugOutput; } + bool GetDebugOutput() const { return this->DebugOutput; } void SetDebugOutputOn(bool b) { this->DebugOutput = b; } //! Do we want debug output from the find commands during the cmake run. - bool GetDebugFindOutput() { return this->DebugFindOutput; } + bool GetDebugFindOutput() const { return this->DebugFindOutput; } void SetDebugFindOutputOn(bool b) { this->DebugFindOutput = b; } //! Do we want trace output during the cmake run. @@ -482,11 +482,11 @@ public: void SetTraceFile(std::string const& file); void PrintTraceFormatVersion(); - bool GetWarnUninitialized() { return this->WarnUninitialized; } + bool GetWarnUninitialized() const { return this->WarnUninitialized; } void SetWarnUninitialized(bool b) { this->WarnUninitialized = b; } - bool GetWarnUnusedCli() { return this->WarnUnusedCli; } + bool GetWarnUnusedCli() const { return this->WarnUnusedCli; } void SetWarnUnusedCli(bool b) { this->WarnUnusedCli = b; } - bool GetCheckSystemVars() { return this->CheckSystemVars; } + bool GetCheckSystemVars() const { return this->CheckSystemVars; } void SetCheckSystemVars(bool b) { this->CheckSystemVars = b; } void MarkCliAsUsed(const std::string& variable); @@ -591,8 +591,8 @@ protected: using RegisteredExtraGeneratorsVector = std::vector<cmExternalMakefileProjectGeneratorFactory*>; RegisteredExtraGeneratorsVector ExtraGenerators; - void AddScriptingCommands(); - void AddProjectCommands(); + void AddScriptingCommands() const; + void AddProjectCommands() const; void AddDefaultGenerators(); void AddDefaultExtraGenerators(); @@ -811,6 +811,7 @@ private: F(cxx_std_14) \ F(cxx_std_17) \ F(cxx_std_20) \ + F(cxx_std_23) \ FOR_EACH_CXX98_FEATURE(F) \ FOR_EACH_CXX11_FEATURE(F) \ FOR_EACH_CXX14_FEATURE(F) @@ -820,4 +821,5 @@ private: F(cuda_std_11) \ F(cuda_std_14) \ F(cuda_std_17) \ - F(cuda_std_20) + F(cuda_std_20) \ + F(cuda_std_23) diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index e2ff8b7..73fbfb6 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -20,6 +20,7 @@ #include "cmStateSnapshot.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" +#include "cmTransformDepfile.h" #include "cmUVProcessChain.h" #include "cmUtils.hxx" #include "cmVersion.h" @@ -28,12 +29,17 @@ #if !defined(CMAKE_BOOTSTRAP) # include "cmDependsFortran.h" // For -E cmake_copy_f90_mod callback. # include "cmFileTime.h" -# include "cmServer.h" -# include "cmServerConnection.h" # include "bindexplib.h" #endif +#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES) +# include <algorithm> + +# include "cmCMakePath.h" +# include "cmProcessTools.h" +#endif + #if !defined(CMAKE_BOOTSTRAP) && defined(_WIN32) && !defined(__CYGWIN__) # include "cmVisualStudioWCEPlatformParser.h" #endif @@ -59,15 +65,15 @@ #include "cmsys/Directory.hxx" #include "cmsys/FStream.hxx" #include "cmsys/Process.h" +#include "cmsys/RegularExpression.hxx" #include "cmsys/Terminal.h" -class cmConnection; - int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg, std::vector<std::string>::const_iterator argEnd); int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg, std::vector<std::string>::const_iterator argEnd); +namespace { void CMakeCommandUsage(const char* program) { std::ostringstream errorStream; @@ -121,7 +127,6 @@ void CMakeCommandUsage(const char* program) "(on one volume)\n" << " rm [-rRf] <file/dir>... - remove files or directories, use -f to " "force it, r or R to remove directories and their contents recursively\n" - << " server - start cmake in server mode\n" << " sleep <number>... - sleep for given number of seconds\n" << " tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]\n" << " - create or extract a tar or zip archive\n" @@ -147,8 +152,7 @@ void CMakeCommandUsage(const char* program) cmSystemTools::Error(errorStream.str()); } -static bool cmTarFilesFrom(std::string const& file, - std::vector<std::string>& files) +bool cmTarFilesFrom(std::string const& file, std::vector<std::string>& files) { if (cmSystemTools::FileIsDirectory(file)) { std::ostringstream e; @@ -183,7 +187,7 @@ static bool cmTarFilesFrom(std::string const& file, return true; } -static void cmCatFile(const std::string& fileToAppend) +void cmCatFile(const std::string& fileToAppend) { #ifdef _WIN32 _setmode(fileno(stdout), _O_BINARY); @@ -193,7 +197,7 @@ static void cmCatFile(const std::string& fileToAppend) std::cout << source.rdbuf(); } -static bool cmRemoveDirectory(const std::string& dir, bool recursive = true) +bool cmRemoveDirectory(const std::string& dir, bool recursive = true) { if (cmSystemTools::FileIsSymlink(dir)) { if (!cmSystemTools::RemoveFile(dir)) { @@ -211,9 +215,123 @@ static bool cmRemoveDirectory(const std::string& dir, bool recursive = true) return true; } -static int HandleIWYU(const std::string& runCmd, - const std::string& /* sourceFile */, - const std::vector<std::string>& orig_cmd) +#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES) +class CLIncludeParser : public cmProcessTools::LineParser +{ +public: + CLIncludeParser(cm::string_view includePrefix, cmsys::ofstream& depFile, + std::ostream& output) + : IncludePrefix(includePrefix) + , DepFile(depFile) + , Output(output) + { + } + +private: + bool ProcessLine() override + { + if (cmHasPrefix(this->Line, this->IncludePrefix)) { + this->DepFile << cmCMakePath( + cmTrimWhitespace(this->Line.c_str() + + this->IncludePrefix.size())) + .GenericString() + << std::endl; + } else { + this->Output << this->Line << std::endl << std::flush; + } + + return true; + } + + cm::string_view IncludePrefix; + cmsys::ofstream& DepFile; + std::ostream& Output; +}; + +class CLOutputLogger : public cmProcessTools::OutputLogger +{ +public: + CLOutputLogger(std::ostream& log) + : cmProcessTools::OutputLogger(log) + { + } + + bool ProcessLine() override + { + *this->Log << std::flush; + return true; + } +}; + +int CLCompileAndDependencies(const std::vector<std::string>& args) +{ + std::string depFile; + std::string currentBinaryDir; + std::string filterPrefix; + std::vector<std::string> command; + for (auto it = args.cbegin() + 2; it != args.cend(); it++) { + if (cmHasLiteralPrefix(*it, "--dep-file=")) { + depFile = it->substr(11); + } else if (cmHasLiteralPrefix(*it, "--working-dir=")) { + currentBinaryDir = it->substr(14); + } else if (cmHasLiteralPrefix(*it, "--filter-prefix=")) { + filterPrefix = it->substr(16); + } else if (*it == "--") { + command.insert(command.begin(), ++it, args.cend()); + break; + } else { + return 1; + } + } + + std::unique_ptr<cmsysProcess, void (*)(cmsysProcess*)> cp( + cmsysProcess_New(), cmsysProcess_Delete); + std::vector<const char*> argv(command.size() + 1); + std::transform(command.begin(), command.end(), argv.begin(), + [](std::string const& s) { return s.c_str(); }); + argv.back() = nullptr; + cmsysProcess_SetCommand(cp.get(), argv.data()); + cmsysProcess_SetWorkingDirectory(cp.get(), currentBinaryDir.c_str()); + + cmsys::ofstream fout(depFile.c_str()); + if (!fout) { + return 3; + } + + CLIncludeParser includeParser(filterPrefix, fout, std::cout); + CLOutputLogger errLogger(std::cerr); + + // Start the process. + cmProcessTools::RunProcess(cp.get(), &includeParser, &errLogger); + + int status = 0; + // handle status of process + switch (cmsysProcess_GetState(cp.get())) { + case cmsysProcess_State_Exited: + status = cmsysProcess_GetExitValue(cp.get()); + break; + case cmsysProcess_State_Exception: + status = 1; + break; + case cmsysProcess_State_Error: + status = 2; + break; + default: + break; + } + + if (status != 0) { + // remove the dependencies file because potentially invalid + fout.close(); + cmSystemTools::RemoveFile(depFile); + } + + return status; +} +#endif + +int HandleIWYU(const std::string& runCmd, const std::string& /* sourceFile */, + const std::vector<std::string>& orig_cmd) { // Construct the iwyu command line by taking what was given // and adding all the arguments we give to the compiler. @@ -238,8 +356,8 @@ static int HandleIWYU(const std::string& runCmd, return 0; } -static int HandleTidy(const std::string& runCmd, const std::string& sourceFile, - const std::vector<std::string>& orig_cmd) +int HandleTidy(const std::string& runCmd, const std::string& sourceFile, + const std::vector<std::string>& orig_cmd) { // Construct the clang-tidy command line by taking what was given // and adding our compiler command line. The clang-tidy tool will @@ -268,9 +386,8 @@ static int HandleTidy(const std::string& runCmd, const std::string& sourceFile, return ret; } -static int HandleLWYU(const std::string& runCmd, - const std::string& /* sourceFile */, - const std::vector<std::string>&) +int HandleLWYU(const std::string& runCmd, const std::string& /* sourceFile */, + const std::vector<std::string>&) { // Construct the ldd -r -u (link what you use lwyu) command line // ldd -u -r lwuy target @@ -301,9 +418,8 @@ static int HandleLWYU(const std::string& runCmd, return 0; } -static int HandleCppLint(const std::string& runCmd, - const std::string& sourceFile, - const std::vector<std::string>&) +int HandleCppLint(const std::string& runCmd, const std::string& sourceFile, + const std::vector<std::string>&) { // Construct the cpplint command line. std::vector<std::string> cpplint_cmd = cmExpandedList(runCmd, true); @@ -329,9 +445,8 @@ static int HandleCppLint(const std::string& runCmd, return 0; } -static int HandleCppCheck(const std::string& runCmd, - const std::string& sourceFile, - const std::vector<std::string>& orig_cmd) +int HandleCppCheck(const std::string& runCmd, const std::string& sourceFile, + const std::vector<std::string>& orig_cmd) { // Construct the cpplint command line. std::vector<std::string> cppcheck_cmd = cmExpandedList(runCmd, true); @@ -394,7 +509,7 @@ struct CoCompiler bool NoOriginalCommand; }; -static const std::array<CoCompiler, 5> CoCompilers = { +const std::array<CoCompiler, 5> CoCompilers = { { // Table of options and handlers. { "--cppcheck=", HandleCppCheck, false }, { "--cpplint=", HandleCppLint, false }, @@ -408,6 +523,7 @@ struct CoCompileJob std::string Command; CoCompileHandler Handler; }; +} // called when args[0] == "__run_co_compile" int cmcmd::HandleCoCompileCommands(std::vector<std::string> const& args) @@ -589,7 +705,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, } else if (args[2] == "--ignore-eol") { filesDiffer = cmsys::SystemTools::TextFilesDiffer(args[3], args[4]); } else { - ::CMakeCommandUsage(args[0].c_str()); + CMakeCommandUsage(args[0].c_str()); return 2; } @@ -624,8 +740,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, } } if (outValid) { - // The def file already exists and all input files are older than the - // existing def file. + // The def file already exists and all input files are older than + // the existing def file. return 0; } } @@ -1156,6 +1272,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, cmStateSnapshot snapshot = cm.GetCurrentSnapshot(); snapshot.GetDirectory().SetCurrentBinary(startOutDir); snapshot.GetDirectory().SetCurrentSource(startDir); + snapshot.GetDirectory().SetRelativePathTopSource(homeDir.c_str()); + snapshot.GetDirectory().SetRelativePathTopBinary(homeOutDir.c_str()); cmMakefile mf(cm.GetGlobalGenerator(), snapshot); auto lgd = cm.GetGlobalGenerator()->CreateLocalGenerator(&mf); @@ -1165,12 +1283,19 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, return 1; } +#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES) + // Internal CMake compiler dependencies filtering + if (args[1] == "cmake_cl_compile_depends") { + return CLCompileAndDependencies(args); + } +#endif + // Internal CMake link script support. if (args[1] == "cmake_link_script" && args.size() >= 3) { return cmcmd::ExecuteLinkScript(args); } -#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_NINJA) +#if !defined(CMAKE_BOOTSTRAP) // Internal CMake ninja dependency scanning support. if (args[1] == "cmake_ninja_depends") { return cmcmd_cmake_ninja_depends(args.begin() + 2, args.end()); @@ -1359,47 +1484,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, } if (args[1] == "server") { - const std::string pipePrefix = "--pipe="; - bool supportExperimental = false; - bool isDebug = false; - std::string pipe; - - for (auto const& arg : cmMakeRange(args).advance(2)) { - if (arg == "--experimental") { - supportExperimental = true; - } else if (arg == "--debug") { - pipe.clear(); - isDebug = true; - } else if (cmHasPrefix(arg, pipePrefix)) { - isDebug = false; - pipe = arg.substr(pipePrefix.size()); - if (pipe.empty()) { - cmSystemTools::Error("No pipe given after --pipe="); - return 2; - } - } else { - cmSystemTools::Error("Unknown argument for server mode"); - return 1; - } - } -#if !defined(CMAKE_BOOTSTRAP) - cmConnection* conn; - if (isDebug) { - conn = new cmServerStdIoConnection; - } else { - conn = new cmServerPipeConnection(pipe); - } - cmServer server(conn, supportExperimental); - std::string errorMessage; - if (server.Serve(&errorMessage)) { - return 0; - } - cmSystemTools::Error(errorMessage); -#else - static_cast<void>(supportExperimental); - static_cast<void>(isDebug); - cmSystemTools::Error("CMake was not built with server mode enabled"); -#endif + cmSystemTools::Error( + "CMake server mode has been removed in favor of the file-api."); return 1; } @@ -1435,9 +1521,26 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, return cmcmd::WindowsCEEnvironment("9.0", args[2]); } #endif + + // Internal depfile transformation + if (args[1] == "cmake_transform_depfile" && args.size() == 6) { + auto format = cmDepfileFormat::GccDepfile; + if (args[2] == "gccdepfile") { + format = cmDepfileFormat::GccDepfile; + } else if (args[2] == "vstlog") { + format = cmDepfileFormat::VsTlog; + } else { + return 1; + } + std::string prefix = args[3]; + if (prefix == "./") { + prefix.clear(); + } + return cmTransformDepfile(format, prefix, args[4], args[5]) ? 0 : 1; + } } - ::CMakeCommandUsage(args[0].c_str()); + CMakeCommandUsage(args[0].c_str()); return 1; } @@ -1737,7 +1840,6 @@ int cmcmd::WindowsCEEnvironment(const char* version, const std::string& name) int cmcmd::RunPreprocessor(const std::vector<std::string>& command, const std::string& intermediate_file) { - cmUVProcessChainBuilder builder; uv_fs_t fs_req; @@ -1769,7 +1871,6 @@ int cmcmd::RunPreprocessor(const std::vector<std::string>& command, return 1; } - return 0; } @@ -1787,19 +1888,56 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args) std::cerr << "Invalid cmake_llvm_rc arguments"; return 1; } + const std::string& intermediate_file = args[3]; const std::string& source_file = args[2]; std::vector<std::string> preprocess; std::vector<std::string> resource_compile; std::vector<std::string>* pArgTgt = &preprocess; + + static const cmsys::RegularExpression llvm_rc_only_single_arg("^[-/](N|Y)"); + static const cmsys::RegularExpression llvm_rc_only_double_arg( + "^[-/](C|LN|L)(.)?"); + static const cmsys::RegularExpression common_double_arg( + "^[-/](D|U|I|FO|fo|Fo)(.)?"); + bool acceptNextArg = false; + bool skipNextArg = false; for (std::string const& arg : cmMakeRange(args).advance(4)) { - // We use ++ as seperator between the preprocessing step definition and the - // rc compilation step becase we need to prepend a -- to seperate the + if (skipNextArg) { + skipNextArg = false; + continue; + } + // We use ++ as seperator between the preprocessing step definition and + // the rc compilation step becase we need to prepend a -- to seperate the // source file properly from other options when using clang-cl for // preprocessing. if (arg == "++") { pArgTgt = &resource_compile; + skipNextArg = false; + acceptNextArg = true; } else { + cmsys::RegularExpressionMatch match; + if (!acceptNextArg) { + if (common_double_arg.find(arg.c_str(), match)) { + acceptNextArg = match.match(2).empty(); + } else { + if (llvm_rc_only_single_arg.find(arg.c_str(), match)) { + if (pArgTgt == &preprocess) { + continue; + } + } else if (llvm_rc_only_double_arg.find(arg.c_str(), match)) { + if (pArgTgt == &preprocess) { + skipNextArg = match.match(2).empty(); + continue; + } + acceptNextArg = match.match(2).empty(); + } else if (pArgTgt == &resource_compile) { + continue; + } + } + } else { + acceptNextArg = false; + } if (arg.find("SOURCE_DIR") != std::string::npos) { std::string sourceDirArg = arg; cmSystemTools::ReplaceString( @@ -1819,10 +1957,15 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args) std::cerr << "Empty resource compilation command"; return 1; } + // Since we might have skipped the last argument to llvm-rc + // we need to make sure the llvm-rc source file is present in the + // commandline + if (resource_compile.back() != intermediate_file) { + resource_compile.push_back(intermediate_file); + } auto result = RunPreprocessor(preprocess, intermediate_file); if (result != 0) { - cmSystemTools::RemoveFile(intermediate_file); return result; } @@ -1986,7 +2129,7 @@ static bool RunCommand(const char* comment, << NumberFormatter(exitFormat, retCode) << ") with the following output:\n" << output; - } else { + } else if (verbose) { // always print the output of the command, unless // it is the dumb rc command banner if (output.find("Resource Compiler Version") == std::string::npos) { @@ -2109,8 +2252,8 @@ int cmVSLink::LinkIncremental() // http://blogs.msdn.com/zakramer/archive/2006/05/22/603558.aspx // 1. Compiler compiles the application and generates the *.obj files. - // 2. An empty manifest file is generated if this is a clean build and if - // not the previous one is reused. + // 2. An empty manifest file is generated if this is a clean build and + // if not the previous one is reused. // 3. The resource compiler (rc.exe) compiles the *.manifest file to a // *.res file. // 4. Linker generates the binary (EXE or DLL) with the /incremental diff --git a/Source/ctest.cxx b/Source/ctest.cxx index d0bc061..600df1d 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -111,6 +111,7 @@ static const char* cmDocumentationOptions[][2] = { { "--no-subproject-summary", "Disable timing summary information for " "subprojects." }, + { "--test-dir <dir>", "Specify the directory in which to look for tests." }, { "--build-and-test", "Configure, build and run a test." }, { "--build-target", "Specify a specific target to build." }, { "--build-nocmake", "Run the build without running cmake first." }, |