/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmConfigure.h" // IWYU pragma: keep #include "cmMakefile.h" #include #include #include #include #include #include #include #include #include #include #include #include // IWYU pragma: keep #include #include #include #ifndef CMAKE_BOOTSTRAP # include # include #endif #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" #include "cmCommandArgumentParserHelper.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmCustomCommandTypes.h" #include "cmExecutionStatus.h" #include "cmExpandedCommandArgument.h" // IWYU pragma: keep #include "cmExportBuildFileGenerator.h" #include "cmFileLockPool.h" #include "cmFunctionBlocker.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" #include "cmGeneratorExpressionEvaluationFile.h" #include "cmGlobalGenerator.h" #include "cmInstallGenerator.h" // IWYU pragma: keep #include "cmInstallSubdirectoryGenerator.h" #include "cmList.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMessageType.h" #include "cmRange.h" #include "cmSourceFile.h" #include "cmSourceFileLocation.h" #include "cmState.h" #include "cmStateDirectory.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetLinkLibraryType.h" #include "cmTest.h" #include "cmTestGenerator.h" // IWYU pragma: keep #include "cmVersion.h" #include "cmWorkingDirectory.h" #include "cmake.h" #ifndef CMAKE_BOOTSTRAP # include "cmMakefileProfilingData.h" # include "cmVariableWatch.h" #endif #ifdef CMake_ENABLE_DEBUGGER # include "cmDebuggerAdapter.h" #endif #ifndef __has_feature # define __has_feature(x) 0 #endif // Select a recursion limit that fits within the stack size. // See stack size flags in '../CompileFlags.cmake'. #ifndef CMake_DEFAULT_RECURSION_LIMIT # if __has_feature(address_sanitizer) # define CMake_DEFAULT_RECURSION_LIMIT 400 # elif defined(_MSC_VER) && defined(_DEBUG) # define CMake_DEFAULT_RECURSION_LIMIT 600 # elif defined(__ibmxl__) && defined(__linux) # define CMake_DEFAULT_RECURSION_LIMIT 600 # else # define CMake_DEFAULT_RECURSION_LIMIT 1000 # endif #endif class cmMessenger; cmDirectoryId::cmDirectoryId(std::string s) : String(std::move(s)) { } // default is not to be building executables cmMakefile::cmMakefile(cmGlobalGenerator* globalGenerator, cmStateSnapshot const& snapshot) : GlobalGenerator(globalGenerator) , StateSnapshot(snapshot) { this->IsSourceFileTryCompile = false; this->CheckSystemVars = this->GetCMakeInstance()->GetCheckSystemVars(); this->SuppressSideEffects = false; // Setup the default include complaint regular expression (match nothing). this->ComplainFileRegularExpression = "^$"; this->DefineFlags = " "; this->cmDefineRegex.compile("#([ \t]*)cmakedefine[ \t]+([A-Za-z_0-9]*)"); this->cmDefine01Regex.compile("#([ \t]*)cmakedefine01[ \t]+([A-Za-z_0-9]*)"); this->cmAtVarRegex.compile("(@[A-Za-z_0-9/.+-]+@)"); this->cmNamedCurly.compile("^[A-Za-z0-9/_.+-]+{"); this->StateSnapshot = this->StateSnapshot.GetState()->CreatePolicyScopeSnapshot( this->StateSnapshot); // Enter a policy level for this directory. this->PushPolicy(); // push empty loop block this->PushLoopBlockBarrier(); // By default the check is not done. It is enabled by // cmListFileCache in the top level if necessary. this->CheckCMP0000 = false; #if !defined(CMAKE_BOOTSTRAP) this->AddSourceGroup("", "^.*$"); this->AddSourceGroup("Source Files", CM_SOURCE_REGEX); this->AddSourceGroup("Header Files", CM_HEADER_REGEX); this->AddSourceGroup("Precompile Header File", CM_PCH_REGEX); this->AddSourceGroup("CMake Rules", "\\.rule$"); this->AddSourceGroup("Resources", CM_RESOURCE_REGEX); this->AddSourceGroup("Object Files", "\\.(lo|o|obj)$"); this->ObjectLibrariesSourceGroupIndex = this->SourceGroups.size(); this->SourceGroups.emplace_back("Object Libraries", "^MATCH_NO_SOURCES$"); #endif } cmMakefile::~cmMakefile() = default; cmDirectoryId cmMakefile::GetDirectoryId() const { // Use the instance pointer value to uniquely identify this directory. // If we ever need to expose this to CMake language code we should // add a read-only property in cmMakefile::GetProperty. char buf[32]; snprintf(buf, sizeof(buf), "(%p)", static_cast(this)); // cast avoids format warning return std::string(buf); } void cmMakefile::IssueMessage(MessageType t, std::string const& text) const { if (!this->ExecutionStatusStack.empty()) { if ((t == MessageType::FATAL_ERROR) || (t == MessageType::INTERNAL_ERROR)) { this->ExecutionStatusStack.back()->SetNestedError(); } } this->GetCMakeInstance()->IssueMessage(t, text, this->Backtrace); } Message::LogLevel cmMakefile::GetCurrentLogLevel() const { const cmake* cmakeInstance = this->GetCMakeInstance(); const Message::LogLevel logLevelCliOrDefault = cmakeInstance->GetLogLevel(); assert("Expected a valid log level here" && logLevelCliOrDefault != Message::LogLevel::LOG_UNDEFINED); Message::LogLevel result = logLevelCliOrDefault; // If the log-level was set via the command line option, it takes precedence // over the CMAKE_MESSAGE_LOG_LEVEL variable. if (!cmakeInstance->WasLogLevelSetViaCLI()) { const Message::LogLevel logLevelFromVar = cmake::StringToLogLevel( this->GetSafeDefinition("CMAKE_MESSAGE_LOG_LEVEL")); if (logLevelFromVar != Message::LogLevel::LOG_UNDEFINED) { result = logLevelFromVar; } } return result; } bool cmMakefile::CheckCMP0037(std::string const& targetName, cmStateEnums::TargetType targetType) const { MessageType messageType = MessageType::AUTHOR_WARNING; std::ostringstream e; bool issueMessage = false; switch (this->GetPolicyStatus(cmPolicies::CMP0037)) { case cmPolicies::WARN: if (targetType != cmStateEnums::INTERFACE_LIBRARY) { e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0037) << "\n"; issueMessage = true; } CM_FALLTHROUGH; case cmPolicies::OLD: break; case cmPolicies::NEW: case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: issueMessage = true; messageType = MessageType::FATAL_ERROR; break; } if (issueMessage) { e << "The target name \"" << targetName << "\" is reserved or not valid for certain " "CMake features, such as generator expressions, and may result " "in undefined behavior."; this->IssueMessage(messageType, e.str()); if (messageType == MessageType::FATAL_ERROR) { return false; } } return true; } void cmMakefile::MaybeWarnCMP0074(std::string const& rootVar, cmValue rootDef, cm::optional const& rootEnv) { // Warn if a _ROOT variable we may use is set. if ((rootDef || rootEnv) && this->WarnedCMP0074.insert(rootVar).second) { std::ostringstream w; w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0074) << "\n"; if (rootDef) { w << "CMake variable " << rootVar << " is set to:\n" << " " << *rootDef << "\n"; } if (rootEnv) { w << "Environment variable " << rootVar << " is set to:\n" << " " << *rootEnv << "\n"; } w << "For compatibility, CMake is ignoring the variable."; this->IssueMessage(MessageType::AUTHOR_WARNING, w.str()); } } void cmMakefile::MaybeWarnCMP0144(std::string const& rootVAR, cmValue rootDEF, cm::optional const& rootENV) { // Warn if a _ROOT variable we may use is set. if ((rootDEF || rootENV) && this->WarnedCMP0144.insert(rootVAR).second) { std::ostringstream w; w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0144) << "\n"; if (rootDEF) { w << "CMake variable " << rootVAR << " is set to:\n" << " " << *rootDEF << "\n"; } if (rootENV) { w << "Environment variable " << rootVAR << " is set to:\n" << " " << *rootENV << "\n"; } w << "For compatibility, find_package is ignoring the variable, but " "code in a .cmake module might still use it."; this->IssueMessage(MessageType::AUTHOR_WARNING, w.str()); } } cmBTStringRange cmMakefile::GetIncludeDirectoriesEntries() const { return this->StateSnapshot.GetDirectory().GetIncludeDirectoriesEntries(); } cmBTStringRange cmMakefile::GetCompileOptionsEntries() const { return this->StateSnapshot.GetDirectory().GetCompileOptionsEntries(); } cmBTStringRange cmMakefile::GetCompileDefinitionsEntries() const { return this->StateSnapshot.GetDirectory().GetCompileDefinitionsEntries(); } cmBTStringRange cmMakefile::GetLinkOptionsEntries() const { return this->StateSnapshot.GetDirectory().GetLinkOptionsEntries(); } cmBTStringRange cmMakefile::GetLinkDirectoriesEntries() const { return this->StateSnapshot.GetDirectory().GetLinkDirectoriesEntries(); } cmListFileBacktrace cmMakefile::GetBacktrace() const { return this->Backtrace; } cmFindPackageStack cmMakefile::GetFindPackageStack() const { return this->FindPackageStack; } void cmMakefile::PrintCommandTrace(cmListFileFunction const& lff, cmListFileBacktrace const& bt, CommandMissingFromStack missing) const { // Check if current file in the list of requested to trace... std::vector const& trace_only_this_files = this->GetCMakeInstance()->GetTraceSources(); std::string const& full_path = bt.Top().FilePath; std::string const& only_filename = cmSystemTools::GetFilenameName(full_path); bool trace = trace_only_this_files.empty(); if (!trace) { for (std::string const& file : trace_only_this_files) { std::string::size_type const pos = full_path.rfind(file); trace = (pos != std::string::npos) && ((pos + file.size()) == full_path.size()) && (only_filename == cmSystemTools::GetFilenameName(file)); if (trace) { break; } } // Do nothing if current file wasn't requested for trace... if (!trace) { return; } } std::ostringstream msg; std::vector args; std::string temp; bool expand = this->GetCMakeInstance()->GetTraceExpand(); args.reserve(lff.Arguments().size()); for (cmListFileArgument const& arg : lff.Arguments()) { if (expand && arg.Delim != cmListFileArgument::Bracket) { temp = arg.Value; this->ExpandVariablesInString(temp); args.push_back(temp); } else { args.push_back(arg.Value); } } cm::optional const& deferId = bt.Top().DeferId; switch (this->GetCMakeInstance()->GetTraceFormat()) { case cmake::TraceFormat::JSONv1: { #ifndef CMAKE_BOOTSTRAP Json::Value val; Json::StreamWriterBuilder builder; builder["indentation"] = ""; val["file"] = full_path; val["line"] = static_cast(lff.Line()); if (lff.Line() != lff.LineEnd()) { val["line_end"] = static_cast(lff.LineEnd()); } if (deferId) { val["defer"] = *deferId; } val["cmd"] = lff.OriginalName(); val["args"] = Json::Value(Json::arrayValue); for (std::string const& arg : args) { val["args"].append(arg); } val["time"] = cmSystemTools::GetTime(); val["frame"] = (missing == CommandMissingFromStack::Yes ? 1 : 0) + static_cast(this->ExecutionStatusStack.size()); val["global_frame"] = (missing == CommandMissingFromStack::Yes ? 1 : 0) + static_cast(this->RecursionDepth); msg << Json::writeString(builder, val); #endif break; } case cmake::TraceFormat::Human: msg << full_path << "(" << lff.Line() << "):"; if (deferId) { msg << "DEFERRED:" << *deferId << ":"; } msg << " " << lff.OriginalName() << "("; for (std::string const& arg : args) { msg << arg << " "; } msg << ")"; break; case cmake::TraceFormat::Undefined: msg << "INTERNAL ERROR: Trace format is Undefined"; break; } auto& f = this->GetCMakeInstance()->GetTraceFile(); if (f) { f << msg.str() << '\n'; } else { cmSystemTools::Message(msg.str()); } } // Helper class to make sure the call stack is valid. class cmMakefileCall { public: cmMakefileCall(cmMakefile* mf, cmListFileFunction const& lff, cm::optional deferId, cmExecutionStatus& status) : Makefile(mf) { cmListFileContext const& lfc = cmListFileContext::FromListFileFunction( lff, this->Makefile->StateSnapshot.GetExecutionListFile(), std::move(deferId)); this->Makefile->Backtrace = this->Makefile->Backtrace.Push(lfc); ++this->Makefile->RecursionDepth; this->Makefile->ExecutionStatusStack.push_back(&status); #if !defined(CMAKE_BOOTSTRAP) this->ProfilingDataRAII = this->Makefile->GetCMakeInstance()->CreateProfilingEntry( "script", lff.LowerCaseName(), [&lff, &lfc]() -> Json::Value { Json::Value argsValue = Json::objectValue; if (!lff.Arguments().empty()) { std::string args; for (auto const& a : lff.Arguments()) { args = cmStrCat(args, args.empty() ? "" : " ", a.Value); } argsValue["functionArgs"] = args; } argsValue["location"] = cmStrCat(lfc.FilePath, ':', std::to_string(lfc.Line)); return argsValue; }); #endif #ifdef CMake_ENABLE_DEBUGGER if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->Makefile->GetCMakeInstance() ->GetDebugAdapter() ->OnBeginFunctionCall(mf, lfc.FilePath, lff); } #endif } ~cmMakefileCall() { #if !defined(CMAKE_BOOTSTRAP) this->ProfilingDataRAII.reset(); #endif this->Makefile->ExecutionStatusStack.pop_back(); --this->Makefile->RecursionDepth; this->Makefile->Backtrace = this->Makefile->Backtrace.Pop(); #ifdef CMake_ENABLE_DEBUGGER if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->Makefile->GetCMakeInstance() ->GetDebugAdapter() ->OnEndFunctionCall(); } #endif } cmMakefileCall(const cmMakefileCall&) = delete; cmMakefileCall& operator=(const cmMakefileCall&) = delete; private: cmMakefile* Makefile; #if !defined(CMAKE_BOOTSTRAP) cm::optional ProfilingDataRAII; #endif }; void cmMakefile::OnExecuteCommand(std::function callback) { this->ExecuteCommandCallback = std::move(callback); } bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff, cmExecutionStatus& status, cm::optional deferId) { bool result = true; // quick return if blocked if (this->IsFunctionBlocked(lff, status)) { // No error. return result; } if (this->ExecuteCommandCallback) { this->ExecuteCommandCallback(); } // Place this call on the call stack. cmMakefileCall stack_manager(this, lff, std::move(deferId), status); static_cast(stack_manager); // Check for maximum recursion depth. size_t depthLimit = this->GetRecursionDepthLimit(); if (this->RecursionDepth > depthLimit) { std::ostringstream e; e << "Maximum recursion depth of " << depthLimit << " exceeded"; this->IssueMessage(MessageType::FATAL_ERROR, e.str()); cmSystemTools::SetFatalErrorOccurred(); return false; } // Lookup the command prototype. if (cmState::Command command = this->GetState()->GetCommandByExactName(lff.LowerCaseName())) { // Decide whether to invoke the command. if (!cmSystemTools::GetFatalErrorOccurred()) { // if trace is enabled, print out invoke information if (this->GetCMakeInstance()->GetTrace()) { this->PrintCommandTrace(lff, this->Backtrace); } // Try invoking the command. bool invokeSucceeded = command(lff.Arguments(), status); bool hadNestedError = status.GetNestedError(); if (!invokeSucceeded || hadNestedError) { if (!hadNestedError) { // The command invocation requested that we report an error. std::string const error = cmStrCat(lff.OriginalName(), ' ', status.GetError()); this->IssueMessage(MessageType::FATAL_ERROR, error); } result = false; if (this->GetCMakeInstance()->GetWorkingMode() != cmake::NORMAL_MODE) { cmSystemTools::SetFatalErrorOccurred(); } } if (this->GetCMakeInstance()->HasScriptModeExitCode() && this->GetCMakeInstance()->GetWorkingMode() == cmake::SCRIPT_MODE) { // pass-through the exit code from inner cmake_language(EXIT) , // possibly from include() or similar command... status.SetExitCode(this->GetCMakeInstance()->GetScriptModeExitCode()); } } } else { if (!cmSystemTools::GetFatalErrorOccurred()) { std::string error = cmStrCat("Unknown CMake command \"", lff.OriginalName(), "\"."); this->IssueMessage(MessageType::FATAL_ERROR, error); result = false; cmSystemTools::SetFatalErrorOccurred(); } } return result; } bool cmMakefile::IsImportedTargetGlobalScope() const { return this->CurrentImportedTargetScope == ImportedTargetScope::Global; } class cmMakefile::IncludeScope { public: IncludeScope(cmMakefile* mf, std::string const& filenametoread, bool noPolicyScope); ~IncludeScope(); void Quiet() { this->ReportError = false; } IncludeScope(const IncludeScope&) = delete; IncludeScope& operator=(const IncludeScope&) = delete; private: cmMakefile* Makefile; bool NoPolicyScope; bool CheckCMP0011 = false; bool ReportError = true; void EnforceCMP0011(); }; cmMakefile::IncludeScope::IncludeScope(cmMakefile* mf, std::string const& filenametoread, bool noPolicyScope) : Makefile(mf) , NoPolicyScope(noPolicyScope) { this->Makefile->Backtrace = this->Makefile->Backtrace.Push( cmListFileContext::FromListFilePath(filenametoread)); this->Makefile->PushFunctionBlockerBarrier(); this->Makefile->StateSnapshot = this->Makefile->GetState()->CreateIncludeFileSnapshot( this->Makefile->StateSnapshot, filenametoread); if (!this->NoPolicyScope) { // Check CMP0011 to determine the policy scope type. switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0011)) { case cmPolicies::WARN: // We need to push a scope to detect whether the script sets // any policies that would affect the includer and therefore // requires a warning. We use a weak scope to simulate OLD // behavior by allowing policy changes to affect the includer. this->Makefile->PushPolicy(true); this->CheckCMP0011 = true; break; case cmPolicies::OLD: // OLD behavior is to not push a scope at all. this->NoPolicyScope = true; break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: // We should never make this policy required, but we handle it // here just in case. this->CheckCMP0011 = true; CM_FALLTHROUGH; case cmPolicies::NEW: // NEW behavior is to push a (strong) scope. this->Makefile->PushPolicy(); break; } } } cmMakefile::IncludeScope::~IncludeScope() { if (!this->NoPolicyScope) { // If we need to enforce policy CMP0011 then the top entry is the // one we pushed above. If the entry is empty, then the included // script did not set any policies that might affect the includer so // we do not need to enforce the policy. if (this->CheckCMP0011 && !this->Makefile->StateSnapshot.HasDefinedPolicyCMP0011()) { this->CheckCMP0011 = false; } // Pop the scope we pushed for the script. this->Makefile->PopPolicy(); // We enforce the policy after the script's policy stack entry has // been removed. if (this->CheckCMP0011) { this->EnforceCMP0011(); } } this->Makefile->PopSnapshot(this->ReportError); this->Makefile->PopFunctionBlockerBarrier(this->ReportError); this->Makefile->Backtrace = this->Makefile->Backtrace.Pop(); } void cmMakefile::IncludeScope::EnforceCMP0011() { // We check the setting of this policy again because the included // script might actually set this policy for its includer. switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0011)) { case cmPolicies::WARN: // Warn because the user did not set this policy. { std::ostringstream w; w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0011) << "\n" << "The included script\n " << this->Makefile->GetBacktrace().Top().FilePath << "\n" << "affects policy settings. " << "CMake is implying the NO_POLICY_SCOPE option for compatibility, " << "so the effects are applied to the including context."; this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str()); } break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: { std::ostringstream e; /* clang-format off */ e << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0011) << "\n" << "The included script\n " << this->Makefile->GetBacktrace().Top().FilePath << "\n" << "affects policy settings, so it requires this policy to be set."; /* clang-format on */ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); } break; case cmPolicies::OLD: case cmPolicies::NEW: // The script set this policy. We assume the purpose of the // script is to initialize policies for its includer, and since // the policy is now set for later scripts, we do not warn. break; } } bool cmMakefile::ReadDependentFile(const std::string& filename, bool noPolicyScope) { if (cmValue def = this->GetDefinition("CMAKE_CURRENT_LIST_FILE")) { this->AddDefinition("CMAKE_PARENT_LIST_FILE", *def); } std::string filenametoread = cmSystemTools::CollapseFullPath( filename, this->GetCurrentSourceDirectory()); IncludeScope incScope(this, filenametoread, noPolicyScope); #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( this, filenametoread); } #endif cmListFile listFile; if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(), this->Backtrace)) { #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); } #endif return false; } #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( filenametoread, listFile.Functions); } #endif this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { incScope.Quiet(); } return true; } class cmMakefile::ListFileScope { public: ListFileScope(cmMakefile* mf, std::string const& filenametoread) : Makefile(mf) { this->Makefile->Backtrace = this->Makefile->Backtrace.Push( cmListFileContext::FromListFilePath(filenametoread)); this->Makefile->StateSnapshot = this->Makefile->GetState()->CreateInlineListFileSnapshot( this->Makefile->StateSnapshot, filenametoread); assert(this->Makefile->StateSnapshot.IsValid()); this->Makefile->PushFunctionBlockerBarrier(); } ~ListFileScope() { this->Makefile->PopSnapshot(this->ReportError); this->Makefile->PopFunctionBlockerBarrier(this->ReportError); this->Makefile->Backtrace = this->Makefile->Backtrace.Pop(); } void Quiet() { this->ReportError = false; } ListFileScope(const ListFileScope&) = delete; ListFileScope& operator=(const ListFileScope&) = delete; private: cmMakefile* Makefile; bool ReportError = true; }; class cmMakefile::DeferScope { public: DeferScope(cmMakefile* mf, std::string const& deferredInFile) : Makefile(mf) { cmListFileContext lfc; lfc.Line = cmListFileContext::DeferPlaceholderLine; lfc.FilePath = deferredInFile; this->Makefile->Backtrace = this->Makefile->Backtrace.Push(lfc); this->Makefile->DeferRunning = true; } ~DeferScope() { this->Makefile->DeferRunning = false; this->Makefile->Backtrace = this->Makefile->Backtrace.Pop(); } DeferScope(const DeferScope&) = delete; DeferScope& operator=(const DeferScope&) = delete; private: cmMakefile* Makefile; }; class cmMakefile::DeferCallScope { public: DeferCallScope(cmMakefile* mf, std::string const& deferredFromFile) : Makefile(mf) { this->Makefile->StateSnapshot = this->Makefile->GetState()->CreateDeferCallSnapshot( this->Makefile->StateSnapshot, deferredFromFile); assert(this->Makefile->StateSnapshot.IsValid()); } ~DeferCallScope() { this->Makefile->PopSnapshot(); } DeferCallScope(const DeferCallScope&) = delete; DeferCallScope& operator=(const DeferCallScope&) = delete; private: cmMakefile* Makefile; }; bool cmMakefile::ReadListFile(const std::string& filename) { std::string filenametoread = cmSystemTools::CollapseFullPath( filename, this->GetCurrentSourceDirectory()); ListFileScope scope(this, filenametoread); #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( this, filenametoread); } #endif cmListFile listFile; if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(), this->Backtrace)) { #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); } #endif return false; } #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( filenametoread, listFile.Functions); } #endif this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { scope.Quiet(); } return true; } bool cmMakefile::ReadListFileAsString(const std::string& content, const std::string& virtualFileName) { std::string filenametoread = cmSystemTools::CollapseFullPath( virtualFileName, this->GetCurrentSourceDirectory()); ListFileScope scope(this, filenametoread); cmListFile listFile; if (!listFile.ParseString(content.c_str(), virtualFileName.c_str(), this->GetMessenger(), this->Backtrace)) { return false; } #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( filenametoread, listFile.Functions); } #endif this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { scope.Quiet(); } return true; } void cmMakefile::RunListFile(cmListFile const& listFile, std::string const& filenametoread, DeferCommands* defer) { // add this list file to the list of dependencies this->ListFiles.push_back(filenametoread); std::string currentParentFile = this->GetSafeDefinition("CMAKE_PARENT_LIST_FILE"); std::string currentFile = this->GetSafeDefinition("CMAKE_CURRENT_LIST_FILE"); this->AddDefinition("CMAKE_CURRENT_LIST_FILE", filenametoread); this->AddDefinition("CMAKE_CURRENT_LIST_DIR", cmSystemTools::GetFilenamePath(filenametoread)); this->MarkVariableAsUsed("CMAKE_PARENT_LIST_FILE"); this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_FILE"); this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_DIR"); // Run the parsed commands. const size_t numberFunctions = listFile.Functions.size(); for (size_t i = 0; i < numberFunctions; ++i) { cmExecutionStatus status(*this); this->ExecuteCommand(listFile.Functions[i], status); if (cmSystemTools::GetFatalErrorOccurred()) { break; } if (status.HasExitCode()) { // cmake_language EXIT was requested, early break. this->GetCMakeInstance()->SetScriptModeExitCode(status.GetExitCode()); break; } if (status.GetReturnInvoked()) { this->RaiseScope(status.GetReturnVariables()); // Exit early due to return command. break; } } // Run any deferred commands. if (defer) { // Add a backtrace level indicating calls are deferred. DeferScope scope(this, filenametoread); // Iterate by index in case one deferred call schedules another. // NOLINTNEXTLINE(modernize-loop-convert) for (size_t i = 0; i < defer->Commands.size(); ++i) { DeferCommand& d = defer->Commands[i]; if (d.Id.empty()) { // Canceled. continue; } // Mark as executed. std::string id = std::move(d.Id); // The deferred call may have come from another file. DeferCallScope callScope(this, d.FilePath); cmExecutionStatus status(*this); this->ExecuteCommand(d.Command, status, std::move(id)); if (cmSystemTools::GetFatalErrorOccurred()) { break; } } } this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentParentFile); this->AddDefinition("CMAKE_CURRENT_LIST_FILE", currentFile); this->AddDefinition("CMAKE_CURRENT_LIST_DIR", cmSystemTools::GetFilenamePath(currentFile)); this->MarkVariableAsUsed("CMAKE_PARENT_LIST_FILE"); this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_FILE"); this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_DIR"); } void cmMakefile::EnforceDirectoryLevelRules() const { // Diagnose a violation of CMP0000 if necessary. if (this->CheckCMP0000) { std::ostringstream msg; msg << "No cmake_minimum_required command is present. " << "A line of code such as\n" << " cmake_minimum_required(VERSION " << cmVersion::GetMajorVersion() << "." << cmVersion::GetMinorVersion() << ")\n" << "should be added at the top of the file. " << "The version specified may be lower if you wish to " << "support older CMake versions for this project. " << "For more information run " << "\"cmake --help-policy CMP0000\"."; switch (this->GetPolicyStatus(cmPolicies::CMP0000)) { case cmPolicies::WARN: // Warn because the user did not provide a minimum required // version. this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING, msg.str(), this->Backtrace); CM_FALLTHROUGH; case cmPolicies::OLD: // OLD behavior is to use policy version 2.4 set in // cmListFileCache. break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: // NEW behavior is to issue an error. this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, msg.str(), this->Backtrace); cmSystemTools::SetFatalErrorOccurred(); break; } } } void cmMakefile::AddEvaluationFile( const std::string& inputFile, const std::string& targetName, std::unique_ptr outputName, std::unique_ptr condition, const std::string& newLineCharacter, mode_t permissions, bool inputIsContent) { this->EvaluationFiles.push_back( cm::make_unique( inputFile, targetName, std::move(outputName), std::move(condition), inputIsContent, newLineCharacter, permissions, this->GetPolicyStatus(cmPolicies::CMP0070))); } const std::vector>& cmMakefile::GetEvaluationFiles() const { return this->EvaluationFiles; } std::vector> const& cmMakefile::GetExportBuildFileGenerators() const { return this->ExportBuildFileGenerators; } void cmMakefile::RemoveExportBuildFileGeneratorCMP0024( cmExportBuildFileGenerator* gen) { auto it = std::find_if(this->ExportBuildFileGenerators.begin(), this->ExportBuildFileGenerators.end(), [gen](std::unique_ptr const& p) { return p.get() == gen; }); if (it != this->ExportBuildFileGenerators.end()) { this->ExportBuildFileGenerators.erase(it); } } void cmMakefile::AddExportBuildFileGenerator( std::unique_ptr gen) { this->ExportBuildFileGenerators.emplace_back(std::move(gen)); } namespace { struct file_not_persistent { bool operator()(const std::string& path) const { return !(path.find("CMakeTmp") == std::string::npos && cmSystemTools::FileExists(path)); } }; } void cmMakefile::AddGeneratorAction(GeneratorAction&& action) { assert(!this->GeneratorActionsInvoked); this->GeneratorActions.emplace_back(std::move(action), this->Backtrace); } void cmMakefile::GeneratorAction::operator()(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt) { if (cc) { CCAction(lg, lfbt, std::move(cc)); } else { assert(Action); Action(lg, lfbt); } } void cmMakefile::DoGenerate(cmLocalGenerator& lg) { // do all the variable expansions here this->ExpandVariablesCMP0019(); // give all the commands a chance to do something // after the file has been parsed before generation for (auto& action : this->GeneratorActions) { action.Value(lg, action.Backtrace); } this->GeneratorActionsInvoked = true; // 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 // during processing as that would make it a transient file that can't // influence the build process cm::erase_if(this->OutputFiles, file_not_persistent()); // if a configured file is used as input for another configured file, // and then deleted it will show up in the input list files so we // need to scan those too cm::erase_if(this->ListFiles, file_not_persistent()); } // Generate the output file void cmMakefile::Generate(cmLocalGenerator& lg) { this->DoGenerate(lg); cmValue oldValue = this->GetDefinition("CMAKE_BACKWARDS_COMPATIBILITY"); if (oldValue && cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, *oldValue, "2.4")) { this->GetCMakeInstance()->IssueMessage( MessageType::FATAL_ERROR, "You have set CMAKE_BACKWARDS_COMPATIBILITY to a CMake version less " "than 2.4. This version of CMake only supports backwards compatibility " "with CMake 2.4 or later. For compatibility with older versions please " "use any CMake 2.8.x release or lower.", this->Backtrace); } } namespace { // There are still too many implicit backtraces through cmMakefile. As a // workaround we reset the backtrace temporarily. struct BacktraceGuard { BacktraceGuard(cmListFileBacktrace& lfbt, cmListFileBacktrace current) : Backtrace(lfbt) , Previous(lfbt) { this->Backtrace = std::move(current); } ~BacktraceGuard() { this->Backtrace = std::move(this->Previous); } private: cmListFileBacktrace& Backtrace; cmListFileBacktrace Previous; }; } bool cmMakefile::ValidateCustomCommand( const cmCustomCommandLines& commandLines) const { // TODO: More strict? for (cmCustomCommandLine const& cl : commandLines) { if (!cl.empty() && !cl[0].empty() && cl[0][0] == '"') { std::ostringstream e; e << "COMMAND may not contain literal quotes:\n " << cl[0] << "\n"; this->IssueMessage(MessageType::FATAL_ERROR, e.str()); return false; } } return true; } cmTarget* cmMakefile::GetCustomCommandTarget( const std::string& target, cmObjectLibraryCommands objLibCommands, const cmListFileBacktrace& lfbt) const { auto realTarget = target; auto ai = this->AliasTargets.find(target); if (ai != this->AliasTargets.end()) { realTarget = ai->second; } // Find the target to which to add the custom command. auto ti = this->Targets.find(realTarget); if (ti == this->Targets.end()) { MessageType messageType = MessageType::AUTHOR_WARNING; bool issueMessage = false; std::ostringstream e; switch (this->GetPolicyStatus(cmPolicies::CMP0040)) { case cmPolicies::WARN: e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0040) << "\n"; issueMessage = true; CM_FALLTHROUGH; case cmPolicies::OLD: break; case cmPolicies::NEW: case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: issueMessage = true; messageType = MessageType::FATAL_ERROR; break; } if (issueMessage) { if (cmTarget const* t = this->FindTargetToUse(target)) { if (t->IsImported()) { e << "TARGET '" << target << "' is IMPORTED and does not build here."; } else { e << "TARGET '" << target << "' was not created in this directory."; } } else { e << "No TARGET '" << target << "' has been created in this directory."; } this->GetCMakeInstance()->IssueMessage(messageType, e.str(), lfbt); } return nullptr; } cmTarget* t = &ti->second; if (objLibCommands == cmObjectLibraryCommands::Reject && t->GetType() == cmStateEnums::OBJECT_LIBRARY) { std::ostringstream e; e << "Target \"" << target << "\" is an OBJECT library " "that may not have PRE_BUILD, PRE_LINK, or POST_BUILD commands."; this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str(), lfbt); return nullptr; } if (t->GetType() == cmStateEnums::INTERFACE_LIBRARY) { std::ostringstream e; e << "Target \"" << target << "\" is an INTERFACE library " "that may not have PRE_BUILD, PRE_LINK, or POST_BUILD commands."; this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str(), lfbt); return nullptr; } return t; } cmTarget* cmMakefile::AddCustomCommandToTarget( const std::string& target, cmCustomCommandType type, std::unique_ptr cc) { const auto& byproducts = cc->GetByproducts(); const auto& commandLines = cc->GetCommandLines(); cmTarget* t = this->GetCustomCommandTarget( target, cmObjectLibraryCommands::Reject, this->Backtrace); // Validate custom commands. if (!t || !this->ValidateCustomCommand(commandLines)) { return t; } // Always create the byproduct sources and mark them generated. this->CreateGeneratedOutputs(byproducts); cc->RecordPolicyValues(this->GetStateSnapshot()); // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( std::move(cc), [this, t, type](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, std::unique_ptr tcc) { BacktraceGuard guard(this->Backtrace, lfbt); tcc->SetBacktrace(lfbt); detail::AddCustomCommandToTarget(lg, cmCommandOrigin::Project, t, type, std::move(tcc)); }); return t; } void cmMakefile::AddCustomCommandToOutput( std::unique_ptr cc, const CommandSourceCallback& callback, bool replace) { const auto& outputs = cc->GetOutputs(); const auto& byproducts = cc->GetByproducts(); const auto& commandLines = cc->GetCommandLines(); // Make sure there is at least one output. if (outputs.empty()) { cmSystemTools::Error("Attempt to add a custom rule with no output!"); return; } // Validate custom commands. if (!this->ValidateCustomCommand(commandLines)) { return; } // Always create the output sources and mark them generated. this->CreateGeneratedOutputs(outputs); this->CreateGeneratedOutputs(byproducts); cc->RecordPolicyValues(this->GetStateSnapshot()); // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( std::move(cc), [this, replace, callback](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, std::unique_ptr tcc) { BacktraceGuard guard(this->Backtrace, lfbt); tcc->SetBacktrace(lfbt); cmSourceFile* sf = detail::AddCustomCommandToOutput( lg, cmCommandOrigin::Project, std::move(tcc), replace); if (callback && sf) { callback(sf); } }); } void cmMakefile::AddCustomCommandOldStyle( const std::string& target, const std::vector& outputs, const std::vector& depends, const std::string& source, const cmCustomCommandLines& commandLines, const char* comment) { auto cc = cm::make_unique(); cc->SetDepends(depends); cc->SetCommandLines(commandLines); cc->SetComment(comment); // Translate the old-style signature to one of the new-style // signatures. if (source == target) { // In the old-style signature if the source and target were the // same then it added a post-build rule to the target. Preserve // this behavior. this->AddCustomCommandToTarget(target, cmCustomCommandType::POST_BUILD, std::move(cc)); return; } auto ti = this->Targets.find(target); cmTarget* t = ti != this->Targets.end() ? &ti->second : nullptr; auto addRuleFileToTarget = [=](cmSourceFile* sf) { // If the rule was added to the source (and not a .rule file), // then add the source to the target to make sure the rule is // included. if (!sf->GetPropertyAsBool("__CMAKE_RULE")) { if (t) { t->AddSource(sf->ResolveFullPath()); } else { cmSystemTools::Error("Attempt to add a custom rule to a target " "that does not exist yet for target " + target); } } }; // Each output must get its own copy of this rule. cmsys::RegularExpression sourceFiles( "\\.(C|M|c|c\\+\\+|cc|cpp|cxx|mpp|ixx|cppm|ccm|cxxm|c\\+\\+m|cu|m|mm|" "rc|def|r|odl|idl|hpj|bat|h|h\\+\\+|" "hm|hpp|hxx|in|txx|inl)$"); // Choose whether to use a main dependency. if (sourceFiles.find(source)) { // The source looks like a real file. Use it as the main dependency. for (std::string const& output : outputs) { auto cc1 = cm::make_unique(*cc); cc1->SetOutputs(output); cc1->SetMainDependency(source); this->AddCustomCommandToOutput(std::move(cc1), addRuleFileToTarget); } } else { cc->AppendDepends({ source }); // The source may not be a real file. Do not use a main dependency. for (std::string const& output : outputs) { auto cc1 = cm::make_unique(*cc); cc1->SetOutputs(output); this->AddCustomCommandToOutput(std::move(cc1), addRuleFileToTarget); } } } void cmMakefile::AppendCustomCommandToOutput( const std::string& output, const std::vector& depends, const cmImplicitDependsList& implicit_depends, const cmCustomCommandLines& commandLines) { // Validate custom commands. if (this->ValidateCustomCommand(commandLines)) { // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( [this, output, depends, implicit_depends, commandLines](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt) { BacktraceGuard guard(this->Backtrace, lfbt); detail::AppendCustomCommandToOutput(lg, lfbt, output, depends, implicit_depends, commandLines); }); } } cmTarget* cmMakefile::AddUtilityCommand(const std::string& utilityName, bool excludeFromAll, std::unique_ptr cc) { const auto& depends = cc->GetDepends(); const auto& byproducts = cc->GetByproducts(); const auto& commandLines = cc->GetCommandLines(); cmTarget* target = this->AddNewUtilityTarget(utilityName, excludeFromAll); // Validate custom commands. if ((commandLines.empty() && depends.empty()) || !this->ValidateCustomCommand(commandLines)) { return target; } // Always create the byproduct sources and mark them generated. this->CreateGeneratedOutputs(byproducts); cc->RecordPolicyValues(this->GetStateSnapshot()); // Dispatch command creation to allow generator expressions in outputs. this->AddGeneratorAction( std::move(cc), [this, target](cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, std::unique_ptr tcc) { BacktraceGuard guard(this->Backtrace, lfbt); tcc->SetBacktrace(lfbt); detail::AddUtilityCommand(lg, cmCommandOrigin::Project, target, std::move(tcc)); }); return target; } static void s_AddDefineFlag(std::string const& flag, std::string& dflags) { // remove any \n\r std::string::size_type initSize = dflags.size(); dflags += ' '; dflags += flag; std::string::iterator flagStart = dflags.begin() + initSize + 1; std::replace(flagStart, dflags.end(), '\n', ' '); std::replace(flagStart, dflags.end(), '\r', ' '); } void cmMakefile::AddDefineFlag(std::string const& flag) { if (flag.empty()) { return; } // Update the string used for the old DEFINITIONS property. s_AddDefineFlag(flag, this->DefineFlagsOrig); // If this is really a definition, update COMPILE_DEFINITIONS. if (this->ParseDefineFlag(flag, false)) { return; } // Add this flag that does not look like a definition. s_AddDefineFlag(flag, this->DefineFlags); } static void s_RemoveDefineFlag(std::string const& flag, std::string& dflags) { std::string::size_type const len = flag.length(); // Remove all instances of the flag that are surrounded by // whitespace or the beginning/end of the string. for (std::string::size_type lpos = dflags.find(flag, 0); lpos != std::string::npos; lpos = dflags.find(flag, lpos)) { std::string::size_type rpos = lpos + len; if ((lpos <= 0 || cmIsSpace(dflags[lpos - 1])) && (rpos >= dflags.size() || cmIsSpace(dflags[rpos]))) { dflags.erase(lpos, len); } else { ++lpos; } } } void cmMakefile::RemoveDefineFlag(std::string const& flag) { // Check the length of the flag to remove. if (flag.empty()) { return; } // Update the string used for the old DEFINITIONS property. s_RemoveDefineFlag(flag, this->DefineFlagsOrig); // If this is really a definition, update COMPILE_DEFINITIONS. if (this->ParseDefineFlag(flag, true)) { return; } // Remove this flag that does not look like a definition. s_RemoveDefineFlag(flag, this->DefineFlags); } void cmMakefile::AddCompileDefinition(std::string const& option) { this->AppendProperty("COMPILE_DEFINITIONS", option); } void cmMakefile::AddCompileOption(std::string const& option) { this->AppendProperty("COMPILE_OPTIONS", option); } void cmMakefile::AddLinkOption(std::string const& option) { this->AppendProperty("LINK_OPTIONS", option); } void cmMakefile::AddLinkDirectory(std::string const& directory, bool before) { if (before) { this->StateSnapshot.GetDirectory().PrependLinkDirectoriesEntry( BT(directory, this->Backtrace)); } else { this->StateSnapshot.GetDirectory().AppendLinkDirectoriesEntry( BT(directory, this->Backtrace)); } } bool cmMakefile::ParseDefineFlag(std::string const& def, bool remove) { // Create a regular expression to match valid definitions. static cmsys::RegularExpression valid("^[-/]D[A-Za-z_][A-Za-z0-9_]*(=.*)?$"); // Make sure the definition matches. if (!valid.find(def)) { return false; } // Definitions with non-trivial values require a policy check. static cmsys::RegularExpression trivial( "^[-/]D[A-Za-z_][A-Za-z0-9_]*(=[A-Za-z0-9_.]+)?$"); if (!trivial.find(def)) { // This definition has a non-trivial value. switch (this->GetPolicyStatus(cmPolicies::CMP0005)) { case cmPolicies::WARN: this->IssueMessage(MessageType::AUTHOR_WARNING, cmPolicies::GetPolicyWarning(cmPolicies::CMP0005)); CM_FALLTHROUGH; case cmPolicies::OLD: // OLD behavior is to not escape the value. We should not // convert the definition to use the property. return false; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: this->IssueMessage( MessageType::FATAL_ERROR, cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0005)); return false; case cmPolicies::NEW: // NEW behavior is to escape the value. Proceed to convert it // to an entry in the property. break; } } // Get the definition part after the flag. const char* define = def.c_str() + 2; if (remove) { if (cmValue cdefs = this->GetProperty("COMPILE_DEFINITIONS")) { // Expand the list. cmList defs{ *cdefs }; // Recompose the list without the definition. defs.remove_items({ define }); // Store the new list. this->SetProperty("COMPILE_DEFINITIONS", defs.to_string()); } } else { // Append the definition to the directory property. this->AppendProperty("COMPILE_DEFINITIONS", define); } return true; } void cmMakefile::InitializeFromParent(cmMakefile* parent) { this->SystemIncludeDirectories = parent->SystemIncludeDirectories; // define flags this->DefineFlags = parent->DefineFlags; this->DefineFlagsOrig = parent->DefineFlagsOrig; // Include transform property. There is no per-config version. { const char* prop = "IMPLICIT_DEPENDS_INCLUDE_TRANSFORM"; this->SetProperty(prop, parent->GetProperty(prop)); } // compile definitions property and per-config versions cmPolicies::PolicyStatus polSt = this->GetPolicyStatus(cmPolicies::CMP0043); if (polSt == cmPolicies::WARN || polSt == cmPolicies::OLD) { this->SetProperty("COMPILE_DEFINITIONS", parent->GetProperty("COMPILE_DEFINITIONS")); std::vector configs = this->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig); for (std::string const& config : configs) { std::string defPropName = cmStrCat("COMPILE_DEFINITIONS_", cmSystemTools::UpperCase(config)); cmValue prop = parent->GetProperty(defPropName); this->SetProperty(defPropName, prop); } } // labels this->SetProperty("LABELS", parent->GetProperty("LABELS")); // link libraries this->SetProperty("LINK_LIBRARIES", parent->GetProperty("LINK_LIBRARIES")); // the initial project name this->StateSnapshot.SetProjectName(parent->StateSnapshot.GetProjectName()); // Copy include regular expressions. this->ComplainFileRegularExpression = parent->ComplainFileRegularExpression; // Imported targets. this->ImportedTargets = parent->ImportedTargets; // Non-global Alias targets. this->AliasTargets = parent->AliasTargets; // Recursion depth. this->RecursionDepth = parent->RecursionDepth; } void cmMakefile::AddInstallGenerator(std::unique_ptr g) { if (g) { this->InstallGenerators.push_back(std::move(g)); } } void cmMakefile::AddTestGenerator(std::unique_ptr g) { if (g) { this->TestGenerators.push_back(std::move(g)); } } void cmMakefile::PushFunctionScope(std::string const& fileName, const cmPolicies::PolicyMap& pm) { this->StateSnapshot = this->GetState()->CreateFunctionCallSnapshot( this->StateSnapshot, fileName); assert(this->StateSnapshot.IsValid()); this->PushLoopBlockBarrier(); #if !defined(CMAKE_BOOTSTRAP) this->GetGlobalGenerator()->GetFileLockPool().PushFunctionScope(); #endif this->PushFunctionBlockerBarrier(); this->PushPolicy(true, pm); } void cmMakefile::PopFunctionScope(bool reportError) { this->PopPolicy(); this->PopSnapshot(reportError); this->PopFunctionBlockerBarrier(reportError); #if !defined(CMAKE_BOOTSTRAP) this->GetGlobalGenerator()->GetFileLockPool().PopFunctionScope(); #endif this->PopLoopBlockBarrier(); } void cmMakefile::PushMacroScope(std::string const& fileName, const cmPolicies::PolicyMap& pm) { this->StateSnapshot = this->GetState()->CreateMacroCallSnapshot(this->StateSnapshot, fileName); assert(this->StateSnapshot.IsValid()); this->PushFunctionBlockerBarrier(); this->PushPolicy(true, pm); } void cmMakefile::PopMacroScope(bool reportError) { this->PopPolicy(); this->PopSnapshot(reportError); this->PopFunctionBlockerBarrier(reportError); } bool cmMakefile::IsRootMakefile() const { return !this->StateSnapshot.GetBuildsystemDirectoryParent().IsValid(); } class cmMakefile::BuildsystemFileScope { public: BuildsystemFileScope(cmMakefile* mf) : Makefile(mf) { std::string currentStart = cmStrCat(this->Makefile->StateSnapshot.GetDirectory().GetCurrentSource(), "/CMakeLists.txt"); this->Makefile->StateSnapshot.SetListFile(currentStart); this->Makefile->StateSnapshot = this->Makefile->StateSnapshot.GetState()->CreatePolicyScopeSnapshot( this->Makefile->StateSnapshot); this->Makefile->PushFunctionBlockerBarrier(); this->GG = mf->GetGlobalGenerator(); this->CurrentMakefile = this->GG->GetCurrentMakefile(); this->Snapshot = this->GG->GetCMakeInstance()->GetCurrentSnapshot(); this->GG->GetCMakeInstance()->SetCurrentSnapshot(this->Snapshot); this->GG->SetCurrentMakefile(mf); #if !defined(CMAKE_BOOTSTRAP) this->GG->GetFileLockPool().PushFileScope(); #endif } ~BuildsystemFileScope() { this->Makefile->PopFunctionBlockerBarrier(this->ReportError); this->Makefile->PopSnapshot(this->ReportError); #if !defined(CMAKE_BOOTSTRAP) this->GG->GetFileLockPool().PopFileScope(); #endif this->GG->SetCurrentMakefile(this->CurrentMakefile); this->GG->GetCMakeInstance()->SetCurrentSnapshot(this->Snapshot); } void Quiet() { this->ReportError = false; } BuildsystemFileScope(const BuildsystemFileScope&) = delete; BuildsystemFileScope& operator=(const BuildsystemFileScope&) = delete; private: cmMakefile* Makefile; cmGlobalGenerator* GG; cmMakefile* CurrentMakefile; cmStateSnapshot Snapshot; bool ReportError = true; }; void cmMakefile::Configure() { std::string currentStart = cmStrCat( this->StateSnapshot.GetDirectory().GetCurrentSource(), "/CMakeLists.txt"); // Add the bottom of all backtraces within this directory. // We will never pop this scope because it should be available // for messages during the generate step too. this->Backtrace = this->Backtrace.Push(cmListFileContext::FromListFilePath(currentStart)); BuildsystemFileScope scope(this); // make sure the CMakeFiles dir is there std::string filesDir = cmStrCat( this->StateSnapshot.GetDirectory().GetCurrentBinary(), "/CMakeFiles"); cmSystemTools::MakeDirectory(filesDir); assert(cmSystemTools::FileExists(currentStart, true)); this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentStart); #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( this, currentStart); } #endif cmListFile listFile; if (!listFile.ParseFile(currentStart.c_str(), this->GetMessenger(), this->Backtrace)) { #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); } #endif return; } #ifdef CMake_ENABLE_DEBUGGER if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( currentStart, listFile.Functions); } #endif if (this->IsRootMakefile()) { bool hasVersion = false; // search for the right policy command for (cmListFileFunction const& func : listFile.Functions) { if (func.LowerCaseName() == "cmake_minimum_required"_s) { hasVersion = true; break; } } // if no policy command is found this is an error if they use any // non advanced functions or a lot of functions if (!hasVersion) { bool isProblem = true; if (listFile.Functions.size() < 30) { // the list of simple commands DO NOT ADD TO THIS LIST!!!!! // these commands must have backwards compatibility forever and // and that is a lot longer than your tiny mind can comprehend mortal std::set allowedCommands; allowedCommands.insert("project"); allowedCommands.insert("set"); allowedCommands.insert("if"); allowedCommands.insert("endif"); allowedCommands.insert("else"); allowedCommands.insert("elseif"); allowedCommands.insert("add_executable"); allowedCommands.insert("add_library"); allowedCommands.insert("target_link_libraries"); allowedCommands.insert("option"); allowedCommands.insert("message"); isProblem = false; for (cmListFileFunction const& func : listFile.Functions) { if (!cm::contains(allowedCommands, func.LowerCaseName())) { isProblem = true; break; } } } if (isProblem) { // Tell the top level cmMakefile to diagnose // this violation of CMP0000. this->SetCheckCMP0000(true); // Implicitly set the version for the user. cmPolicies::ApplyPolicyVersion(this, 2, 4, 0, cmPolicies::WarnCompat::Off); } } bool hasProject = false; // search for a project command for (cmListFileFunction const& func : listFile.Functions) { if (func.LowerCaseName() == "project"_s) { hasProject = true; break; } } // if no project command is found, add one if (!hasProject) { this->GetCMakeInstance()->IssueMessage( MessageType::AUTHOR_WARNING, "No project() command is present. The top-level CMakeLists.txt " "file must contain a literal, direct call to the project() command. " "Add a line of code such as\n" " project(ProjectName)\n" "near the top of the file, but after cmake_minimum_required().\n" "CMake is pretending there is a \"project(Project)\" command on " "the first line.", this->Backtrace); cmListFileFunction project{ "project", 0, 0, { { "Project", cmListFileArgument::Unquoted, 0 }, { "__CMAKE_INJECTED_PROJECT_COMMAND__", cmListFileArgument::Unquoted, 0 } } }; listFile.Functions.insert(listFile.Functions.begin(), project); } } this->Defer = cm::make_unique(); this->RunListFile(listFile, currentStart, this->Defer.get()); this->Defer.reset(); if (cmSystemTools::GetFatalErrorOccurred()) { scope.Quiet(); } // at the end handle any old style subdirs std::vector subdirs = this->UnConfiguredDirectories; // for each subdir recurse auto sdi = subdirs.begin(); for (; sdi != subdirs.end(); ++sdi) { (*sdi)->StateSnapshot.InitializeFromParent_ForSubdirsCommand(); this->ConfigureSubDirectory(*sdi); } this->AddCMakeDependFilesFromUser(); } void cmMakefile::ConfigureSubDirectory(cmMakefile* mf) { mf->InitializeFromParent(this); std::string currentStart = mf->GetCurrentSourceDirectory(); if (this->GetCMakeInstance()->GetDebugOutput()) { std::string msg = cmStrCat(" Entering ", currentStart); cmSystemTools::Message(msg); } std::string const currentStartFile = cmStrCat(currentStart, "/CMakeLists.txt"); if (!cmSystemTools::FileExists(currentStartFile, true)) { // The file is missing. Check policy CMP0014. std::ostringstream e; /* clang-format off */ e << "The source directory\n" << " " << currentStart << "\n" << "does not contain a CMakeLists.txt file."; /* clang-format on */ switch (this->GetPolicyStatus(cmPolicies::CMP0014)) { case cmPolicies::WARN: // Print the warning. /* clang-format off */ e << "\n" << "CMake does not support this case but it used " << "to work accidentally and is being allowed for " << "compatibility." << "\n" << cmPolicies::GetPolicyWarning(cmPolicies::CMP0014); /* clang-format on */ this->IssueMessage(MessageType::AUTHOR_WARNING, e.str()); CM_FALLTHROUGH; case cmPolicies::OLD: // OLD behavior does not warn. break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: e << "\n" << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0014); CM_FALLTHROUGH; case cmPolicies::NEW: // NEW behavior prints the error. this->IssueMessage(MessageType::FATAL_ERROR, e.str()); break; } return; } // finally configure the subdir mf->Configure(); if (this->GetCMakeInstance()->GetDebugOutput()) { std::string msg = cmStrCat(" Returning to ", this->GetCurrentSourceDirectory()); cmSystemTools::Message(msg); } } void cmMakefile::AddSubDirectory(const std::string& srcPath, const std::string& binPath, bool excludeFromAll, bool immediate, bool system) { if (this->DeferRunning) { this->IssueMessage( MessageType::FATAL_ERROR, "Subdirectories may not be created during deferred execution."); return; } // Make sure the binary directory is unique. if (!this->EnforceUniqueDir(srcPath, binPath)) { return; } cmStateSnapshot newSnapshot = this->GetState()->CreateBuildsystemDirectorySnapshot(this->StateSnapshot); newSnapshot.GetDirectory().SetCurrentSource(srcPath); newSnapshot.GetDirectory().SetCurrentBinary(binPath); cmSystemTools::MakeDirectory(binPath); auto subMfu = cm::make_unique(this->GlobalGenerator, newSnapshot); auto* subMf = subMfu.get(); this->GetGlobalGenerator()->AddMakefile(std::move(subMfu)); if (excludeFromAll) { subMf->SetProperty("EXCLUDE_FROM_ALL", "TRUE"); } if (system) { subMf->SetProperty("SYSTEM", "TRUE"); } if (immediate) { this->ConfigureSubDirectory(subMf); } else { this->UnConfiguredDirectories.push_back(subMf); } this->AddInstallGenerator(cm::make_unique( subMf, binPath, this->GetBacktrace())); } const std::string& cmMakefile::GetCurrentSourceDirectory() const { return this->StateSnapshot.GetDirectory().GetCurrentSource(); } const std::string& cmMakefile::GetCurrentBinaryDirectory() const { return this->StateSnapshot.GetDirectory().GetCurrentBinary(); } std::vector cmMakefile::GetImportedTargets() const { std::vector tgts; tgts.reserve(this->ImportedTargets.size()); for (auto const& impTarget : this->ImportedTargets) { tgts.push_back(impTarget.second); } return tgts; } void cmMakefile::AddIncludeDirectories(const std::vector& incs, bool before) { if (incs.empty()) { return; } std::string entryString = cmList::to_string(incs); if (before) { this->StateSnapshot.GetDirectory().PrependIncludeDirectoriesEntry( BT(entryString, this->Backtrace)); } else { this->StateSnapshot.GetDirectory().AppendIncludeDirectoriesEntry( BT(entryString, this->Backtrace)); } // Property on each target: for (auto& target : this->Targets) { cmTarget& t = target.second; t.InsertInclude(BT(entryString, this->Backtrace), before); } } void cmMakefile::AddSystemIncludeDirectories(const std::set& incs) { if (incs.empty()) { return; } this->SystemIncludeDirectories.insert(incs.begin(), incs.end()); for (auto& target : this->Targets) { cmTarget& t = target.second; t.AddSystemIncludeDirectories(incs); } } void cmMakefile::AddDefinition(const std::string& name, cm::string_view value) { this->StateSnapshot.SetDefinition(name, value); #ifndef CMAKE_BOOTSTRAP cmVariableWatch* vv = this->GetVariableWatch(); if (vv) { vv->VariableAccessed(name, cmVariableWatch::VARIABLE_MODIFIED_ACCESS, value.data(), this); } #endif } void cmMakefile::AddDefinitionBool(const std::string& name, bool value) { this->AddDefinition(name, value ? "ON" : "OFF"); } void cmMakefile::AddCacheDefinition(const std::string& name, cmValue value, cmValue doc, cmStateEnums::CacheEntryType type, bool force) { cmValue existingValue = this->GetState()->GetInitializedCacheValue(name); // must be outside the following if() to keep it alive long enough std::string nvalue; if (existingValue && (this->GetState()->GetCacheEntryType(name) == cmStateEnums::UNINITIALIZED)) { // if this is not a force, then use the value from the cache // if it is a force, then use the value being passed in if (!force) { value = existingValue; } if (type == cmStateEnums::PATH || type == cmStateEnums::FILEPATH) { cmList files(value); for (auto& file : files) { if (!cmIsOff(file)) { file = cmSystemTools::CollapseFullPath(file); } } nvalue = files.to_string(); value = cmValue{ nvalue }; this->GetCMakeInstance()->AddCacheEntry(name, value, doc, type); value = this->GetState()->GetInitializedCacheValue(name); } } this->GetCMakeInstance()->AddCacheEntry(name, value, doc, type); switch (this->GetPolicyStatus(cmPolicies::CMP0126)) { case cmPolicies::WARN: if (this->PolicyOptionalWarningEnabled("CMAKE_POLICY_WARNING_CMP0126") && this->IsNormalDefinitionSet(name)) { this->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0126), "\nFor compatibility with older versions of CMake, normal " "variable \"", name, "\" will be removed from the current scope.")); } CM_FALLTHROUGH; case cmPolicies::OLD: // if there was a definition then remove it this->StateSnapshot.RemoveDefinition(name); break; case cmPolicies::NEW: case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: break; } } void cmMakefile::MarkVariableAsUsed(const std::string& var) { this->StateSnapshot.GetDefinition(var); } bool cmMakefile::VariableInitialized(const std::string& var) const { return this->StateSnapshot.IsInitialized(var); } void cmMakefile::MaybeWarnUninitialized(std::string const& variable, const char* sourceFilename) const { // check to see if we need to print a warning // if strict mode is on and the variable has // not been "cleared"/initialized with a set(foo ) call if (this->GetCMakeInstance()->GetWarnUninitialized() && !this->VariableInitialized(variable)) { if (this->CheckSystemVars || (sourceFilename && this->IsProjectFile(sourceFilename))) { std::ostringstream msg; msg << "uninitialized variable \'" << variable << "\'"; this->IssueMessage(MessageType::AUTHOR_WARNING, msg.str()); } } } void cmMakefile::RemoveDefinition(const std::string& name) { this->StateSnapshot.RemoveDefinition(name); #ifndef CMAKE_BOOTSTRAP cmVariableWatch* vv = this->GetVariableWatch(); if (vv) { vv->VariableAccessed(name, cmVariableWatch::VARIABLE_REMOVED_ACCESS, nullptr, this); } #endif } void cmMakefile::RemoveCacheDefinition(const std::string& name) const { this->GetState()->RemoveCacheEntry(name); } void cmMakefile::SetProjectName(std::string const& p) { this->StateSnapshot.SetProjectName(p); } void cmMakefile::AddGlobalLinkInformation(cmTarget& target) { // for these targets do not add anything switch (target.GetType()) { case cmStateEnums::UTILITY: case cmStateEnums::GLOBAL_TARGET: case cmStateEnums::INTERFACE_LIBRARY: return; default:; } if (cmValue linkLibsProp = this->GetProperty("LINK_LIBRARIES")) { cmList linkLibs{ *linkLibsProp }; for (auto j = linkLibs.begin(); j != linkLibs.end(); ++j) { std::string libraryName = *j; cmTargetLinkLibraryType libType = GENERAL_LibraryType; if (libraryName == "optimized"_s) { libType = OPTIMIZED_LibraryType; ++j; libraryName = *j; } else if (libraryName == "debug"_s) { libType = DEBUG_LibraryType; ++j; libraryName = *j; } // This is equivalent to the target_link_libraries plain signature. target.AddLinkLibrary(*this, libraryName, libType); target.AppendProperty( "INTERFACE_LINK_LIBRARIES", target.GetDebugGeneratorExpressions(libraryName, libType)); } } } void cmMakefile::AddAlias(const std::string& lname, std::string const& tgtName, bool globallyVisible) { this->AliasTargets[lname] = tgtName; if (globallyVisible) { this->GetGlobalGenerator()->AddAlias(lname, tgtName); } } cmTarget* cmMakefile::AddLibrary(const std::string& lname, cmStateEnums::TargetType type, const std::vector& srcs, bool excludeFromAll) { assert(type == cmStateEnums::STATIC_LIBRARY || type == cmStateEnums::SHARED_LIBRARY || type == cmStateEnums::MODULE_LIBRARY || type == cmStateEnums::OBJECT_LIBRARY || type == cmStateEnums::INTERFACE_LIBRARY); cmTarget* target = this->AddNewTarget(type, lname); // Clear its dependencies. Otherwise, dependencies might persist // over changes in CMakeLists.txt, making the information stale and // hence useless. target->ClearDependencyInformation(*this); if (excludeFromAll) { target->SetProperty("EXCLUDE_FROM_ALL", "TRUE"); } target->AddSources(srcs); this->AddGlobalLinkInformation(*target); return target; } cmTarget* cmMakefile::AddExecutable(const std::string& exeName, const std::vector& srcs, bool excludeFromAll) { cmTarget* target = this->AddNewTarget(cmStateEnums::EXECUTABLE, exeName); if (excludeFromAll) { target->SetProperty("EXCLUDE_FROM_ALL", "TRUE"); } target->AddSources(srcs); this->AddGlobalLinkInformation(*target); return target; } cmTarget* cmMakefile::AddNewTarget(cmStateEnums::TargetType type, const std::string& name) { return &this->CreateNewTarget(name, type).first; } cmTarget* cmMakefile::AddSynthesizedTarget(cmStateEnums::TargetType type, const std::string& name) { return &this ->CreateNewTarget(name, type, cmTarget::PerConfig::Yes, cmTarget::Visibility::Generated) .first; } std::pair cmMakefile::CreateNewTarget( const std::string& name, cmStateEnums::TargetType type, cmTarget::PerConfig perConfig, cmTarget::Visibility vis) { auto ib = this->Targets.emplace(name, cmTarget(name, type, vis, this, perConfig)); auto it = ib.first; if (!ib.second) { return std::make_pair(std::ref(it->second), false); } this->OrderedTargets.push_back(&it->second); this->GetGlobalGenerator()->IndexTarget(&it->second); this->GetStateSnapshot().GetDirectory().AddNormalTargetName(name); return std::make_pair(std::ref(it->second), true); } cmTarget* cmMakefile::AddNewUtilityTarget(const std::string& utilityName, bool excludeFromAll) { cmTarget* target = this->AddNewTarget(cmStateEnums::UTILITY, utilityName); if (excludeFromAll) { target->SetProperty("EXCLUDE_FROM_ALL", "TRUE"); } return target; } namespace { } #if !defined(CMAKE_BOOTSTRAP) cmSourceGroup* cmMakefile::GetSourceGroup( const std::vector& name) const { cmSourceGroup* sg = nullptr; // first look for source group starting with the same as the one we want for (cmSourceGroup const& srcGroup : this->SourceGroups) { std::string const& sgName = srcGroup.GetName(); if (sgName == name[0]) { sg = const_cast(&srcGroup); break; } } if (sg != nullptr) { // iterate through its children to find match source group for (unsigned int i = 1; i < name.size(); ++i) { sg = sg->LookupChild(name[i]); if (sg == nullptr) { break; } } } return sg; } void cmMakefile::AddSourceGroup(const std::string& name, const char* regex) { std::vector nameVector; nameVector.push_back(name); this->AddSourceGroup(nameVector, regex); } void cmMakefile::AddSourceGroup(const std::vector& name, const char* regex) { cmSourceGroup* sg = nullptr; std::vector currentName; int i = 0; const int lastElement = static_cast(name.size() - 1); for (i = lastElement; i >= 0; --i) { currentName.assign(name.begin(), name.begin() + i + 1); sg = this->GetSourceGroup(currentName); if (sg != nullptr) { break; } } // i now contains the index of the last found component if (i == lastElement) { // group already exists, replace its regular expression if (regex && sg) { // We only want to set the regular expression. If there are already // source files in the group, we don't want to remove them. sg->SetGroupRegex(regex); } return; } if (i == -1) { // group does not exist nor belong to any existing group // add its first component this->SourceGroups.emplace_back(name[0], regex); sg = this->GetSourceGroup(currentName); i = 0; // last component found } if (!sg) { cmSystemTools::Error("Could not create source group "); return; } // build the whole source group path for (++i; i <= lastElement; ++i) { sg->AddChild(cmSourceGroup(name[i], nullptr, sg->GetFullName().c_str())); sg = sg->LookupChild(name[i]); } sg->SetGroupRegex(regex); } cmSourceGroup* cmMakefile::GetOrCreateSourceGroup( const std::vector& folders) { cmSourceGroup* sg = this->GetSourceGroup(folders); if (sg == nullptr) { this->AddSourceGroup(folders); sg = this->GetSourceGroup(folders); } return sg; } cmSourceGroup* cmMakefile::GetOrCreateSourceGroup(const std::string& name) { std::string delimiters; if (cmValue p = this->GetDefinition("SOURCE_GROUP_DELIMITER")) { delimiters = *p; } else { delimiters = "/\\"; } return this->GetOrCreateSourceGroup(cmTokenize(name, delimiters)); } /** * Find a source group whose regular expression matches the filename * part of the given source name. Search backward through the list of * source groups, and take the first matching group found. This way * non-inherited SOURCE_GROUP commands will have precedence over * inherited ones. */ cmSourceGroup* cmMakefile::FindSourceGroup( const std::string& source, std::vector& groups) const { // First search for a group that lists the file explicitly. for (auto sg = groups.rbegin(); sg != groups.rend(); ++sg) { cmSourceGroup* result = sg->MatchChildrenFiles(source); if (result) { return result; } } // Now search for a group whose regex matches the file. for (auto sg = groups.rbegin(); sg != groups.rend(); ++sg) { cmSourceGroup* result = sg->MatchChildrenRegex(source); if (result) { return result; } } // Shouldn't get here, but just in case, return the default group. return groups.data(); } #endif static bool mightExpandVariablesCMP0019(const char* s) { return s && *s && strstr(s, "${") && strchr(s, '}'); } void cmMakefile::ExpandVariablesCMP0019() { // Drop this ancient compatibility behavior with a policy. cmPolicies::PolicyStatus pol = this->GetPolicyStatus(cmPolicies::CMP0019); if (pol != cmPolicies::OLD && pol != cmPolicies::WARN) { return; } std::ostringstream w; cmValue includeDirs = this->GetProperty("INCLUDE_DIRECTORIES"); if (includeDirs && mightExpandVariablesCMP0019(includeDirs->c_str())) { std::string dirs = *includeDirs; this->ExpandVariablesInString(dirs, true, true); if (pol == cmPolicies::WARN && dirs != *includeDirs) { /* clang-format off */ w << "Evaluated directory INCLUDE_DIRECTORIES\n" << " " << *includeDirs << "\n" << "as\n" << " " << dirs << "\n"; /* clang-format on */ } this->SetProperty("INCLUDE_DIRECTORIES", dirs); } // Also for each target's INCLUDE_DIRECTORIES property: for (auto& target : this->Targets) { cmTarget& t = target.second; if (t.GetType() == cmStateEnums::INTERFACE_LIBRARY || t.GetType() == cmStateEnums::GLOBAL_TARGET) { continue; } includeDirs = t.GetProperty("INCLUDE_DIRECTORIES"); if (includeDirs && mightExpandVariablesCMP0019(includeDirs->c_str())) { std::string dirs = *includeDirs; this->ExpandVariablesInString(dirs, true, true); if (pol == cmPolicies::WARN && dirs != *includeDirs) { /* clang-format off */ w << "Evaluated target " << t.GetName() << " INCLUDE_DIRECTORIES\n" << " " << *includeDirs << "\n" << "as\n" << " " << dirs << "\n"; /* clang-format on */ } t.SetProperty("INCLUDE_DIRECTORIES", dirs); } } if (cmValue linkDirsProp = this->GetProperty("LINK_DIRECTORIES")) { if (mightExpandVariablesCMP0019(linkDirsProp->c_str())) { std::string d = *linkDirsProp; const std::string orig = d; this->ExpandVariablesInString(d, true, true); if (pol == cmPolicies::WARN && d != orig) { /* clang-format off */ w << "Evaluated link directories\n" << " " << orig << "\n" << "as\n" << " " << d << "\n"; /* clang-format on */ } } } if (cmValue linkLibsProp = this->GetProperty("LINK_LIBRARIES")) { cmList linkLibs{ *linkLibsProp }; for (auto l = linkLibs.begin(); l != linkLibs.end(); ++l) { std::string libName = *l; if (libName == "optimized"_s || libName == "debug"_s) { ++l; libName = *l; } if (mightExpandVariablesCMP0019(libName.c_str())) { const std::string orig = libName; this->ExpandVariablesInString(libName, true, true); if (pol == cmPolicies::WARN && libName != orig) { /* clang-format off */ w << "Evaluated link library\n" << " " << orig << "\n" << "as\n" << " " << libName << "\n"; /* clang-format on */ } } } } if (!w.str().empty()) { std::ostringstream m; /* clang-format off */ m << cmPolicies::GetPolicyWarning(cmPolicies::CMP0019) << "\n" << "The following variable evaluations were encountered:\n" << w.str(); /* clang-format on */ this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING, m.str(), this->Backtrace); } } bool cmMakefile::IsOn(const std::string& name) const { return this->GetDefinition(name).IsOn(); } bool cmMakefile::IsSet(const std::string& name) const { cmValue value = this->GetDefinition(name); if (!value) { return false; } if (value->empty()) { return false; } if (cmIsNOTFOUND(*value)) { return false; } return true; } bool cmMakefile::PlatformIs32Bit() const { if (cmValue plat_abi = this->GetDefinition("CMAKE_INTERNAL_PLATFORM_ABI")) { if (*plat_abi == "ELF X32"_s) { return false; } } if (cmValue sizeof_dptr = this->GetDefinition("CMAKE_SIZEOF_VOID_P")) { return atoi(sizeof_dptr->c_str()) == 4; } return false; } bool cmMakefile::PlatformIs64Bit() const { if (cmValue sizeof_dptr = this->GetDefinition("CMAKE_SIZEOF_VOID_P")) { return atoi(sizeof_dptr->c_str()) == 8; } return false; } bool cmMakefile::PlatformIsx32() const { if (cmValue plat_abi = this->GetDefinition("CMAKE_INTERNAL_PLATFORM_ABI")) { if (*plat_abi == "ELF X32"_s) { return true; } } return false; } cmMakefile::AppleSDK cmMakefile::GetAppleSDKType() const { std::string sdkRoot; sdkRoot = this->GetSafeDefinition("CMAKE_OSX_SYSROOT"); sdkRoot = cmSystemTools::LowerCase(sdkRoot); struct { std::string name; AppleSDK sdk; } const sdkDatabase[]{ { "appletvos", AppleSDK::AppleTVOS }, { "appletvsimulator", AppleSDK::AppleTVSimulator }, { "iphoneos", AppleSDK::IPhoneOS }, { "iphonesimulator", AppleSDK::IPhoneSimulator }, { "watchos", AppleSDK::WatchOS }, { "watchsimulator", AppleSDK::WatchSimulator }, { "xros", AppleSDK::XROS }, { "xrsimulator", AppleSDK::XRSimulator }, }; for (auto const& entry : sdkDatabase) { if (cmHasPrefix(sdkRoot, entry.name) || sdkRoot.find(cmStrCat('/', entry.name)) != std::string::npos) { return entry.sdk; } } return AppleSDK::MacOS; } bool cmMakefile::PlatformIsAppleEmbedded() const { return this->GetAppleSDKType() != AppleSDK::MacOS; } bool cmMakefile::PlatformIsAppleSimulator() const { return std::set{ AppleSDK::AppleTVSimulator, AppleSDK::IPhoneSimulator, AppleSDK::WatchSimulator, AppleSDK::XRSimulator, } .count(this->GetAppleSDKType()); } bool cmMakefile::PlatformSupportsAppleTextStubs() const { return this->IsOn("APPLE") && this->IsSet("CMAKE_TAPI"); } const char* cmMakefile::GetSONameFlag(const std::string& language) const { std::string name = "CMAKE_SHARED_LIBRARY_SONAME"; if (!language.empty()) { name += "_"; name += language; } name += "_FLAG"; return this->GetDefinition(name).GetCStr(); } bool cmMakefile::CanIWriteThisFile(std::string const& fileName) const { if (!this->IsOn("CMAKE_DISABLE_SOURCE_CHANGES")) { return true; } // If we are doing an in-source build, then the test will always fail if (cmSystemTools::SameFile(this->GetHomeDirectory(), this->GetHomeOutputDirectory())) { return !this->IsOn("CMAKE_DISABLE_IN_SOURCE_BUILD"); } return !cmSystemTools::IsSubDirectory(fileName, this->GetHomeDirectory()) || cmSystemTools::IsSubDirectory(fileName, this->GetHomeOutputDirectory()) || cmSystemTools::SameFile(fileName, this->GetHomeOutputDirectory()); } const std::string& cmMakefile::GetRequiredDefinition( const std::string& name) const { static std::string const empty; cmValue def = this->GetDefinition(name); if (!def) { cmSystemTools::Error("Error required internal CMake variable not " "set, cmake may not be built correctly.\n" "Missing variable is:\n" + name); return empty; } return *def; } bool cmMakefile::IsDefinitionSet(const std::string& name) const { cmValue def = this->StateSnapshot.GetDefinition(name); if (!def) { def = this->GetState()->GetInitializedCacheValue(name); } #ifndef CMAKE_BOOTSTRAP if (cmVariableWatch* vv = this->GetVariableWatch()) { if (!def) { vv->VariableAccessed( name, cmVariableWatch::UNKNOWN_VARIABLE_DEFINED_ACCESS, nullptr, this); } } #endif return def != nullptr; } bool cmMakefile::IsNormalDefinitionSet(const std::string& name) const { cmValue def = this->StateSnapshot.GetDefinition(name); #ifndef CMAKE_BOOTSTRAP if (cmVariableWatch* vv = this->GetVariableWatch()) { if (!def) { vv->VariableAccessed( name, cmVariableWatch::UNKNOWN_VARIABLE_DEFINED_ACCESS, nullptr, this); } } #endif return def != nullptr; } cmValue cmMakefile::GetDefinition(const std::string& name) const { cmValue def = this->StateSnapshot.GetDefinition(name); if (!def) { def = this->GetState()->GetInitializedCacheValue(name); } #ifndef CMAKE_BOOTSTRAP cmVariableWatch* vv = this->GetVariableWatch(); if (vv && !this->SuppressSideEffects) { bool const watch_function_executed = vv->VariableAccessed(name, def ? cmVariableWatch::VARIABLE_READ_ACCESS : cmVariableWatch::UNKNOWN_VARIABLE_READ_ACCESS, def.GetCStr(), this); if (watch_function_executed) { // A callback was executed and may have caused re-allocation of the // variable storage. Look it up again for now. // FIXME: Refactor variable storage to avoid this problem. def = this->StateSnapshot.GetDefinition(name); if (!def) { def = this->GetState()->GetInitializedCacheValue(name); } } } #endif return def; } const std::string& cmMakefile::GetSafeDefinition(const std::string& name) const { return this->GetDefinition(name); } std::vector cmMakefile::GetDefinitions() const { std::vector res = this->StateSnapshot.ClosureKeys(); cm::append(res, this->GetState()->GetCacheEntryKeys()); std::sort(res.begin(), res.end()); return res; } const std::string& cmMakefile::ExpandVariablesInString( std::string& source) const { return this->ExpandVariablesInString(source, false, false); } const std::string& cmMakefile::ExpandVariablesInString( std::string& source, bool escapeQuotes, bool noEscapes, bool atOnly, const char* filename, long line, bool removeEmpty, bool replaceAt) const { bool compareResults = false; MessageType mtype = MessageType::LOG; std::string errorstr; std::string original; // Sanity check the @ONLY mode. if (atOnly && (!noEscapes || !removeEmpty)) { // This case should never be called. At-only is for // configure-file/string which always does no escapes. this->IssueMessage(MessageType::INTERNAL_ERROR, "ExpandVariablesInString @ONLY called " "on something with escapes."); return source; } // Variables used in the WARN case. std::string newResult; std::string newErrorstr; MessageType newError = MessageType::LOG; switch (this->GetPolicyStatus(cmPolicies::CMP0053)) { case cmPolicies::WARN: { // Save the original string for the warning. original = source; newResult = source; compareResults = true; // Suppress variable watches to avoid calling hooks twice. Suppress new // dereferences since the OLD behavior is still what is actually used. this->SuppressSideEffects = true; newError = this->ExpandVariablesInStringNew( newErrorstr, newResult, escapeQuotes, noEscapes, atOnly, filename, line, replaceAt); this->SuppressSideEffects = false; CM_FALLTHROUGH; } case cmPolicies::OLD: mtype = this->ExpandVariablesInStringOld(errorstr, source, escapeQuotes, noEscapes, atOnly, filename, line, removeEmpty, true); break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: // Messaging here would be *very* verbose. case cmPolicies::NEW: mtype = this->ExpandVariablesInStringNew(errorstr, source, escapeQuotes, noEscapes, atOnly, filename, line, replaceAt); break; } // If it's an error in either case, just report the error... if (mtype != MessageType::LOG) { if (mtype == MessageType::FATAL_ERROR) { cmSystemTools::SetFatalErrorOccurred(); } this->IssueMessage(mtype, errorstr); } // ...otherwise, see if there's a difference that needs to be warned about. else if (compareResults && (newResult != source || newError != mtype)) { std::string msg = cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0053), '\n'); std::string msg_input = original; cmSystemTools::ReplaceString(msg_input, "\n", "\n "); msg += "For input:\n '"; msg += msg_input; msg += "'\n"; std::string msg_old = source; cmSystemTools::ReplaceString(msg_old, "\n", "\n "); msg += "the old evaluation rules produce:\n '"; msg += msg_old; msg += "'\n"; if (newError == mtype) { std::string msg_new = newResult; cmSystemTools::ReplaceString(msg_new, "\n", "\n "); msg += "but the new evaluation rules produce:\n '"; msg += msg_new; msg += "'\n"; } else { std::string msg_err = newErrorstr; cmSystemTools::ReplaceString(msg_err, "\n", "\n "); msg += "but the new evaluation rules produce an error:\n "; msg += msg_err; msg += "\n"; } msg += "Using the old result for compatibility since the policy is not set."; this->IssueMessage(MessageType::AUTHOR_WARNING, msg); } return source; } MessageType cmMakefile::ExpandVariablesInStringOld( std::string& errorstr, std::string& source, bool escapeQuotes, bool noEscapes, bool atOnly, const char* filename, long line, bool removeEmpty, bool replaceAt) const { // Fast path strings without any special characters. if (source.find_first_of("$@\\") == std::string::npos) { return MessageType::LOG; } // Special-case the @ONLY mode. if (atOnly) { // Store an original copy of the input. std::string input = source; // Start with empty output. source.clear(); // Look for one @VAR@ at a time. const char* in = input.c_str(); while (this->cmAtVarRegex.find(in)) { // Get the range of the string to replace. const char* first = in + this->cmAtVarRegex.start(); const char* last = in + this->cmAtVarRegex.end(); // Store the unchanged part of the string now. source.append(in, first - in); // Lookup the definition of VAR. std::string var(first + 1, last - first - 2); if (cmValue val = this->GetDefinition(var)) { // Store the value in the output escaping as requested. if (escapeQuotes) { source.append(cmEscapeQuotes(*val)); } else { source.append(*val); } } // Continue looking for @VAR@ further along the string. in = last; } // Append the rest of the unchanged part of the string. source.append(in); return MessageType::LOG; } // This method replaces ${VAR} and @VAR@ where VAR is looked up // with GetDefinition(), if not found in the map, nothing is expanded. // It also supports the $ENV{VAR} syntax where VAR is looked up in // the current environment variables. cmCommandArgumentParserHelper parser; parser.SetMakefile(this); parser.SetLineFile(line, filename); parser.SetEscapeQuotes(escapeQuotes); parser.SetNoEscapeMode(noEscapes); parser.SetReplaceAtSyntax(replaceAt); parser.SetRemoveEmpty(removeEmpty); int res = parser.ParseString(source, 0); const char* emsg = parser.GetError(); MessageType mtype = MessageType::LOG; if (res && !emsg[0]) { source = parser.GetResult(); } else { // Construct the main error message. std::ostringstream error; error << "Syntax error in cmake code "; if (filename && line > 0) { // This filename and line number may be more specific than the // command context because one command invocation can have // arguments on multiple lines. error << "at\n" << " " << filename << ":" << line << "\n"; } error << "when parsing string\n" << " " << source << "\n"; error << emsg; // If the parser failed ("res" is false) then this is a real // argument parsing error, so the policy applies. Otherwise the // parser reported an error message without failing because the // helper implementation is unhappy, which has always reported an // error. mtype = MessageType::FATAL_ERROR; if (!res) { // This is a real argument parsing error. Use policy CMP0010 to // decide whether it is an error. switch (this->GetPolicyStatus(cmPolicies::CMP0010)) { case cmPolicies::WARN: error << "\n" << cmPolicies::GetPolicyWarning(cmPolicies::CMP0010); CM_FALLTHROUGH; case cmPolicies::OLD: // OLD behavior is to just warn and continue. mtype = MessageType::AUTHOR_WARNING; break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: error << "\n" << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0010); break; case cmPolicies::NEW: // NEW behavior is to report the error. break; } } errorstr = error.str(); } return mtype; } enum t_domain { NORMAL, ENVIRONMENT, CACHE }; struct t_lookup { t_domain domain = NORMAL; size_t loc = 0; }; bool cmMakefile::IsProjectFile(const char* filename) const { return cmSystemTools::IsSubDirectory(filename, this->GetHomeDirectory()) || (cmSystemTools::IsSubDirectory(filename, this->GetHomeOutputDirectory()) && !cmSystemTools::IsSubDirectory(filename, "/CMakeFiles")); } size_t cmMakefile::GetRecursionDepthLimit() const { size_t depth = CMake_DEFAULT_RECURSION_LIMIT; if (cmValue depthStr = this->GetDefinition("CMAKE_MAXIMUM_RECURSION_DEPTH")) { unsigned long depthUL; if (cmStrToULong(depthStr.GetCStr(), &depthUL)) { depth = depthUL; } } else if (cm::optional depthEnv = cmSystemTools::GetEnvVar("CMAKE_MAXIMUM_RECURSION_DEPTH")) { unsigned long depthUL; if (cmStrToULong(*depthEnv, &depthUL)) { depth = depthUL; } } return depth; } size_t cmMakefile::GetRecursionDepth() const { return this->RecursionDepth; } void cmMakefile::SetRecursionDepth(size_t recursionDepth) { this->RecursionDepth = recursionDepth; } std::string cmMakefile::NewDeferId() const { return this->GetGlobalGenerator()->NewDeferId(); } bool cmMakefile::DeferCall(std::string id, std::string file, cmListFileFunction lff) { if (!this->Defer) { return false; } this->Defer->Commands.emplace_back( DeferCommand{ std::move(id), std::move(file), std::move(lff) }); return true; } bool cmMakefile::DeferCancelCall(std::string const& id) { if (!this->Defer) { return false; } for (DeferCommand& dc : this->Defer->Commands) { if (dc.Id == id) { dc.Id.clear(); } } return true; } cm::optional cmMakefile::DeferGetCallIds() const { cm::optional ids; if (this->Defer) { ids = cmList::to_string( cmMakeRange(this->Defer->Commands) .filter([](DeferCommand const& dc) -> bool { return !dc.Id.empty(); }) .transform( [](DeferCommand const& dc) -> std::string const& { return dc.Id; })); } return ids; } cm::optional cmMakefile::DeferGetCall(std::string const& id) const { cm::optional call; if (this->Defer) { std::string tmp; for (DeferCommand const& dc : this->Defer->Commands) { if (dc.Id == id) { tmp = dc.Command.OriginalName(); for (cmListFileArgument const& arg : dc.Command.Arguments()) { tmp = cmStrCat(tmp, ';', arg.Value); } break; } } call = std::move(tmp); } return call; } MessageType cmMakefile::ExpandVariablesInStringNew( std::string& errorstr, std::string& source, bool escapeQuotes, bool noEscapes, bool atOnly, const char* filename, long line, bool replaceAt) const { // This method replaces ${VAR} and @VAR@ where VAR is looked up // with GetDefinition(), if not found in the map, nothing is expanded. // It also supports the $ENV{VAR} syntax where VAR is looked up in // the current environment variables. const char* in = source.c_str(); const char* last = in; std::string result; result.reserve(source.size()); std::vector openstack; bool error = false; bool done = false; MessageType mtype = MessageType::LOG; cmState* state = this->GetCMakeInstance()->GetState(); static const std::string lineVar = "CMAKE_CURRENT_LIST_LINE"; do { char inc = *in; switch (inc) { case '}': if (!openstack.empty()) { t_lookup var = openstack.back(); openstack.pop_back(); result.append(last, in - last); std::string const& lookup = result.substr(var.loc); cmValue value = nullptr; std::string varresult; std::string svalue; switch (var.domain) { case NORMAL: if (filename && lookup == lineVar) { cmListFileContext const& top = this->Backtrace.Top(); if (top.DeferId) { varresult = cmStrCat("DEFERRED:"_s, *top.DeferId); } else { varresult = std::to_string(line); } } else { value = this->GetDefinition(lookup); } break; case ENVIRONMENT: if (cmSystemTools::GetEnv(lookup, svalue)) { value = cmValue(svalue); } break; case CACHE: value = state->GetCacheEntryValue(lookup); break; } // Get the string we're meant to append to. if (value) { if (escapeQuotes) { varresult = cmEscapeQuotes(*value); } else { varresult = *value; } } else if (!this->SuppressSideEffects) { this->MaybeWarnUninitialized(lookup, filename); } result.replace(var.loc, result.size() - var.loc, varresult); // Start looking from here on out. last = in + 1; } break; case '$': if (!atOnly) { t_lookup lookup; const char* next = in + 1; const char* start = nullptr; char nextc = *next; if (nextc == '{') { // Looking for a variable. start = in + 2; lookup.domain = NORMAL; } else if (nextc == '<') { } else if (!nextc) { result.append(last, next - last); last = next; } else if (cmHasLiteralPrefix(next, "ENV{")) { // Looking for an environment variable. start = in + 5; lookup.domain = ENVIRONMENT; } else if (cmHasLiteralPrefix(next, "CACHE{")) { // Looking for a cache variable. start = in + 7; lookup.domain = CACHE; } else { if (this->cmNamedCurly.find(next)) { errorstr = "Syntax $" + std::string(next, this->cmNamedCurly.end()) + "{} is not supported. Only ${}, $ENV{}, " "and $CACHE{} are allowed."; mtype = MessageType::FATAL_ERROR; error = true; } } if (start) { result.append(last, in - last); last = start; in = start - 1; lookup.loc = result.size(); openstack.push_back(lookup); } break; } CM_FALLTHROUGH; case '\\': if (!noEscapes) { const char* next = in + 1; char nextc = *next; if (nextc == 't') { result.append(last, in - last); result.push_back('\t'); last = next + 1; } else if (nextc == 'n') { result.append(last, in - last); result.push_back('\n'); last = next + 1; } else if (nextc == 'r') { result.append(last, in - last); result.push_back('\r'); last = next + 1; } else if (nextc == ';' && openstack.empty()) { // Handled in ExpandListArgument; pass the backslash literally. } else if (isalnum(nextc) || nextc == '\0') { errorstr += "Invalid character escape '\\"; if (nextc) { errorstr += nextc; errorstr += "'."; } else { errorstr += "' (at end of input)."; } error = true; } else { // Take what we've found so far, skipping the escape character. result.append(last, in - last); // Start tracking from the next character. last = in + 1; } // Skip the next character since it was escaped, but don't read past // the end of the string. if (*last) { ++in; } } break; case '\n': // Onto the next line. ++line; break; case '\0': done = true; break; case '@': if (replaceAt) { const char* nextAt = strchr(in + 1, '@'); if (nextAt && nextAt != in + 1 && nextAt == in + 1 + strspn(in + 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789/_.+-")) { std::string variable(in + 1, nextAt - in - 1); std::string varresult; if (filename && variable == lineVar) { varresult = std::to_string(line); } else { cmValue def = this->GetDefinition(variable); if (def) { varresult = *def; } else if (!this->SuppressSideEffects) { this->MaybeWarnUninitialized(variable, filename); } } if (escapeQuotes) { varresult = cmEscapeQuotes(varresult); } // Skip over the variable. result.append(last, in - last); result.append(varresult); in = nextAt; last = in + 1; break; } } // Failed to find a valid @ expansion; treat it as literal. CM_FALLTHROUGH; default: { if (!openstack.empty() && !(isalnum(inc) || inc == '_' || inc == '/' || inc == '.' || inc == '+' || inc == '-')) { errorstr += "Invalid character (\'"; errorstr += inc; result.append(last, in - last); errorstr += cmStrCat("\') in a variable name: " "'", result.substr(openstack.back().loc), '\''); mtype = MessageType::FATAL_ERROR; error = true; } break; } } // Look at the next character. } while (!error && !done && *++in); // Check for open variable references yet. if (!error && !openstack.empty()) { // There's an open variable reference waiting. Policy CMP0010 flags // whether this is an error or not. The new parser now enforces // CMP0010 as well. errorstr += "There is an unterminated variable reference."; error = true; } if (error) { std::ostringstream emsg; emsg << "Syntax error in cmake code "; if (filename) { // This filename and line number may be more specific than the // command context because one command invocation can have // arguments on multiple lines. emsg << "at\n" << " " << filename << ":" << line << "\n"; } emsg << "when parsing string\n" << " " << source << "\n"; emsg << errorstr; mtype = MessageType::FATAL_ERROR; errorstr = emsg.str(); } else { // Append the rest of the unchanged part of the string. result.append(last); source = result; } return mtype; } void cmMakefile::RemoveVariablesInString(std::string& source, bool atOnly) const { if (!atOnly) { cmsys::RegularExpression var("(\\${[A-Za-z_0-9]*})"); while (var.find(source)) { source.erase(var.start(), var.end() - var.start()); } } if (!atOnly) { cmsys::RegularExpression varb("(\\$ENV{[A-Za-z_0-9]*})"); while (varb.find(source)) { source.erase(varb.start(), varb.end() - varb.start()); } } cmsys::RegularExpression var2("(@[A-Za-z_0-9]*@)"); while (var2.find(source)) { source.erase(var2.start(), var2.end() - var2.start()); } } void cmMakefile::InitCMAKE_CONFIGURATION_TYPES(std::string const& genDefault) { if (this->GetDefinition("CMAKE_CONFIGURATION_TYPES")) { return; } std::string initConfigs; if (this->GetCMakeInstance()->GetIsInTryCompile() || !cmSystemTools::GetEnv("CMAKE_CONFIGURATION_TYPES", initConfigs)) { initConfigs = genDefault; } this->AddCacheDefinition( "CMAKE_CONFIGURATION_TYPES", initConfigs, "Semicolon separated list of supported configuration types, " "only supports Debug, Release, MinSizeRel, and RelWithDebInfo, " "anything else will be ignored.", cmStateEnums::STRING); } std::string cmMakefile::GetDefaultConfiguration() const { if (this->GetGlobalGenerator()->IsMultiConfig()) { return std::string{}; } return this->GetSafeDefinition("CMAKE_BUILD_TYPE"); } std::vector cmMakefile::GetGeneratorConfigs( GeneratorConfigQuery mode) const { cmList configs; if (this->GetGlobalGenerator()->IsMultiConfig()) { configs.assign(this->GetDefinition("CMAKE_CONFIGURATION_TYPES")); } else if (mode != cmMakefile::OnlyMultiConfig) { const std::string& buildType = this->GetSafeDefinition("CMAKE_BUILD_TYPE"); if (!buildType.empty()) { configs.emplace_back(buildType); } } if (mode == cmMakefile::IncludeEmptyConfig && configs.empty()) { configs.emplace_back(); } return std::move(configs.data()); } bool cmMakefile::IsFunctionBlocked(const cmListFileFunction& lff, cmExecutionStatus& status) { // if there are no blockers get out of here if (this->FunctionBlockers.empty()) { return false; } return this->FunctionBlockers.top()->IsFunctionBlocked(lff, status); } void cmMakefile::PushFunctionBlockerBarrier() { this->FunctionBlockerBarriers.push_back(this->FunctionBlockers.size()); } void cmMakefile::PopFunctionBlockerBarrier(bool reportError) { // Remove any extra entries pushed on the barrier. FunctionBlockersType::size_type barrier = this->FunctionBlockerBarriers.back(); while (this->FunctionBlockers.size() > barrier) { std::unique_ptr fb( std::move(this->FunctionBlockers.top())); this->FunctionBlockers.pop(); if (reportError) { // Report the context in which the unclosed block was opened. cmListFileContext const& lfc = fb->GetStartingContext(); std::ostringstream e; /* clang-format off */ e << "A logical block opening on the line\n" << " " << lfc << "\n" << "is not closed."; /* clang-format on */ this->IssueMessage(MessageType::FATAL_ERROR, e.str()); reportError = false; } } // Remove the barrier. this->FunctionBlockerBarriers.pop_back(); } void cmMakefile::PushLoopBlock() { assert(!this->LoopBlockCounter.empty()); this->LoopBlockCounter.top()++; } void cmMakefile::PopLoopBlock() { assert(!this->LoopBlockCounter.empty()); assert(this->LoopBlockCounter.top() > 0); this->LoopBlockCounter.top()--; } void cmMakefile::PushLoopBlockBarrier() { this->LoopBlockCounter.push(0); } void cmMakefile::PopLoopBlockBarrier() { assert(!this->LoopBlockCounter.empty()); assert(this->LoopBlockCounter.top() == 0); this->LoopBlockCounter.pop(); } bool cmMakefile::IsLoopBlock() const { assert(!this->LoopBlockCounter.empty()); return !this->LoopBlockCounter.empty() && this->LoopBlockCounter.top() > 0; } bool cmMakefile::ExpandArguments(std::vector const& inArgs, std::vector& outArgs) const { std::string const& filename = this->GetBacktrace().Top().FilePath; std::string value; outArgs.reserve(inArgs.size()); for (cmListFileArgument const& i : inArgs) { // No expansion in a bracket argument. if (i.Delim == cmListFileArgument::Bracket) { outArgs.push_back(i.Value); continue; } // Expand the variables in the argument. value = i.Value; this->ExpandVariablesInString(value, false, false, false, filename.c_str(), i.Line, false, false); // If the argument is quoted, it should be one argument. // Otherwise, it may be a list of arguments. if (i.Delim == cmListFileArgument::Quoted) { outArgs.push_back(value); } else { cmExpandList(value, outArgs); } } return !cmSystemTools::GetFatalErrorOccurred(); } bool cmMakefile::ExpandArguments( std::vector const& inArgs, std::vector& outArgs) const { std::string const& filename = this->GetBacktrace().Top().FilePath; std::string value; outArgs.reserve(inArgs.size()); for (cmListFileArgument const& i : inArgs) { // No expansion in a bracket argument. if (i.Delim == cmListFileArgument::Bracket) { outArgs.emplace_back(i.Value, true); continue; } // Expand the variables in the argument. value = i.Value; this->ExpandVariablesInString(value, false, false, false, filename.c_str(), i.Line, false, false); // If the argument is quoted, it should be one argument. // Otherwise, it may be a list of arguments. if (i.Delim == cmListFileArgument::Quoted) { outArgs.emplace_back(value, true); } else { cmList stringArgs{ value }; for (std::string const& stringArg : stringArgs) { outArgs.emplace_back(stringArg, false); } } } return !cmSystemTools::GetFatalErrorOccurred(); } void cmMakefile::AddFunctionBlocker(std::unique_ptr fb) { if (!this->ExecutionStatusStack.empty()) { // Record the context in which the blocker is created. fb->SetStartingContext(this->Backtrace.Top()); } this->FunctionBlockers.push(std::move(fb)); } std::unique_ptr cmMakefile::RemoveFunctionBlocker() { assert(!this->FunctionBlockers.empty()); assert(this->FunctionBlockerBarriers.empty() || this->FunctionBlockers.size() > this->FunctionBlockerBarriers.back()); auto b = std::move(this->FunctionBlockers.top()); this->FunctionBlockers.pop(); return b; } std::string const& cmMakefile::GetHomeDirectory() const { return this->GetCMakeInstance()->GetHomeDirectory(); } std::string const& cmMakefile::GetHomeOutputDirectory() const { return this->GetCMakeInstance()->GetHomeOutputDirectory(); } void cmMakefile::SetScriptModeFile(std::string const& scriptfile) { this->AddDefinition("CMAKE_SCRIPT_MODE_FILE", scriptfile); } void cmMakefile::SetArgcArgv(const std::vector& args) { this->AddDefinition("CMAKE_ARGC", std::to_string(args.size())); // this->MarkVariableAsUsed("CMAKE_ARGC"); for (unsigned int t = 0; t < args.size(); ++t) { std::ostringstream tmpStream; tmpStream << "CMAKE_ARGV" << t; this->AddDefinition(tmpStream.str(), args[t]); // this->MarkVariableAsUsed(tmpStream.str().c_str()); } } cmSourceFile* cmMakefile::GetSource(const std::string& sourceName, cmSourceFileLocationKind kind) const { // First check "Known" paths (avoids the creation of cmSourceFileLocation) if (kind == cmSourceFileLocationKind::Known) { auto sfsi = this->KnownFileSearchIndex.find(sourceName); if (sfsi != this->KnownFileSearchIndex.end()) { return sfsi->second; } } cmSourceFileLocation sfl(this, sourceName, kind); auto name = this->GetCMakeInstance()->StripExtension(sfl.GetName()); #if defined(_WIN32) || defined(__APPLE__) name = cmSystemTools::LowerCase(name); #endif auto sfsi = this->SourceFileSearchIndex.find(name); if (sfsi != this->SourceFileSearchIndex.end()) { for (auto* sf : sfsi->second) { if (sf->Matches(sfl)) { return sf; } } } return nullptr; } cmSourceFile* cmMakefile::CreateSource(const std::string& sourceName, bool generated, cmSourceFileLocationKind kind) { auto sf = cm::make_unique(this, sourceName, generated, kind); auto name = this->GetCMakeInstance()->StripExtension(sf->GetLocation().GetName()); #if defined(_WIN32) || defined(__APPLE__) name = cmSystemTools::LowerCase(name); #endif this->SourceFileSearchIndex[name].push_back(sf.get()); // for "Known" paths add direct lookup (used for faster lookup in GetSource) if (kind == cmSourceFileLocationKind::Known) { this->KnownFileSearchIndex[sourceName] = sf.get(); } this->SourceFiles.push_back(std::move(sf)); return this->SourceFiles.back().get(); } cmSourceFile* cmMakefile::GetOrCreateSource(const std::string& sourceName, bool generated, cmSourceFileLocationKind kind) { if (cmSourceFile* esf = this->GetSource(sourceName, kind)) { return esf; } return this->CreateSource(sourceName, generated, kind); } cmSourceFile* cmMakefile::GetOrCreateGeneratedSource( const std::string& sourceName) { cmSourceFile* sf = this->GetOrCreateSource(sourceName, true, cmSourceFileLocationKind::Known); sf->MarkAsGenerated(); // In case we did not create the source file. return sf; } void cmMakefile::CreateGeneratedOutputs( const std::vector& outputs) { for (std::string const& o : outputs) { if (cmGeneratorExpression::Find(o) == std::string::npos) { this->GetOrCreateGeneratedSource(o); } } } void cmMakefile::AddTargetObject(std::string const& tgtName, std::string const& objFile) { cmSourceFile* sf = this->GetOrCreateSource(objFile, true, cmSourceFileLocationKind::Known); sf->SetObjectLibrary(tgtName); sf->SetProperty("EXTERNAL_OBJECT", "1"); // TODO: Compute a language for this object based on the associated source // file that compiles to it. Needs a policy as it likely affects link // language selection if done unconditionally. #if !defined(CMAKE_BOOTSTRAP) this->SourceGroups[this->ObjectLibrariesSourceGroupIndex].AddGroupFile( sf->ResolveFullPath()); #endif } void cmMakefile::EnableLanguage(std::vector const& languages, bool optional) { if (this->DeferRunning) { this->IssueMessage( MessageType::FATAL_ERROR, "Languages may not be enabled during deferred execution."); return; } if (const char* def = this->GetGlobalGenerator()->GetCMakeCFGIntDir()) { this->AddDefinition("CMAKE_CFG_INTDIR", def); } std::vector unique_languages; { std::vector duplicate_languages; for (std::string const& language : languages) { if (!cm::contains(unique_languages, language)) { unique_languages.push_back(language); } else if (!cm::contains(duplicate_languages, language)) { duplicate_languages.push_back(language); } } if (!duplicate_languages.empty()) { auto quantity = duplicate_languages.size() == 1 ? " has"_s : "s have"_s; this->IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat("Languages to be enabled may not be specified more " "than once at the same time. The following language", quantity, " been specified multiple times: ", cmJoin(duplicate_languages, ", "))); } } // If RC is explicitly listed we need to do it after other languages. // On some platforms we enable RC implicitly while enabling others. // Do not let that look like recursive enable_language(RC). std::vector languages_without_RC; std::vector languages_for_RC; languages_without_RC.reserve(unique_languages.size()); for (std::string const& language : unique_languages) { if (language == "RC"_s) { languages_for_RC.push_back(language); } else { languages_without_RC.push_back(language); } } if (!languages_without_RC.empty()) { this->GetGlobalGenerator()->EnableLanguage(languages_without_RC, this, optional); } if (!languages_for_RC.empty()) { this->GetGlobalGenerator()->EnableLanguage(languages_for_RC, this, optional); } } int cmMakefile::TryCompile(const std::string& srcdir, const std::string& bindir, const std::string& projectName, const std::string& targetName, bool fast, int jobs, const std::vector* cmakeArgs, std::string& output) { this->IsSourceFileTryCompile = fast; // does the binary directory exist ? If not create it... if (!cmSystemTools::FileIsDirectory(bindir)) { cmSystemTools::MakeDirectory(bindir); } // change to the tests directory and run cmake // use the cmake object instead of calling cmake cmWorkingDirectory workdir(bindir); if (workdir.Failed()) { this->IssueMessage(MessageType::FATAL_ERROR, cmStrCat("Failed to set working directory to ", bindir, " : ", std::strerror(workdir.GetLastResult()))); cmSystemTools::SetFatalErrorOccurred(); this->IsSourceFileTryCompile = false; return 1; } // make sure the same generator is used // use this program as the cmake to be run, it should not // be run that way but the cmake object requires a valid path cmake cm(cmake::RoleProject, cmState::Project, cmState::ProjectKind::TryCompile); auto gg = cm.CreateGlobalGenerator(this->GetGlobalGenerator()->GetName()); if (!gg) { this->IssueMessage(MessageType::INTERNAL_ERROR, "Global generator '" + this->GetGlobalGenerator()->GetName() + "' could not be created."); cmSystemTools::SetFatalErrorOccurred(); this->IsSourceFileTryCompile = false; return 1; } gg->RecursionDepth = this->RecursionDepth; cm.SetGlobalGenerator(std::move(gg)); // copy trace state cm.SetTraceRedirect(this->GetCMakeInstance()); // do a configure cm.SetHomeDirectory(srcdir); cm.SetHomeOutputDirectory(bindir); cm.SetGeneratorInstance(this->GetSafeDefinition("CMAKE_GENERATOR_INSTANCE")); cm.SetGeneratorPlatform(this->GetSafeDefinition("CMAKE_GENERATOR_PLATFORM")); cm.SetGeneratorToolset(this->GetSafeDefinition("CMAKE_GENERATOR_TOOLSET")); cm.LoadCache(); if (!cm.GetGlobalGenerator()->IsMultiConfig()) { if (cmValue config = this->GetDefinition("CMAKE_TRY_COMPILE_CONFIGURATION")) { // Tell the single-configuration generator which one to use. // Add this before the user-provided CMake arguments in case // one of the arguments is -DCMAKE_BUILD_TYPE=... cm.AddCacheEntry("CMAKE_BUILD_TYPE", config, "Build configuration", cmStateEnums::STRING); } } cmValue recursionDepth = this->GetDefinition("CMAKE_MAXIMUM_RECURSION_DEPTH"); if (recursionDepth) { cm.AddCacheEntry("CMAKE_MAXIMUM_RECURSION_DEPTH", recursionDepth, "Maximum recursion depth", cmStateEnums::STRING); } // if cmake args were provided then pass them in if (cmakeArgs) { // FIXME: Workaround to ignore unused CLI variables in try-compile. // // Ideally we should use SetArgs for options like --no-warn-unused-cli. // However, there is a subtle problem when certain arguments are passed to // a macro wrapping around try_compile or try_run that does not escape // semicolons in its parameters but just passes ${ARGV} or ${ARGN}. In // this case a list argument like "-DVAR=a;b" gets split into multiple // cmake arguments "-DVAR=a" and "b". Currently SetCacheArgs ignores // argument "b" and uses just "-DVAR=a", leading to a subtle bug in that // the try_compile or try_run does not get the proper value of VAR. If we // call SetArgs here then it would treat "b" as the source directory and // cause an error such as "The source directory .../CMakeFiles/CMakeTmp/b // does not exist", thus breaking the try_compile or try_run completely. // // Strictly speaking the bug is in the wrapper macro because the CMake // language has always flattened nested lists and the macro should escape // the semicolons in its arguments before forwarding them. However, this // bug is so subtle that projects typically work anyway, usually because // the value VAR=a is sufficient for the try_compile or try_run to get the // correct result. Calling SetArgs here would break such projects that // previously built. Instead we work around the issue by never reporting // unused arguments and ignoring options such as --no-warn-unused-cli. cm.SetWarnUnusedCli(false); // cm.SetArgs(*cmakeArgs, true); cm.SetCacheArgs(*cmakeArgs); } // to save time we pass the EnableLanguage info directly cm.GetGlobalGenerator()->EnableLanguagesFromGenerator( this->GetGlobalGenerator(), this); if (this->IsOn("CMAKE_SUPPRESS_DEVELOPER_WARNINGS")) { cm.AddCacheEntry("CMAKE_SUPPRESS_DEVELOPER_WARNINGS", "TRUE", "", cmStateEnums::INTERNAL); } else { cm.AddCacheEntry("CMAKE_SUPPRESS_DEVELOPER_WARNINGS", "FALSE", "", cmStateEnums::INTERNAL); } if (cm.Configure() != 0) { this->IssueMessage(MessageType::FATAL_ERROR, "Failed to configure test project build system."); cmSystemTools::SetFatalErrorOccurred(); this->IsSourceFileTryCompile = false; return 1; } if (cm.Generate() != 0) { this->IssueMessage(MessageType::FATAL_ERROR, "Failed to generate test project build system."); cmSystemTools::SetFatalErrorOccurred(); this->IsSourceFileTryCompile = false; return 1; } // finally call the generator to actually build the resulting project int ret = this->GetGlobalGenerator()->TryCompile( jobs, srcdir, bindir, projectName, targetName, fast, output, this); this->IsSourceFileTryCompile = false; return ret; } bool cmMakefile::GetIsSourceFileTryCompile() const { return this->IsSourceFileTryCompile; } cmake* cmMakefile::GetCMakeInstance() const { return this->GlobalGenerator->GetCMakeInstance(); } cmMessenger* cmMakefile::GetMessenger() const { return this->GetCMakeInstance()->GetMessenger(); } cmGlobalGenerator* cmMakefile::GetGlobalGenerator() const { return this->GlobalGenerator; } #ifndef CMAKE_BOOTSTRAP cmVariableWatch* cmMakefile::GetVariableWatch() const { if (this->GetCMakeInstance() && this->GetCMakeInstance()->GetVariableWatch()) { return this->GetCMakeInstance()->GetVariableWatch(); } return nullptr; } #endif cmState* cmMakefile::GetState() const { return this->GetCMakeInstance()->GetState(); } void cmMakefile::DisplayStatus(const std::string& message, float s) const { cmake* cm = this->GetCMakeInstance(); if (cm->GetWorkingMode() == cmake::FIND_PACKAGE_MODE) { // don't output any STATUS message in FIND_PACKAGE_MODE, since they will // directly be fed to the compiler, which will be confused. return; } cm->UpdateProgress(message, s); #ifdef CMake_ENABLE_DEBUGGER if (cm->GetDebugAdapter() != nullptr) { cm->GetDebugAdapter()->OnMessageOutput(MessageType::MESSAGE, message); } #endif } std::string cmMakefile::GetModulesFile(cm::string_view filename, bool& system, bool debug, std::string& debugBuffer) const { std::string result; // We search the module always in CMAKE_ROOT and in CMAKE_MODULE_PATH, // and then decide based on the policy setting which one to return. // See CMP0017 for more details. // The specific problem was that KDE 4.5.0 installs a // FindPackageHandleStandardArgs.cmake which doesn't have the new features // of FPHSA.cmake introduced in CMake 2.8.3 yet, and by setting // CMAKE_MODULE_PATH also e.g. FindZLIB.cmake from cmake included // FPHSA.cmake from kdelibs and not from CMake, and tried to use the // new features, which were not there in the version from kdelibs, and so // failed (" std::string moduleInCMakeRoot; std::string moduleInCMakeModulePath; // Always search in CMAKE_MODULE_PATH: cmValue cmakeModulePath = this->GetDefinition("CMAKE_MODULE_PATH"); if (cmakeModulePath) { cmList modulePath{ *cmakeModulePath }; // Look through the possible module directories. for (std::string itempl : modulePath) { cmSystemTools::ConvertToUnixSlashes(itempl); itempl += "/"; itempl += filename; if (cmSystemTools::FileExists(itempl)) { moduleInCMakeModulePath = itempl; break; } if (debug) { debugBuffer = cmStrCat(debugBuffer, " ", itempl, "\n"); } } } // Always search in the standard modules location. moduleInCMakeRoot = cmStrCat(cmSystemTools::GetCMakeRoot(), "/Modules/", filename); cmSystemTools::ConvertToUnixSlashes(moduleInCMakeRoot); if (!cmSystemTools::FileExists(moduleInCMakeRoot)) { if (debug) { debugBuffer = cmStrCat(debugBuffer, " ", moduleInCMakeRoot, "\n"); } moduleInCMakeRoot.clear(); } // Normally, prefer the files found in CMAKE_MODULE_PATH. Only when the file // from which we are being called is located itself in CMAKE_ROOT, then // prefer results from CMAKE_ROOT depending on the policy setting. system = false; result = moduleInCMakeModulePath; if (result.empty()) { system = true; result = moduleInCMakeRoot; } if (!moduleInCMakeModulePath.empty() && !moduleInCMakeRoot.empty()) { cmValue currentFile = this->GetDefinition("CMAKE_CURRENT_LIST_FILE"); std::string mods = cmStrCat(cmSystemTools::GetCMakeRoot(), "/Modules/"); if (currentFile && cmSystemTools::IsSubDirectory(*currentFile, mods)) { switch (this->GetPolicyStatus(cmPolicies::CMP0017)) { case cmPolicies::WARN: { std::ostringstream e; /* clang-format off */ e << "File " << *currentFile << " includes " << moduleInCMakeModulePath << " (found via CMAKE_MODULE_PATH) which shadows " << moduleInCMakeRoot << ". This may cause errors later on .\n" << cmPolicies::GetPolicyWarning(cmPolicies::CMP0017); /* clang-format on */ this->IssueMessage(MessageType::AUTHOR_WARNING, e.str()); CM_FALLTHROUGH; } case cmPolicies::OLD: system = false; result = moduleInCMakeModulePath; break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: system = true; result = moduleInCMakeRoot; break; } } } return result; } void cmMakefile::ConfigureString(const std::string& input, std::string& output, bool atOnly, bool escapeQuotes) const { // Split input to handle one line at a time. std::string::const_iterator lineStart = input.begin(); while (lineStart != input.end()) { // Find the end of this line. std::string::const_iterator lineEnd = lineStart; while (lineEnd != input.end() && *lineEnd != '\n') { ++lineEnd; } // Copy the line. std::string line(lineStart, lineEnd); // Skip the newline character. bool haveNewline = (lineEnd != input.end()); if (haveNewline) { ++lineEnd; } // Replace #cmakedefine instances. if (this->cmDefineRegex.find(line)) { cmValue def = this->GetDefinition(this->cmDefineRegex.match(2)); if (!def.IsOff()) { const std::string indentation = this->cmDefineRegex.match(1); cmSystemTools::ReplaceString(line, cmStrCat("#", indentation, "cmakedefine"), cmStrCat("#", indentation, "define")); output += line; } else { output += "/* #undef "; output += this->cmDefineRegex.match(2); output += " */"; } } else if (this->cmDefine01Regex.find(line)) { const std::string indentation = this->cmDefine01Regex.match(1); cmValue def = this->GetDefinition(this->cmDefine01Regex.match(2)); cmSystemTools::ReplaceString(line, cmStrCat("#", indentation, "cmakedefine01"), cmStrCat("#", indentation, "define")); output += line; if (!def.IsOff()) { output += " 1"; } else { output += " 0"; } } else { output += line; } if (haveNewline) { output += "\n"; } // Move to the next line. lineStart = lineEnd; } // Perform variable replacements. const char* filename = nullptr; long lineNumber = -1; if (!this->Backtrace.Empty()) { const auto& currentTrace = this->Backtrace.Top(); filename = currentTrace.FilePath.c_str(); lineNumber = currentTrace.Line; } this->ExpandVariablesInString(output, escapeQuotes, true, atOnly, filename, lineNumber, true, true); } int cmMakefile::ConfigureFile(const std::string& infile, const std::string& outfile, bool copyonly, bool atOnly, bool escapeQuotes, mode_t permissions, cmNewLineStyle newLine) { int res = 1; if (!this->CanIWriteThisFile(outfile)) { cmSystemTools::Error(cmStrCat("Attempt to write file: ", outfile, " into a source directory.")); return 0; } if (!cmSystemTools::FileExists(infile)) { cmSystemTools::Error(cmStrCat("File ", infile, " does not exist.")); return 0; } std::string soutfile = outfile; const std::string& sinfile = infile; this->AddCMakeDependFile(sinfile); cmSystemTools::ConvertToUnixSlashes(soutfile); // Re-generate if non-temporary outputs are missing. // when we finalize the configuration we will remove all // output files that now don't exist. this->AddCMakeOutputFile(soutfile); if (permissions == 0) { cmSystemTools::GetPermissions(sinfile, permissions); } std::string::size_type pos = soutfile.rfind('/'); if (pos != std::string::npos) { std::string path = soutfile.substr(0, pos); cmSystemTools::MakeDirectory(path); } if (copyonly) { if (!cmSystemTools::CopyFileIfDifferent(sinfile, soutfile)) { this->IssueMessage(MessageType::FATAL_ERROR, cmSystemTools::GetLastSystemError()); return 0; } if (!cmSystemTools::SetPermissions(soutfile, permissions)) { this->IssueMessage(MessageType::FATAL_ERROR, cmSystemTools::GetLastSystemError()); return 0; } } else { std::string newLineCharacters; std::ios::openmode omode = std::ios::out | std::ios::trunc; if (newLine.IsValid()) { newLineCharacters = newLine.GetCharacters(); omode |= std::ios::binary; } else { newLineCharacters = "\n"; } std::string tempOutputFile = cmStrCat(soutfile, ".tmp"); cmsys::ofstream fout(tempOutputFile.c_str(), omode); if (!fout) { cmSystemTools::Error("Could not open file for write in copy operation " + tempOutputFile); cmSystemTools::ReportLastSystemError(""); return 0; } cmsys::ifstream fin(sinfile.c_str()); if (!fin) { cmSystemTools::Error("Could not open file for read in copy operation " + sinfile); return 0; } cmsys::FStream::BOM bom = cmsys::FStream::ReadBOM(fin); if (bom != cmsys::FStream::BOM_None && bom != cmsys::FStream::BOM_UTF8) { std::ostringstream e; e << "File starts with a Byte-Order-Mark that is not UTF-8:\n " << sinfile; this->IssueMessage(MessageType::FATAL_ERROR, e.str()); return 0; } // rewind to copy BOM to output file fin.seekg(0); // now copy input to output and expand variables in the // input file at the same time std::string inLine; std::string outLine; while (cmSystemTools::GetLineFromStream(fin, inLine)) { outLine.clear(); this->ConfigureString(inLine, outLine, atOnly, escapeQuotes); fout << outLine << newLineCharacters; } // close the files before attempting to copy fin.close(); fout.close(); if (!cmSystemTools::CopyFileIfDifferent(tempOutputFile, soutfile)) { this->IssueMessage(MessageType::FATAL_ERROR, cmSystemTools::GetLastSystemError()); res = 0; } else { if (!cmSystemTools::SetPermissions(soutfile, permissions)) { this->IssueMessage(MessageType::FATAL_ERROR, cmSystemTools::GetLastSystemError()); res = 0; } } cmSystemTools::RemoveFile(tempOutputFile); } return res; } void cmMakefile::SetProperty(const std::string& prop, cmValue value) { this->StateSnapshot.GetDirectory().SetProperty(prop, value, this->Backtrace); } void cmMakefile::AppendProperty(const std::string& prop, const std::string& value, bool asString) { this->StateSnapshot.GetDirectory().AppendProperty(prop, value, asString, this->Backtrace); } cmValue cmMakefile::GetProperty(const std::string& prop) const { // Check for computed properties. static std::string output; if (prop == "TESTS"_s) { std::vector keys; // get list of keys const auto* t = this; std::transform( t->Tests.begin(), t->Tests.end(), std::back_inserter(keys), [](decltype(t->Tests)::value_type const& pair) { return pair.first; }); output = cmList::to_string(keys); return cmValue(output); } return this->StateSnapshot.GetDirectory().GetProperty(prop); } cmValue cmMakefile::GetProperty(const std::string& prop, bool chain) const { return this->StateSnapshot.GetDirectory().GetProperty(prop, chain); } bool cmMakefile::GetPropertyAsBool(const std::string& prop) const { return this->GetProperty(prop).IsOn(); } std::vector cmMakefile::GetPropertyKeys() const { return this->StateSnapshot.GetDirectory().GetPropertyKeys(); } cmTarget* cmMakefile::FindLocalNonAliasTarget(const std::string& name) const { auto i = this->Targets.find(name); if (i != this->Targets.end()) { return &i->second; } return nullptr; } cmTest* cmMakefile::CreateTest(const std::string& testName) { cmTest* test = this->GetTest(testName); if (test) { return test; } auto newTest = cm::make_unique(this); test = newTest.get(); newTest->SetName(testName); this->Tests[testName] = std::move(newTest); return test; } cmTest* cmMakefile::GetTest(const std::string& testName) const { auto mi = this->Tests.find(testName); if (mi != this->Tests.end()) { return mi->second.get(); } return nullptr; } void cmMakefile::GetTests(const std::string& config, std::vector& tests) const { for (const auto& generator : this->GetTestGenerators()) { if (generator->TestsForConfig(config)) { tests.push_back(generator->GetTest()); } } } void cmMakefile::AddCMakeDependFilesFromUser() { cmList deps; if (cmValue deps_str = this->GetProperty("CMAKE_CONFIGURE_DEPENDS")) { deps.assign(*deps_str); } for (auto const& dep : deps) { if (cmSystemTools::FileIsFullPath(dep)) { this->AddCMakeDependFile(dep); } else { std::string f = cmStrCat(this->GetCurrentSourceDirectory(), '/', dep); this->AddCMakeDependFile(f); } } } std::string cmMakefile::FormatListFileStack() const { std::vector listFiles; cmStateSnapshot snp = this->StateSnapshot; while (snp.IsValid()) { listFiles.push_back(snp.GetExecutionListFile()); snp = snp.GetCallStackParent(); } std::reverse(listFiles.begin(), listFiles.end()); std::ostringstream tmp; size_t depth = listFiles.size(); if (depth > 0) { auto it = listFiles.end(); do { if (depth != listFiles.size()) { tmp << "\n "; } --it; tmp << "["; tmp << depth; tmp << "]\t"; tmp << *it; depth--; } while (it != listFiles.begin()); } return tmp.str(); } void cmMakefile::PushScope() { this->StateSnapshot = this->GetState()->CreateVariableScopeSnapshot(this->StateSnapshot); this->PushLoopBlockBarrier(); #if !defined(CMAKE_BOOTSTRAP) this->GetGlobalGenerator()->GetFileLockPool().PushFunctionScope(); #endif } void cmMakefile::PopScope() { #if !defined(CMAKE_BOOTSTRAP) this->GetGlobalGenerator()->GetFileLockPool().PopFunctionScope(); #endif this->PopLoopBlockBarrier(); this->PopSnapshot(); } void cmMakefile::RaiseScope(const std::string& var, const char* varDef) { if (var.empty()) { return; } if (!this->StateSnapshot.RaiseScope(var, varDef)) { std::ostringstream m; m << "Cannot set \"" << var << "\": current scope has no parent."; this->IssueMessage(MessageType::AUTHOR_WARNING, m.str()); return; } #ifndef CMAKE_BOOTSTRAP cmVariableWatch* vv = this->GetVariableWatch(); if (vv) { vv->VariableAccessed(var, cmVariableWatch::VARIABLE_MODIFIED_ACCESS, varDef, this); } #endif } void cmMakefile::RaiseScope(const std::vector& variables) { for (auto const& varName : variables) { if (this->IsNormalDefinitionSet(varName)) { this->RaiseScope(varName, this->GetDefinition(varName)); } else { // unset variable in parent scope this->RaiseScope(varName, nullptr); } } } cmTarget* cmMakefile::AddImportedTarget(const std::string& name, cmStateEnums::TargetType type, bool global) { // Create the target. std::unique_ptr target( new cmTarget(name, type, global ? cmTarget::Visibility::ImportedGlobally : cmTarget::Visibility::Imported, this, cmTarget::PerConfig::Yes)); // Add to the set of available imported targets. this->ImportedTargets[name] = target.get(); this->GetGlobalGenerator()->IndexTarget(target.get()); this->GetStateSnapshot().GetDirectory().AddImportedTargetName(name); // Transfer ownership to this cmMakefile object. this->ImportedTargetsOwned.push_back(std::move(target)); return this->ImportedTargetsOwned.back().get(); } cmTarget* cmMakefile::FindTargetToUse(const std::string& name, bool excludeAliases) const { // Look for an imported target. These take priority because they // are more local in scope and do not have to be globally unique. auto targetName = name; if (!excludeAliases) { // Look for local alias targets. auto alias = this->AliasTargets.find(name); if (alias != this->AliasTargets.end()) { targetName = alias->second; } } auto imported = this->ImportedTargets.find(targetName); if (imported != this->ImportedTargets.end()) { return imported->second; } // Look for a target built in this directory. if (cmTarget* t = this->FindLocalNonAliasTarget(name)) { return t; } // Look for a target built in this project. return this->GetGlobalGenerator()->FindTarget(name, excludeAliases); } bool cmMakefile::IsAlias(const std::string& name) const { if (cm::contains(this->AliasTargets, name)) { return true; } return this->GetGlobalGenerator()->IsAlias(name); } bool cmMakefile::EnforceUniqueName(std::string const& name, std::string& msg, bool isCustom) const { if (this->IsAlias(name)) { std::ostringstream e; e << "cannot create target \"" << name << "\" because an alias with the same name already exists."; msg = e.str(); return false; } if (cmTarget* existing = this->FindTargetToUse(name)) { // The name given conflicts with an existing target. Produce an // error in a compatible way. if (existing->IsImported()) { // Imported targets were not supported in previous versions. // This is new code, so we can make it an error. std::ostringstream e; e << "cannot create target \"" << name << "\" because an imported target with the same name already exists."; msg = e.str(); return false; } // target names must be globally unique switch (this->GetPolicyStatus(cmPolicies::CMP0002)) { case cmPolicies::WARN: this->IssueMessage(MessageType::AUTHOR_WARNING, cmPolicies::GetPolicyWarning(cmPolicies::CMP0002)); CM_FALLTHROUGH; case cmPolicies::OLD: return true; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: this->IssueMessage( MessageType::FATAL_ERROR, cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0002)); return true; case cmPolicies::NEW: break; } // The conflict is with a non-imported target. // Allow this if the user has requested support. cmake* cm = this->GetCMakeInstance(); if (isCustom && existing->GetType() == cmStateEnums::UTILITY && this != existing->GetMakefile() && cm->GetState()->GetGlobalPropertyAsBool( "ALLOW_DUPLICATE_CUSTOM_TARGETS")) { return true; } // Produce an error that tells the user how to work around the // problem. std::ostringstream e; e << "cannot create target \"" << name << "\" because another target with the same name already exists. " << "The existing target is "; switch (existing->GetType()) { case cmStateEnums::EXECUTABLE: e << "an executable "; break; case cmStateEnums::STATIC_LIBRARY: e << "a static library "; break; case cmStateEnums::SHARED_LIBRARY: e << "a shared library "; break; case cmStateEnums::MODULE_LIBRARY: e << "a module library "; break; case cmStateEnums::UTILITY: e << "a custom target "; break; case cmStateEnums::INTERFACE_LIBRARY: e << "an interface library "; break; default: break; } e << "created in source directory \"" << existing->GetMakefile()->GetCurrentSourceDirectory() << "\". " << "See documentation for policy CMP0002 for more details."; msg = e.str(); return false; } return true; } bool cmMakefile::EnforceUniqueDir(const std::string& srcPath, const std::string& binPath) const { // Make sure the binary directory is unique. cmGlobalGenerator* gg = this->GetGlobalGenerator(); if (gg->BinaryDirectoryIsNew(binPath)) { return true; } std::ostringstream e; switch (this->GetPolicyStatus(cmPolicies::CMP0013)) { case cmPolicies::WARN: // Print the warning. /* clang-format off */ e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0013) << "\n" << "The binary directory\n" << " " << binPath << "\n" << "is already used to build a source directory. " << "This command uses it to build source directory\n" << " " << srcPath << "\n" << "which can generate conflicting build files. " << "CMake does not support this use case but it used " << "to work accidentally and is being allowed for " << "compatibility."; /* clang-format on */ this->IssueMessage(MessageType::AUTHOR_WARNING, e.str()); CM_FALLTHROUGH; case cmPolicies::OLD: // OLD behavior does not warn. return true; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: e << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0013) << "\n"; CM_FALLTHROUGH; case cmPolicies::NEW: // NEW behavior prints the error. /* clang-format off */ e << "The binary directory\n" << " " << binPath << "\n" << "is already used to build a source directory. " << "It cannot be used to build source directory\n" << " " << srcPath << "\n" << "Specify a unique binary directory name."; /* clang-format on */ this->IssueMessage(MessageType::FATAL_ERROR, e.str()); break; } return false; } static std::string const matchVariables[] = { "CMAKE_MATCH_0", "CMAKE_MATCH_1", "CMAKE_MATCH_2", "CMAKE_MATCH_3", "CMAKE_MATCH_4", "CMAKE_MATCH_5", "CMAKE_MATCH_6", "CMAKE_MATCH_7", "CMAKE_MATCH_8", "CMAKE_MATCH_9" }; static std::string const nMatchesVariable = "CMAKE_MATCH_COUNT"; void cmMakefile::ClearMatches() { cmValue nMatchesStr = this->GetDefinition(nMatchesVariable); if (!nMatchesStr) { return; } int nMatches = atoi(nMatchesStr->c_str()); for (int i = 0; i <= nMatches; i++) { std::string const& var = matchVariables[i]; std::string const& s = this->GetSafeDefinition(var); if (!s.empty()) { this->AddDefinition(var, ""); this->MarkVariableAsUsed(var); } } this->AddDefinition(nMatchesVariable, "0"); this->MarkVariableAsUsed(nMatchesVariable); } void cmMakefile::StoreMatches(cmsys::RegularExpression& re) { char highest = 0; for (int i = 0; i < 10; i++) { std::string const& m = re.match(i); if (!m.empty()) { std::string const& var = matchVariables[i]; this->AddDefinition(var, m); this->MarkVariableAsUsed(var); highest = static_cast('0' + i); } } char nMatches[] = { highest, '\0' }; this->AddDefinition(nMatchesVariable, nMatches); this->MarkVariableAsUsed(nMatchesVariable); } cmStateSnapshot cmMakefile::GetStateSnapshot() const { return this->StateSnapshot; } const char* cmMakefile::GetDefineFlagsCMP0059() const { return this->DefineFlagsOrig.c_str(); } cmPolicies::PolicyStatus cmMakefile::GetPolicyStatus(cmPolicies::PolicyID id, bool parent_scope) const { return this->StateSnapshot.GetPolicy(id, parent_scope); } bool cmMakefile::PolicyOptionalWarningEnabled(std::string const& var) const { // Check for an explicit CMAKE_POLICY_WARNING_CMP setting. if (cmValue val = this->GetDefinition(var)) { return val.IsOn(); } // Enable optional policy warnings with --debug-output, --trace, // or --trace-expand. cmake* cm = this->GetCMakeInstance(); return cm->GetDebugOutput() || cm->GetTrace(); } bool cmMakefile::SetPolicy(const char* id, cmPolicies::PolicyStatus status) { cmPolicies::PolicyID pid; if (!cmPolicies::GetPolicyID(id, /* out */ pid)) { std::ostringstream e; e << "Policy \"" << id << "\" is not known to this version of CMake."; this->IssueMessage(MessageType::FATAL_ERROR, e.str()); return false; } return this->SetPolicy(pid, status); } bool cmMakefile::SetPolicy(cmPolicies::PolicyID id, cmPolicies::PolicyStatus status) { // A REQUIRED_ALWAYS policy may be set only to NEW. if (status != cmPolicies::NEW && cmPolicies::GetPolicyStatus(id) == cmPolicies::REQUIRED_ALWAYS) { std::string msg = cmPolicies::GetRequiredAlwaysPolicyError(id); this->IssueMessage(MessageType::FATAL_ERROR, msg); return false; } // Deprecate old policies. if (status == cmPolicies::OLD && id <= cmPolicies::CMP0128 && !(this->GetCMakeInstance()->GetIsInTryCompile() && ( // Policies set by cmCoreTryCompile::TryCompileCode. id == cmPolicies::CMP0065 || id == cmPolicies::CMP0083 || id == cmPolicies::CMP0091 || id == cmPolicies::CMP0104 || id == cmPolicies::CMP0123 || id == cmPolicies::CMP0126 || id == cmPolicies::CMP0128)) && (!this->IsSet("CMAKE_WARN_DEPRECATED") || this->IsOn("CMAKE_WARN_DEPRECATED"))) { this->IssueMessage(MessageType::DEPRECATION_WARNING, cmPolicies::GetPolicyDeprecatedWarning(id)); } this->StateSnapshot.SetPolicy(id, status); return true; } cmMakefile::PolicyPushPop::PolicyPushPop(cmMakefile* m) : Makefile(m) { this->Makefile->PushPolicy(); } cmMakefile::PolicyPushPop::~PolicyPushPop() { this->Makefile->PopPolicy(); } void cmMakefile::PushPolicy(bool weak, cmPolicies::PolicyMap const& pm) { this->StateSnapshot.PushPolicy(pm, weak); } void cmMakefile::PopPolicy() { if (!this->StateSnapshot.PopPolicy()) { this->IssueMessage(MessageType::FATAL_ERROR, "cmake_policy POP without matching PUSH"); } } void cmMakefile::PopSnapshot(bool reportError) { // cmStateSnapshot manages nested policy scopes within it. // Since the scope corresponding to the snapshot is closing, // reject any still-open nested policy scopes with an error. while (this->StateSnapshot.CanPopPolicyScope()) { if (reportError) { this->IssueMessage(MessageType::FATAL_ERROR, "cmake_policy PUSH without matching POP"); reportError = false; } this->PopPolicy(); } this->StateSnapshot = this->GetState()->Pop(this->StateSnapshot); assert(this->StateSnapshot.IsValid()); } bool cmMakefile::SetPolicyVersion(std::string const& version_min, std::string const& version_max) { return cmPolicies::ApplyPolicyVersion(this, version_min, version_max, cmPolicies::WarnCompat::On); } cmMakefile::VariablePushPop::VariablePushPop(cmMakefile* m) : Makefile(m) { this->Makefile->StateSnapshot = this->Makefile->GetState()->CreateVariableScopeSnapshot( this->Makefile->StateSnapshot); } cmMakefile::VariablePushPop::~VariablePushPop() { this->Makefile->PopSnapshot(); } bool cmMakefile::HasCMP0054AlreadyBeenReported( cmListFileContext const& context) const { return !this->CMP0054ReportedIds.insert(context).second; } void cmMakefile::RecordPolicies(cmPolicies::PolicyMap& pm) const { /* Record the setting of every policy. */ using PolicyID = cmPolicies::PolicyID; for (PolicyID pid = cmPolicies::CMP0000; pid != cmPolicies::CMPCOUNT; pid = static_cast(pid + 1)) { pm.Set(pid, this->GetPolicyStatus(pid)); } } bool cmMakefile::IgnoreErrorsCMP0061() const { bool ignoreErrors = true; switch (this->GetPolicyStatus(cmPolicies::CMP0061)) { case cmPolicies::WARN: // No warning for this policy! CM_FALLTHROUGH; case cmPolicies::OLD: break; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: ignoreErrors = false; break; } return ignoreErrors; } cmMakefile::FunctionPushPop::FunctionPushPop(cmMakefile* mf, const std::string& fileName, cmPolicies::PolicyMap const& pm) : Makefile(mf) { this->Makefile->PushFunctionScope(fileName, pm); } cmMakefile::FunctionPushPop::~FunctionPushPop() { this->Makefile->PopFunctionScope(this->ReportError); } cmMakefile::MacroPushPop::MacroPushPop(cmMakefile* mf, const std::string& fileName, const cmPolicies::PolicyMap& pm) : Makefile(mf) { this->Makefile->PushMacroScope(fileName, pm); } cmMakefile::MacroPushPop::~MacroPushPop() { this->Makefile->PopMacroScope(this->ReportError); } cmMakefile::FindPackageStackRAII::FindPackageStackRAII(cmMakefile* mf, std::string const& name) : Makefile(mf) { this->Makefile->FindPackageStack = this->Makefile->FindPackageStack.Push(cmFindPackageCall{ name, this->Makefile->FindPackageStackNextIndex, }); this->Makefile->FindPackageStackNextIndex++; } cmMakefile::FindPackageStackRAII::~FindPackageStackRAII() { this->Makefile->FindPackageStackNextIndex = this->Makefile->FindPackageStack.Top().Index + 1; this->Makefile->FindPackageStack = this->Makefile->FindPackageStack.Pop(); if (!this->Makefile->FindPackageStack.Empty()) { auto top = this->Makefile->FindPackageStack.Top(); this->Makefile->FindPackageStack = this->Makefile->FindPackageStack.Pop(); top.Index = this->Makefile->FindPackageStackNextIndex; this->Makefile->FindPackageStackNextIndex++; this->Makefile->FindPackageStack = this->Makefile->FindPackageStack.Push(top); } } cmMakefile::DebugFindPkgRAII::DebugFindPkgRAII(cmMakefile* mf, std::string const& pkg) : Makefile(mf) , OldValue(this->Makefile->DebugFindPkg) { this->Makefile->DebugFindPkg = this->Makefile->GetCMakeInstance()->GetDebugFindPkgOutput(pkg); } cmMakefile::DebugFindPkgRAII::~DebugFindPkgRAII() { this->Makefile->DebugFindPkg = this->OldValue; } bool cmMakefile::GetDebugFindPkgMode() const { return this->DebugFindPkg; }