Commit c257c254 authored by Brad King's avatar Brad King
Browse files

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
parent f36af922
Pipeline #204481 waiting for manual action with stages
in 6 minutes and 17 seconds
......@@ -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
......@@ -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-$<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
^^^^^^^^^^^^
......@@ -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 "$<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.
......@@ -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,
......
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.
......@@ -181,8 +181,6 @@ set(SRCS
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.h
cmCacheManager.cxx
cmCacheManager.h
cmCheckCustomOutputs.h
cmCheckCustomOutputs.cxx
cmCLocaleEnvironmentScope.h
cmCLocaleEnvironmentScope.cxx
cmCMakePath.h
......
......@@ -5,7 +5,6 @@
#include <sstream>
#include <unordered_set>
#include "cmCheckCustomOutputs.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmCustomCommandTypes.h"
......@@ -298,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,
......
......@@ -4,7 +4,6 @@
#include <utility>
#include "cmCheckCustomOutputs.h"
#include "cmCustomCommandLines.h"
#include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
......@@ -210,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(
......
/* 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;
}
/* 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);
......@@ -17,6 +17,7 @@
#include <cm/memory>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
#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
......
......@@ -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.
......
#ifdef CUSTOM_CFG_DEBUG
int custom1_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom1_other()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_DEBUG
int custom2_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom2_other()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_DEBUG
int custom3_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom3_other()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_DEBUG
int custom4_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom4_other()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_DEBUG
int custom5_debug()
{
return 0;
}
#endif
#ifdef CUSTOM_CFG_OTHER
int custom5_other()
{
return 0;
}
#endif
......@@ -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();
}
......@@ -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();
}
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()
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")
......@@ -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)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment