diff options
author | Brad King <brad.king@kitware.com> | 2020-09-30 15:55:24 (GMT) |
---|---|---|
committer | Kitware Robot <kwrobot@kitware.com> | 2020-09-30 15:55:33 (GMT) |
commit | d91c3e33cbf9592f90f65bd7d990a8b54ad140d1 (patch) | |
tree | 5b84e72c9c2d5ca1e3098d304f8582a140f52c3d /Source | |
parent | f002c1cfc7f66edb9c9821524671574c23f92cd2 (diff) | |
parent | e8b0359a4318bb682c96e527de7ed7f5be02c38f (diff) | |
download | CMake-d91c3e33cbf9592f90f65bd7d990a8b54ad140d1.zip CMake-d91c3e33cbf9592f90f65bd7d990a8b54ad140d1.tar.gz CMake-d91c3e33cbf9592f90f65bd7d990a8b54ad140d1.tar.bz2 |
Merge topic 'cmake_language-DEFER'
e8b0359a43 cmake_language: Add signature to DEFER calls to later times
9880549405 cmake_language: Make all errors fatal
4f33f3dcff cmake_language(CALL): Accept empty ${var} expansions
4ebe9c4ce1 cmake_language(EVAL): Factor out internal helper
78ff24a3a7 Help: Use singular placeholder name in cmake_language signature
edd60d4419 Tests: Simplify RunCMake.cmake_language invalid command cases
1a5bf8245e cmMakefile: Clarify name of internal list file run method
Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !5262
Diffstat (limited to 'Source')
-rw-r--r-- | Source/cmCMakeLanguageCommand.cxx | 380 | ||||
-rw-r--r-- | Source/cmCommandArgumentParserHelper.cxx | 12 | ||||
-rw-r--r-- | Source/cmGlobalGenerator.cxx | 7 | ||||
-rw-r--r-- | Source/cmGlobalGenerator.h | 5 | ||||
-rw-r--r-- | Source/cmListFileCache.cxx | 7 | ||||
-rw-r--r-- | Source/cmListFileCache.h | 10 | ||||
-rw-r--r-- | Source/cmMakefile.cxx | 198 | ||||
-rw-r--r-- | Source/cmMakefile.h | 39 | ||||
-rw-r--r-- | Source/cmState.cxx | 15 | ||||
-rw-r--r-- | Source/cmState.h | 2 | ||||
-rw-r--r-- | Source/cmStateTypes.h | 1 | ||||
-rw-r--r-- | Source/cmake.cxx | 2 |
12 files changed, 576 insertions, 102 deletions
diff --git a/Source/cmCMakeLanguageCommand.cxx b/Source/cmCMakeLanguageCommand.cxx index d513611..9277c20 100644 --- a/Source/cmCMakeLanguageCommand.cxx +++ b/Source/cmCMakeLanguageCommand.cxx @@ -7,11 +7,14 @@ #include <cstddef> #include <memory> #include <string> +#include <utility> +#include <cm/optional> #include <cm/string_view> #include <cmext/string_view> #include "cmExecutionStatus.h" +#include "cmGlobalGenerator.h" #include "cmListFileCache.h" #include "cmMakefile.h" #include "cmRange.h" @@ -19,6 +22,14 @@ #include "cmSystemTools.h" namespace { + +bool FatalError(cmExecutionStatus& status, std::string const& error) +{ + status.SetError(error); + cmSystemTools::SetFatalErrorOccured(); + return false; +} + std::array<cm::static_string_view, 12> InvalidCommands{ { // clang-format off "function"_s, "endfunction"_s, @@ -28,110 +39,327 @@ std::array<cm::static_string_view, 12> InvalidCommands{ "foreach"_s, "endforeach"_s } // clang-format on }; -} -bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args, - cmExecutionStatus& status) +std::array<cm::static_string_view, 1> InvalidDeferCommands{ + { + // clang-format off + "return"_s, + } // clang-format on +}; + +struct Defer +{ + std::string Id; + std::string IdVar; + cmMakefile* Directory = nullptr; +}; + +bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args, + std::string const& callCommand, + size_t startArg, cm::optional<Defer> defer, + cmExecutionStatus& status) { - if (args.empty()) { - status.SetError("called with incorrect number of arguments"); - return false; + // ensure specified command is valid + // start/end flow control commands are not allowed + auto cmd = cmSystemTools::LowerCase(callCommand); + if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) != + InvalidCommands.cend()) { + return FatalError(status, + cmStrCat("invalid command specified: "_s, callCommand)); + } + if (defer && + std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(), + cmd) != InvalidDeferCommands.cend()) { + return FatalError(status, + cmStrCat("invalid command specified: "_s, callCommand)); } cmMakefile& makefile = status.GetMakefile(); cmListFileContext context = makefile.GetBacktrace().Top(); - bool result = false; + cmListFileFunction func; + func.Name = callCommand; + func.Line = context.Line; + + // The rest of the arguments are passed to the function call above + for (size_t i = startArg; i < args.size(); ++i) { + cmListFileArgument lfarg; + lfarg.Delim = args[i].Delim; + lfarg.Line = context.Line; + lfarg.Value = args[i].Value; + func.Arguments.emplace_back(lfarg); + } + + if (defer) { + if (defer->Id.empty()) { + defer->Id = makefile.NewDeferId(); + } + if (!defer->IdVar.empty()) { + makefile.AddDefinition(defer->IdVar, defer->Id); + } + cmMakefile* deferMakefile = + defer->Directory ? defer->Directory : &makefile; + if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) { + return FatalError( + status, + cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s, + deferMakefile->GetCurrentBinaryDirectory(), + "\nat this time."_s)); + } + return true; + } + return makefile.ExecuteCommand(func, status); +} + +bool cmCMakeLanguageCommandDEFER(Defer const& defer, + std::vector<std::string> const& args, + size_t arg, cmExecutionStatus& status) +{ + cmMakefile* deferMakefile = + defer.Directory ? defer.Directory : &status.GetMakefile(); + if (args[arg] == "CANCEL_CALL"_s) { + ++arg; // Consume CANCEL_CALL. + auto ids = cmMakeRange(args).advance(arg); + for (std::string const& id : ids) { + if (id[0] >= 'A' && id[0] <= 'Z') { + return FatalError( + status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id)); + } + if (!deferMakefile->DeferCancelCall(id)) { + return FatalError( + status, + cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s, + deferMakefile->GetCurrentBinaryDirectory(), + "\nat this time."_s)); + } + } + return true; + } + if (args[arg] == "GET_CALL_IDS"_s) { + ++arg; // Consume GET_CALL_IDS. + if (arg == args.size()) { + return FatalError(status, "DEFER GET_CALL_IDS missing output variable"); + } + std::string const& var = args[arg++]; + if (arg != args.size()) { + return FatalError(status, "DEFER GET_CALL_IDS given too many arguments"); + } + cm::optional<std::string> ids = deferMakefile->DeferGetCallIds(); + if (!ids) { + return FatalError( + status, + cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s, + deferMakefile->GetCurrentBinaryDirectory(), + "\nat this time."_s)); + } + status.GetMakefile().AddDefinition(var, *ids); + return true; + } + if (args[arg] == "GET_CALL"_s) { + ++arg; // Consume GET_CALL. + if (arg == args.size()) { + return FatalError(status, "DEFER GET_CALL missing id"); + } + std::string const& id = args[arg++]; + if (arg == args.size()) { + return FatalError(status, "DEFER GET_CALL missing output variable"); + } + std::string const& var = args[arg++]; + if (arg != args.size()) { + return FatalError(status, "DEFER GET_CALL given too many arguments"); + } + if (id.empty()) { + return FatalError(status, "DEFER GET_CALL id may not be empty"); + } + if (id[0] >= 'A' && id[0] <= 'Z') { + return FatalError(status, + cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id)); + } + cm::optional<std::string> call = deferMakefile->DeferGetCall(id); + if (!call) { + return FatalError( + status, + cmStrCat("DEFER GET_CALL may not access directory:\n "_s, + deferMakefile->GetCurrentBinaryDirectory(), + "\nat this time."_s)); + } + status.GetMakefile().AddDefinition(var, *call); + return true; + } + return FatalError(status, + cmStrCat("DEFER operation unknown: "_s, args[arg])); +} - std::vector<std::string> dispatchExpandedArgs; - std::vector<cmListFileArgument> dispatchArgs; - dispatchArgs.emplace_back(args[0]); - makefile.ExpandArguments(dispatchArgs, dispatchExpandedArgs); +bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args, + cmExecutionStatus& status) +{ + cmMakefile& makefile = status.GetMakefile(); + cmListFileContext context = makefile.GetBacktrace().Top(); + std::vector<std::string> expandedArgs; + makefile.ExpandArguments(args, expandedArgs); - if (dispatchExpandedArgs.empty()) { - status.SetError("called with incorrect number of arguments"); - return false; + if (expandedArgs.size() < 2) { + return FatalError(status, "called with incorrect number of arguments"); } - if (dispatchExpandedArgs[0] == "CALL") { - if ((args.size() == 1 && dispatchExpandedArgs.size() != 2) || - dispatchExpandedArgs.size() > 2) { - status.SetError("called with incorrect number of arguments"); - return false; + if (expandedArgs[1] != "CODE") { + auto code_iter = + std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE"); + if (code_iter == expandedArgs.end()) { + return FatalError(status, "called without CODE argument"); } + return FatalError( + status, + "called with unsupported arguments between EVAL and CODE arguments"); + } + + const std::string code = + cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " "); + return makefile.ReadListFileAsString( + code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL")); +} +} - // First argument is the name of the function to call - std::string callCommand; - size_t startArg; - if (dispatchExpandedArgs.size() == 1) { - std::vector<std::string> functionExpandedArg; - std::vector<cmListFileArgument> functionArg; - functionArg.emplace_back(args[1]); - makefile.ExpandArguments(functionArg, functionExpandedArg); +bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args, + cmExecutionStatus& status) +{ + std::vector<std::string> expArgs; + size_t rawArg = 0; + size_t expArg = 0; - if (functionExpandedArg.size() != 1) { - status.SetError("called with incorrect number of arguments"); + // Helper to consume and expand one raw argument at a time. + auto moreArgs = [&]() -> bool { + while (expArg >= expArgs.size()) { + if (rawArg >= args.size()) { return false; } - - callCommand = functionExpandedArg[0]; - startArg = 2; - } else { - callCommand = dispatchExpandedArgs[1]; - startArg = 1; + std::vector<cmListFileArgument> tmpArg; + tmpArg.emplace_back(args[rawArg++]); + status.GetMakefile().ExpandArguments(tmpArg, expArgs); } + return true; + }; + auto finishArgs = [&]() { + std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end()); + status.GetMakefile().ExpandArguments(tmpArgs, expArgs); + rawArg = args.size(); + }; + + if (!moreArgs()) { + return FatalError(status, "called with incorrect number of arguments"); + } + + cm::optional<Defer> maybeDefer; + if (expArgs[expArg] == "DEFER"_s) { + ++expArg; // Consume "DEFER". - // ensure specified command is valid - // start/end flow control commands are not allowed - auto cmd = cmSystemTools::LowerCase(callCommand); - if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) != - InvalidCommands.cend()) { - status.SetError(cmStrCat("invalid command specified: "_s, callCommand)); - return false; + if (!moreArgs()) { + return FatalError(status, "DEFER requires at least one argument"); } - cmListFileFunction func; - func.Name = callCommand; - func.Line = context.Line; + Defer defer; + + // Process optional arguments. + while (moreArgs()) { + if (expArgs[expArg] == "CALL"_s) { + break; + } + if (expArgs[expArg] == "CANCEL_CALL"_s || + expArgs[expArg] == "GET_CALL_IDS"_s || + expArgs[expArg] == "GET_CALL"_s) { + if (!defer.Id.empty() || !defer.IdVar.empty()) { + return FatalError(status, + cmStrCat("DEFER "_s, expArgs[expArg], + " does not accept ID or ID_VAR."_s)); + } + finishArgs(); + return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status); + } + if (expArgs[expArg] == "DIRECTORY"_s) { + ++expArg; // Consume "DIRECTORY". + if (defer.Directory) { + return FatalError(status, + "DEFER given multiple DIRECTORY arguments"); + } + if (!moreArgs()) { + return FatalError(status, "DEFER DIRECTORY missing value"); + } + std::string dir = expArgs[expArg++]; + if (dir.empty()) { + return FatalError(status, "DEFER DIRECTORY may not be empty"); + } + dir = cmSystemTools::CollapseFullPath( + dir, status.GetMakefile().GetCurrentSourceDirectory()); + defer.Directory = + status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir); + if (!defer.Directory) { + return FatalError(status, + cmStrCat("DEFER DIRECTORY:\n "_s, dir, + "\nis not known. "_s, + "It may not have been processed yet."_s)); + } + } else if (expArgs[expArg] == "ID"_s) { + ++expArg; // Consume "ID". + if (!defer.Id.empty()) { + return FatalError(status, "DEFER given multiple ID arguments"); + } + if (!moreArgs()) { + return FatalError(status, "DEFER ID missing value"); + } + defer.Id = expArgs[expArg++]; + if (defer.Id.empty()) { + return FatalError(status, "DEFER ID may not be empty"); + } + if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') { + return FatalError(status, "DEFER ID may not start in A-Z."); + } + } else if (expArgs[expArg] == "ID_VAR"_s) { + ++expArg; // Consume "ID_VAR". + if (!defer.IdVar.empty()) { + return FatalError(status, "DEFER given multiple ID_VAR arguments"); + } + if (!moreArgs()) { + return FatalError(status, "DEFER ID_VAR missing variable name"); + } + defer.IdVar = expArgs[expArg++]; + if (defer.IdVar.empty()) { + return FatalError(status, "DEFER ID_VAR may not be empty"); + } + } else { + return FatalError( + status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg])); + } + } - // The rest of the arguments are passed to the function call above - for (size_t i = startArg; i < args.size(); ++i) { - cmListFileArgument lfarg; - lfarg.Delim = args[i].Delim; - lfarg.Line = context.Line; - lfarg.Value = args[i].Value; - func.Arguments.emplace_back(lfarg); + if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) { + return FatalError(status, "DEFER must be followed by a CALL argument"); } - result = makefile.ExecuteCommand(func, status); - } else if (dispatchExpandedArgs[0] == "EVAL") { - std::vector<std::string> expandedArgs; - makefile.ExpandArguments(args, expandedArgs); + maybeDefer = std::move(defer); + } + + if (expArgs[expArg] == "CALL") { + ++expArg; // Consume "CALL". - if (expandedArgs.size() < 2) { - status.SetError("called with incorrect number of arguments"); - return false; + // CALL requires a command name. + if (!moreArgs()) { + return FatalError(status, "CALL missing command name"); } + std::string const& callCommand = expArgs[expArg++]; - if (expandedArgs[1] != "CODE") { - auto code_iter = - std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE"); - if (code_iter == expandedArgs.end()) { - status.SetError("called without CODE argument"); - } else { - status.SetError( - "called with unsupported arguments between EVAL and CODE arguments"); - } - return false; + // CALL accepts no further expanded arguments. + if (expArg != expArgs.size()) { + return FatalError(status, "CALL command's arguments must be literal"); } - const std::string code = - cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " "); - result = makefile.ReadListFileAsString( - code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL")); - } else { - status.SetError("called with unknown meta-operation"); + // Run the CALL. + return cmCMakeLanguageCommandCALL(args, callCommand, rawArg, + std::move(maybeDefer), status); + } + + if (expArgs[expArg] == "EVAL") { + return cmCMakeLanguageCommandEVAL(args, status); } - return result; + return FatalError(status, "called with unknown meta-operation"); } diff --git a/Source/cmCommandArgumentParserHelper.cxx b/Source/cmCommandArgumentParserHelper.cxx index e3d014e..d4f5022 100644 --- a/Source/cmCommandArgumentParserHelper.cxx +++ b/Source/cmCommandArgumentParserHelper.cxx @@ -8,8 +8,11 @@ #include <utility> #include <cm/memory> +#include <cm/optional> +#include <cmext/string_view> #include "cmCommandArgumentLexer.h" +#include "cmListFileCache.h" #include "cmMakefile.h" #include "cmProperty.h" #include "cmState.h" @@ -91,7 +94,14 @@ const char* cmCommandArgumentParserHelper::ExpandVariable(const char* var) return nullptr; } if (this->FileLine >= 0 && strcmp(var, "CMAKE_CURRENT_LIST_LINE") == 0) { - return this->AddString(std::to_string(this->FileLine)); + std::string line; + cmListFileContext const& top = this->Makefile->GetBacktrace().Top(); + if (top.DeferId) { + line = cmStrCat("DEFERRED:"_s, *top.DeferId); + } else { + line = std::to_string(this->FileLine); + } + return this->AddString(line); } cmProp value = this->Makefile->GetDefinition(var); if (!value) { diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 46030e0..1197db6 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -15,6 +15,7 @@ #include <cm/memory> #include <cmext/algorithm> +#include <cmext/string_view> #include "cmsys/Directory.hxx" #include "cmsys/FStream.hxx" @@ -1211,6 +1212,7 @@ void cmGlobalGenerator::Configure() { this->FirstTimeProgress = 0.0f; this->ClearGeneratorMembers(); + this->NextDeferId = 0; cmStateSnapshot snapshot = this->CMakeInstance->GetCurrentSnapshot(); @@ -3256,6 +3258,11 @@ const std::string& cmGlobalGenerator::GetRealPath(const std::string& dir) return i->second; } +std::string cmGlobalGenerator::NewDeferId() +{ + return cmStrCat("__"_s, std::to_string(this->NextDeferId++)); +} + void cmGlobalGenerator::ProcessEvaluationFiles() { std::vector<std::string> generatedFiles; diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 478028e..b532a43 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -508,6 +508,8 @@ public: std::string const& GetRealPath(std::string const& dir); + std::string NewDeferId(); + protected: // for a project collect all its targets by following depend // information, and also collect all the targets @@ -633,6 +635,9 @@ private: std::map<std::string, int> LanguageToLinkerPreference; std::map<std::string, std::string> LanguageToOriginalSharedLibFlags; + // Deferral id generation. + size_t NextDeferId = 0; + // Record hashes for rules and outputs. struct RuleHash { diff --git a/Source/cmListFileCache.cxx b/Source/cmListFileCache.cxx index 7ebb02f..d678b56 100644 --- a/Source/cmListFileCache.cxx +++ b/Source/cmListFileCache.cxx @@ -446,7 +446,8 @@ void cmListFileBacktrace::PrintCallStack(std::ostream& out) const cmStateSnapshot bottom = this->GetBottom(); for (Entry const* cur = this->TopEntry->Parent.get(); !cur->IsBottom(); cur = cur->Parent.get()) { - if (cur->Context.Name.empty()) { + if (cur->Context.Name.empty() && + cur->Context.Line != cmListFileContext::DeferPlaceholderLine) { // Skip this whole-file scope. When we get here we already will // have printed a more-specific context within the file. continue; @@ -483,11 +484,13 @@ bool cmListFileBacktrace::Empty() const std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc) { os << lfc.FilePath; - if (lfc.Line) { + if (lfc.Line > 0) { os << ":" << lfc.Line; if (!lfc.Name.empty()) { os << " (" << lfc.Name << ")"; } + } else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) { + os << ":DEFERRED"; } return os; } diff --git a/Source/cmListFileCache.h b/Source/cmListFileCache.h index 5773e6a..5617536 100644 --- a/Source/cmListFileCache.h +++ b/Source/cmListFileCache.h @@ -11,6 +11,8 @@ #include <utility> #include <vector> +#include <cm/optional> + #include "cmStateSnapshot.h" /** \class cmListFileCache @@ -72,6 +74,8 @@ public: std::string Name; std::string FilePath; long Line = 0; + static long const DeferPlaceholderLine = -1; + cm::optional<std::string> DeferId; cmListFileContext() = default; cmListFileContext(std::string name, std::string filePath, long line) @@ -81,13 +85,15 @@ public: { } - static cmListFileContext FromCommandContext(cmCommandContext const& lfcc, - std::string const& fileName) + static cmListFileContext FromCommandContext( + cmCommandContext const& lfcc, std::string const& fileName, + cm::optional<std::string> deferId = {}) { cmListFileContext lfc; lfc.FilePath = fileName; lfc.Line = lfcc.Line; lfc.Name = lfcc.Name.Original; + lfc.DeferId = std::move(deferId); return lfc; } }; diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index c8b2133..ac3a193 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -16,6 +16,7 @@ #include <cm/iterator> #include <cm/memory> #include <cm/optional> +#include <cm/type_traits> // IWYU pragma: keep #include <cm/vector> #include <cmext/algorithm> #include <cmext/string_view> @@ -274,7 +275,9 @@ cmListFileBacktrace cmMakefile::GetBacktrace() const return this->Backtrace; } -void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const +void cmMakefile::PrintCommandTrace( + cmListFileFunction const& lff, + cm::optional<std::string> const& deferId) const { // Check if current file in the list of requested to trace... std::vector<std::string> const& trace_only_this_files = @@ -322,6 +325,9 @@ void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const builder["indentation"] = ""; val["file"] = full_path; val["line"] = static_cast<Json::Value::Int64>(lff.Line); + if (deferId) { + val["defer"] = *deferId; + } val["cmd"] = lff.Name.Original; val["args"] = Json::Value(Json::arrayValue); for (std::string const& arg : args) { @@ -335,8 +341,11 @@ void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const break; } case cmake::TraceFormat::TRACE_HUMAN: - msg << full_path << "(" << lff.Line << "): "; - msg << lff.Name.Original << "("; + msg << full_path << "(" << lff.Line << "):"; + if (deferId) { + msg << "DEFERRED:" << *deferId << ":"; + } + msg << " " << lff.Name.Original << "("; for (std::string const& arg : args) { msg << arg << " "; @@ -361,11 +370,12 @@ class cmMakefileCall { public: cmMakefileCall(cmMakefile* mf, cmListFileFunction const& lff, - cmExecutionStatus& status) + cm::optional<std::string> deferId, cmExecutionStatus& status) : Makefile(mf) { cmListFileContext const& lfc = cmListFileContext::FromCommandContext( - lff, this->Makefile->StateSnapshot.GetExecutionListFile()); + 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); @@ -402,7 +412,8 @@ void cmMakefile::OnExecuteCommand(std::function<void()> callback) } bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff, - cmExecutionStatus& status) + cmExecutionStatus& status, + cm::optional<std::string> deferId) { bool result = true; @@ -417,7 +428,7 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff, } // Place this call on the call stack. - cmMakefileCall stack_manager(this, lff, status); + cmMakefileCall stack_manager(this, lff, std::move(deferId), status); static_cast<void>(stack_manager); // Check for maximum recursion depth. @@ -445,7 +456,7 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff, if (!cmSystemTools::GetFatalErrorOccured()) { // if trace is enabled, print out invoke information if (this->GetCMakeInstance()->GetTrace()) { - this->PrintCommandTrace(lff); + this->PrintCommandTrace(lff, this->Backtrace.Top().DeferId); } // Try invoking the command. bool invokeSucceeded = command(lff.Arguments, status); @@ -622,7 +633,7 @@ bool cmMakefile::ReadDependentFile(const std::string& filename, return false; } - this->ReadListFile(listFile, filenametoread); + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccured()) { incScope.Quiet(); } @@ -663,6 +674,53 @@ private: bool ReportError; }; +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( @@ -676,7 +734,7 @@ bool cmMakefile::ReadListFile(const std::string& filename) return false; } - this->ReadListFile(listFile, filenametoread); + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccured()) { scope.Quiet(); } @@ -697,15 +755,16 @@ bool cmMakefile::ReadListFileAsString(const std::string& content, return false; } - this->ReadListFile(listFile, filenametoread); + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccured()) { scope.Quiet(); } return true; } -void cmMakefile::ReadListFile(cmListFile const& listFile, - std::string const& filenametoread) +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); @@ -736,6 +795,33 @@ void cmMakefile::ReadListFile(cmListFile const& listFile, } } + // 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()) { + // Cancelled. + 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::GetFatalErrorOccured()) { + break; + } + } + } + this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentParentFile); this->AddDefinition("CMAKE_CURRENT_LIST_FILE", currentFile); this->AddDefinition("CMAKE_CURRENT_LIST_DIR", @@ -1678,7 +1764,9 @@ void cmMakefile::Configure() } } - this->ReadListFile(listFile, currentStart); + this->Defer = cm::make_unique<DeferCommands>(); + this->RunListFile(listFile, currentStart, this->Defer.get()); + this->Defer.reset(); if (cmSystemTools::GetFatalErrorOccured()) { scope.Quiet(); } @@ -1753,6 +1841,13 @@ void cmMakefile::AddSubDirectory(const std::string& srcPath, const std::string& binPath, bool excludeFromAll, bool immediate) { + 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; @@ -2960,6 +3055,68 @@ void cmMakefile::SetRecursionDepth(int recursionDepth) this->RecursionDepth = recursionDepth; } +std::string cmMakefile::NewDeferId() +{ + 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<std::string> cmMakefile::DeferGetCallIds() const +{ + cm::optional<std::string> ids; + if (this->Defer) { + ids = cmJoin( + 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<std::string> cmMakefile::DeferGetCall(std::string const& id) const +{ + cm::optional<std::string> call; + if (this->Defer) { + std::string tmp; + for (DeferCommand const& dc : this->Defer->Commands) { + if (dc.Id == id) { + tmp = dc.Command.Name.Original; + 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, @@ -2997,7 +3154,12 @@ MessageType cmMakefile::ExpandVariablesInStringNew( switch (var.domain) { case NORMAL: if (filename && lookup == lineVar) { - varresult = std::to_string(line); + 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); } @@ -3561,6 +3723,12 @@ void cmMakefile::AddTargetObject(std::string const& tgtName, void cmMakefile::EnableLanguage(std::vector<std::string> const& lang, 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); } diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 7c3348d..c7940fb 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -15,6 +15,7 @@ #include <unordered_map> #include <vector> +#include <cm/optional> #include <cm/string_view> #include "cmsys/RegularExpression.hxx" @@ -695,7 +696,8 @@ public: /** * Print a command's invocation */ - void PrintCommandTrace(const cmListFileFunction& lff) const; + void PrintCommandTrace(cmListFileFunction const& lff, + cm::optional<std::string> const& deferId = {}) const; /** * Set a callback that is invoked whenever ExecuteCommand is called. @@ -706,8 +708,8 @@ public: * Execute a single CMake command. Returns true if the command * succeeded or false if it failed. */ - bool ExecuteCommand(const cmListFileFunction& lff, - cmExecutionStatus& status); + bool ExecuteCommand(const cmListFileFunction& lff, cmExecutionStatus& status, + cm::optional<std::string> deferId = {}); //! Enable support for named language, if nil then all languages are /// enabled. @@ -965,6 +967,12 @@ public: int GetRecursionDepth() const; void SetRecursionDepth(int recursionDepth); + std::string NewDeferId(); + bool DeferCall(std::string id, std::string fileName, cmListFileFunction lff); + bool DeferCancelCall(std::string const& id); + cm::optional<std::string> DeferGetCallIds() const; + cm::optional<std::string> DeferGetCall(std::string const& id) const; + protected: // add link libraries and directories to the target void AddGlobalLinkInformation(cmTarget& target); @@ -1026,10 +1034,25 @@ private: cmListFileBacktrace Backtrace; int RecursionDepth; + struct DeferCommand + { + // Id is empty for an already-executed or cancelled operation. + std::string Id; + std::string FilePath; + cmListFileFunction Command; + }; + struct DeferCommands + { + std::vector<DeferCommand> Commands; + }; + std::unique_ptr<DeferCommands> Defer; + bool DeferRunning = false; + void DoGenerate(cmLocalGenerator& lg); - void ReadListFile(cmListFile const& listFile, - const std::string& filenametoread); + void RunListFile(cmListFile const& listFile, + const std::string& filenametoread, + DeferCommands* defer = nullptr); bool ParseDefineFlag(std::string const& definition, bool remove); @@ -1080,6 +1103,12 @@ private: class ListFileScope; friend class ListFileScope; + class DeferScope; + friend class DeferScope; + + class DeferCallScope; + friend class DeferCallScope; + class BuildsystemFileScope; friend class BuildsystemFileScope; diff --git a/Source/cmState.cxx b/Source/cmState.cxx index e96c82f..d5ac9ae 100644 --- a/Source/cmState.cxx +++ b/Source/cmState.cxx @@ -837,6 +837,21 @@ cmStateSnapshot cmState::CreateBuildsystemDirectorySnapshot( return snapshot; } +cmStateSnapshot cmState::CreateDeferCallSnapshot( + cmStateSnapshot const& originSnapshot, std::string const& fileName) +{ + cmStateDetail::PositionType pos = + this->SnapshotData.Push(originSnapshot.Position, *originSnapshot.Position); + pos->SnapshotType = cmStateEnums::DeferCallType; + pos->Keep = false; + pos->ExecutionListFile = this->ExecutionListFiles.Push( + originSnapshot.Position->ExecutionListFile, fileName); + assert(originSnapshot.Position->Vars.IsValid()); + pos->BuildSystemDirectory->DirectoryEnd = pos; + pos->PolicyScope = originSnapshot.Position->Policies; + return { this, pos }; +} + cmStateSnapshot cmState::CreateFunctionCallSnapshot( cmStateSnapshot const& originSnapshot, std::string const& fileName) { diff --git a/Source/cmState.h b/Source/cmState.h index dc3ba65..2aa57e0 100644 --- a/Source/cmState.h +++ b/Source/cmState.h @@ -55,6 +55,8 @@ public: cmStateSnapshot CreateBaseSnapshot(); cmStateSnapshot CreateBuildsystemDirectorySnapshot( cmStateSnapshot const& originSnapshot); + cmStateSnapshot CreateDeferCallSnapshot( + cmStateSnapshot const& originSnapshot, std::string const& fileName); cmStateSnapshot CreateFunctionCallSnapshot( cmStateSnapshot const& originSnapshot, std::string const& fileName); cmStateSnapshot CreateMacroCallSnapshot( diff --git a/Source/cmStateTypes.h b/Source/cmStateTypes.h index b8c1cca..010d7e3 100644 --- a/Source/cmStateTypes.h +++ b/Source/cmStateTypes.h @@ -18,6 +18,7 @@ enum SnapshotType { BaseType, BuildsystemDirectoryType, + DeferCallType, FunctionCallType, MacroCallType, IncludeFileType, diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 014a707..dc06fae 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -983,7 +983,7 @@ void cmake::PrintTraceFormatVersion() Json::StreamWriterBuilder builder; builder["indentation"] = ""; version["major"] = 1; - version["minor"] = 0; + version["minor"] = 1; val["version"] = version; msg = Json::writeString(builder, val); #endif |