summaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2020-09-30 15:55:24 (GMT)
committerKitware Robot <kwrobot@kitware.com>2020-09-30 15:55:33 (GMT)
commitd91c3e33cbf9592f90f65bd7d990a8b54ad140d1 (patch)
tree5b84e72c9c2d5ca1e3098d304f8582a140f52c3d /Source
parentf002c1cfc7f66edb9c9821524671574c23f92cd2 (diff)
parente8b0359a4318bb682c96e527de7ed7f5be02c38f (diff)
downloadCMake-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.cxx380
-rw-r--r--Source/cmCommandArgumentParserHelper.cxx12
-rw-r--r--Source/cmGlobalGenerator.cxx7
-rw-r--r--Source/cmGlobalGenerator.h5
-rw-r--r--Source/cmListFileCache.cxx7
-rw-r--r--Source/cmListFileCache.h10
-rw-r--r--Source/cmMakefile.cxx198
-rw-r--r--Source/cmMakefile.h39
-rw-r--r--Source/cmState.cxx15
-rw-r--r--Source/cmState.h2
-rw-r--r--Source/cmStateTypes.h1
-rw-r--r--Source/cmake.cxx2
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