Browse Source

define_property(): Add INITIALIZE_FROM_VARIABLE argument

Fixes: #20698
Kyle Edwards 4 years ago
parent
commit
fce24e4f10
27 changed files with 198 additions and 16 deletions
  1. 7 1
      Help/command/define_property.rst
  2. 5 0
      Help/release/dev/target-properties-from-variables.rst
  3. 43 1
      Source/cmDefinePropertyCommand.cxx
  4. 10 7
      Source/cmPropertyDefinition.cxx
  5. 18 4
      Source/cmPropertyDefinition.h
  6. 4 2
      Source/cmState.cxx
  7. 7 1
      Source/cmState.h
  8. 11 0
      Source/cmTarget.cxx
  9. 6 0
      Tests/RunCMake/define_property/RunCMakeTest.cmake
  10. 1 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_1-result.txt
  11. 4 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_1-stderr.txt
  12. 3 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_1.cmake
  13. 1 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_2-result.txt
  14. 4 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_2-stderr.txt
  15. 3 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_2.cmake
  16. 1 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-no_prefix-result.txt
  17. 5 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-no_prefix-stderr.txt
  18. 3 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-no_prefix.cmake
  19. 1 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-non_target-result.txt
  20. 5 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-non_target-stderr.txt
  21. 3 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-non_target.cmake
  22. 11 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-subdirectory/CMakeLists.txt
  23. 1 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix-result.txt
  24. 5 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix-stderr.txt
  25. 3 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix.cmake
  26. 29 0
      Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE.cmake
  27. 4 0
      Tests/RunCMake/define_property/main.c

+ 7 - 1
Help/command/define_property.rst

@@ -9,7 +9,8 @@ Define and document custom properties.
                    TEST | VARIABLE | CACHED_VARIABLE>
                    PROPERTY <name> [INHERITED]
                    [BRIEF_DOCS <brief-doc> [docs...]]
-                   [FULL_DOCS <full-doc> [docs...]])
+                   [FULL_DOCS <full-doc> [docs...]]
+                   [INITIALIZE_FROM_VARIABLE <variable>])
 
 Defines one property in a scope for use with the :command:`set_property` and
 :command:`get_property` commands.  This is primarily useful to associate
@@ -57,3 +58,8 @@ The ``BRIEF_DOCS`` and ``FULL_DOCS`` options are followed by strings to be
 associated with the property as its brief and full documentation.
 Corresponding options to the :command:`get_property` command will retrieve
 the documentation.
+
+The ``INITIALIZE_FROM_VARIABLE`` option is followed by the name of a variable
+from which to initialize the property. The variable name must end with the
+property name, must have a prefix before the property name, and must not begin
+with ``CMAKE_`` or ``_CMAKE_``.

+ 5 - 0
Help/release/dev/target-properties-from-variables.rst

@@ -0,0 +1,5 @@
+target-properties-from-variables
+--------------------------------
+
+* The :command:`define_property` command gained a new
+  ``INITIALIZE_FROM_VARIABLE`` argument.

+ 43 - 1
Source/cmDefinePropertyCommand.cxx

@@ -2,6 +2,9 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmDefinePropertyCommand.h"
 
+#include <algorithm>
+#include <iterator>
+
 #include <cmext/string_view>
 
 #include "cmArgumentParser.h"
@@ -50,12 +53,14 @@ bool cmDefinePropertyCommand(std::vector<std::string> const& args,
   std::string PropertyName;
   std::vector<std::string> BriefDocs;
   std::vector<std::string> FullDocs;
+  std::string initializeFromVariable;
 
   cmArgumentParser<void> parser;
   parser.Bind("PROPERTY"_s, PropertyName);
   parser.Bind("BRIEF_DOCS"_s, BriefDocs);
   parser.Bind("FULL_DOCS"_s, FullDocs);
   parser.Bind("INHERITED"_s, inherited);
+  parser.Bind("INITIALIZE_FROM_VARIABLE"_s, initializeFromVariable);
   std::vector<std::string> invalidArgs;
 
   parser.Parse(cmMakeRange(args).advance(1), &invalidArgs);
@@ -71,10 +76,47 @@ bool cmDefinePropertyCommand(std::vector<std::string> const& args,
     return false;
   }
 
+  if (!initializeFromVariable.empty()) {
+    // Make sure property scope is TARGET.
+    if (scope != cmProperty::TARGET) {
+      status.SetError(
+        "Scope must be TARGET if INITIALIZE_FROM_VARIABLE is specified");
+      return false;
+    }
+
+    // Make sure the variable has the property name as a suffix.
+    if (!cmHasSuffix(initializeFromVariable, PropertyName)) {
+      status.SetError(cmStrCat("Variable name \"", initializeFromVariable,
+                               "\" does not end with property name \"",
+                               PropertyName, "\""));
+      return false;
+    }
+    if (initializeFromVariable == PropertyName) {
+      status.SetError(cmStrCat(
+        "Variable name must have a non-empty prefix before property name \"",
+        PropertyName, "\""));
+      return false;
+    }
+  }
+
+  // Make sure the variable is not reserved.
+  static constexpr const char* reservedPrefixes[] = {
+    "CMAKE_",
+    "_CMAKE_",
+  };
+  if (std::any_of(std::begin(reservedPrefixes), std::end(reservedPrefixes),
+                  [&initializeFromVariable](const char* prefix) {
+                    return cmHasPrefix(initializeFromVariable, prefix);
+                  })) {
+    status.SetError(
+      cmStrCat("variable name \"", initializeFromVariable, "\" is reserved"));
+    return false;
+  }
+
   // Actually define the property.
   status.GetMakefile().GetState()->DefineProperty(
     PropertyName, scope, cmJoin(BriefDocs, ""), cmJoin(FullDocs, ""),
-    inherited);
+    inherited, initializeFromVariable);
 
   return true;
 }

+ 10 - 7
Source/cmPropertyDefinition.cxx

@@ -6,31 +6,34 @@
 
 cmPropertyDefinition::cmPropertyDefinition(std::string shortDescription,
                                            std::string fullDescription,
-                                           bool chained)
+                                           bool chained,
+                                           std::string initializeFromVariable)
   : ShortDescription(std::move(shortDescription))
   , FullDescription(std::move(fullDescription))
   , Chained(chained)
+  , InitializeFromVariable(std::move(initializeFromVariable))
 {
 }
 
 void cmPropertyDefinitionMap::DefineProperty(
   const std::string& name, cmProperty::ScopeType scope,
   const std::string& ShortDescription, const std::string& FullDescription,
-  bool chain)
+  bool chain, const std::string& initializeFromVariable)
 {
-  auto it = this->Map_.find(key_type(name, scope));
+  auto it = this->Map_.find(KeyType(name, scope));
   if (it == this->Map_.end()) {
     // try_emplace() since C++17
-    this->Map_.emplace(
-      std::piecewise_construct, std::forward_as_tuple(name, scope),
-      std::forward_as_tuple(ShortDescription, FullDescription, chain));
+    this->Map_.emplace(std::piecewise_construct,
+                       std::forward_as_tuple(name, scope),
+                       std::forward_as_tuple(ShortDescription, FullDescription,
+                                             chain, initializeFromVariable));
   }
 }
 
 cmPropertyDefinition const* cmPropertyDefinitionMap::GetPropertyDefinition(
   const std::string& name, cmProperty::ScopeType scope) const
 {
-  auto it = this->Map_.find(key_type(name, scope));
+  auto it = this->Map_.find(KeyType(name, scope));
   if (it != this->Map_.end()) {
     return &it->second;
   }

+ 18 - 4
Source/cmPropertyDefinition.h

@@ -22,7 +22,8 @@ class cmPropertyDefinition
 public:
   /// Constructor
   cmPropertyDefinition(std::string shortDescription,
-                       std::string fullDescription, bool chained);
+                       std::string fullDescription, bool chained,
+                       std::string initializeFromVariable);
 
   /// Is the property chained?
   bool IsChained() const { return this->Chained; }
@@ -39,10 +40,17 @@ public:
     return this->FullDescription;
   }
 
+  /// Get the variable the property is initialized from
+  const std::string& GetInitializeFromVariable() const
+  {
+    return this->InitializeFromVariable;
+  }
+
 private:
   std::string ShortDescription;
   std::string FullDescription;
   bool Chained;
+  std::string InitializeFromVariable;
 };
 
 /** \class cmPropertyDefinitionMap
@@ -54,13 +62,19 @@ public:
   // define the property
   void DefineProperty(const std::string& name, cmProperty::ScopeType scope,
                       const std::string& ShortDescription,
-                      const std::string& FullDescription, bool chain);
+                      const std::string& FullDescription, bool chain,
+                      const std::string& initializeFromVariable);
 
   // get the property definition if present, otherwise nullptr
   cmPropertyDefinition const* GetPropertyDefinition(
     const std::string& name, cmProperty::ScopeType scope) const;
 
+  using KeyType = std::pair<std::string, cmProperty::ScopeType>;
+  const std::map<KeyType, cmPropertyDefinition>& GetMap() const
+  {
+    return this->Map_;
+  }
+
 private:
-  using key_type = std::pair<std::string, cmProperty::ScopeType>;
-  std::map<key_type, cmPropertyDefinition> Map_;
+  std::map<KeyType, cmPropertyDefinition> Map_;
 };

+ 4 - 2
Source/cmState.cxx

@@ -327,10 +327,12 @@ cmStateSnapshot cmState::Reset()
 void cmState::DefineProperty(const std::string& name,
                              cmProperty::ScopeType scope,
                              const std::string& ShortDescription,
-                             const std::string& FullDescription, bool chained)
+                             const std::string& FullDescription, bool chained,
+                             const std::string& initializeFromVariable)
 {
   this->PropertyDefinitions.DefineProperty(name, scope, ShortDescription,
-                                           FullDescription, chained);
+                                           FullDescription, chained,
+                                           initializeFromVariable);
 }
 
 cmPropertyDefinition const* cmState::GetPropertyDefinition(

+ 7 - 1
Source/cmState.h

@@ -133,12 +133,18 @@ public:
   // Define a property
   void DefineProperty(const std::string& name, cmProperty::ScopeType scope,
                       const std::string& ShortDescription,
-                      const std::string& FullDescription, bool chain = false);
+                      const std::string& FullDescription, bool chain = false,
+                      const std::string& initializeFromVariable = "");
 
   // get property definition
   cmPropertyDefinition const* GetPropertyDefinition(
     const std::string& name, cmProperty::ScopeType scope) const;
 
+  const cmPropertyDefinitionMap& GetPropertyDefinitions() const
+  {
+    return this->PropertyDefinitions;
+  }
+
   bool IsPropertyChained(const std::string& name,
                          cmProperty::ScopeType scope) const;
 

+ 11 - 0
Source/cmTarget.cxx

@@ -28,6 +28,7 @@
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmProperty.h"
+#include "cmPropertyDefinition.h"
 #include "cmPropertyMap.h"
 #include "cmRange.h"
 #include "cmSourceFile.h"
@@ -557,6 +558,16 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
       }
     }
   }
+
+  for (auto const& prop : mf->GetState()->GetPropertyDefinitions().GetMap()) {
+    if (prop.first.second == cmProperty::TARGET &&
+        !prop.second.GetInitializeFromVariable().empty()) {
+      if (auto value =
+            mf->GetDefinition(prop.second.GetInitializeFromVariable())) {
+        this->SetProperty(prop.first.first, value);
+      }
+    }
+  }
 }
 
 cmTarget::cmTarget(cmTarget&&) noexcept = default;

+ 6 - 0
Tests/RunCMake/define_property/RunCMakeTest.cmake

@@ -1,3 +1,9 @@
 include(RunCMake)
 
 run_cmake(define_property)
+run_cmake(define_property-INITIALIZE_FROM_VARIABLE)
+run_cmake(define_property-INITIALIZE_FROM_VARIABLE-invalid_1)
+run_cmake(define_property-INITIALIZE_FROM_VARIABLE-invalid_2)
+run_cmake(define_property-INITIALIZE_FROM_VARIABLE-non_target)
+run_cmake(define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix)
+run_cmake(define_property-INITIALIZE_FROM_VARIABLE-no_prefix)

+ 1 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_1-result.txt

@@ -0,0 +1 @@
+

+ 4 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_1-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at define_property-INITIALIZE_FROM_VARIABLE-invalid_1\.cmake:[0-9]+ \(define_property\):
+  define_property variable name "CMAKE_PROP1" is reserved
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 3 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_1.cmake

@@ -0,0 +1,3 @@
+define_property(TARGET PROPERTY PROP1
+  INITIALIZE_FROM_VARIABLE CMAKE_PROP1
+  )

+ 1 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_2-result.txt

@@ -0,0 +1 @@
+

+ 4 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_2-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at define_property-INITIALIZE_FROM_VARIABLE-invalid_2\.cmake:[0-9]+ \(define_property\):
+  define_property variable name "_CMAKE_PROP1" is reserved
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 3 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_2.cmake

@@ -0,0 +1,3 @@
+define_property(TARGET PROPERTY PROP1
+  INITIALIZE_FROM_VARIABLE _CMAKE_PROP1
+  )

+ 1 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-no_prefix-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-no_prefix-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at define_property-INITIALIZE_FROM_VARIABLE-no_prefix\.cmake:[0-9]+ \(define_property\):
+  define_property Variable name must have a non-empty prefix before property
+  name "PROP1"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 3 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-no_prefix.cmake

@@ -0,0 +1,3 @@
+define_property(TARGET PROPERTY PROP1
+  INITIALIZE_FROM_VARIABLE PROP1
+  )

+ 1 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-non_target-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-non_target-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at define_property-INITIALIZE_FROM_VARIABLE-non_target\.cmake:[0-9]+ \(define_property\):
+  define_property Scope must be TARGET if INITIALIZE_FROM_VARIABLE is
+  specified
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 3 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-non_target.cmake

@@ -0,0 +1,3 @@
+define_property(GLOBAL PROPERTY PROP1
+  INITIALIZE_FROM_VARIABLE Test_PROP1
+  )

+ 11 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-subdirectory/CMakeLists.txt

@@ -0,0 +1,11 @@
+define_property(TARGET PROPERTY PROP2
+  INITIALIZE_FROM_VARIABLE Test_PROP2
+  )
+define_property(TARGET PROPERTY PROP3
+  INITIALIZE_FROM_VARIABLE Test_PROP3
+  )
+
+add_executable(sub_exe ../main.c)
+assert_prop_eq(sub_exe PROP1 "Hello")
+assert_prop_eq(sub_exe PROP2 "world")
+assert_prop_eq(sub_exe PROP3 "!")

+ 1 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix\.cmake:[0-9]+ \(define_property\):
+  define_property Variable name "Test_PROP2" does not end with property name
+  "PROP1"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 3 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE-wrong_suffix.cmake

@@ -0,0 +1,3 @@
+define_property(TARGET PROPERTY PROP1
+  INITIALIZE_FROM_VARIABLE Test_PROP2
+  )

+ 29 - 0
Tests/RunCMake/define_property/define_property-INITIALIZE_FROM_VARIABLE.cmake

@@ -0,0 +1,29 @@
+enable_language(C)
+
+function(assert_prop_eq tgt name value)
+  get_property(actual_value TARGET ${tgt} PROPERTY ${name})
+  if(NOT actual_value STREQUAL value)
+    message(SEND_ERROR "Expected value of ${name}:\n  ${value}\nActual value:\n  ${actual_value}")
+  endif()
+endfunction()
+
+function(assert_prop_undef tgt name)
+  get_property(actual_value TARGET ${tgt} PROPERTY ${name})
+  if(DEFINED actual_value)
+    message(SEND_ERROR "Expected ${name} to be undefined, actual value:\n  ${actual_value}")
+  endif()
+endfunction()
+
+set(Test_PROP1 "Hello")
+set(Test_PROP2 "world")
+set(Test_PROP3 "!")
+define_property(TARGET PROPERTY PROP1
+  INITIALIZE_FROM_VARIABLE Test_PROP1
+  )
+
+add_subdirectory(define_property-INITIALIZE_FROM_VARIABLE-subdirectory)
+
+add_executable(top_exe main.c)
+assert_prop_eq(top_exe PROP1 "Hello")
+assert_prop_eq(top_exe PROP2 "world")
+assert_prop_eq(top_exe PROP3 "!")

+ 4 - 0
Tests/RunCMake/define_property/main.c

@@ -0,0 +1,4 @@
+int main(void)
+{
+  return 0;
+}