From c523d1cc32f53a176ea485aa33e076e577db2d17 Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 20 Oct 2020 10:28:53 -0400 Subject: Help: Add examples to add_custom_command reference documentation --- Help/command/add_custom_command.rst | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst index 45e4e3e..85d56a3 100644 --- a/Help/command/add_custom_command.rst +++ b/Help/command/add_custom_command.rst @@ -259,6 +259,26 @@ The options are: ``DEPFILE`` should also be relative to :variable:`CMAKE_CURRENT_BINARY_DIR` (see policy :policy:`CMP0116`.) +Examples: Generating Files +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Custom commands may be used to generate source files. +For example, the code: + +.. code-block:: cmake + + add_custom_command( + OUTPUT out.c + COMMAND someTool -i ${CMAKE_CURRENT_SOURCE_DIR}/in.txt + -o out.c + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/in.txt + VERBATIM) + add_library(myLib out.c) + +adds a custom command to run ``someTool`` to generate ``out.c`` and then +compile the generated source as part of a library. The generation rule +will re-run whenever ``in.txt`` changes. + Build Events ^^^^^^^^^^^^ @@ -308,3 +328,21 @@ of the following is specified: configuration and no "empty-string-command" will be added. This allows to add individual build events for every configuration. + +Examples: Build Events +^^^^^^^^^^^^^^^^^^^^^^ + +A ``POST_BUILD`` event may be used to post-process a binary after linking. +For example, the code: + +.. code-block:: cmake + + add_executable(myExe myExe.c) + add_custom_command( + TARGET myExe POST_BUILD + COMMAND someHasher -i "$" + -o "$.hash" + VERBATIM) + +will run ``someHasher`` to produce a ``.hash`` file next to the executable +after linking. -- cgit v0.12 From b285748f79b642f3ab970912bc4512ded2f18124 Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 16 Oct 2020 10:19:25 -0400 Subject: cmAddCustom{Command,Target}Command: Skip conversions on genex paths If an output or byproduct path starts in a generator expression, do not convert it to a full path yet. That will have to be done at generate time after evaluating the generator expressions. Also update the `add_custom_target` byproduct path conversion added by commit 445ff5ccdf (Byproducts: collapse full paths of custom target byproducts, 2019-09-11, v3.16.0-rc1~103^2~1) to match the behavior of `add_custom_command` when a path starts in a generator expression. --- Source/cmAddCustomCommandCommand.cxx | 4 +++- Source/cmAddCustomTargetCommand.cxx | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx index c1f98fa..8194226 100644 --- a/Source/cmAddCustomCommandCommand.cxx +++ b/Source/cmAddCustomCommandCommand.cxx @@ -10,6 +10,7 @@ #include "cmCustomCommandLines.h" #include "cmCustomCommandTypes.h" #include "cmExecutionStatus.h" +#include "cmGeneratorExpression.h" #include "cmGlobalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" @@ -188,7 +189,8 @@ bool cmAddCustomCommandCommand(std::vector const& args, case doing_output: case doing_outputs: case doing_byproducts: - if (!cmSystemTools::FileIsFullPath(copy)) { + if (!cmSystemTools::FileIsFullPath(copy) && + cmGeneratorExpression::Find(copy) != 0) { // This is an output to be generated, so it should be // under the build tree. filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/'); diff --git a/Source/cmAddCustomTargetCommand.cxx b/Source/cmAddCustomTargetCommand.cxx index aa98d89..fd509bd 100644 --- a/Source/cmAddCustomTargetCommand.cxx +++ b/Source/cmAddCustomTargetCommand.cxx @@ -120,12 +120,16 @@ bool cmAddCustomTargetCommand(std::vector const& args, break; case doing_byproducts: { std::string filename; - if (!cmSystemTools::FileIsFullPath(copy)) { + if (!cmSystemTools::FileIsFullPath(copy) && + cmGeneratorExpression::Find(copy) != 0) { filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/'); } filename += copy; cmSystemTools::ConvertToUnixSlashes(filename); - byproducts.push_back(cmSystemTools::CollapseFullPath(filename)); + if (cmSystemTools::FileIsFullPath(filename)) { + filename = cmSystemTools::CollapseFullPath(filename); + } + byproducts.push_back(filename); } break; case doing_depends: { std::string dep = copy; -- cgit v0.12 From 24156c02696b686ed38f431c30e7faf24a561ddb Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 16 Oct 2020 12:12:26 -0400 Subject: cmCustomCommandGenerator: Evaluate generator expressions in outputs This was already done for byproducts by commit a583b7bc17 (Genex: Evaluate byproduct generator expressions in cmCustomCommandGenerator, 2019-09-11, v3.16.0-rc1~86^2). Like that commit, this does not actually expose the feature to projects because the front-end commands still reject generator expressions. Issue: #12877 --- Source/cmCustomCommandGenerator.cxx | 3 ++- Source/cmCustomCommandGenerator.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index 08a0574..5970709 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -121,6 +121,7 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc, this->CommandLines.push_back(std::move(argv)); } + AppendPaths(cc.GetOutputs(), ge, this->LG, this->Config, this->Outputs); AppendPaths(cc.GetByproducts(), ge, this->LG, this->Config, this->Byproducts); AppendPaths(cc.GetDepends(), ge, this->LG, this->Config, this->Depends); @@ -326,7 +327,7 @@ std::string cmCustomCommandGenerator::GetWorkingDirectory() const std::vector const& cmCustomCommandGenerator::GetOutputs() const { - return this->CC->GetOutputs(); + return this->Outputs; } std::vector const& cmCustomCommandGenerator::GetByproducts() const diff --git a/Source/cmCustomCommandGenerator.h b/Source/cmCustomCommandGenerator.h index cb0d7df..dac3596 100644 --- a/Source/cmCustomCommandGenerator.h +++ b/Source/cmCustomCommandGenerator.h @@ -24,6 +24,7 @@ class cmCustomCommandGenerator bool MakeVars; cmCustomCommandLines CommandLines; std::vector> EmulatorsWithArguments; + std::vector Outputs; std::vector Byproducts; std::vector Depends; std::string WorkingDirectory; -- cgit v0.12 From 5d23c5446e1a4645f00230f9d4beeb2429a39f31 Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 27 Oct 2020 10:14:45 -0400 Subject: cmCustomCommandGenerator: Refactor OUTPUT and DEPENDS path evaluation * Use value semantics. * Normalize paths in a separate loop. * If CollapseFullPath is used, ConvertToUnixSlashes is unnecessary. --- Source/cmCustomCommandGenerator.cxx | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index 5970709..e099630 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -24,22 +24,25 @@ #include "cmTransformDepfile.h" namespace { -void AppendPaths(const std::vector& inputs, - cmGeneratorExpression const& ge, cmLocalGenerator* lg, - std::string const& config, std::vector& output) +std::vector EvaluatePaths(std::vector const& paths, + cmGeneratorExpression const& ge, + cmLocalGenerator* lg, + std::string const& config) { - for (std::string const& in : inputs) { - std::unique_ptr cge = ge.Parse(in); - std::vector result = - cmExpandedList(cge->Evaluate(lg, config)); - for (std::string& it : result) { - cmSystemTools::ConvertToUnixSlashes(it); - if (cmSystemTools::FileIsFullPath(it)) { - it = cmSystemTools::CollapseFullPath(it); - } + std::vector result; + for (std::string const& p : paths) { + std::unique_ptr cge = ge.Parse(p); + std::string const& ep = cge->Evaluate(lg, config); + cm::append(result, cmExpandedList(ep)); + } + for (std::string& p : result) { + if (cmSystemTools::FileIsFullPath(p)) { + p = cmSystemTools::CollapseFullPath(p); + } else { + cmSystemTools::ConvertToUnixSlashes(p); } - cm::append(output, result); } + return result; } } @@ -121,10 +124,10 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc, this->CommandLines.push_back(std::move(argv)); } - AppendPaths(cc.GetOutputs(), ge, this->LG, this->Config, this->Outputs); - AppendPaths(cc.GetByproducts(), ge, this->LG, this->Config, - this->Byproducts); - AppendPaths(cc.GetDepends(), ge, this->LG, this->Config, this->Depends); + this->Outputs = EvaluatePaths(cc.GetOutputs(), ge, this->LG, this->Config); + this->Byproducts = + EvaluatePaths(cc.GetByproducts(), ge, this->LG, this->Config); + this->Depends = EvaluatePaths(cc.GetDepends(), ge, this->LG, this->Config); const std::string& workingdirectory = this->CC->GetWorkingDirectory(); if (!workingdirectory.empty()) { -- cgit v0.12 From 706c48301d29124e2ec4cd554b0de86d3743d62d Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 27 Oct 2020 10:06:21 -0400 Subject: cmCustomCommandGenerator: Treat relative outputs w.r.t. build dir The `add_custom_command` and `add_custom_target` commands already do this for plain output and byproduct paths. Perform the same conversion for such paths discovered after generator expression evaluation too. --- Source/cmCustomCommandGenerator.cxx | 39 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index e099630..cc0419d 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -24,25 +24,42 @@ #include "cmTransformDepfile.h" namespace { -std::vector EvaluatePaths(std::vector const& paths, - cmGeneratorExpression const& ge, - cmLocalGenerator* lg, - std::string const& config) +std::vector EvaluateDepends(std::vector const& paths, + cmGeneratorExpression const& ge, + cmLocalGenerator* lg, + std::string const& config) { - std::vector result; + std::vector depends; for (std::string const& p : paths) { std::unique_ptr cge = ge.Parse(p); std::string const& ep = cge->Evaluate(lg, config); - cm::append(result, cmExpandedList(ep)); + cm::append(depends, cmExpandedList(ep)); } - for (std::string& p : result) { + for (std::string& p : depends) { if (cmSystemTools::FileIsFullPath(p)) { p = cmSystemTools::CollapseFullPath(p); } else { cmSystemTools::ConvertToUnixSlashes(p); } } - return result; + return depends; +} + +std::vector EvaluateOutputs(std::vector const& paths, + cmGeneratorExpression const& ge, + cmLocalGenerator* lg, + std::string const& config) +{ + std::vector outputs; + for (std::string const& p : paths) { + std::unique_ptr cge = ge.Parse(p); + std::string const& ep = cge->Evaluate(lg, config); + cm::append(outputs, cmExpandedList(ep)); + } + for (std::string& p : outputs) { + p = cmSystemTools::CollapseFullPath(p, lg->GetCurrentBinaryDirectory()); + } + return outputs; } } @@ -124,10 +141,10 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc, this->CommandLines.push_back(std::move(argv)); } - this->Outputs = EvaluatePaths(cc.GetOutputs(), ge, this->LG, this->Config); + this->Outputs = EvaluateOutputs(cc.GetOutputs(), ge, this->LG, this->Config); this->Byproducts = - EvaluatePaths(cc.GetByproducts(), ge, this->LG, this->Config); - this->Depends = EvaluatePaths(cc.GetDepends(), ge, this->LG, this->Config); + EvaluateOutputs(cc.GetByproducts(), ge, this->LG, this->Config); + this->Depends = EvaluateDepends(cc.GetDepends(), ge, this->LG, this->Config); const std::string& workingdirectory = this->CC->GetWorkingDirectory(); if (!workingdirectory.empty()) { -- cgit v0.12 From e4034eabe930fb677fb9b5c65cf29336d1ff123c Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 19 Oct 2020 15:06:39 -0400 Subject: cmLocalGenerator: Re-order logic in CreateGeneratedSource Return early on errors to reduce nesting. --- Source/cmLocalGenerator.cxx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index c9dfdea..5739c03 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -3816,18 +3816,19 @@ void CreateGeneratedSource(cmLocalGenerator& lg, const std::string& output, cmCommandOrigin origin, const cmListFileBacktrace& lfbt) { - if (cmGeneratorExpression::Find(output) == std::string::npos) { - // Outputs without generator expressions from the project are already - // created and marked as generated. Do not mark them again, because - // other commands might have overwritten the property. - if (origin == cmCommandOrigin::Generator) { - lg.GetMakefile()->GetOrCreateGeneratedSource(output); - } - } else { + if (cmGeneratorExpression::Find(output) != std::string::npos) { lg.GetCMakeInstance()->IssueMessage( MessageType::FATAL_ERROR, "Generator expressions in custom command outputs are not implemented!", lfbt); + return; + } + + // Outputs without generator expressions from the project are already + // created and marked as generated. Do not mark them again, because + // other commands might have overwritten the property. + if (origin == cmCommandOrigin::Generator) { + lg.GetMakefile()->GetOrCreateGeneratedSource(output); } } -- cgit v0.12 From 1902d28ebc50ee93acb1be2320b79f2e844f7f41 Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 16 Oct 2020 09:40:30 -0400 Subject: cmLocalGenerator: Refactor UpdateOutputToSourceMap to avoid boolean trap --- Source/cmLocalGenerator.cxx | 17 ++++++++--------- Source/cmLocalGenerator.h | 12 +++++++++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 5739c03..832c36c 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -3924,7 +3924,9 @@ cmSourceFile* AddCustomCommand( cc->SetJobPool(job_pool); file->SetCustomCommand(std::move(cc)); - lg.AddSourceOutputs(file, outputs, byproducts); + lg.AddSourceOutputs(file, outputs, cmLocalGenerator::OutputRole::Primary); + lg.AddSourceOutputs(file, byproducts, + cmLocalGenerator::OutputRole::Byproduct); } return file; } @@ -4177,13 +4179,10 @@ void cmLocalGenerator::AddTargetByproducts( void cmLocalGenerator::AddSourceOutputs( cmSourceFile* source, const std::vector& outputs, - const std::vector& byproducts) + OutputRole role) { for (std::string const& o : outputs) { - this->UpdateOutputToSourceMap(o, source, false); - } - for (std::string const& o : byproducts) { - this->UpdateOutputToSourceMap(o, source, true); + this->UpdateOutputToSourceMap(o, source, role); } } @@ -4211,18 +4210,18 @@ void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& byproduct, void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& output, cmSourceFile* source, - bool byproduct) + OutputRole role) { SourceEntry entry; entry.Sources.Source = source; - entry.Sources.SourceIsByproduct = byproduct; + entry.Sources.SourceIsByproduct = role == OutputRole::Byproduct; auto pr = this->OutputToSource.emplace(output, entry); if (!pr.second) { SourceEntry& current = pr.first->second; // Outputs take precedence over byproducts if (!current.Sources.Source || - (current.Sources.SourceIsByproduct && !byproduct)) { + (current.Sources.SourceIsByproduct && role == OutputRole::Primary)) { current.Sources.Source = source; current.Sources.SourceIsByproduct = false; } else { diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index 09e820a..9ed3b15 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -368,12 +368,18 @@ public: void AddTargetByproducts(cmTarget* target, const std::vector& byproducts); + enum class OutputRole + { + Primary, + Byproduct, + }; + /** * Add source file outputs. */ void AddSourceOutputs(cmSourceFile* source, - const std::vector& outputs, - const std::vector& byproducts); + std::vector const& outputs, + OutputRole role); /** * Return the target if the provided source name is a byproduct of a utility @@ -609,7 +615,7 @@ private: void UpdateOutputToSourceMap(std::string const& byproduct, cmTarget* target); void UpdateOutputToSourceMap(std::string const& output, cmSourceFile* source, - bool byproduct); + OutputRole role); void AddSharedFlags(std::string& flags, const std::string& lang, bool shared); -- cgit v0.12 From 947ba01bf987bce09c2fdd5e2547f79af2c5844b Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 16 Oct 2020 09:55:54 -0400 Subject: cmLocalGenerator: Factor out helper to expand custom command output paths --- Source/cmCustomCommandGenerator.cxx | 6 +----- Source/cmLocalGenerator.cxx | 10 ++++++++++ Source/cmLocalGenerator.h | 4 ++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index cc0419d..64cd88e 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -53,11 +53,7 @@ std::vector EvaluateOutputs(std::vector const& paths, std::vector outputs; for (std::string const& p : paths) { std::unique_ptr cge = ge.Parse(p); - std::string const& ep = cge->Evaluate(lg, config); - cm::append(outputs, cmExpandedList(ep)); - } - for (std::string& p : outputs) { - p = cmSystemTools::CollapseFullPath(p, lg->GetCurrentBinaryDirectory()); + cm::append(outputs, lg->ExpandCustomCommandOutputPaths(*cge, config)); } return outputs; } diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 832c36c..8a9cb04 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -4169,6 +4169,16 @@ cmSourceFile* cmLocalGenerator::GetSourceFileWithOutput( return nullptr; } +std::vector cmLocalGenerator::ExpandCustomCommandOutputPaths( + cmCompiledGeneratorExpression const& cge, std::string const& config) +{ + std::vector paths = cmExpandedList(cge.Evaluate(this, config)); + for (std::string& p : paths) { + p = cmSystemTools::CollapseFullPath(p, this->GetCurrentBinaryDirectory()); + } + return paths; +} + void cmLocalGenerator::AddTargetByproducts( cmTarget* target, const std::vector& byproducts) { diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index 9ed3b15..c246e2e 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -22,6 +22,7 @@ #include "cmProperty.h" #include "cmStateSnapshot.h" +class cmCompiledGeneratorExpression; class cmComputeLinkInformation; class cmCustomCommandGenerator; class cmCustomCommandLines; @@ -362,6 +363,9 @@ public: bool command_expand_lists = false, const std::string& job_pool = "", bool stdPipesUTF8 = false); + std::vector ExpandCustomCommandOutputPaths( + cmCompiledGeneratorExpression const& cge, std::string const& config); + /** * Add target byproducts. */ -- cgit v0.12 From c887cefd9a12342a3ac8dc5c1dbb692b7e8d27af Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 16 Oct 2020 10:35:29 -0400 Subject: cmLocalGenerator: Simplify custom command output cmSourceFile creation Move calls to `CreateGeneratedSource` over to `UpdateOutputToSourceMap`, which is called for all generated outputs and byproducts. --- Source/cmLocalGenerator.cxx | 68 +++++++++++++++++++-------------------------- Source/cmLocalGenerator.h | 14 +++++++--- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 8a9cb04..a8c9b77 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -3832,19 +3832,9 @@ void CreateGeneratedSource(cmLocalGenerator& lg, const std::string& output, } } -void CreateGeneratedSources(cmLocalGenerator& lg, - const std::vector& outputs, - cmCommandOrigin origin, - const cmListFileBacktrace& lfbt) -{ - for (std::string const& o : outputs) { - CreateGeneratedSource(lg, o, origin, lfbt); - } -} - cmSourceFile* AddCustomCommand( cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, - const std::vector& outputs, + cmCommandOrigin origin, const std::vector& outputs, const std::vector& byproducts, const std::vector& depends, const std::string& main_dependency, const cmImplicitDependsList& implicit_depends, @@ -3924,9 +3914,10 @@ cmSourceFile* AddCustomCommand( cc->SetJobPool(job_pool); file->SetCustomCommand(std::move(cc)); - lg.AddSourceOutputs(file, outputs, cmLocalGenerator::OutputRole::Primary); + lg.AddSourceOutputs(file, outputs, cmLocalGenerator::OutputRole::Primary, + lfbt, origin); lg.AddSourceOutputs(file, byproducts, - cmLocalGenerator::OutputRole::Byproduct); + cmLocalGenerator::OutputRole::Byproduct, lfbt, origin); } return file; } @@ -3970,9 +3961,6 @@ void AddCustomCommandToTarget(cmLocalGenerator& lg, const std::string& job_pool, bool command_expand_lists, bool stdPipesUTF8) { - // Always create the byproduct sources and mark them generated. - CreateGeneratedSources(lg, byproducts, origin, lfbt); - // Add the command to the appropriate build step for the target. std::vector no_output; cmCustomCommand cc(no_output, byproducts, depends, commandLines, lfbt, @@ -3995,7 +3983,7 @@ void AddCustomCommandToTarget(cmLocalGenerator& lg, break; } - lg.AddTargetByproducts(target, byproducts); + lg.AddTargetByproducts(target, byproducts, lfbt, origin); } cmSourceFile* AddCustomCommandToOutput( @@ -4009,14 +3997,11 @@ cmSourceFile* AddCustomCommandToOutput( bool uses_terminal, bool command_expand_lists, const std::string& depfile, const std::string& job_pool, bool stdPipesUTF8) { - // Always create the output sources and mark them generated. - CreateGeneratedSources(lg, outputs, origin, lfbt); - CreateGeneratedSources(lg, byproducts, origin, lfbt); - - return AddCustomCommand( - lg, lfbt, outputs, byproducts, depends, main_dependency, implicit_depends, - commandLines, comment, workingDir, replace, escapeOldStyle, uses_terminal, - command_expand_lists, depfile, job_pool, stdPipesUTF8); + return AddCustomCommand(lg, lfbt, origin, outputs, byproducts, depends, + main_dependency, implicit_depends, commandLines, + comment, workingDir, replace, escapeOldStyle, + uses_terminal, command_expand_lists, depfile, + job_pool, stdPipesUTF8); } void AppendCustomCommandToOutput(cmLocalGenerator& lg, @@ -4054,10 +4039,6 @@ void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, bool uses_terminal, bool command_expand_lists, const std::string& job_pool, bool stdPipesUTF8) { - // Always create the byproduct sources and mark them generated. - CreateGeneratedSource(lg, force.Name, origin, lfbt); - CreateGeneratedSources(lg, byproducts, origin, lfbt); - // Use an empty comment to avoid generation of default comment. if (!comment) { comment = ""; @@ -4066,12 +4047,12 @@ void AddUtilityCommand(cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, std::string no_main_dependency; cmImplicitDependsList no_implicit_depends; cmSourceFile* rule = AddCustomCommand( - lg, lfbt, { force.Name }, byproducts, depends, no_main_dependency, + lg, lfbt, origin, { force.Name }, byproducts, depends, no_main_dependency, no_implicit_depends, commandLines, comment, workingDir, /*replace=*/false, escapeOldStyle, uses_terminal, command_expand_lists, /*depfile=*/"", job_pool, stdPipesUTF8); if (rule) { - lg.AddTargetByproducts(target, byproducts); + lg.AddTargetByproducts(target, byproducts, lfbt, origin); } if (!force.NameCMP0049.empty()) { @@ -4180,30 +4161,35 @@ std::vector cmLocalGenerator::ExpandCustomCommandOutputPaths( } void cmLocalGenerator::AddTargetByproducts( - cmTarget* target, const std::vector& byproducts) + cmTarget* target, const std::vector& byproducts, + cmListFileBacktrace const& bt, cmCommandOrigin origin) { for (std::string const& o : byproducts) { - this->UpdateOutputToSourceMap(o, target); + this->UpdateOutputToSourceMap(o, target, bt, origin); } } void cmLocalGenerator::AddSourceOutputs( cmSourceFile* source, const std::vector& outputs, - OutputRole role) + OutputRole role, cmListFileBacktrace const& bt, cmCommandOrigin origin) { for (std::string const& o : outputs) { - this->UpdateOutputToSourceMap(o, source, role); + this->UpdateOutputToSourceMap(o, source, role, bt, origin); } } void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& byproduct, - cmTarget* target) + cmTarget* target, + cmListFileBacktrace const& bt, + cmCommandOrigin origin) { SourceEntry entry; entry.Sources.Target = target; auto pr = this->OutputToSource.emplace(byproduct, entry); - if (!pr.second) { + if (pr.second) { + CreateGeneratedSource(*this, byproduct, origin, bt); + } else { SourceEntry& current = pr.first->second; // Has the target already been set? if (!current.Sources.Target) { @@ -4220,14 +4206,18 @@ void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& byproduct, void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& output, cmSourceFile* source, - OutputRole role) + OutputRole role, + cmListFileBacktrace const& bt, + cmCommandOrigin origin) { SourceEntry entry; entry.Sources.Source = source; entry.Sources.SourceIsByproduct = role == OutputRole::Byproduct; auto pr = this->OutputToSource.emplace(output, entry); - if (!pr.second) { + if (pr.second) { + CreateGeneratedSource(*this, output, origin, bt); + } else { SourceEntry& current = pr.first->second; // Outputs take precedence over byproducts if (!current.Sources.Source || diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index c246e2e..79ffcfb 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -370,7 +370,9 @@ public: * Add target byproducts. */ void AddTargetByproducts(cmTarget* target, - const std::vector& byproducts); + const std::vector& byproducts, + cmListFileBacktrace const& bt, + cmCommandOrigin origin); enum class OutputRole { @@ -383,7 +385,8 @@ public: */ void AddSourceOutputs(cmSourceFile* source, std::vector const& outputs, - OutputRole role); + OutputRole role, cmListFileBacktrace const& bt, + cmCommandOrigin origin); /** * Return the target if the provided source name is a byproduct of a utility @@ -617,9 +620,12 @@ private: using OutputToSourceMap = std::unordered_map; OutputToSourceMap OutputToSource; - void UpdateOutputToSourceMap(std::string const& byproduct, cmTarget* target); + void UpdateOutputToSourceMap(std::string const& byproduct, cmTarget* target, + cmListFileBacktrace const& bt, + cmCommandOrigin origin); void UpdateOutputToSourceMap(std::string const& output, cmSourceFile* source, - OutputRole role); + OutputRole role, cmListFileBacktrace const& bt, + cmCommandOrigin origin); void AddSharedFlags(std::string& flags, const std::string& lang, bool shared); -- cgit v0.12 From f36af9228b2ad36442f0cce9e8c8533fadef65aa Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 16 Oct 2020 12:25:19 -0400 Subject: cmLocalGenerator: Evaluate generator expressions in custom command outputs Custom commands with generator expressions in their OUTPUTs or BYPRODUCTS are still attached to a single `.rule` file. We use an internal map to look up the source file holding the custom command for a given output. Populate this map using the outputs and byproducts from all configurations after evaluating the generator expressions for each configuration. Issue: #12877 --- Source/cmLocalGenerator.cxx | 103 ++++++++++++++++++++++++++++++++++++++++++-- Source/cmLocalGenerator.h | 2 + 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index a8c9b77..dd1779b 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -20,6 +20,7 @@ #include "cmsys/RegularExpression.hxx" +#include "cmAlgorithms.h" #include "cmComputeLinkInformation.h" #include "cmCustomCommand.h" #include "cmCustomCommandGenerator.h" @@ -3832,6 +3833,43 @@ void CreateGeneratedSource(cmLocalGenerator& lg, const std::string& output, } } +std::string ComputeCustomCommandRuleFileName(cmLocalGenerator& lg, + cmListFileBacktrace const& bt, + std::string const& output) +{ + // If the output path has no generator expressions, use it directly. + if (cmGeneratorExpression::Find(output) == std::string::npos) { + return output; + } + + // The output path contains a generator expression, but we must choose + // a single source file path to which to attach the custom command. + // Use some heuristics to provie a nice-looking name when possible. + + // If the only genex is $, replace that gracefully. + { + std::string simple = output; + cmSystemTools::ReplaceString(simple, "$", "(CONFIG)"); + if (cmGeneratorExpression::Find(simple) == std::string::npos) { + return simple; + } + } + + // If the genex evaluates to the same value in all configurations, use that. + { + std::vector allConfigOutputs = + lg.ExpandCustomCommandOutputGenex(output, bt); + if (allConfigOutputs.size() == 1) { + return allConfigOutputs.front(); + } + } + + // Fall back to a deterministic unique name. + cmCryptoHash h(cmCryptoHash::AlgoSHA256); + return cmStrCat(lg.GetCurrentBinaryDirectory(), "/CMakeFiles/", + h.HashString(output).substr(0, 16)); +} + cmSourceFile* AddCustomCommand( cmLocalGenerator& lg, const cmListFileBacktrace& lfbt, cmCommandOrigin origin, const std::vector& outputs, @@ -3871,7 +3909,8 @@ cmSourceFile* AddCustomCommand( cmGlobalGenerator* gg = lg.GetGlobalGenerator(); // Construct a rule file associated with the first output produced. - std::string outName = gg->GenerateRuleFile(outputs[0]); + std::string outName = gg->GenerateRuleFile( + ComputeCustomCommandRuleFileName(lg, lfbt, outputs[0])); // Check if the rule file already exists. file = mf->GetSource(outName, cmSourceFileLocationKind::Known); @@ -4012,7 +4051,22 @@ void AppendCustomCommandToOutput(cmLocalGenerator& lg, const cmCustomCommandLines& commandLines) { // Lookup an existing command. - if (cmSourceFile* sf = lg.GetSourceFileWithOutput(output)) { + cmSourceFile* sf = nullptr; + if (cmGeneratorExpression::Find(output) == std::string::npos) { + sf = lg.GetSourceFileWithOutput(output); + } else { + // This output path has a generator expression. Evaluate it to + // find the output for any configurations. + for (std::string const& out : + lg.ExpandCustomCommandOutputGenex(output, lfbt)) { + sf = lg.GetSourceFileWithOutput(out); + if (sf) { + break; + } + } + } + + if (sf) { if (cmCustomCommand* cc = sf->GetCustomCommand()) { cc->AppendCommands(commandLines); cc->AppendDepends(depends); @@ -4160,12 +4214,42 @@ std::vector cmLocalGenerator::ExpandCustomCommandOutputPaths( return paths; } +std::vector cmLocalGenerator::ExpandCustomCommandOutputGenex( + std::string const& o, cmListFileBacktrace const& bt) +{ + std::vector allConfigOutputs; + cmGeneratorExpression ge(bt); + std::unique_ptr cge = ge.Parse(o); + std::vector configs = + this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); + for (std::string const& config : configs) { + std::vector configOutputs = + this->ExpandCustomCommandOutputPaths(*cge, config); + allConfigOutputs.reserve(allConfigOutputs.size() + configOutputs.size()); + std::move(configOutputs.begin(), configOutputs.end(), + std::back_inserter(allConfigOutputs)); + } + auto endUnique = + cmRemoveDuplicates(allConfigOutputs.begin(), allConfigOutputs.end()); + allConfigOutputs.erase(endUnique, allConfigOutputs.end()); + return allConfigOutputs; +} + void cmLocalGenerator::AddTargetByproducts( cmTarget* target, const std::vector& byproducts, cmListFileBacktrace const& bt, cmCommandOrigin origin) { for (std::string const& o : byproducts) { - this->UpdateOutputToSourceMap(o, target, bt, origin); + if (cmGeneratorExpression::Find(o) == std::string::npos) { + this->UpdateOutputToSourceMap(o, target, bt, origin); + continue; + } + + // This byproduct path has a generator expression. Evaluate it to + // register the byproducts for all configurations. + for (std::string const& b : this->ExpandCustomCommandOutputGenex(o, bt)) { + this->UpdateOutputToSourceMap(b, target, bt, cmCommandOrigin::Generator); + } } } @@ -4174,7 +4258,18 @@ void cmLocalGenerator::AddSourceOutputs( OutputRole role, cmListFileBacktrace const& bt, cmCommandOrigin origin) { for (std::string const& o : outputs) { - this->UpdateOutputToSourceMap(o, source, role, bt, origin); + if (cmGeneratorExpression::Find(o) == std::string::npos) { + this->UpdateOutputToSourceMap(o, source, role, bt, origin); + continue; + } + + // This output path has a generator expression. Evaluate it to + // register the outputs for all configurations. + for (std::string const& out : + this->ExpandCustomCommandOutputGenex(o, bt)) { + this->UpdateOutputToSourceMap(out, source, role, bt, + cmCommandOrigin::Generator); + } } } diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index 79ffcfb..581badb 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -365,6 +365,8 @@ public: std::vector ExpandCustomCommandOutputPaths( cmCompiledGeneratorExpression const& cge, std::string const& config); + std::vector ExpandCustomCommandOutputGenex( + std::string const& o, cmListFileBacktrace const& bt); /** * Add target byproducts. -- cgit v0.12 From c257c25419c68e755b0f8289d8d563437bf9e0c2 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 19 Oct 2020 15:20:53 -0400 Subject: add_custom_{command,target}: Add genex support to OUTPUT and BYPRODUCTS Move rejection of `#`, `<`, and `>` characters in outputs and byproducts to a generate-time check. This removes the front-end check that disallowed generator expressions. The generators have already been updated to handle them. Fixes: #12877 --- Help/command/add_custom_command.rst | 48 +++++++++++++++ Help/command/add_custom_target.rst | 3 + Help/release/dev/custom-command-output-genex.rst | 6 ++ Source/CMakeLists.txt | 2 - Source/cmAddCustomCommandCommand.cxx | 8 --- Source/cmAddCustomTargetCommand.cxx | 6 -- Source/cmCheckCustomOutputs.cxx | 36 ----------- Source/cmCheckCustomOutputs.h | 15 ----- Source/cmLocalGenerator.cxx | 34 ++++++++++- Tests/ConfigSources/CMakeLists.txt | 71 +++++++++++++++++++++- Tests/ConfigSources/custom1.cpp.in | 13 ++++ Tests/ConfigSources/custom2.cpp.in | 13 ++++ Tests/ConfigSources/custom3.cpp.in | 13 ++++ Tests/ConfigSources/custom4.cpp.in | 13 ++++ Tests/ConfigSources/custom5.cpp.in | 13 ++++ Tests/ConfigSources/main_debug.cpp | 9 ++- Tests/ConfigSources/main_other.cpp | 9 ++- .../VS10Project/CustomCommandGenex-check.cmake | 37 +++++++++++ .../RunCMake/VS10Project/CustomCommandGenex.cmake | 21 +++++++ Tests/RunCMake/VS10Project/RunCMakeTest.cmake | 1 + .../add_custom_command/BadByproduct-stderr.txt | 33 ++++++---- .../RunCMake/add_custom_command/BadByproduct.cmake | 1 + .../add_custom_command/BadOutput-stderr.txt | 33 ++++++---- Tests/RunCMake/add_custom_command/BadOutput.cmake | 1 + .../add_custom_target/BadByproduct-stderr.txt | 33 ++++++---- .../RunCMake/add_custom_target/BadByproduct.cmake | 1 + bootstrap | 1 - 27 files changed, 367 insertions(+), 107 deletions(-) create mode 100644 Help/release/dev/custom-command-output-genex.rst delete mode 100644 Source/cmCheckCustomOutputs.cxx delete mode 100644 Source/cmCheckCustomOutputs.h create mode 100644 Tests/ConfigSources/custom1.cpp.in create mode 100644 Tests/ConfigSources/custom2.cpp.in create mode 100644 Tests/ConfigSources/custom3.cpp.in create mode 100644 Tests/ConfigSources/custom4.cpp.in create mode 100644 Tests/ConfigSources/custom5.cpp.in create mode 100644 Tests/RunCMake/VS10Project/CustomCommandGenex-check.cmake create mode 100644 Tests/RunCMake/VS10Project/CustomCommandGenex.cmake diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst index 85d56a3..4464ad6 100644 --- a/Help/command/add_custom_command.rst +++ b/Help/command/add_custom_command.rst @@ -46,6 +46,12 @@ The options are: Append the ``COMMAND`` and ``DEPENDS`` option values to the custom command for the first output specified. There must have already been a previous call to this command with the same output. + + If the previous call specified the output via a generator expression, + the output specified by the current call must match in at least one + configuration after evaluating generator expressions. In this case, + the appended commands and dependencies apply to all configurations. + The ``COMMENT``, ``MAIN_DEPENDENCY``, and ``WORKING_DIRECTORY`` options are currently ignored when APPEND is given, but may be used in the future. @@ -73,6 +79,9 @@ The options are: The :ref:`Makefile Generators` will remove ``BYPRODUCTS`` and other :prop_sf:`GENERATED` files during ``make clean``. + Since CMake 3.20, arguments to ``BYPRODUCTS`` may use + :manual:`generator expressions `. + ``COMMAND`` Specify the command-line(s) to execute at build time. If more than one ``COMMAND`` is specified they will be executed in order, @@ -220,6 +229,9 @@ The options are: as a file on disk it should be marked with the :prop_sf:`SYMBOLIC` source file property. + Since CMake 3.20, arguments to ``OUTPUT`` may use + :manual:`generator expressions `. + ``USES_TERMINAL`` .. versionadded:: 3.2 @@ -279,6 +291,24 @@ adds a custom command to run ``someTool`` to generate ``out.c`` and then compile the generated source as part of a library. The generation rule will re-run whenever ``in.txt`` changes. +Since CMake 3.20, one may use generator expressions to specify +per-configuration outputs. For example, the code: + +.. code-block:: cmake + + add_custom_command( + OUTPUT "out-$.c" + COMMAND someTool -i ${CMAKE_CURRENT_SOURCE_DIR}/in.txt + -o "out-$.c" + -c "$" + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/in.txt + VERBATIM) + add_library(myLib "out-$.c") + +adds a custom command to run ``someTool`` to generate ``out-.c``, +where ```` is the build configuration, and then compile the generated +source as part of a library. + Build Events ^^^^^^^^^^^^ @@ -346,3 +376,21 @@ For example, the code: will run ``someHasher`` to produce a ``.hash`` file next to the executable after linking. + +Since CMake 3.20, one may use generator expressions to specify +per-configuration byproducts. For example, the code: + +.. code-block:: cmake + + add_library(myPlugin MODULE myPlugin.c) + add_custom_command( + TARGET myPlugin POST_BUILD + COMMAND someHasher -i "$" + --as-code "myPlugin-hash-$.c" + BYPRODUCTS "myPlugin-hash-$.c" + VERBATIM) + add_executable(myExe myExe.c "myPlugin-hash-$.c") + +will run ``someHasher`` after linking ``myPlugin``, e.g. to produce a ``.c`` +file containing code to check the hash of ``myPlugin`` that the ``myExe`` +executable can use to verify it before loading. diff --git a/Help/command/add_custom_target.rst b/Help/command/add_custom_target.rst index 7c29dda..85e1e16 100644 --- a/Help/command/add_custom_target.rst +++ b/Help/command/add_custom_target.rst @@ -54,6 +54,9 @@ The options are: The :ref:`Makefile Generators` will remove ``BYPRODUCTS`` and other :prop_sf:`GENERATED` files during ``make clean``. + Since CMake 3.20, arguments to ``BYPRODUCTS`` may use + :manual:`generator expressions `. + ``COMMAND`` Specify the command-line(s) to execute at build time. If more than one ``COMMAND`` is specified they will be executed in order, diff --git a/Help/release/dev/custom-command-output-genex.rst b/Help/release/dev/custom-command-output-genex.rst new file mode 100644 index 0000000..215349f --- /dev/null +++ b/Help/release/dev/custom-command-output-genex.rst @@ -0,0 +1,6 @@ +custom-command-output-genex +--------------------------- + +* :command:`add_custom_command` and :command:`add_custom_target` now + support :manual:`generator expressions ` + in their ``OUTPUT`` and ``BYPRODUCTS`` options. diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index c3b7e50..c5b67c0 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -181,8 +181,6 @@ set(SRCS cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.h cmCacheManager.cxx cmCacheManager.h - cmCheckCustomOutputs.h - cmCheckCustomOutputs.cxx cmCLocaleEnvironmentScope.h cmCLocaleEnvironmentScope.cxx cmCMakePath.h diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx index 8194226..ccd7255 100644 --- a/Source/cmAddCustomCommandCommand.cxx +++ b/Source/cmAddCustomCommandCommand.cxx @@ -5,7 +5,6 @@ #include #include -#include "cmCheckCustomOutputs.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmCustomCommandTypes.h" @@ -298,13 +297,6 @@ bool cmAddCustomCommandCommand(std::vector const& args, return false; } - // Make sure the output names and locations are safe. - if (!cmCheckCustomOutputs(output, "OUTPUT", status) || - !cmCheckCustomOutputs(outputs, "OUTPUTS", status) || - !cmCheckCustomOutputs(byproducts, "BYPRODUCTS", status)) { - return false; - } - // Check for an append request. if (append) { mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends, diff --git a/Source/cmAddCustomTargetCommand.cxx b/Source/cmAddCustomTargetCommand.cxx index fd509bd..104065f 100644 --- a/Source/cmAddCustomTargetCommand.cxx +++ b/Source/cmAddCustomTargetCommand.cxx @@ -4,7 +4,6 @@ #include -#include "cmCheckCustomOutputs.h" #include "cmCustomCommandLines.h" #include "cmExecutionStatus.h" #include "cmGeneratorExpression.h" @@ -210,11 +209,6 @@ bool cmAddCustomTargetCommand(std::vector const& args, return false; } - // Make sure the byproduct names and locations are safe. - if (!cmCheckCustomOutputs(byproducts, "BYPRODUCTS", status)) { - return false; - } - // Add the utility target to the makefile. bool escapeOldStyle = !verbatim; cmTarget* target = mf.AddUtilityCommand( diff --git a/Source/cmCheckCustomOutputs.cxx b/Source/cmCheckCustomOutputs.cxx deleted file mode 100644 index 7645c88..0000000 --- a/Source/cmCheckCustomOutputs.cxx +++ /dev/null @@ -1,36 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#include "cmCheckCustomOutputs.h" - -#include "cmExecutionStatus.h" -#include "cmMakefile.h" -#include "cmStringAlgorithms.h" -#include "cmSystemTools.h" - -bool cmCheckCustomOutputs(const std::vector& outputs, - cm::string_view keyword, cmExecutionStatus& status) -{ - cmMakefile& mf = status.GetMakefile(); - - for (std::string const& o : outputs) { - // Make sure the file will not be generated into the source - // directory during an out of source build. - if (!mf.CanIWriteThisFile(o)) { - status.SetError( - cmStrCat("attempted to have a file\n ", o, - "\nin a source directory as an output of custom command.")); - cmSystemTools::SetFatalErrorOccured(); - return false; - } - - // Make sure the output file name has no invalid characters. - std::string::size_type pos = o.find_first_of("#<>"); - if (pos != std::string::npos) { - status.SetError(cmStrCat("called with ", keyword, " containing a \"", - o[pos], "\". This character is not allowed.")); - return false; - } - } - - return true; -} diff --git a/Source/cmCheckCustomOutputs.h b/Source/cmCheckCustomOutputs.h deleted file mode 100644 index 2752ed4..0000000 --- a/Source/cmCheckCustomOutputs.h +++ /dev/null @@ -1,15 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ -#pragma once - -#include "cmConfigure.h" // IWYU pragma: keep - -#include -#include - -#include - -class cmExecutionStatus; - -bool cmCheckCustomOutputs(const std::vector& outputs, - cm::string_view keyword, cmExecutionStatus& status); diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index dd1779b..9f9d725 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -17,6 +17,7 @@ #include #include #include +#include #include "cmsys/RegularExpression.hxx" @@ -3813,7 +3814,14 @@ void cmLocalGenerator::GenerateFrameworkInfoPList( } namespace { +cm::string_view CustomOutputRoleKeyword(cmLocalGenerator::OutputRole role) +{ + return (role == cmLocalGenerator::OutputRole::Primary ? "OUTPUT"_s + : "BYPRODUCTS"_s); +} + void CreateGeneratedSource(cmLocalGenerator& lg, const std::string& output, + cmLocalGenerator::OutputRole role, cmCommandOrigin origin, const cmListFileBacktrace& lfbt) { @@ -3825,6 +3833,28 @@ void CreateGeneratedSource(cmLocalGenerator& lg, const std::string& output, return; } + // Make sure the file will not be generated into the source + // directory during an out of source build. + if (!lg.GetMakefile()->CanIWriteThisFile(output)) { + lg.GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat(CustomOutputRoleKeyword(role), " path\n ", output, + "\nin a source directory as an output of custom command."), + lfbt); + return; + } + + // Make sure the output file name has no invalid characters. + std::string::size_type pos = output.find_first_of("#<>"); + if (pos != std::string::npos) { + lg.GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat(CustomOutputRoleKeyword(role), " containing a \"", output[pos], + "\" is not allowed."), + lfbt); + return; + } + // Outputs without generator expressions from the project are already // created and marked as generated. Do not mark them again, because // other commands might have overwritten the property. @@ -4283,7 +4313,7 @@ void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& byproduct, auto pr = this->OutputToSource.emplace(byproduct, entry); if (pr.second) { - CreateGeneratedSource(*this, byproduct, origin, bt); + CreateGeneratedSource(*this, byproduct, OutputRole::Byproduct, origin, bt); } else { SourceEntry& current = pr.first->second; // Has the target already been set? @@ -4311,7 +4341,7 @@ void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& output, auto pr = this->OutputToSource.emplace(output, entry); if (pr.second) { - CreateGeneratedSource(*this, output, origin, bt); + CreateGeneratedSource(*this, output, role, origin, bt); } else { SourceEntry& current = pr.first->second; // Outputs take precedence over byproducts diff --git a/Tests/ConfigSources/CMakeLists.txt b/Tests/ConfigSources/CMakeLists.txt index 1db00cc..5513af8 100644 --- a/Tests/ConfigSources/CMakeLists.txt +++ b/Tests/ConfigSources/CMakeLists.txt @@ -16,6 +16,72 @@ void config_$() {} ]] ) +# Custom command outputs named with the configuration(s). +add_custom_command( + OUTPUT "custom1_$.cpp" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom1.cpp.in" "custom1_$.cpp" + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/custom1.cpp.in + VERBATIM + ) +# Output path starts in a generator expression. +add_custom_command( + OUTPUT "$<1:custom2_$.cpp>" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom2.cpp.in" "custom2_$.cpp" + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/custom2.cpp.in + VERBATIM + ) +# Source file generated as a custom command's byproduct. +add_custom_command( + OUTPUT custom3.txt + BYPRODUCTS "$<1:custom3_$.cpp>" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom3.cpp.in" "custom3_$.cpp" + COMMAND ${CMAKE_COMMAND} -E touch custom3.txt + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/custom3.cpp.in + VERBATIM + ) +# Source file generated as a custom target's byproduct. +add_custom_target(custom4 + BYPRODUCTS "custom4_$.cpp" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/custom4.cpp.in" "custom4_$.cpp" + VERBATIM + ) +# Source file generated by appended custom command. +add_custom_command( + OUTPUT "custom5_$.cpp" + COMMAND ${CMAKE_COMMAND} -E echo custom5_$.cpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/custom5.cpp.in + VERBATIM + ) +add_custom_command(APPEND + OUTPUT "custom5_$.cpp" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom5.cpp.in" "custom5_$.cpp.in" + VERBATIM + ) +# Appending through any configuration's output affects all configurations. +if(CMAKE_CONFIGURATION_TYPES MATCHES ";([^;]+)$") + set(last_config "${CMAKE_MATCH_1}") +else() + set(last_config ${CMAKE_BUILD_TYPE}) +endif() +add_custom_command(APPEND + OUTPUT "custom5_${last_config}.cpp" + COMMAND ${CMAKE_COMMAND} -E copy "custom5_$.cpp.in" "custom5_$.cpp" + VERBATIM + ) +foreach(n RANGE 1 5) + set_property(SOURCE custom${n}_Debug.cpp PROPERTY COMPILE_DEFINITIONS CUSTOM_CFG_DEBUG) + foreach(other Release RelWithDebInfo MinSizeRel) + set_property(SOURCE custom${n}_${other}.cpp PROPERTY COMPILE_DEFINITIONS CUSTOM_CFG_OTHER) + endforeach() +endforeach() +add_library(Custom STATIC + custom1_$.cpp + custom2_$.cpp + custom3_$.cpp custom3.txt + custom4_$.cpp + custom5_$.cpp + ) + # Per-config sources via INTERFACE_SOURCES. add_library(iface INTERFACE) target_sources(iface INTERFACE @@ -34,7 +100,7 @@ add_executable(ConfigSources $<$:does_not_exist.cpp> ${CMAKE_CURRENT_BINARY_DIR}/config_$.cpp ) -target_link_libraries(ConfigSources iface) +target_link_libraries(ConfigSources Custom iface) # Per-config sources via LINK_LIBRARIES. add_library(iface_debug INTERFACE) @@ -53,6 +119,7 @@ target_compile_definitions(ConfigSourcesLink PRIVATE "$<$>:CFG_OTHER>" ) target_link_libraries(ConfigSourcesLink PRIVATE + Custom "$<$:iface_debug>" "$<$>:iface_other>" "$<$:iface_does_not_exist>" @@ -70,7 +137,7 @@ target_compile_definitions(ConfigSourcesLinkIface PRIVATE "$<$:CFG_DEBUG>" "$<$>:CFG_OTHER>" ) -target_link_libraries(ConfigSourcesLinkIface ConfigSourcesIface) +target_link_libraries(ConfigSourcesLinkIface Custom ConfigSourcesIface) # A target with sources in only one configuration that is not the # first in CMAKE_CONFIGURATION_TYPES. diff --git a/Tests/ConfigSources/custom1.cpp.in b/Tests/ConfigSources/custom1.cpp.in new file mode 100644 index 0000000..e5f21c7 --- /dev/null +++ b/Tests/ConfigSources/custom1.cpp.in @@ -0,0 +1,13 @@ +#ifdef CUSTOM_CFG_DEBUG +int custom1_debug() +{ + return 0; +} +#endif + +#ifdef CUSTOM_CFG_OTHER +int custom1_other() +{ + return 0; +} +#endif diff --git a/Tests/ConfigSources/custom2.cpp.in b/Tests/ConfigSources/custom2.cpp.in new file mode 100644 index 0000000..438c1fd --- /dev/null +++ b/Tests/ConfigSources/custom2.cpp.in @@ -0,0 +1,13 @@ +#ifdef CUSTOM_CFG_DEBUG +int custom2_debug() +{ + return 0; +} +#endif + +#ifdef CUSTOM_CFG_OTHER +int custom2_other() +{ + return 0; +} +#endif diff --git a/Tests/ConfigSources/custom3.cpp.in b/Tests/ConfigSources/custom3.cpp.in new file mode 100644 index 0000000..4545b69 --- /dev/null +++ b/Tests/ConfigSources/custom3.cpp.in @@ -0,0 +1,13 @@ +#ifdef CUSTOM_CFG_DEBUG +int custom3_debug() +{ + return 0; +} +#endif + +#ifdef CUSTOM_CFG_OTHER +int custom3_other() +{ + return 0; +} +#endif diff --git a/Tests/ConfigSources/custom4.cpp.in b/Tests/ConfigSources/custom4.cpp.in new file mode 100644 index 0000000..8a8b2a8 --- /dev/null +++ b/Tests/ConfigSources/custom4.cpp.in @@ -0,0 +1,13 @@ +#ifdef CUSTOM_CFG_DEBUG +int custom4_debug() +{ + return 0; +} +#endif + +#ifdef CUSTOM_CFG_OTHER +int custom4_other() +{ + return 0; +} +#endif diff --git a/Tests/ConfigSources/custom5.cpp.in b/Tests/ConfigSources/custom5.cpp.in new file mode 100644 index 0000000..51f40ae --- /dev/null +++ b/Tests/ConfigSources/custom5.cpp.in @@ -0,0 +1,13 @@ +#ifdef CUSTOM_CFG_DEBUG +int custom5_debug() +{ + return 0; +} +#endif + +#ifdef CUSTOM_CFG_OTHER +int custom5_other() +{ + return 0; +} +#endif diff --git a/Tests/ConfigSources/main_debug.cpp b/Tests/ConfigSources/main_debug.cpp index 9b1e68a..ef776f8 100644 --- a/Tests/ConfigSources/main_debug.cpp +++ b/Tests/ConfigSources/main_debug.cpp @@ -7,7 +7,14 @@ #include "iface.h" +extern int custom1_debug(); +extern int custom2_debug(); +extern int custom3_debug(); +extern int custom4_debug(); +extern int custom5_debug(); + int main(int argc, char** argv) { - return iface_src() + iface_debug(); + return iface_src() + iface_debug() + custom1_debug() + custom2_debug() + + custom3_debug() + custom4_debug() + custom5_debug(); } diff --git a/Tests/ConfigSources/main_other.cpp b/Tests/ConfigSources/main_other.cpp index 3184a19..74f2156 100644 --- a/Tests/ConfigSources/main_other.cpp +++ b/Tests/ConfigSources/main_other.cpp @@ -7,7 +7,14 @@ #include "iface.h" +extern int custom1_other(); +extern int custom2_other(); +extern int custom3_other(); +extern int custom4_other(); +extern int custom5_other(); + int main(int argc, char** argv) { - return iface_src() + iface_other(); + return iface_src() + iface_other() + custom1_other() + custom2_other() + + custom3_other() + custom4_other() + custom5_other(); } diff --git a/Tests/RunCMake/VS10Project/CustomCommandGenex-check.cmake b/Tests/RunCMake/VS10Project/CustomCommandGenex-check.cmake new file mode 100644 index 0000000..a7047bc --- /dev/null +++ b/Tests/RunCMake/VS10Project/CustomCommandGenex-check.cmake @@ -0,0 +1,37 @@ +set(vcProjectFile "${RunCMake_TEST_BINARY_DIR}/foo.vcxproj") +if(NOT EXISTS "${vcProjectFile}") + set(RunCMake_TEST_FAILED "Project file ${vcProjectFile} does not exist.") + return() +endif() + +set(found_CustomBuild_out 0) +set(found_CustomBuild_out_CONFIG 0) +set(found_CustomBuild_out_CONFIG_CONFIG 0) +set(found_CustomBuild_out_HASH 0) +file(STRINGS "${vcProjectFile}" lines) +foreach(line IN LISTS lines) + if(line MATCHES [[]]) + set(found_CustomBuild_out 1) + endif() + if(line MATCHES [[]]) + set(found_CustomBuild_out_CONFIG 1) + endif() + if(line MATCHES [[]]) + set(found_CustomBuild_out_CONFIG_CONFIG 1) + endif() + if(line MATCHES [[]]) + set(found_CustomBuild_out_HASH 1) + endif() +endforeach() +if(NOT found_CustomBuild_out) + string(APPEND RunCMake_TEST_FAILED "CustomBuild for out.txt.rule not found in\n ${vcProjectFile}\n") +endif() +if(NOT found_CustomBuild_out_CONFIG) + string(APPEND RunCMake_TEST_FAILED "CustomBuild for out-(CONFIG).txt.rule not found in\n ${vcProjectFile}\n") +endif() +if(NOT found_CustomBuild_out_CONFIG_CONFIG) + string(APPEND RunCMake_TEST_FAILED "CustomBuild for out-(CONFIG)-(CONFIG).txt.rule not found in\n ${vcProjectFile}\n") +endif() +if(NOT found_CustomBuild_out_HASH) + string(APPEND RunCMake_TEST_FAILED "CustomBuild for .rule not found in\n ${vcProjectFile}\n") +endif() diff --git a/Tests/RunCMake/VS10Project/CustomCommandGenex.cmake b/Tests/RunCMake/VS10Project/CustomCommandGenex.cmake new file mode 100644 index 0000000..5b69dc2 --- /dev/null +++ b/Tests/RunCMake/VS10Project/CustomCommandGenex.cmake @@ -0,0 +1,21 @@ +add_custom_command( + OUTPUT "$<1:out.txt>" + COMMAND ${CMAKE_COMMAND} -E touch "out.txt" + VERBATIM + ) +add_custom_command( + OUTPUT "out-$.txt" + COMMAND ${CMAKE_COMMAND} -E touch "out-$.txt" + VERBATIM + ) +add_custom_command( + OUTPUT "out-$-$.txt" + COMMAND ${CMAKE_COMMAND} -E touch "out-$-$.txt" + VERBATIM + ) +add_custom_command( + OUTPUT "out-$-$.txt" + COMMAND ${CMAKE_COMMAND} -E touch "out-$-$.txt" + VERBATIM + ) +add_custom_target(foo DEPENDS "out.txt" "out-$.txt" "out-$-$.txt" "out-$-$.txt") diff --git a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake index 133dacc..d5ed136 100644 --- a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake +++ b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake @@ -7,6 +7,7 @@ if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" AND CMAKE_C_COMPILER_VERSION VERSION_GREA run_cmake(LanguageStandard) endif() +run_cmake(CustomCommandGenex) run_cmake(VsCsharpSourceGroup) run_cmake(VsCSharpCompilerOpts) run_cmake(ExplicitCMakeLists) diff --git a/Tests/RunCMake/add_custom_command/BadByproduct-stderr.txt b/Tests/RunCMake/add_custom_command/BadByproduct-stderr.txt index 086e397..6d51575 100644 --- a/Tests/RunCMake/add_custom_command/BadByproduct-stderr.txt +++ b/Tests/RunCMake/add_custom_command/BadByproduct-stderr.txt @@ -1,36 +1,47 @@ CMake Error at BadByproduct.cmake:2 \(add_custom_command\): - add_custom_command called with BYPRODUCTS containing a "#". This character - is not allowed. + BYPRODUCTS containing a "#" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) CMake Error at BadByproduct.cmake:3 \(add_custom_command\): - add_custom_command called with BYPRODUCTS containing a "<". This character - is not allowed. + BYPRODUCTS containing a "<" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) CMake Error at BadByproduct.cmake:4 \(add_custom_command\): - add_custom_command called with BYPRODUCTS containing a ">". This character - is not allowed. + BYPRODUCTS containing a ">" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) - +( CMake Error at BadByproduct.cmake:5 \(add_custom_command\): - add_custom_command called with BYPRODUCTS containing a "<". This character - is not allowed. + BYPRODUCTS containing a "#" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) - +)+ CMake Error at BadByproduct.cmake:6 \(add_custom_command\): - add_custom_command attempted to have a file + BYPRODUCTS path .*RunCMake/add_custom_command/f in a source directory as an output of custom command. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) + +( +CMake Error at BadByproduct.cmake:7 \(add_custom_command\): + Error evaluating generator expression: + + \$ + + \$ may only be used with binary targets. It may not + be used with add_custom_command or add_custom_target. Specify the target + to read a property from using the \$ signature + instead. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) + +)+ diff --git a/Tests/RunCMake/add_custom_command/BadByproduct.cmake b/Tests/RunCMake/add_custom_command/BadByproduct.cmake index 91bca52..7c786a4 100644 --- a/Tests/RunCMake/add_custom_command/BadByproduct.cmake +++ b/Tests/RunCMake/add_custom_command/BadByproduct.cmake @@ -4,3 +4,4 @@ add_custom_command(OUTPUT b BYPRODUCTS "a<") add_custom_command(OUTPUT c BYPRODUCTS "a>") add_custom_command(OUTPUT d BYPRODUCTS "$/#") add_custom_command(OUTPUT e BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/f) +add_custom_command(OUTPUT f BYPRODUCTS "$") diff --git a/Tests/RunCMake/add_custom_command/BadOutput-stderr.txt b/Tests/RunCMake/add_custom_command/BadOutput-stderr.txt index 731e58d..506bec9 100644 --- a/Tests/RunCMake/add_custom_command/BadOutput-stderr.txt +++ b/Tests/RunCMake/add_custom_command/BadOutput-stderr.txt @@ -1,36 +1,47 @@ CMake Error at BadOutput.cmake:2 \(add_custom_command\): - add_custom_command called with OUTPUT containing a "#". This character is - not allowed. + OUTPUT containing a "#" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) CMake Error at BadOutput.cmake:3 \(add_custom_command\): - add_custom_command called with OUTPUT containing a "<". This character is - not allowed. + OUTPUT containing a "<" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) CMake Error at BadOutput.cmake:4 \(add_custom_command\): - add_custom_command called with OUTPUT containing a ">". This character is - not allowed. + OUTPUT containing a ">" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) - +( CMake Error at BadOutput.cmake:5 \(add_custom_command\): - add_custom_command called with OUTPUT containing a "<". This character is - not allowed. + OUTPUT containing a "#" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) - +)+ CMake Error at BadOutput.cmake:6 \(add_custom_command\): - add_custom_command attempted to have a file + OUTPUT path .*RunCMake/add_custom_command/e in a source directory as an output of custom command. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) + +( +CMake Error at BadOutput.cmake:7 \(add_custom_command\): + Error evaluating generator expression: + + \$ + + \$ may only be used with binary targets. It may not + be used with add_custom_command or add_custom_target. Specify the target + to read a property from using the \$ signature + instead. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) + +)+ diff --git a/Tests/RunCMake/add_custom_command/BadOutput.cmake b/Tests/RunCMake/add_custom_command/BadOutput.cmake index 6875fe9..77acb7f 100644 --- a/Tests/RunCMake/add_custom_command/BadOutput.cmake +++ b/Tests/RunCMake/add_custom_command/BadOutput.cmake @@ -4,3 +4,4 @@ add_custom_command(OUTPUT "a<" COMMAND b) add_custom_command(OUTPUT "a>" COMMAND c) add_custom_command(OUTPUT "$/#" COMMAND d) add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/e COMMAND f) +add_custom_command(OUTPUT "$" COMMAND g) diff --git a/Tests/RunCMake/add_custom_target/BadByproduct-stderr.txt b/Tests/RunCMake/add_custom_target/BadByproduct-stderr.txt index 0f58550..4f0f005 100644 --- a/Tests/RunCMake/add_custom_target/BadByproduct-stderr.txt +++ b/Tests/RunCMake/add_custom_target/BadByproduct-stderr.txt @@ -1,36 +1,47 @@ CMake Error at BadByproduct.cmake:2 \(add_custom_target\): - add_custom_target called with BYPRODUCTS containing a "#". This character - is not allowed. + BYPRODUCTS containing a "#" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) CMake Error at BadByproduct.cmake:3 \(add_custom_target\): - add_custom_target called with BYPRODUCTS containing a "<". This character - is not allowed. + BYPRODUCTS containing a "<" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) CMake Error at BadByproduct.cmake:4 \(add_custom_target\): - add_custom_target called with BYPRODUCTS containing a ">". This character - is not allowed. + BYPRODUCTS containing a ">" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) - +( CMake Error at BadByproduct.cmake:5 \(add_custom_target\): - add_custom_target called with BYPRODUCTS containing a "<". This character - is not allowed. + BYPRODUCTS containing a "#" is not allowed. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) - +)+ CMake Error at BadByproduct.cmake:6 \(add_custom_target\): - add_custom_target attempted to have a file + BYPRODUCTS path .*RunCMake/add_custom_target/j in a source directory as an output of custom command. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) + +( +CMake Error at BadByproduct.cmake:7 \(add_custom_target\): + Error evaluating generator expression: + + \$ + + \$ may only be used with binary targets. It may not + be used with add_custom_command or add_custom_target. Specify the target + to read a property from using the \$ signature + instead. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) + +)+ diff --git a/Tests/RunCMake/add_custom_target/BadByproduct.cmake b/Tests/RunCMake/add_custom_target/BadByproduct.cmake index 963d641..e97f9fd 100644 --- a/Tests/RunCMake/add_custom_target/BadByproduct.cmake +++ b/Tests/RunCMake/add_custom_target/BadByproduct.cmake @@ -4,3 +4,4 @@ add_custom_target(c BYPRODUCTS "a<" COMMAND d) add_custom_target(e BYPRODUCTS "a>" COMMAND f) add_custom_target(g BYPRODUCTS "$/#" COMMAND h) add_custom_target(i BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/j COMMAND k) +add_custom_target(l BYPRODUCTS "$" COMMAND m) diff --git a/bootstrap b/bootstrap index c342f10..aae4f22 100755 --- a/bootstrap +++ b/bootstrap @@ -293,7 +293,6 @@ CMAKE_CXX_SOURCES="\ cmCMakePolicyCommand \ cmCPackPropertiesGenerator \ cmCacheManager \ - cmCheckCustomOutputs \ cmCommand \ cmCommandArgumentParserHelper \ cmCommands \ -- cgit v0.12