From 27a912193bfe77e400784b152b1cd67003915c37 Mon Sep 17 00:00:00 2001
From: Raul Tambre <raul@tambre.ee>
Date: Sat, 15 Aug 2020 21:53:15 +0300
Subject: file(GENERATE): Add TARGET argument

Adds TARGET argument to file(GENERATE) to make resolving generator expressions
requiring a target possible.

Implements #21101, fixes #21074.
---
 Help/command/file.rst                            |  6 +++-
 Help/release/dev/file-generate-target.rst        |  5 ++++
 Source/cmFileCommand.cxx                         | 35 +++++++++++++++++-------
 Source/cmGeneratorExpressionEvaluationFile.cxx   | 21 ++++++++------
 Source/cmGeneratorExpressionEvaluationFile.h     |  5 +++-
 Source/cmMakefile.cxx                            |  6 ++--
 Source/cmMakefile.h                              |  2 +-
 Tests/RunCMake/File_Generate/RunCMakeTest.cmake  |  7 +++++
 Tests/RunCMake/File_Generate/Target.cmake        |  2 ++
 Tests/RunCMake/File_Generate/sub1/CMakeLists.txt |  7 +++++
 Tests/RunCMake/File_Generate/sub2/CMakeLists.txt |  7 +++++
 11 files changed, 79 insertions(+), 24 deletions(-)
 create mode 100644 Help/release/dev/file-generate-target.rst
 create mode 100644 Tests/RunCMake/File_Generate/Target.cmake
 create mode 100644 Tests/RunCMake/File_Generate/sub1/CMakeLists.txt
 create mode 100644 Tests/RunCMake/File_Generate/sub2/CMakeLists.txt

diff --git a/Help/command/file.rst b/Help/command/file.rst
index 2cf938b..953172b 100644
--- a/Help/command/file.rst
+++ b/Help/command/file.rst
@@ -449,7 +449,7 @@ modified.
 
   file(GENERATE OUTPUT output-file
        <INPUT input-file|CONTENT content>
-       [CONDITION expression])
+       [CONDITION expression] [TARGET target])
 
 Generate an output file for each build configuration supported by the current
 :manual:`CMake Generator <cmake-generators(7)>`.  Evaluate
@@ -479,6 +479,10 @@ from the input content to produce the output content.  The options are:
   with respect to the value of :variable:`CMAKE_CURRENT_BINARY_DIR`.
   See policy :policy:`CMP0070`.
 
+``TARGET <target>``
+  Specify target which to use when evaluating generator expressions.  Enables
+  use of generator expressions requiring a target.
+
 Exactly one ``CONTENT`` or ``INPUT`` option must be given.  A specific
 ``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
 Generated files are modified and their timestamp updated on subsequent cmake
diff --git a/Help/release/dev/file-generate-target.rst b/Help/release/dev/file-generate-target.rst
new file mode 100644
index 0000000..09fb460
--- /dev/null
+++ b/Help/release/dev/file-generate-target.rst
@@ -0,0 +1,5 @@
+file-generate-target
+--------------------
+
+* The :command:`file(GENERATE)` command gained a new ``TARGET`` keyword to
+  support resolving target-dependent generator expressions.
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index 8d20d35..550ad6e 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -2240,6 +2240,7 @@ bool HandleUploadCommand(std::vector<std::string> const& args,
 }
 
 void AddEvaluationFile(const std::string& inputName,
+                       const std::string& targetName,
                        const std::string& outputExpr,
                        const std::string& condition, bool inputIsContent,
                        cmExecutionStatus& status)
@@ -2255,7 +2256,8 @@ void AddEvaluationFile(const std::string& inputName,
     conditionGe.Parse(condition);
 
   status.GetMakefile().AddEvaluationFile(
-    inputName, std::move(outputCge), std::move(conditionCge), inputIsContent);
+    inputName, targetName, std::move(outputCge), std::move(conditionCge),
+    inputIsContent);
 }
 
 bool HandleGenerateCommand(std::vector<std::string> const& args,
@@ -2269,23 +2271,36 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
     status.SetError("Incorrect arguments to GENERATE subcommand.");
     return false;
   }
+
   std::string condition;
-  if (args.size() > 5) {
-    if (args[5] != "CONDITION") {
+  std::string target;
+
+  for (std::size_t i = 5; i < args.size();) {
+    const std::string& arg = args[i++];
+
+    if (args.size() - i == 0) {
       status.SetError("Incorrect arguments to GENERATE subcommand.");
       return false;
     }
-    if (args.size() != 7) {
-      status.SetError("Incorrect arguments to GENERATE subcommand.");
+
+    const std::string& value = args[i++];
+
+    if (value.empty()) {
+      status.SetError(
+        arg + " of sub-command GENERATE must not be empty if specified.");
       return false;
     }
-    condition = args[6];
-    if (condition.empty()) {
-      status.SetError("CONDITION of sub-command GENERATE must not be empty if "
-                      "specified.");
+
+    if (arg == "CONDITION") {
+      condition = value;
+    } else if (arg == "TARGET") {
+      target = value;
+    } else {
+      status.SetError("Unknown argument to GENERATE subcommand.");
       return false;
     }
   }
+
   std::string output = args[2];
   const bool inputIsContent = args[3] != "INPUT";
   if (inputIsContent && args[3] != "CONTENT") {
@@ -2294,7 +2309,7 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
   }
   std::string input = args[4];
 
-  AddEvaluationFile(input, output, condition, inputIsContent, status);
+  AddEvaluationFile(input, target, output, condition, inputIsContent, status);
   return true;
 }
 
diff --git a/Source/cmGeneratorExpressionEvaluationFile.cxx b/Source/cmGeneratorExpressionEvaluationFile.cxx
index 6647e62..9e5023d 100644
--- a/Source/cmGeneratorExpressionEvaluationFile.cxx
+++ b/Source/cmGeneratorExpressionEvaluationFile.cxx
@@ -19,11 +19,12 @@
 #include "cmSystemTools.h"
 
 cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
-  std::string input,
+  std::string input, std::string target,
   std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
   std::unique_ptr<cmCompiledGeneratorExpression> condition,
   bool inputIsContent, cmPolicies::PolicyStatus policyStatusCMP0070)
   : Input(std::move(input))
+  , Target(std::move(target))
   , OutputFileExpr(std::move(outputFileExpr))
   , Condition(std::move(condition))
   , InputIsContent(inputIsContent)
@@ -37,9 +38,10 @@ void cmGeneratorExpressionEvaluationFile::Generate(
   std::map<std::string, std::string>& outputFiles, mode_t perm)
 {
   std::string rawCondition = this->Condition->GetInput();
+  cmGeneratorTarget* target = lg->FindGeneratorTargetToUse(Target);
   if (!rawCondition.empty()) {
     std::string condResult =
-      this->Condition->Evaluate(lg, config, nullptr, nullptr, nullptr, lang);
+      this->Condition->Evaluate(lg, config, target, nullptr, nullptr, lang);
     if (condResult == "0") {
       return;
     }
@@ -54,9 +56,10 @@ void cmGeneratorExpressionEvaluationFile::Generate(
     }
   }
 
-  const std::string outputFileName = this->GetOutputFileName(lg, config, lang);
+  const std::string outputFileName =
+    this->GetOutputFileName(lg, target, config, lang);
   const std::string& outputContent =
-    inputExpression->Evaluate(lg, config, nullptr, nullptr, nullptr, lang);
+    inputExpression->Evaluate(lg, config, target, nullptr, nullptr, lang);
 
   auto it = outputFiles.find(outputFileName);
 
@@ -91,10 +94,11 @@ void cmGeneratorExpressionEvaluationFile::CreateOutputFile(
 {
   std::vector<std::string> enabledLanguages;
   cmGlobalGenerator* gg = lg->GetGlobalGenerator();
+  cmGeneratorTarget* target = lg->FindGeneratorTargetToUse(Target);
   gg->GetEnabledLanguages(enabledLanguages);
 
   for (std::string const& le : enabledLanguages) {
-    std::string const name = this->GetOutputFileName(lg, config, le);
+    std::string const name = this->GetOutputFileName(lg, target, config, le);
     cmSourceFile* sf = lg->GetMakefile()->GetOrCreateSource(
       name, false, cmSourceFileLocationKind::Known);
     // Tell TraceDependencies that the file is not expected to exist
@@ -176,10 +180,11 @@ std::string cmGeneratorExpressionEvaluationFile::GetInputFileName(
 }
 
 std::string cmGeneratorExpressionEvaluationFile::GetOutputFileName(
-  cmLocalGenerator* lg, const std::string& config, const std::string& lang)
+  cmLocalGenerator* lg, cmGeneratorTarget* target, const std::string& config,
+  const std::string& lang)
 {
-  std::string outputFileName = this->OutputFileExpr->Evaluate(
-    lg, config, nullptr, nullptr, nullptr, lang);
+  std::string outputFileName =
+    this->OutputFileExpr->Evaluate(lg, config, target, nullptr, nullptr, lang);
 
   if (cmSystemTools::FileIsFullPath(outputFileName)) {
     outputFileName = cmSystemTools::CollapseFullPath(outputFileName);
diff --git a/Source/cmGeneratorExpressionEvaluationFile.h b/Source/cmGeneratorExpressionEvaluationFile.h
index a258a2d..caa8064 100644
--- a/Source/cmGeneratorExpressionEvaluationFile.h
+++ b/Source/cmGeneratorExpressionEvaluationFile.h
@@ -15,13 +15,14 @@
 #include "cmGeneratorExpression.h"
 #include "cmPolicies.h"
 
+class cmGeneratorTarget;
 class cmLocalGenerator;
 
 class cmGeneratorExpressionEvaluationFile
 {
 public:
   cmGeneratorExpressionEvaluationFile(
-    std::string input,
+    std::string input, std::string target,
     std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
     std::unique_ptr<cmCompiledGeneratorExpression> condition,
     bool inputIsContent, cmPolicies::PolicyStatus policyStatusCMP0070);
@@ -40,6 +41,7 @@ private:
 
   std::string GetInputFileName(cmLocalGenerator* lg);
   std::string GetOutputFileName(cmLocalGenerator* lg,
+                                cmGeneratorTarget* target,
                                 const std::string& config,
                                 const std::string& lang);
   enum PathRole
@@ -52,6 +54,7 @@ private:
 
 private:
   const std::string Input;
+  const std::string Target;
   const std::unique_ptr<cmCompiledGeneratorExpression> OutputFileExpr;
   const std::unique_ptr<cmCompiledGeneratorExpression> Condition;
   std::vector<std::string> Files;
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 5cfe5f1..d9d4ae8 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -801,15 +801,15 @@ void cmMakefile::EnforceDirectoryLevelRules() const
 }
 
 void cmMakefile::AddEvaluationFile(
-  const std::string& inputFile,
+  const std::string& inputFile, const std::string& targetName,
   std::unique_ptr<cmCompiledGeneratorExpression> outputName,
   std::unique_ptr<cmCompiledGeneratorExpression> condition,
   bool inputIsContent)
 {
   this->EvaluationFiles.push_back(
     cm::make_unique<cmGeneratorExpressionEvaluationFile>(
-      inputFile, std::move(outputName), std::move(condition), inputIsContent,
-      this->GetPolicyStatus(cmPolicies::CMP0070)));
+      inputFile, targetName, std::move(outputName), std::move(condition),
+      inputIsContent, this->GetPolicyStatus(cmPolicies::CMP0070)));
 }
 
 const std::vector<std::unique_ptr<cmGeneratorExpressionEvaluationFile>>&
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index 80d80d3..69894b1 100644
--- a/Source/cmMakefile.h
+++ b/Source/cmMakefile.h
@@ -949,7 +949,7 @@ public:
   void EnforceDirectoryLevelRules() const;
 
   void AddEvaluationFile(
-    const std::string& inputFile,
+    const std::string& inputFile, const std::string& targetName,
     std::unique_ptr<cmCompiledGeneratorExpression> outputName,
     std::unique_ptr<cmCompiledGeneratorExpression> condition,
     bool inputIsContent);
diff --git a/Tests/RunCMake/File_Generate/RunCMakeTest.cmake b/Tests/RunCMake/File_Generate/RunCMakeTest.cmake
index 770fc6e..48fb71c 100644
--- a/Tests/RunCMake/File_Generate/RunCMakeTest.cmake
+++ b/Tests/RunCMake/File_Generate/RunCMakeTest.cmake
@@ -33,6 +33,13 @@ foreach(l CXX C)
   endif()
 endforeach()
 
+run_cmake(Target)
+file(READ "${RunCMake_BINARY_DIR}/Target-build/sub1/output.txt" sub_1)
+file(READ "${RunCMake_BINARY_DIR}/Target-build/sub2/output.txt" sub_2)
+if(NOT sub_1 MATCHES "first" OR NOT sub_2 MATCHES "second")
+  message(SEND_ERROR "Wrong target used by TARGET argument! ${sub_1} ${sub_2}")
+endif()
+
 set(timeformat "%Y%j%H%M%S")
 
 file(REMOVE "${RunCMake_BINARY_DIR}/WriteIfDifferent-build/output_file.txt")
diff --git a/Tests/RunCMake/File_Generate/Target.cmake b/Tests/RunCMake/File_Generate/Target.cmake
new file mode 100644
index 0000000..16e8457
--- /dev/null
+++ b/Tests/RunCMake/File_Generate/Target.cmake
@@ -0,0 +1,2 @@
+add_subdirectory(sub1)
+add_subdirectory(sub2)
diff --git a/Tests/RunCMake/File_Generate/sub1/CMakeLists.txt b/Tests/RunCMake/File_Generate/sub1/CMakeLists.txt
new file mode 100644
index 0000000..34c51a4
--- /dev/null
+++ b/Tests/RunCMake/File_Generate/sub1/CMakeLists.txt
@@ -0,0 +1,7 @@
+add_library(library IMPORTED STATIC)
+set_property(TARGET library PROPERTY COMPILE_DEFINITIONS "first")
+
+file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output.txt"
+              CONTENT "$<TARGET_PROPERTY:COMPILE_DEFINITIONS>"
+              TARGET library
+)
diff --git a/Tests/RunCMake/File_Generate/sub2/CMakeLists.txt b/Tests/RunCMake/File_Generate/sub2/CMakeLists.txt
new file mode 100644
index 0000000..09b81ac
--- /dev/null
+++ b/Tests/RunCMake/File_Generate/sub2/CMakeLists.txt
@@ -0,0 +1,7 @@
+add_library(library IMPORTED STATIC)
+set_property(TARGET library PROPERTY COMPILE_DEFINITIONS "second")
+
+file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output.txt"
+              CONTENT "$<TARGET_PROPERTY:COMPILE_DEFINITIONS>"
+              TARGET library
+)
-- 
cgit v0.12