summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuan Ramos <juan.ramos@kitware.com>2024-05-28 03:19:05 (GMT)
committerBrad King <brad.king@kitware.com>2024-07-01 16:02:49 (GMT)
commit197cb419d13d5dbb704be97bd3ee04ce514fa58f (patch)
tree7fae6062e544659541c9dd97946ada61fe1c7310
parent033713530a077893be93e52ef577205cb8466b9d (diff)
downloadCMake-197cb419d13d5dbb704be97bd3ee04ce514fa58f.zip
CMake-197cb419d13d5dbb704be97bd3ee04ce514fa58f.tar.gz
CMake-197cb419d13d5dbb704be97bd3ee04ce514fa58f.tar.bz2
add_custom_command: Add CODEGEN support
By specifying CODEGEN as an argument to add_custom_command the custom command will be added to a codegen build target. The intent is to provide a convenient way for users to get their generated files without having to build the whole project. This can be helpful for code analysis tools which can be useful for IDEs and CI.
-rw-r--r--Help/command/add_custom_command.rst32
-rw-r--r--Help/manual/cmake-policies.7.rst8
-rw-r--r--Help/policy/CMP0171.rst26
-rw-r--r--Help/release/dev/codegen.rst10
-rw-r--r--Source/cmAddCustomCommandCommand.cxx30
-rw-r--r--Source/cmAddDependenciesCommand.cxx1
-rw-r--r--Source/cmCustomCommand.h5
-rw-r--r--Source/cmGlobalGenerator.cxx49
-rw-r--r--Source/cmGlobalGenerator.h5
-rw-r--r--Source/cmGlobalNinjaGenerator.cxx86
-rw-r--r--Source/cmGlobalUnixMakefileGenerator3.cxx39
-rw-r--r--Source/cmGlobalUnixMakefileGenerator3.h3
-rw-r--r--Source/cmLocalGenerator.cxx6
-rw-r--r--Source/cmLocalUnixMakefileGenerator3.cxx15
-rw-r--r--Source/cmMakefileTargetGenerator.cxx29
-rw-r--r--Source/cmPolicies.h4
-rw-r--r--Source/cmTarget.cxx11
-rw-r--r--Source/cmTarget.h5
-rw-r--r--Source/cmTargetTraceDependencies.cxx11
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-NEW-result.txt1
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-NEW-stderr.txt4
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-NEW.cmake6
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-OLD-result.txt1
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-OLD-stderr.txt4
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-OLD.cmake9
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-WARN-stderr.txt9
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-WARN.cmake6
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-codegen-build-result.txt1
-rw-r--r--Tests/RunCMake/CMP0171/CMP0171-codegen.cmake0
-rw-r--r--Tests/RunCMake/CMP0171/CMakeLists.txt6
-rw-r--r--Tests/RunCMake/CMP0171/RunCMakeTest.cmake18
-rw-r--r--Tests/RunCMake/CMakeLists.txt4
-rw-r--r--Tests/RunCMake/Codegen/CMakeLists.txt7
-rw-r--r--Tests/RunCMake/Codegen/RunCMakeTest.cmake33
-rw-r--r--Tests/RunCMake/Codegen/add-custom-command-depends-build-check.cmake5
-rw-r--r--Tests/RunCMake/Codegen/add-custom-command-depends.cmake16
-rw-r--r--Tests/RunCMake/Codegen/add-dependencies-build-check.cmake5
-rw-r--r--Tests/RunCMake/Codegen/add-dependencies.cmake18
-rw-r--r--Tests/RunCMake/Codegen/append-implicit-depends-result.txt1
-rw-r--r--Tests/RunCMake/Codegen/append-implicit-depends-stderr.txt2
-rw-r--r--Tests/RunCMake/Codegen/append-implicit-depends.cmake19
-rw-r--r--Tests/RunCMake/Codegen/byproducts-build-check.cmake5
-rw-r--r--Tests/RunCMake/Codegen/byproducts.cmake19
-rw-r--r--Tests/RunCMake/Codegen/error.c1
-rw-r--r--Tests/RunCMake/Codegen/exclude-from-all.cmake11
-rw-r--r--Tests/RunCMake/Codegen/generated.h.in1
-rw-r--r--Tests/RunCMake/Codegen/implicit-depends-append-codegen-result.txt1
-rw-r--r--Tests/RunCMake/Codegen/implicit-depends-append-codegen-stderr.txt4
-rw-r--r--Tests/RunCMake/Codegen/implicit-depends-append-codegen.cmake18
-rw-r--r--Tests/RunCMake/Codegen/implicit-depends-result.txt1
-rw-r--r--Tests/RunCMake/Codegen/implicit-depends-stderr.txt4
-rw-r--r--Tests/RunCMake/Codegen/implicit-depends.cmake11
-rw-r--r--Tests/RunCMake/Codegen/main.c4
-rw-r--r--Tests/RunCMake/Codegen/min-graph-1-build-check.cmake13
-rw-r--r--Tests/RunCMake/Codegen/min-graph-1.cmake26
-rw-r--r--Tests/RunCMake/Codegen/min-graph-2-build-check.cmake5
-rw-r--r--Tests/RunCMake/Codegen/min-graph-2.cmake18
-rw-r--r--Tests/RunCMake/Codegen/min-graph-3-build-check.cmake5
-rw-r--r--Tests/RunCMake/Codegen/min-graph-3.cmake12
-rw-r--r--Tests/RunCMake/Codegen/no-codegen-check.cmake5
-rw-r--r--Tests/RunCMake/Codegen/no-codegen.cmake6
-rw-r--r--Tests/RunCMake/Codegen/no-output-result.txt1
-rw-r--r--Tests/RunCMake/Codegen/no-output-stderr.txt4
-rw-r--r--Tests/RunCMake/Codegen/no-output.cmake11
64 files changed, 733 insertions, 3 deletions
diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst
index 77357c0..6169038 100644
--- a/Help/command/add_custom_command.rst
+++ b/Help/command/add_custom_command.rst
@@ -26,6 +26,7 @@ The first signature is for adding a custom command to produce an output:
[JOB_POOL job_pool]
[JOB_SERVER_AWARE <bool>]
[VERBATIM] [APPEND] [USES_TERMINAL]
+ [CODEGEN]
[COMMAND_EXPAND_LISTS]
[DEPENDS_EXPLICIT_ONLY])
@@ -203,6 +204,18 @@ The options are:
``${CC} "-I$<JOIN:$<TARGET_PROPERTY:foo,INCLUDE_DIRECTORIES>,;-I>" foo.cc``
to be properly expanded.
+``CODEGEN``
+ .. versionadded:: 3.31
+
+ Adds the custom command to a global ``codegen`` target that can be
+ used to execute the custom command while avoiding the majority of the
+ build graph.
+
+ This option is supported only by :ref:`Ninja Generators` and
+ :ref:`Makefile Generators`, and is ignored by other generators.
+ Furthermore, this option is allowed only if policy :policy:`CMP0171`
+ is set to ``NEW``.
+
``IMPLICIT_DEPENDS``
Request scanning of implicit dependencies of an input file.
The language given specifies the programming language whose
@@ -454,6 +467,25 @@ will re-run whenever ``in.txt`` changes.
where ``<config>`` is the build configuration, and then compile the generated
source as part of a library.
+.. versionadded:: 3.31
+ Use the ``CODEGEN`` option to add a custom command's outputs to the builtin
+ ``codegen`` target. This is useful to make generated code available for
+ static analysis without building the entire project. For example:
+
+ .. code-block:: cmake
+
+ add_executable(someTool someTool.c)
+
+ add_custom_command(
+ OUTPUT out.c
+ COMMAND someTool -o out.c
+ CODEGEN)
+
+ add_library(myLib out.c)
+
+ A user may build the ``codegen`` target to generate ``out.c``.
+ ``someTool`` is built as dependency, but ``myLib`` is not built at all.
+
Example: Generating Files for Multiple Targets
""""""""""""""""""""""""""""""""""""""""""""""
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index 7155404..90c71b7 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -51,6 +51,14 @@ The :variable:`CMAKE_MINIMUM_REQUIRED_VERSION` variable may also be used
to determine whether to report an error on use of deprecated macros or
functions.
+Policies Introduced by CMake 3.31
+=================================
+
+.. toctree::
+ :maxdepth: 1
+
+ CMP0171: 'codegen' is a reserved target name. </policy/CMP0171>
+
Policies Introduced by CMake 3.30
=================================
diff --git a/Help/policy/CMP0171.rst b/Help/policy/CMP0171.rst
new file mode 100644
index 0000000..c364bf4
--- /dev/null
+++ b/Help/policy/CMP0171.rst
@@ -0,0 +1,26 @@
+CMP0171
+-------
+
+.. versionadded:: 3.31
+
+``codegen`` is a reserved target name.
+
+CMake 3.30 and earlier did not reserve ``codegen`` as a builtin target name,
+leaving projects free to create their own target with that name.
+CMake 3.31 and later prefer to reserve ``codegen`` as a builtin target name
+to drive custom commands created with the ``CODEGEN`` option to
+:command:`add_custom_command`. In order to support building the ``codegen``
+target in scripted environments, e.g., ``cmake --build . --target codegen``,
+the ``codegen`` target needs to be generated even if no custom commands
+use the ``CODEGEN`` option. This policy provides compatibility for projects
+that have not been updated to avoid creating a target named ``codegen``.
+
+The ``OLD`` behavior of this policy allows projects to create a target
+with the name ``codegen``. The ``NEW`` behavior halts with a fatal error
+if a target with the name ``codegen`` is created.
+
+.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 3.31
+.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
+.. include:: STANDARD_ADVICE.txt
+
+.. include:: DEPRECATED.txt
diff --git a/Help/release/dev/codegen.rst b/Help/release/dev/codegen.rst
new file mode 100644
index 0000000..e8b61c9
--- /dev/null
+++ b/Help/release/dev/codegen.rst
@@ -0,0 +1,10 @@
+codegen
+-------
+
+* The :ref:`Ninja Generators` and :ref:`Makefile Generators` now produce
+ a ``codegen`` build target. See policy :policy:`CMP0171`. It drives a
+ subset of the build graph sufficient to run custom commands created with
+ :command:`add_custom_command`'s new ``CODEGEN`` option.
+
+* The :command:`add_custom_command` command gained a ``CODEGEN`` option
+ to mark a custom commands outputs as dependencies of a ``codegen`` target.
diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx
index d943011..d5adba7 100644
--- a/Source/cmAddCustomCommandCommand.cxx
+++ b/Source/cmAddCustomCommandCommand.cxx
@@ -53,6 +53,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
bool command_expand_lists = false;
bool depends_explicit_only =
mf.IsOn("CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY");
+ bool codegen = false;
std::string implicit_depends_lang;
cmImplicitDependsList implicit_depends;
@@ -111,6 +112,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
MAKE_STATIC_KEYWORD(VERBATIM);
MAKE_STATIC_KEYWORD(WORKING_DIRECTORY);
MAKE_STATIC_KEYWORD(DEPENDS_EXPLICIT_ONLY);
+ MAKE_STATIC_KEYWORD(CODEGEN);
#undef MAKE_STATIC_KEYWORD
static std::unordered_set<std::string> const keywords{
keyAPPEND,
@@ -135,7 +137,8 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
keyUSES_TERMINAL,
keyVERBATIM,
keyWORKING_DIRECTORY,
- keyDEPENDS_EXPLICIT_ONLY
+ keyDEPENDS_EXPLICIT_ONLY,
+ keyCODEGEN
};
for (std::string const& copy : args) {
@@ -166,6 +169,8 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
command_expand_lists = true;
} else if (copy == keyDEPENDS_EXPLICIT_ONLY) {
depends_explicit_only = true;
+ } else if (copy == keyCODEGEN) {
+ codegen = true;
} else if (copy == keyTARGET) {
doing = doing_target;
} else if (copy == keyARGS) {
@@ -322,6 +327,28 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
return false;
}
+ if (codegen) {
+ if (output.empty()) {
+ status.SetError("CODEGEN requires at least 1 OUTPUT.");
+ return false;
+ }
+
+ if (append) {
+ status.SetError("CODEGEN may not be used with APPEND.");
+ return false;
+ }
+
+ if (!implicit_depends.empty()) {
+ status.SetError("CODEGEN is not compatible with IMPLICIT_DEPENDS.");
+ return false;
+ }
+
+ if (mf.GetPolicyStatus(cmPolicies::CMP0171) != cmPolicies::NEW) {
+ status.SetError("CODEGEN option requires policy CMP0171 be set to NEW!");
+ return false;
+ }
+ }
+
// Check for an append request.
if (append) {
mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends,
@@ -355,6 +382,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
cc->SetOutputs(output);
cc->SetMainDependency(main_dependency);
cc->SetDepends(depends);
+ cc->SetCodegen(codegen);
cc->SetImplicitDepends(implicit_depends);
mf.AddCustomCommandToOutput(std::move(cc));
} else {
diff --git a/Source/cmAddDependenciesCommand.cxx b/Source/cmAddDependenciesCommand.cxx
index 2d55a5a..aa04018 100644
--- a/Source/cmAddDependenciesCommand.cxx
+++ b/Source/cmAddDependenciesCommand.cxx
@@ -30,6 +30,7 @@ bool cmAddDependenciesCommand(std::vector<std::string> const& args,
// skip over target_name
for (std::string const& arg : cmMakeRange(args).advance(1)) {
target->AddUtility(arg, false, &mf);
+ target->AddCodegenDependency(arg);
}
} else {
mf.IssueMessage(
diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h
index 167e601..6f63d0a 100644
--- a/Source/cmCustomCommand.h
+++ b/Source/cmCustomCommand.h
@@ -132,6 +132,10 @@ public:
const std::string& GetTarget() const;
void SetTarget(const std::string& target);
+ /** Record if the custom command can be used for code generation. */
+ bool GetCodegen() const { return Codegen; }
+ void SetCodegen(bool b) { Codegen = b; }
+
private:
std::vector<std::string> Outputs;
std::vector<std::string> Byproducts;
@@ -153,6 +157,7 @@ private:
bool StdPipesUTF8 = false;
bool HasMainDependency_ = false;
bool DependsExplicitOnly = false;
+ bool Codegen = false;
// Policies are NEW for synthesized custom commands, and set by cmMakefile for
// user-created custom commands.
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 019271b..bf2f0fc 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1398,6 +1398,8 @@ void cmGlobalGenerator::Configure()
}
}
+ this->ReserveGlobalTargetCodegen();
+
// update the cache entry for the number of local generators, this is used
// for progress
this->GetCMakeInstance()->AddCacheEntry(
@@ -2914,6 +2916,53 @@ void cmGlobalGenerator::AddGlobalTarget_Test(
targets.push_back(std::move(gti));
}
+void cmGlobalGenerator::ReserveGlobalTargetCodegen()
+{
+ // Read the policy value at the end of the top-level CMakeLists.txt file
+ // since it's a global policy that affects the whole project.
+ auto& mf = this->Makefiles[0];
+ const auto policyStatus = mf->GetPolicyStatus(cmPolicies::CMP0171);
+
+ this->AllowGlobalTargetCodegen = (policyStatus == cmPolicies::NEW);
+
+ cmTarget* tgt = this->FindTarget("codegen");
+ if (!tgt) {
+ return;
+ }
+
+ MessageType messageType = MessageType::AUTHOR_WARNING;
+ std::ostringstream e;
+ bool issueMessage = false;
+ switch (policyStatus) {
+ case cmPolicies::WARN:
+ e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0171) << "\n";
+ issueMessage = true;
+ CM_FALLTHROUGH;
+ case cmPolicies::OLD:
+ break;
+ case cmPolicies::NEW:
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::REQUIRED_ALWAYS:
+ issueMessage = true;
+ messageType = MessageType::FATAL_ERROR;
+ break;
+ }
+ if (issueMessage) {
+ e << "The target name \"codegen\" is reserved.";
+ this->GetCMakeInstance()->IssueMessage(messageType, e.str(),
+ tgt->GetBacktrace());
+ if (messageType == MessageType::FATAL_ERROR) {
+ cmSystemTools::SetFatalErrorOccurred();
+ return;
+ }
+ }
+}
+
+bool cmGlobalGenerator::CheckCMP0171() const
+{
+ return this->AllowGlobalTargetCodegen;
+}
+
void cmGlobalGenerator::AddGlobalTarget_EditCache(
std::vector<GlobalTargetInfo>& targets) const
{
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index 1ca02d9..33c9889 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -653,6 +653,8 @@ public:
virtual std::string& EncodeLiteral(std::string& lit) { return lit; }
+ bool CheckCMP0171() const;
+
protected:
// for a project collect all its targets by following depend
// information, and also collect all the targets
@@ -719,6 +721,8 @@ protected:
void AddGlobalTarget_Install(std::vector<GlobalTargetInfo>& targets);
void CreateGlobalTarget(GlobalTargetInfo const& gti, cmMakefile* mf);
+ void ReserveGlobalTargetCodegen();
+
std::string FindMakeProgramFile;
std::string ConfiguredFilesPath;
cmake* CMakeInstance;
@@ -891,4 +895,5 @@ protected:
bool ToolSupportsColor;
bool InstallTargetEnabled;
bool ConfigureDoneCMP0026AndCMP0024;
+ bool AllowGlobalTargetCodegen;
};
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index 96c8f25..7d62fa8 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -25,6 +25,7 @@
#include "cmsys/FStream.hxx"
+#include "cmCustomCommand.h"
#include "cmCxxModuleMapper.h"
#include "cmDyndepCollation.h"
#include "cmFortranParser.h"
@@ -43,6 +44,7 @@
#include "cmOutputConverter.h"
#include "cmRange.h"
#include "cmScanDepFormat.h"
+#include "cmSourceFile.h"
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
@@ -1627,6 +1629,90 @@ void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
std::map<std::string, DirectoryTarget> dirTargets =
this->ComputeDirectoryTargets();
+ // Codegen target
+ if (this->CheckCMP0171()) {
+ for (auto const& it : dirTargets) {
+ cmNinjaBuild build("phony");
+ cmGlobalNinjaGenerator::WriteDivider(os);
+ std::string const& currentBinaryDir = it.first;
+ DirectoryTarget const& dt = it.second;
+ std::vector<std::string> configs =
+ dt.LG->GetMakefile()->GetGeneratorConfigs(
+ cmMakefile::IncludeEmptyConfig);
+
+ // Setup target
+ cmNinjaDeps configDeps;
+ build.Comment = cmStrCat("Folder: ", currentBinaryDir);
+ build.Outputs.emplace_back();
+ std::string const buildDirAllTarget =
+ this->ConvertToNinjaPath(cmStrCat(currentBinaryDir, "/codegen"));
+
+ cmNinjaDeps& explicitDeps = build.ExplicitDeps;
+
+ for (auto const& config : configs) {
+ explicitDeps.clear();
+
+ for (DirectoryTarget::Target const& t : dt.Targets) {
+ if (this->IsExcludedFromAllInConfig(t, config)) {
+ continue;
+ }
+
+ std::vector<cmSourceFile const*> customCommandSources;
+ t.GT->GetCustomCommands(customCommandSources, config);
+ for (cmSourceFile const* sf : customCommandSources) {
+ cmCustomCommand const* cc = sf->GetCustomCommand();
+ if (cc->GetCodegen()) {
+ auto const& outputs = cc->GetOutputs();
+
+ std::transform(outputs.begin(), outputs.end(),
+ std::back_inserter(explicitDeps),
+ this->MapToNinjaPath());
+ }
+ }
+ }
+
+ build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
+ // Write target
+ this->WriteBuild(this->EnableCrossConfigBuild() &&
+ this->CrossConfigs.count(config)
+ ? os
+ : *this->GetImplFileStream(config),
+ build);
+ }
+
+ // Add shortcut target
+ if (this->IsMultiConfig()) {
+ for (auto const& config : configs) {
+ build.ExplicitDeps = { this->BuildAlias(buildDirAllTarget, config) };
+ build.Outputs.front() = buildDirAllTarget;
+ this->WriteBuild(*this->GetConfigFileStream(config), build);
+ }
+
+ if (!this->DefaultFileConfig.empty()) {
+ build.ExplicitDeps.clear();
+ for (auto const& config : this->DefaultConfigs) {
+ build.ExplicitDeps.push_back(
+ this->BuildAlias(buildDirAllTarget, config));
+ }
+ build.Outputs.front() = buildDirAllTarget;
+ this->WriteBuild(*this->GetDefaultFileStream(), build);
+ }
+ }
+
+ // Add target for all configs
+ if (this->EnableCrossConfigBuild()) {
+ build.ExplicitDeps.clear();
+ for (auto const& config : this->CrossConfigs) {
+ build.ExplicitDeps.push_back(
+ this->BuildAlias(buildDirAllTarget, config));
+ }
+ build.Outputs.front() = this->BuildAlias(buildDirAllTarget, "codegen");
+ this->WriteBuild(os, build);
+ }
+ }
+ }
+
+ // All target
for (auto const& it : dirTargets) {
cmNinjaBuild build("phony");
cmGlobalNinjaGenerator::WriteDivider(os);
diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx
index 56748a5..38cc4f6 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.cxx
+++ b/Source/cmGlobalUnixMakefileGenerator3.cxx
@@ -23,6 +23,7 @@
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
+#include "cmTarget.h"
#include "cmTargetDepend.h"
#include "cmValue.h"
#include "cmake.h"
@@ -438,6 +439,10 @@ void cmGlobalUnixMakefileGenerator3::WriteDirectoryRules2(
// Write directory-level rules for "all".
this->WriteDirectoryRule2(ruleFileStream, rootLG, dt, "all", true, false);
+ // Write directory-level rules for "codegen".
+ this->WriteDirectoryRule2(ruleFileStream, rootLG, dt, "codegen", true,
+ false);
+
// Write directory-level rules for "preinstall".
this->WriteDirectoryRule2(ruleFileStream, rootLG, dt, "preinstall", true,
true);
@@ -765,6 +770,17 @@ void cmGlobalUnixMakefileGenerator3::WriteConvenienceRules2(
depends, commands, true);
}
+ // add the codegen rule
+ localName = lg.GetRelativeTargetDirectory(gtarget.get());
+ depends.clear();
+ commands.clear();
+ makeTargetName = cmStrCat(localName, "/codegen");
+ commands.push_back(
+ lg.GetRecursiveMakeCall(makefileName, makeTargetName));
+ this->AppendCodegenTargetDepends(depends, gtarget.get());
+ rootLG.WriteMakeRule(ruleFileStream, "codegen rule for target.",
+ makeTargetName, depends, commands, true);
+
// add the clean rule
localName = lg.GetRelativeTargetDirectory(gtarget.get());
makeTargetName = cmStrCat(localName, "/clean");
@@ -893,6 +909,29 @@ void cmGlobalUnixMakefileGenerator3::AppendGlobalTargetDepends(
}
}
+void cmGlobalUnixMakefileGenerator3::AppendCodegenTargetDepends(
+ std::vector<std::string>& depends, cmGeneratorTarget* target)
+{
+ const std::set<std::string>& codegen_depends =
+ target->Target->GetCodegenDeps();
+
+ for (cmTargetDepend const& i : this->GetTargetDirectDepends(target)) {
+ // Create the target-level dependency.
+ cmGeneratorTarget const* dep = i;
+ if (!dep->IsInBuildSystem()) {
+ continue;
+ }
+ if (codegen_depends.find(dep->GetName()) != codegen_depends.end()) {
+ cmLocalUnixMakefileGenerator3* lg3 =
+ static_cast<cmLocalUnixMakefileGenerator3*>(dep->GetLocalGenerator());
+ std::string tgtName = cmStrCat(
+ lg3->GetRelativeTargetDirectory(const_cast<cmGeneratorTarget*>(dep)),
+ "/all");
+ depends.push_back(tgtName);
+ }
+ }
+}
+
void cmGlobalUnixMakefileGenerator3::WriteHelpRule(
std::ostream& ruleFileStream, cmLocalUnixMakefileGenerator3* lg)
{
diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h
index ee78351..d4fcf88 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.h
+++ b/Source/cmGlobalUnixMakefileGenerator3.h
@@ -223,6 +223,9 @@ protected:
void AppendGlobalTargetDepends(std::vector<std::string>& depends,
cmGeneratorTarget* target);
+ void AppendCodegenTargetDepends(std::vector<std::string>& depends,
+ cmGeneratorTarget* target);
+
// Target name hooks for superclass.
const char* GetAllTargetName() const override { return "all"; }
const char* GetInstallTargetName() const override { return "install"; }
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index aa3f28e..e6d56d3 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -4626,6 +4626,12 @@ void AppendCustomCommandToOutput(cmLocalGenerator& lg,
if (cmCustomCommand* cc = sf->GetCustomCommand()) {
cc->AppendCommands(commandLines);
cc->AppendDepends(depends);
+ if (cc->GetCodegen() && !implicit_depends.empty()) {
+ lg.GetCMakeInstance()->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "Cannot append IMPLICIT_DEPENDS to existing CODEGEN custom "
+ "command.");
+ }
cc->AppendImplicitDepends(implicit_depends);
return;
}
diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx
index 7def1fe..4a776b4 100644
--- a/Source/cmLocalUnixMakefileGenerator3.cxx
+++ b/Source/cmLocalUnixMakefileGenerator3.cxx
@@ -1763,6 +1763,21 @@ void cmLocalUnixMakefileGenerator3::WriteLocalAllRules(
this->WriteMakeRule(ruleFileStream, "The main all target", "all", depends,
commands, true);
+ // Write the codegen rule.
+ if (this->GetGlobalGenerator()->CheckCMP0171()) {
+ recursiveTarget = cmStrCat(this->GetCurrentBinaryDirectory(), "/codegen");
+ depends.clear();
+ commands.clear();
+ if (regenerate) {
+ depends.emplace_back("cmake_check_build_system");
+ }
+ commands.push_back(this->GetRecursiveMakeCall(mf2Dir, recursiveTarget));
+ AppendEcho(commands, "Finished generating code",
+ cmLocalUnixMakefileGenerator3::EchoColor::EchoGenerate);
+ this->WriteMakeRule(ruleFileStream, "The main codegen target", "codegen",
+ depends, commands, true);
+ }
+
// Write the clean rule.
recursiveTarget = cmStrCat(this->GetCurrentBinaryDirectory(), "/clean");
commands.clear();
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index d5c50bc..9ff0a4a 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -251,6 +251,8 @@ void cmMakefileTargetGenerator::WriteTargetBuildRules()
std::vector<cmSourceFile const*> customCommands;
this->GeneratorTarget->GetCustomCommands(customCommands,
this->GetConfigName());
+ std::vector<std::string> codegen_depends;
+ codegen_depends.reserve(customCommands.size());
for (cmSourceFile const* sf : customCommands) {
if (this->CMP0113New &&
!this->LocalGenerator->GetCommandsVisited(this->GeneratorTarget)
@@ -273,6 +275,33 @@ void cmMakefileTargetGenerator::WriteTargetBuildRules()
this->LocalGenerator->MaybeRelativeToCurBinDir(byproduct));
}
}
+
+ if (ccg.GetCC().GetCodegen()) {
+ std::string const& output = ccg.GetOutputs().front();
+
+ // We always attach the actual commands to the first output.
+ codegen_depends.emplace_back(output);
+ }
+ }
+
+ // Some make tools need a special dependency for an empty rule.
+ if (codegen_depends.empty()) {
+ std::string hack = this->GlobalGenerator->GetEmptyRuleHackDepends();
+ if (!hack.empty()) {
+ codegen_depends.emplace_back(std::move(hack));
+ }
+ }
+
+ // Construct the codegen target.
+ {
+ std::string const codegenTarget = cmStrCat(
+ this->LocalGenerator->GetRelativeTargetDirectory(this->GeneratorTarget),
+ "/codegen");
+
+ // Write the rule.
+ this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, nullptr,
+ codegenTarget, codegen_depends, {},
+ true);
}
// Add byproducts from build events to the clean rules
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index d893c44..85f3293 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -525,7 +525,9 @@ class cmMakefile;
3, 30, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0170, \
"FETCHCONTENT_FULLY_DISCONNECTED requirements are enforced.", 3, 30, \
- 0, cmPolicies::WARN)
+ 0, cmPolicies::WARN) \
+ SELECT(POLICY, CMP0171, "'codegen' is a reserved target name.", 3, 31, 0, \
+ cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index 1284130..a6e6984 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -657,6 +657,7 @@ public:
bool PerConfig;
cmTarget::Visibility TargetVisibility;
std::set<BT<std::pair<std::string, bool>>> Utilities;
+ std::set<std::string> CodegenDependencies;
std::vector<cmCustomCommand> PreBuildCommands;
std::vector<cmCustomCommand> PreLinkCommands;
std::vector<cmCustomCommand> PostBuildCommands;
@@ -1238,6 +1239,16 @@ void cmTarget::AddUtility(BT<std::pair<std::string, bool>> util)
this->impl->Utilities.emplace(std::move(util));
}
+void cmTarget::AddCodegenDependency(std::string const& name)
+{
+ this->impl->CodegenDependencies.emplace(name);
+}
+
+std::set<std::string> const& cmTarget::GetCodegenDeps() const
+{
+ return this->impl->CodegenDependencies;
+}
+
std::set<BT<std::pair<std::string, bool>>> const& cmTarget::GetUtilities()
const
{
diff --git a/Source/cmTarget.h b/Source/cmTarget.h
index 385dfe7..220ac13 100644
--- a/Source/cmTarget.h
+++ b/Source/cmTarget.h
@@ -173,6 +173,11 @@ public:
void AddUtility(std::string const& name, bool cross,
cmMakefile const* mf = nullptr);
void AddUtility(BT<std::pair<std::string, bool>> util);
+
+ void AddCodegenDependency(std::string const& name);
+
+ std::set<std::string> const& GetCodegenDeps() const;
+
//! Get the utilities used by this target
std::set<BT<std::pair<std::string, bool>>> const& GetUtilities() const;
diff --git a/Source/cmTargetTraceDependencies.cxx b/Source/cmTargetTraceDependencies.cxx
index cc91a42..f14cfbf 100644
--- a/Source/cmTargetTraceDependencies.cxx
+++ b/Source/cmTargetTraceDependencies.cxx
@@ -7,10 +7,12 @@
#include <cmext/algorithm>
+#include "cmCustomCommand.h"
#include "cmCustomCommandGenerator.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmList.h"
+#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmSourceFile.h"
@@ -132,6 +134,8 @@ void cmTargetTraceDependencies::FollowName(std::string const& name)
// The name is a byproduct of a utility target or a PRE_BUILD, PRE_LINK, or
// POST_BUILD command.
this->GeneratorTarget->Target->AddUtility(t->GetName(), false);
+
+ this->GeneratorTarget->Target->AddCodegenDependency(t->GetName());
}
if (cmSourceFile* sf = i->second.Source) {
// For now only follow the dependency if the source file is not a
@@ -213,6 +217,11 @@ void cmTargetTraceDependencies::CheckCustomCommand(cmCustomCommand const& cc)
// Collect target-level dependencies referenced in command lines.
for (auto const& util : ccg.GetUtilities()) {
this->GeneratorTarget->Target->AddUtility(util);
+
+ if (ccg.GetCC().GetCodegen()) {
+ this->GeneratorTarget->Target->AddCodegenDependency(
+ util.Value.first);
+ }
}
// Collect file-level dependencies referenced in DEPENDS.
@@ -226,6 +235,8 @@ void cmTargetTraceDependencies::CheckCustomCommand(cmCustomCommand const& cc)
// The dependency does not name a target and may be a file we
// know how to generate. Queue it.
this->FollowName(dep);
+ } else {
+ this->GeneratorTarget->Target->AddCodegenDependency(dep);
}
}
}
diff --git a/Tests/RunCMake/CMP0171/CMP0171-NEW-result.txt b/Tests/RunCMake/CMP0171/CMP0171-NEW-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-NEW-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMP0171/CMP0171-NEW-stderr.txt b/Tests/RunCMake/CMP0171/CMP0171-NEW-stderr.txt
new file mode 100644
index 0000000..155ddd2
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-NEW-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at CMP0171-NEW\.cmake:[0-9]+ \(add_custom_target\):
+ The target name "codegen" is reserved\.
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-NEW.cmake b/Tests/RunCMake/CMP0171/CMP0171-NEW.cmake
new file mode 100644
index 0000000..547f6c0
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-NEW.cmake
@@ -0,0 +1,6 @@
+# codegen is now a reserved name and this will cause an error since the policy is new.
+add_custom_target(codegen
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-OLD-result.txt b/Tests/RunCMake/CMP0171/CMP0171-OLD-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-OLD-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMP0171/CMP0171-OLD-stderr.txt b/Tests/RunCMake/CMP0171/CMP0171-OLD-stderr.txt
new file mode 100644
index 0000000..1ae3318
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-OLD-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at CMP0171-OLD\.cmake:[0-9]+ \(add_custom_command\):
+ add_custom_command CODEGEN option requires policy CMP0171 be set to NEW\!
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-OLD.cmake b/Tests/RunCMake/CMP0171/CMP0171-OLD.cmake
new file mode 100644
index 0000000..c34b3fb
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-OLD.cmake
@@ -0,0 +1,9 @@
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+ # This will cause an error since the CODEGEN option
+ # requires that CMP0171 is set to NEW
+ CODEGEN
+)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-WARN-stderr.txt b/Tests/RunCMake/CMP0171/CMP0171-WARN-stderr.txt
new file mode 100644
index 0000000..ee79553
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-WARN-stderr.txt
@@ -0,0 +1,9 @@
+CMake Warning \(dev\) at CMP0171-WARN\.cmake:[0-9]+ \(add_custom_target\):
+ Policy CMP0171 is not set: 'codegen' is a reserved target name\. Run "cmake
+ --help-policy CMP0171" for policy details. Use the cmake_policy command to
+ set the policy and suppress this warning\.
+
+ The target name "codegen" is reserved\.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers\. Use -Wno-dev to suppress it\.
diff --git a/Tests/RunCMake/CMP0171/CMP0171-WARN.cmake b/Tests/RunCMake/CMP0171/CMP0171-WARN.cmake
new file mode 100644
index 0000000..6d61723
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-WARN.cmake
@@ -0,0 +1,6 @@
+# CMake should warn the user if they have a target named codegen.
+add_custom_target(codegen
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-codegen-build-result.txt b/Tests/RunCMake/CMP0171/CMP0171-codegen-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-codegen-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/CMP0171/CMP0171-codegen.cmake b/Tests/RunCMake/CMP0171/CMP0171-codegen.cmake
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-codegen.cmake
diff --git a/Tests/RunCMake/CMP0171/CMakeLists.txt b/Tests/RunCMake/CMP0171/CMakeLists.txt
new file mode 100644
index 0000000..1a5755c
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.29)
+project(${RunCMake_TEST} LANGUAGES C)
+
+include(${RunCMake_TEST}.cmake)
+
+enable_testing()
diff --git a/Tests/RunCMake/CMP0171/RunCMakeTest.cmake b/Tests/RunCMake/CMP0171/RunCMakeTest.cmake
new file mode 100644
index 0000000..75941d7
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/RunCMakeTest.cmake
@@ -0,0 +1,18 @@
+include(RunCMake)
+
+run_cmake("CMP0171-WARN")
+
+run_cmake_with_options(CMP0171-OLD "-DCMAKE_POLICY_DEFAULT_CMP0171=OLD")
+
+run_cmake_with_options(CMP0171-NEW "-DCMAKE_POLICY_DEFAULT_CMP0171=NEW")
+
+# The entire point of this test is to ensure the codegen target is not created
+# unintentionally. It can only be created if CMP0171 is NEW.
+block()
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0171-codegen-build)
+ run_cmake(CMP0171-codegen)
+ set(RunCMake_TEST_NO_CLEAN 1)
+ set(RunCMake_TEST_OUTPUT_MERGE 1)
+ # This command will fail with either 1 or 2 depending.
+ run_cmake_command(CMP0171-codegen-build ${CMAKE_COMMAND} --build . --config Debug --target codegen)
+endblock()
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index bfa59d6..db5ef96 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -180,6 +180,7 @@ add_RunCMake_test(CMP0163)
add_RunCMake_test(CMP0165)
add_RunCMake_test(CMP0169)
add_RunCMake_test(CMP0170)
+add_RunCMake_test(CMP0171)
# The test for Policy 65 requires the use of the
# CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode
@@ -1228,7 +1229,8 @@ add_RunCMake_test(CMakePresetsWorkflow
add_RunCMake_test(VerifyHeaderSets)
add_RunCMake_test(set_tests_properties)
-if(${CMAKE_GENERATOR} MATCHES "Make|Ninja")
+if(CMAKE_GENERATOR MATCHES "Make|Ninja")
+ add_RunCMake_test(Codegen)
add_RunCMake_test(TransformDepfile)
endif()
diff --git a/Tests/RunCMake/Codegen/CMakeLists.txt b/Tests/RunCMake/Codegen/CMakeLists.txt
new file mode 100644
index 0000000..f65150d
--- /dev/null
+++ b/Tests/RunCMake/Codegen/CMakeLists.txt
@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 3.29)
+project(${RunCMake_TEST} LANGUAGES C)
+
+# This value is read from the top level CMakeLists.txt
+cmake_policy(SET CMP0171 NEW)
+
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/Codegen/RunCMakeTest.cmake b/Tests/RunCMake/Codegen/RunCMakeTest.cmake
new file mode 100644
index 0000000..bbd70b0
--- /dev/null
+++ b/Tests/RunCMake/Codegen/RunCMakeTest.cmake
@@ -0,0 +1,33 @@
+include(RunCMake)
+
+function(run_codegen case)
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
+
+ run_cmake(${case})
+
+ set(RunCMake_TEST_NO_CLEAN 1)
+
+ run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --target codegen --config Debug)
+endfunction()
+
+# Builds codegen target when there are no custom commands marked codegen
+run_codegen("no-codegen")
+
+# We don't want codegen to drive parts of the project that are EXCLUDE_FROM_ALL
+run_codegen("exclude-from-all")
+
+# Ensures codegen builds minimal build graphs
+run_codegen("min-graph-1")
+run_codegen("min-graph-2")
+run_codegen("min-graph-3")
+
+# Handle specific cases that can affect codegen
+run_codegen("add-dependencies")
+run_codegen("add-custom-command-depends")
+run_codegen("byproducts")
+
+# Error handling
+run_cmake("implicit-depends")
+run_cmake("implicit-depends-append-codegen")
+run_cmake("append-implicit-depends")
+run_cmake("no-output")
diff --git a/Tests/RunCMake/Codegen/add-custom-command-depends-build-check.cmake b/Tests/RunCMake/Codegen/add-custom-command-depends-build-check.cmake
new file mode 100644
index 0000000..d371d73
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-custom-command-depends-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if (NOT EXISTS "${filename}")
+ set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+ return()
+endif()
diff --git a/Tests/RunCMake/Codegen/add-custom-command-depends.cmake b/Tests/RunCMake/Codegen/add-custom-command-depends.cmake
new file mode 100644
index 0000000..793ab5f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-custom-command-depends.cmake
@@ -0,0 +1,16 @@
+add_custom_target(foobar
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+add_custom_command(
+ OUTPUT generated.hpp
+ # This test will fail if DEPENDS isn't accounted for in the codegen build graph
+ DEPENDS foobar
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+ CODEGEN
+)
+
+add_custom_target(hpp_creator ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp)
diff --git a/Tests/RunCMake/Codegen/add-dependencies-build-check.cmake b/Tests/RunCMake/Codegen/add-dependencies-build-check.cmake
new file mode 100644
index 0000000..d371d73
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-dependencies-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if (NOT EXISTS "${filename}")
+ set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+ return()
+endif()
diff --git a/Tests/RunCMake/Codegen/add-dependencies.cmake b/Tests/RunCMake/Codegen/add-dependencies.cmake
new file mode 100644
index 0000000..fbb7e99
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-dependencies.cmake
@@ -0,0 +1,18 @@
+add_custom_target(foobar
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+add_custom_command(
+ OUTPUT generated.hpp
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+ CODEGEN
+)
+
+add_custom_target(hpp_creator ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp)
+
+# This test will fail if add_dependencies isn't account for in the
+# codegen build graph
+add_dependencies(hpp_creator foobar)
diff --git a/Tests/RunCMake/Codegen/append-implicit-depends-result.txt b/Tests/RunCMake/Codegen/append-implicit-depends-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/append-implicit-depends-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/append-implicit-depends-stderr.txt b/Tests/RunCMake/Codegen/append-implicit-depends-stderr.txt
new file mode 100644
index 0000000..c8ef03e
--- /dev/null
+++ b/Tests/RunCMake/Codegen/append-implicit-depends-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error:
+ Cannot append IMPLICIT_DEPENDS to existing CODEGEN custom command\.
diff --git a/Tests/RunCMake/Codegen/append-implicit-depends.cmake b/Tests/RunCMake/Codegen/append-implicit-depends.cmake
new file mode 100644
index 0000000..d212fe5
--- /dev/null
+++ b/Tests/RunCMake/Codegen/append-implicit-depends.cmake
@@ -0,0 +1,19 @@
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+ COMMAND
+ ${CMAKE_COMMAND} -E
+ copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+ CODEGEN
+)
+
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+
+ # ERROR out if IMPLICIT_DEPENDS is used with CODEGEN
+ IMPLICIT_DEPENDS C main.c
+
+ APPEND
+)
diff --git a/Tests/RunCMake/Codegen/byproducts-build-check.cmake b/Tests/RunCMake/Codegen/byproducts-build-check.cmake
new file mode 100644
index 0000000..d371d73
--- /dev/null
+++ b/Tests/RunCMake/Codegen/byproducts-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if (NOT EXISTS "${filename}")
+ set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+ return()
+endif()
diff --git a/Tests/RunCMake/Codegen/byproducts.cmake b/Tests/RunCMake/Codegen/byproducts.cmake
new file mode 100644
index 0000000..ea0b6c7
--- /dev/null
+++ b/Tests/RunCMake/Codegen/byproducts.cmake
@@ -0,0 +1,19 @@
+add_custom_target(foobar
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ BYPRODUCTS
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+# This codegen step relies on the BYPRODUCTS of the previous command.
+# If foobar isn't properly accounted for as a dependency it will fail.
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+ CODEGEN
+)
+
+add_custom_target(hpp_creator ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp)
diff --git a/Tests/RunCMake/Codegen/error.c b/Tests/RunCMake/Codegen/error.c
new file mode 100644
index 0000000..34cb350
--- /dev/null
+++ b/Tests/RunCMake/Codegen/error.c
@@ -0,0 +1 @@
+#error "This file should not be compiled"
diff --git a/Tests/RunCMake/Codegen/exclude-from-all.cmake b/Tests/RunCMake/Codegen/exclude-from-all.cmake
new file mode 100644
index 0000000..bcd4ac0
--- /dev/null
+++ b/Tests/RunCMake/Codegen/exclude-from-all.cmake
@@ -0,0 +1,11 @@
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ COMMAND
+ ${CMAKE_COMMAND} -E false
+ CODEGEN
+)
+
+# We don't want codegen to drive parts of the project that are EXCLUDE_FROM_ALL.
+# This tests that foobar is properly excluded from the codegen build.
+add_executable(foobar EXCLUDE_FROM_ALL error.c ${CMAKE_CURRENT_BINARY_DIR}/generated.h)
diff --git a/Tests/RunCMake/Codegen/generated.h.in b/Tests/RunCMake/Codegen/generated.h.in
new file mode 100644
index 0000000..82ccf67
--- /dev/null
+++ b/Tests/RunCMake/Codegen/generated.h.in
@@ -0,0 +1 @@
+// hello
diff --git a/Tests/RunCMake/Codegen/implicit-depends-append-codegen-result.txt b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/implicit-depends-append-codegen-stderr.txt b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-stderr.txt
new file mode 100644
index 0000000..570cf62
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at implicit-depends-append-codegen\.cmake:[0-9]+ \(add_custom_command\):
+ add_custom_command CODEGEN may not be used with APPEND\.
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/Codegen/implicit-depends-append-codegen.cmake b/Tests/RunCMake/Codegen/implicit-depends-append-codegen.cmake
new file mode 100644
index 0000000..76151cc
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-append-codegen.cmake
@@ -0,0 +1,18 @@
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+
+ # ERROR out if IMPLICIT_DEPENDS is used with CODEGEN
+ IMPLICIT_DEPENDS C main.c
+)
+
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+ COMMAND
+ ${CMAKE_COMMAND} -E
+ copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+ CODEGEN
+ APPEND
+)
diff --git a/Tests/RunCMake/Codegen/implicit-depends-result.txt b/Tests/RunCMake/Codegen/implicit-depends-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/implicit-depends-stderr.txt b/Tests/RunCMake/Codegen/implicit-depends-stderr.txt
new file mode 100644
index 0000000..b9ea8f4
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at implicit-depends\.cmake:[0-9]+ \(add_custom_command\):
+ add_custom_command CODEGEN is not compatible with IMPLICIT_DEPENDS\.
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/Codegen/implicit-depends.cmake b/Tests/RunCMake/Codegen/implicit-depends.cmake
new file mode 100644
index 0000000..011d4b3
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends.cmake
@@ -0,0 +1,11 @@
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+ COMMAND
+ ${CMAKE_COMMAND} -E
+ copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+ ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+ CODEGEN
+ # ERROR out if IMPLICIT_DEPENDS is used with CODEGEN
+ IMPLICIT_DEPENDS C main.c
+)
diff --git a/Tests/RunCMake/Codegen/main.c b/Tests/RunCMake/Codegen/main.c
new file mode 100644
index 0000000..8488f4e
--- /dev/null
+++ b/Tests/RunCMake/Codegen/main.c
@@ -0,0 +1,4 @@
+int main(void)
+{
+ return 0;
+}
diff --git a/Tests/RunCMake/Codegen/min-graph-1-build-check.cmake b/Tests/RunCMake/Codegen/min-graph-1-build-check.cmake
new file mode 100644
index 0000000..32e1557
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-1-build-check.cmake
@@ -0,0 +1,13 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.h")
+if (NOT EXISTS "${filename}")
+ set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+ return()
+endif()
+
+# foobar should be built since it was needed
+# by the code generation
+set(filename "${RunCMake_TEST_BINARY_DIR}/foobar.txt")
+if (NOT EXISTS "${filename}")
+ set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+ return()
+endif()
diff --git a/Tests/RunCMake/Codegen/min-graph-1.cmake b/Tests/RunCMake/Codegen/min-graph-1.cmake
new file mode 100644
index 0000000..ea47b8f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-1.cmake
@@ -0,0 +1,26 @@
+add_executable(foobar main.c)
+add_custom_command(
+ TARGET foobar POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E
+ copy ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/foobar.txt
+)
+
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ COMMAND
+ ${CMAKE_COMMAND} -E
+ copy ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ COMMAND
+ # Generate a header file that requires foobar
+ foobar
+ CODEGEN
+)
+
+add_library(errorlib
+ # If this library is built error.c will cause the build to fail
+ error.c
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
diff --git a/Tests/RunCMake/Codegen/min-graph-2-build-check.cmake b/Tests/RunCMake/Codegen/min-graph-2-build-check.cmake
new file mode 100644
index 0000000..fab168b
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-2-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.h")
+if (NOT EXISTS "${filename}")
+ set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+ return()
+endif()
diff --git a/Tests/RunCMake/Codegen/min-graph-2.cmake b/Tests/RunCMake/Codegen/min-graph-2.cmake
new file mode 100644
index 0000000..277a901
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-2.cmake
@@ -0,0 +1,18 @@
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ COMMAND
+ ${CMAKE_COMMAND} -E
+ copy ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+ CODEGEN
+)
+
+# This target should not be built. It has no reason
+# to be part of the codegen build graph
+add_custom_target(error_custom_target ALL
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+
+ # Cause the build to fail
+ COMMAND ${CMAKE_COMMAND} -E false
+)
diff --git a/Tests/RunCMake/Codegen/min-graph-3-build-check.cmake b/Tests/RunCMake/Codegen/min-graph-3-build-check.cmake
new file mode 100644
index 0000000..734777b
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-3-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/error_lib.c")
+if (NOT EXISTS "${filename}")
+ set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+ return()
+endif()
diff --git a/Tests/RunCMake/Codegen/min-graph-3.cmake b/Tests/RunCMake/Codegen/min-graph-3.cmake
new file mode 100644
index 0000000..c7d61dc
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-3.cmake
@@ -0,0 +1,12 @@
+add_custom_command(
+ OUTPUT
+ ${CMAKE_CURRENT_BINARY_DIR}/error_lib.c
+ COMMAND
+ ${CMAKE_COMMAND} -E
+ copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+ ${CMAKE_CURRENT_BINARY_DIR}/error_lib.c
+ CODEGEN
+)
+
+# This test will fail if error_lib.c is actually compiled
+add_executable(foobar ${CMAKE_CURRENT_BINARY_DIR}/error_lib.c)
diff --git a/Tests/RunCMake/Codegen/no-codegen-check.cmake b/Tests/RunCMake/Codegen/no-codegen-check.cmake
new file mode 100644
index 0000000..97fc46b
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-codegen-check.cmake
@@ -0,0 +1,5 @@
+# Verify generated.hpp was NOT created
+set(unexpected "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if(EXISTS "${unexpected}")
+ set(RunCMake_TEST_FAILED "unexpected file created:\n ${unexpected}")
+endif()
diff --git a/Tests/RunCMake/Codegen/no-codegen.cmake b/Tests/RunCMake/Codegen/no-codegen.cmake
new file mode 100644
index 0000000..00ddd03
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-codegen.cmake
@@ -0,0 +1,6 @@
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+)
diff --git a/Tests/RunCMake/Codegen/no-output-result.txt b/Tests/RunCMake/Codegen/no-output-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-output-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/no-output-stderr.txt b/Tests/RunCMake/Codegen/no-output-stderr.txt
new file mode 100644
index 0000000..7aad679
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-output-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at no-output\.cmake:[0-9]+ \(add_custom_command\):
+ add_custom_command CODEGEN requires at least 1 OUTPUT\.
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/Codegen/no-output.cmake b/Tests/RunCMake/Codegen/no-output.cmake
new file mode 100644
index 0000000..61eb83c
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-output.cmake
@@ -0,0 +1,11 @@
+add_custom_target(foobar
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+add_custom_command(TARGET foobar POST_BUILD
+ COMMAND
+ ${CMAKE_COMMAND} -E true
+ CODEGEN
+)