1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-14 02:08:27 +08:00

Merge topic 'custom-command-output-genex'

c257c25419 add_custom_{command,target}: Add genex support to OUTPUT and BYPRODUCTS
f36af9228b cmLocalGenerator: Evaluate generator expressions in custom command outputs
c887cefd9a cmLocalGenerator: Simplify custom command output cmSourceFile creation
947ba01bf9 cmLocalGenerator: Factor out helper to expand custom command output paths
1902d28ebc cmLocalGenerator: Refactor UpdateOutputToSourceMap to avoid boolean trap
e4034eabe9 cmLocalGenerator: Re-order logic in CreateGeneratedSource
706c48301d cmCustomCommandGenerator: Treat relative outputs w.r.t. build dir
5d23c5446e cmCustomCommandGenerator: Refactor OUTPUT and DEPENDS path evaluation
...

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: Kyle Edwards <kyle.edwards@kitware.com>
Acked-by: Pavel Solodovnikov <hellyeahdominate@gmail.com>
Acked-by: Ben Boeckel <ben.boeckel@kitware.com>
Merge-request: !5402
This commit is contained in:
Brad King
2020-12-14 12:22:34 +00:00
committed by Kitware Robot
30 changed files with 615 additions and 180 deletions

View File

@@ -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 <cmake-generator-expressions(7)>`.
``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 <cmake-generator-expressions(7)>`.
``USES_TERMINAL``
.. versionadded:: 3.2
@@ -259,6 +271,44 @@ 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.
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-$<CONFIG>.c"
COMMAND someTool -i ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
-o "out-$<CONFIG>.c"
-c "$<CONFIG>"
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
VERBATIM)
add_library(myLib "out-$<CONFIG>.c")
adds a custom command to run ``someTool`` to generate ``out-<config>.c``,
where ``<config>`` is the build configuration, and then compile the generated
source as part of a library.
Build Events
^^^^^^^^^^^^
@@ -308,3 +358,39 @@ 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 "$<TARGET_FILE:myExe>"
-o "$<TARGET_FILE:myExe>.hash"
VERBATIM)
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 "$<TARGET_FILE:myPlugin>"
--as-code "myPlugin-hash-$<CONFIG>.c"
BYPRODUCTS "myPlugin-hash-$<CONFIG>.c"
VERBATIM)
add_executable(myExe myExe.c "myPlugin-hash-$<CONFIG>.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.

View File

@@ -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 <cmake-generator-expressions(7)>`.
``COMMAND``
Specify the command-line(s) to execute at build time.
If more than one ``COMMAND`` is specified they will be executed in order,

View File

@@ -0,0 +1,6 @@
custom-command-output-genex
---------------------------
* :command:`add_custom_command` and :command:`add_custom_target` now
support :manual:`generator expressions <cmake-generator-expressions(7)>`
in their ``OUTPUT`` and ``BYPRODUCTS`` options.

View File

@@ -181,8 +181,6 @@ set(SRCS
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.h
cmCacheManager.cxx
cmCacheManager.h
cmCheckCustomOutputs.h
cmCheckCustomOutputs.cxx
cmCLocaleEnvironmentScope.h
cmCLocaleEnvironmentScope.cxx
cmCMakePath.h

View File

@@ -5,11 +5,11 @@
#include <sstream>
#include <unordered_set>
#include "cmCheckCustomOutputs.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmCustomCommandTypes.h"
#include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
@@ -188,7 +188,8 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
case doing_output:
case doing_outputs:
case doing_byproducts:
if (!cmSystemTools::FileIsFullPath(copy)) {
if (!cmSystemTools::FileIsFullPath(copy) &&
cmGeneratorExpression::Find(copy) != 0) {
// This is an output to be generated, so it should be
// under the build tree.
filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/');
@@ -296,13 +297,6 @@ bool cmAddCustomCommandCommand(std::vector<std::string> 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,

View File

@@ -4,7 +4,6 @@
#include <utility>
#include "cmCheckCustomOutputs.h"
#include "cmCustomCommandLines.h"
#include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
@@ -120,12 +119,16 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
break;
case doing_byproducts: {
std::string filename;
if (!cmSystemTools::FileIsFullPath(copy)) {
if (!cmSystemTools::FileIsFullPath(copy) &&
cmGeneratorExpression::Find(copy) != 0) {
filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/');
}
filename += copy;
cmSystemTools::ConvertToUnixSlashes(filename);
byproducts.push_back(cmSystemTools::CollapseFullPath(filename));
if (cmSystemTools::FileIsFullPath(filename)) {
filename = cmSystemTools::CollapseFullPath(filename);
}
byproducts.push_back(filename);
} break;
case doing_depends: {
std::string dep = copy;
@@ -206,11 +209,6 @@ bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
return false;
}
// Make sure the byproduct names and locations are safe.
if (!cmCheckCustomOutputs(byproducts, "BYPRODUCTS", status)) {
return false;
}
// Add the utility target to the makefile.
bool escapeOldStyle = !verbatim;
cmTarget* target = mf.AddUtilityCommand(

View File

@@ -1,36 +0,0 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCheckCustomOutputs.h"
#include "cmExecutionStatus.h"
#include "cmMakefile.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
bool cmCheckCustomOutputs(const std::vector<std::string>& outputs,
cm::string_view keyword, cmExecutionStatus& status)
{
cmMakefile& mf = status.GetMakefile();
for (std::string const& o : outputs) {
// Make sure the file will not be generated into the source
// directory during an out of source build.
if (!mf.CanIWriteThisFile(o)) {
status.SetError(
cmStrCat("attempted to have a file\n ", o,
"\nin a source directory as an output of custom command."));
cmSystemTools::SetFatalErrorOccured();
return false;
}
// Make sure the output file name has no invalid characters.
std::string::size_type pos = o.find_first_of("#<>");
if (pos != std::string::npos) {
status.SetError(cmStrCat("called with ", keyword, " containing a \"",
o[pos], "\". This character is not allowed."));
return false;
}
}
return true;
}

View File

@@ -1,15 +0,0 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <string>
#include <vector>
#include <cm/string_view>
class cmExecutionStatus;
bool cmCheckCustomOutputs(const std::vector<std::string>& outputs,
cm::string_view keyword, cmExecutionStatus& status);

View File

@@ -24,22 +24,38 @@
#include "cmTransformDepfile.h"
namespace {
void AppendPaths(const std::vector<std::string>& inputs,
cmGeneratorExpression const& ge, cmLocalGenerator* lg,
std::string const& config, std::vector<std::string>& output)
std::vector<std::string> EvaluateDepends(std::vector<std::string> const& paths,
cmGeneratorExpression const& ge,
cmLocalGenerator* lg,
std::string const& config)
{
for (std::string const& in : inputs) {
std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(in);
std::vector<std::string> result =
cmExpandedList(cge->Evaluate(lg, config));
for (std::string& it : result) {
cmSystemTools::ConvertToUnixSlashes(it);
if (cmSystemTools::FileIsFullPath(it)) {
it = cmSystemTools::CollapseFullPath(it);
}
}
cm::append(output, result);
std::vector<std::string> depends;
for (std::string const& p : paths) {
std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
std::string const& ep = cge->Evaluate(lg, config);
cm::append(depends, cmExpandedList(ep));
}
for (std::string& p : depends) {
if (cmSystemTools::FileIsFullPath(p)) {
p = cmSystemTools::CollapseFullPath(p);
} else {
cmSystemTools::ConvertToUnixSlashes(p);
}
}
return depends;
}
std::vector<std::string> EvaluateOutputs(std::vector<std::string> const& paths,
cmGeneratorExpression const& ge,
cmLocalGenerator* lg,
std::string const& config)
{
std::vector<std::string> outputs;
for (std::string const& p : paths) {
std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
cm::append(outputs, lg->ExpandCustomCommandOutputPaths(*cge, config));
}
return outputs;
}
}
@@ -121,9 +137,10 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
this->CommandLines.push_back(std::move(argv));
}
AppendPaths(cc.GetByproducts(), ge, this->LG, this->Config,
this->Byproducts);
AppendPaths(cc.GetDepends(), ge, this->LG, this->Config, this->Depends);
this->Outputs = EvaluateOutputs(cc.GetOutputs(), ge, this->LG, this->Config);
this->Byproducts =
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()) {
@@ -326,7 +343,7 @@ std::string cmCustomCommandGenerator::GetWorkingDirectory() const
std::vector<std::string> const& cmCustomCommandGenerator::GetOutputs() const
{
return this->CC->GetOutputs();
return this->Outputs;
}
std::vector<std::string> const& cmCustomCommandGenerator::GetByproducts() const

View File

@@ -24,6 +24,7 @@ class cmCustomCommandGenerator
bool MakeVars;
cmCustomCommandLines CommandLines;
std::vector<std::vector<std::string>> EmulatorsWithArguments;
std::vector<std::string> Outputs;
std::vector<std::string> Byproducts;
std::vector<std::string> Depends;
std::string WorkingDirectory;

View File

@@ -17,9 +17,11 @@
#include <cm/memory>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
#include "cmsys/RegularExpression.hxx"
#include "cmAlgorithms.h"
#include "cmComputeLinkInformation.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandGenerator.h"
@@ -3812,38 +3814,95 @@ 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)
{
if (cmGeneratorExpression::Find(output) == std::string::npos) {
// Outputs without generator expressions from the project are already
// created and marked as generated. Do not mark them again, because
// other commands might have overwritten the property.
if (origin == cmCommandOrigin::Generator) {
lg.GetMakefile()->GetOrCreateGeneratedSource(output);
}
} else {
if (cmGeneratorExpression::Find(output) != std::string::npos) {
lg.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
"Generator expressions in custom command outputs are not implemented!",
lfbt);
return;
}
// Make sure the file will not be generated into the source
// directory during an out of source build.
if (!lg.GetMakefile()->CanIWriteThisFile(output)) {
lg.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat(CustomOutputRoleKeyword(role), " path\n ", output,
"\nin a source directory as an output of custom command."),
lfbt);
return;
}
// Make sure the output file name has no invalid characters.
std::string::size_type pos = output.find_first_of("#<>");
if (pos != std::string::npos) {
lg.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat(CustomOutputRoleKeyword(role), " containing a \"", output[pos],
"\" is not allowed."),
lfbt);
return;
}
// Outputs without generator expressions from the project are already
// created and marked as generated. Do not mark them again, because
// other commands might have overwritten the property.
if (origin == cmCommandOrigin::Generator) {
lg.GetMakefile()->GetOrCreateGeneratedSource(output);
}
}
void CreateGeneratedSources(cmLocalGenerator& lg,
const std::vector<std::string>& outputs,
cmCommandOrigin origin,
const cmListFileBacktrace& lfbt)
std::string ComputeCustomCommandRuleFileName(cmLocalGenerator& lg,
cmListFileBacktrace const& bt,
std::string const& output)
{
for (std::string const& o : outputs) {
CreateGeneratedSource(lg, o, origin, lfbt);
// If the output path has no generator expressions, use it directly.
if (cmGeneratorExpression::Find(output) == std::string::npos) {
return output;
}
// The output path contains a generator expression, but we must choose
// a single source file path to which to attach the custom command.
// Use some heuristics to provie a nice-looking name when possible.
// If the only genex is $<CONFIG>, replace that gracefully.
{
std::string simple = output;
cmSystemTools::ReplaceString(simple, "$<CONFIG>", "(CONFIG)");
if (cmGeneratorExpression::Find(simple) == std::string::npos) {
return simple;
}
}
// If the genex evaluates to the same value in all configurations, use that.
{
std::vector<std::string> allConfigOutputs =
lg.ExpandCustomCommandOutputGenex(output, bt);
if (allConfigOutputs.size() == 1) {
return allConfigOutputs.front();
}
}
// Fall back to a deterministic unique name.
cmCryptoHash h(cmCryptoHash::AlgoSHA256);
return cmStrCat(lg.GetCurrentBinaryDirectory(), "/CMakeFiles/",
h.HashString(output).substr(0, 16));
}
cmSourceFile* AddCustomCommand(
cmLocalGenerator& lg, const cmListFileBacktrace& lfbt,
const std::vector<std::string>& outputs,
cmCommandOrigin origin, const std::vector<std::string>& outputs,
const std::vector<std::string>& byproducts,
const std::vector<std::string>& depends, const std::string& main_dependency,
const cmImplicitDependsList& implicit_depends,
@@ -3880,7 +3939,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);
@@ -3923,7 +3983,10 @@ cmSourceFile* AddCustomCommand(
cc->SetJobPool(job_pool);
file->SetCustomCommand(std::move(cc));
lg.AddSourceOutputs(file, outputs, byproducts);
lg.AddSourceOutputs(file, outputs, cmLocalGenerator::OutputRole::Primary,
lfbt, origin);
lg.AddSourceOutputs(file, byproducts,
cmLocalGenerator::OutputRole::Byproduct, lfbt, origin);
}
return file;
}
@@ -3967,9 +4030,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<std::string> no_output;
cmCustomCommand cc(no_output, byproducts, depends, commandLines, lfbt,
@@ -3992,7 +4052,7 @@ void AddCustomCommandToTarget(cmLocalGenerator& lg,
break;
}
lg.AddTargetByproducts(target, byproducts);
lg.AddTargetByproducts(target, byproducts, lfbt, origin);
}
cmSourceFile* AddCustomCommandToOutput(
@@ -4006,14 +4066,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,
@@ -4024,7 +4081,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);
@@ -4051,10 +4123,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 = "";
@@ -4063,12 +4131,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()) {
@@ -4166,34 +4234,87 @@ cmSourceFile* cmLocalGenerator::GetSourceFileWithOutput(
return nullptr;
}
std::vector<std::string> cmLocalGenerator::ExpandCustomCommandOutputPaths(
cmCompiledGeneratorExpression const& cge, std::string const& config)
{
std::vector<std::string> paths = cmExpandedList(cge.Evaluate(this, config));
for (std::string& p : paths) {
p = cmSystemTools::CollapseFullPath(p, this->GetCurrentBinaryDirectory());
}
return paths;
}
std::vector<std::string> cmLocalGenerator::ExpandCustomCommandOutputGenex(
std::string const& o, cmListFileBacktrace const& bt)
{
std::vector<std::string> allConfigOutputs;
cmGeneratorExpression ge(bt);
std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(o);
std::vector<std::string> configs =
this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
for (std::string const& config : configs) {
std::vector<std::string> configOutputs =
this->ExpandCustomCommandOutputPaths(*cge, config);
allConfigOutputs.reserve(allConfigOutputs.size() + configOutputs.size());
std::move(configOutputs.begin(), configOutputs.end(),
std::back_inserter(allConfigOutputs));
}
auto endUnique =
cmRemoveDuplicates(allConfigOutputs.begin(), allConfigOutputs.end());
allConfigOutputs.erase(endUnique, allConfigOutputs.end());
return allConfigOutputs;
}
void cmLocalGenerator::AddTargetByproducts(
cmTarget* target, const std::vector<std::string>& byproducts)
cmTarget* target, const std::vector<std::string>& byproducts,
cmListFileBacktrace const& bt, cmCommandOrigin origin)
{
for (std::string const& o : byproducts) {
this->UpdateOutputToSourceMap(o, target);
if (cmGeneratorExpression::Find(o) == std::string::npos) {
this->UpdateOutputToSourceMap(o, target, bt, origin);
continue;
}
// This byproduct path has a generator expression. Evaluate it to
// register the byproducts for all configurations.
for (std::string const& b : this->ExpandCustomCommandOutputGenex(o, bt)) {
this->UpdateOutputToSourceMap(b, target, bt, cmCommandOrigin::Generator);
}
}
}
void cmLocalGenerator::AddSourceOutputs(
cmSourceFile* source, const std::vector<std::string>& outputs,
const std::vector<std::string>& byproducts)
OutputRole role, cmListFileBacktrace const& bt, cmCommandOrigin origin)
{
for (std::string const& o : outputs) {
this->UpdateOutputToSourceMap(o, source, false);
}
for (std::string const& o : byproducts) {
this->UpdateOutputToSourceMap(o, source, true);
if (cmGeneratorExpression::Find(o) == std::string::npos) {
this->UpdateOutputToSourceMap(o, source, role, bt, origin);
continue;
}
// This output path has a generator expression. Evaluate it to
// register the outputs for all configurations.
for (std::string const& out :
this->ExpandCustomCommandOutputGenex(o, bt)) {
this->UpdateOutputToSourceMap(out, source, role, bt,
cmCommandOrigin::Generator);
}
}
}
void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& byproduct,
cmTarget* target)
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, OutputRole::Byproduct, origin, bt);
} else {
SourceEntry& current = pr.first->second;
// Has the target already been set?
if (!current.Sources.Target) {
@@ -4210,18 +4331,22 @@ void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& byproduct,
void cmLocalGenerator::UpdateOutputToSourceMap(std::string const& output,
cmSourceFile* source,
bool byproduct)
OutputRole role,
cmListFileBacktrace const& bt,
cmCommandOrigin origin)
{
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) {
if (pr.second) {
CreateGeneratedSource(*this, output, role, origin, bt);
} else {
SourceEntry& current = pr.first->second;
// Outputs take precedence over byproducts
if (!current.Sources.Source ||
(current.Sources.SourceIsByproduct && !byproduct)) {
(current.Sources.SourceIsByproduct && role == OutputRole::Primary)) {
current.Sources.Source = source;
current.Sources.SourceIsByproduct = false;
} else {

View File

@@ -22,6 +22,7 @@
#include "cmProperty.h"
#include "cmStateSnapshot.h"
class cmCompiledGeneratorExpression;
class cmComputeLinkInformation;
class cmCustomCommandGenerator;
class cmCustomCommandLines;
@@ -362,18 +363,32 @@ public:
bool command_expand_lists = false, const std::string& job_pool = "",
bool stdPipesUTF8 = false);
std::vector<std::string> ExpandCustomCommandOutputPaths(
cmCompiledGeneratorExpression const& cge, std::string const& config);
std::vector<std::string> ExpandCustomCommandOutputGenex(
std::string const& o, cmListFileBacktrace const& bt);
/**
* Add target byproducts.
*/
void AddTargetByproducts(cmTarget* target,
const std::vector<std::string>& byproducts);
const std::vector<std::string>& byproducts,
cmListFileBacktrace const& bt,
cmCommandOrigin origin);
enum class OutputRole
{
Primary,
Byproduct,
};
/**
* Add source file outputs.
*/
void AddSourceOutputs(cmSourceFile* source,
const std::vector<std::string>& outputs,
const std::vector<std::string>& byproducts);
std::vector<std::string> const& outputs,
OutputRole role, cmListFileBacktrace const& bt,
cmCommandOrigin origin);
/**
* Return the target if the provided source name is a byproduct of a utility
@@ -607,9 +622,12 @@ private:
using OutputToSourceMap = std::unordered_map<std::string, SourceEntry>;
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,
bool byproduct);
OutputRole role, cmListFileBacktrace const& bt,
cmCommandOrigin origin);
void AddSharedFlags(std::string& flags, const std::string& lang,
bool shared);

View File

@@ -16,6 +16,72 @@ void config_$<CONFIG>() {}
]]
)
# Custom command outputs named with the configuration(s).
add_custom_command(
OUTPUT "custom1_$<CONFIG>.cpp"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom1.cpp.in" "custom1_$<CONFIG>.cpp"
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/custom1.cpp.in
VERBATIM
)
# Output path starts in a generator expression.
add_custom_command(
OUTPUT "$<1:custom2_$<CONFIG>.cpp>"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom2.cpp.in" "custom2_$<CONFIG>.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_$<CONFIG>.cpp>"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom3.cpp.in" "custom3_$<CONFIG>.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_$<CONFIG>.cpp"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/custom4.cpp.in" "custom4_$<CONFIG>.cpp"
VERBATIM
)
# Source file generated by appended custom command.
add_custom_command(
OUTPUT "custom5_$<CONFIG>.cpp"
COMMAND ${CMAKE_COMMAND} -E echo custom5_$<CONFIG>.cpp
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/custom5.cpp.in
VERBATIM
)
add_custom_command(APPEND
OUTPUT "custom5_$<CONFIG>.cpp"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/custom5.cpp.in" "custom5_$<CONFIG>.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_$<CONFIG>.cpp.in" "custom5_$<CONFIG>.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_$<CONFIG>.cpp
custom2_$<CONFIG>.cpp
custom3_$<CONFIG>.cpp custom3.txt
custom4_$<CONFIG>.cpp
custom5_$<CONFIG>.cpp
)
# Per-config sources via INTERFACE_SOURCES.
add_library(iface INTERFACE)
target_sources(iface INTERFACE
@@ -34,7 +100,7 @@ add_executable(ConfigSources
$<$<CONFIG:NotAConfig>:does_not_exist.cpp>
${CMAKE_CURRENT_BINARY_DIR}/config_$<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
"$<$<NOT:$<CONFIG:Debug>>:CFG_OTHER>"
)
target_link_libraries(ConfigSourcesLink PRIVATE
Custom
"$<$<CONFIG:Debug>:iface_debug>"
"$<$<NOT:$<CONFIG:Debug>>:iface_other>"
"$<$<CONFIG:NotAConfig>:iface_does_not_exist>"
@@ -70,7 +137,7 @@ target_compile_definitions(ConfigSourcesLinkIface PRIVATE
"$<$<CONFIG:Debug>:CFG_DEBUG>"
"$<$<NOT:$<CONFIG: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.

View File

@@ -0,0 +1,13 @@
#ifdef CUSTOM_CFG_DEBUG
int custom1_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom1_other()
{
return 0;
}
#endif

View File

@@ -0,0 +1,13 @@
#ifdef CUSTOM_CFG_DEBUG
int custom2_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom2_other()
{
return 0;
}
#endif

View File

@@ -0,0 +1,13 @@
#ifdef CUSTOM_CFG_DEBUG
int custom3_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom3_other()
{
return 0;
}
#endif

View File

@@ -0,0 +1,13 @@
#ifdef CUSTOM_CFG_DEBUG
int custom4_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom4_other()
{
return 0;
}
#endif

View File

@@ -0,0 +1,13 @@
#ifdef CUSTOM_CFG_DEBUG
int custom5_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom5_other()
{
return 0;
}
#endif

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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 [[<CustomBuild Include=".*\\out\.txt\.rule">]])
set(found_CustomBuild_out 1)
endif()
if(line MATCHES [[<CustomBuild Include=".*\\out-\(CONFIG\)\.txt\.rule">]])
set(found_CustomBuild_out_CONFIG 1)
endif()
if(line MATCHES [[<CustomBuild Include=".*\\out-\(CONFIG\)-\(CONFIG\)\.txt\.rule">]])
set(found_CustomBuild_out_CONFIG_CONFIG 1)
endif()
if(line MATCHES [[<CustomBuild Include=".*\\[0-9A-Fa-f]+\.rule">]])
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 <hash>.rule not found in\n ${vcProjectFile}\n")
endif()

View File

@@ -0,0 +1,21 @@
add_custom_command(
OUTPUT "$<1:out.txt>"
COMMAND ${CMAKE_COMMAND} -E touch "out.txt"
VERBATIM
)
add_custom_command(
OUTPUT "out-$<CONFIG>.txt"
COMMAND ${CMAKE_COMMAND} -E touch "out-$<CONFIG>.txt"
VERBATIM
)
add_custom_command(
OUTPUT "out-$<CONFIG>-$<CONFIG>.txt"
COMMAND ${CMAKE_COMMAND} -E touch "out-$<CONFIG>-$<CONFIG>.txt"
VERBATIM
)
add_custom_command(
OUTPUT "out-$<CONFIG>-$<CONFIG:Debug>.txt"
COMMAND ${CMAKE_COMMAND} -E touch "out-$<CONFIG>-$<CONFIG:Debug>.txt"
VERBATIM
)
add_custom_target(foo DEPENDS "out.txt" "out-$<CONFIG>.txt" "out-$<CONFIG>-$<CONFIG>.txt" "out-$<CONFIG>-$<CONFIG:Debug>.txt")

View File

@@ -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)

View File

@@ -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:
\$<TARGET_PROPERTY:prop>
\$<TARGET_PROPERTY:prop> 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 \$<TARGET_PROPERTY:tgt,prop> signature
instead.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
)+

View File

@@ -4,3 +4,4 @@ add_custom_command(OUTPUT b BYPRODUCTS "a<")
add_custom_command(OUTPUT c BYPRODUCTS "a>")
add_custom_command(OUTPUT d BYPRODUCTS "$<CONFIG>/#")
add_custom_command(OUTPUT e BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/f)
add_custom_command(OUTPUT f BYPRODUCTS "$<TARGET_PROPERTY:prop>")

View File

@@ -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:
\$<TARGET_PROPERTY:prop>
\$<TARGET_PROPERTY:prop> 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 \$<TARGET_PROPERTY:tgt,prop> signature
instead.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
)+

View File

@@ -4,3 +4,4 @@ add_custom_command(OUTPUT "a<" COMMAND b)
add_custom_command(OUTPUT "a>" COMMAND c)
add_custom_command(OUTPUT "$<CONFIG>/#" COMMAND d)
add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/e COMMAND f)
add_custom_command(OUTPUT "$<TARGET_PROPERTY:prop>" COMMAND g)

View File

@@ -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:
\$<TARGET_PROPERTY:prop>
\$<TARGET_PROPERTY:prop> 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 \$<TARGET_PROPERTY:tgt,prop> signature
instead.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
)+

View File

@@ -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 "$<CONFIG>/#" COMMAND h)
add_custom_target(i BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/j COMMAND k)
add_custom_target(l BYPRODUCTS "$<TARGET_PROPERTY:prop>" COMMAND m)

View File

@@ -293,7 +293,6 @@ CMAKE_CXX_SOURCES="\
cmCMakePolicyCommand \
cmCPackPropertiesGenerator \
cmCacheManager \
cmCheckCustomOutputs \
cmCommand \
cmCommandArgumentParserHelper \
cmCommands \