1
0
Эх сурвалжийг харах

Add option to optimize link dependencies for static libraries

Add an `OPTIMIZE_DEPENDENCIES` target property and supporting
`CMAKE_OPTIMIZE_DEPENDENCIES` variable to optionally enable pruning and
flattening of outgoing dependencies from static libraries.  Since they
do not actually link, they only depend on side effects of their
dependencies.  Therefore we can drop dependencies that contribute no
side effects.
Kyle Edwards 5 жил өмнө
parent
commit
2e42651dff
47 өөрчлөгдсөн 559 нэмэгдсэн , 8 устгасан
  1. 1 0
      Help/manual/cmake-properties.7.rst
  2. 1 0
      Help/manual/cmake-variables.7.rst
  3. 38 0
      Help/prop_tgt/OPTIMIZE_DEPENDENCIES.rst
  4. 7 0
      Help/release/dev/optimize-link-dependencies.rst
  5. 4 0
      Help/variable/CMAKE_OPTIMIZE_DEPENDENCIES.rst
  6. 156 7
      Source/cmComputeTargetDepends.cxx
  7. 18 1
      Source/cmComputeTargetDepends.h
  8. 31 0
      Source/cmGeneratorTarget.cxx
  9. 6 0
      Source/cmGeneratorTarget.h
  10. 1 0
      Source/cmTarget.cxx
  11. 1 0
      Tests/RunCMake/CMakeLists.txt
  12. 3 0
      Tests/RunCMake/DependencyGraph/CMakeLists.txt
  13. 40 0
      Tests/RunCMake/DependencyGraph/OptimizeCommon.cmake
  14. 5 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-both-build-check.cmake
  15. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-both-build-stderr.txt
  16. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-both.cmake
  17. 6 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-middle-build-check.cmake
  18. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-middle-build-stderr.txt
  19. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-middle.cmake
  20. 6 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-none-build-check.cmake
  21. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-none-build-stderr.txt
  22. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-none.cmake
  23. 5 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-top-build-check.cmake
  24. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-top-build-stderr.txt
  25. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeFortran-top.cmake
  26. 25 0
      Tests/RunCMake/DependencyGraph/OptimizeFortranCommon.cmake
  27. 11 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-both-build-check.cmake
  28. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-both.cmake
  29. 11 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-middle-build-check.cmake
  30. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-middle.cmake
  31. 11 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-none-build-check.cmake
  32. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-none.cmake
  33. 11 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-top-build-check.cmake
  34. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeShared-top.cmake
  35. 8 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-both-build-check.cmake
  36. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-both.cmake
  37. 10 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-middle-build-check.cmake
  38. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-middle.cmake
  39. 10 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-none-build-check.cmake
  40. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-none.cmake
  41. 8 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-top-build-check.cmake
  42. 1 0
      Tests/RunCMake/DependencyGraph/OptimizeStatic-top.cmake
  43. 24 0
      Tests/RunCMake/DependencyGraph/Property.cmake
  44. 60 0
      Tests/RunCMake/DependencyGraph/RunCMakeTest.cmake
  45. 16 0
      Tests/RunCMake/DependencyGraph/WriteTargets.cmake
  46. 6 0
      Tests/RunCMake/DependencyGraph/mylib.c
  47. 3 0
      Tests/RunCMake/DependencyGraph/mylib.f90

+ 1 - 0
Help/manual/cmake-properties.7.rst

@@ -307,6 +307,7 @@ Properties on Targets
    /prop_tgt/OBJCXX_EXTENSIONS
    /prop_tgt/OBJCXX_STANDARD
    /prop_tgt/OBJCXX_STANDARD_REQUIRED
+   /prop_tgt/OPTIMIZE_DEPENDENCIES
    /prop_tgt/OSX_ARCHITECTURES_CONFIG
    /prop_tgt/OSX_ARCHITECTURES
    /prop_tgt/OUTPUT_NAME_CONFIG

+ 1 - 0
Help/manual/cmake-variables.7.rst

@@ -437,6 +437,7 @@ Variables that Control the Build
    /variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX
    /variable/CMAKE_NO_BUILTIN_CHRPATH
    /variable/CMAKE_NO_SYSTEM_FROM_IMPORTED
+   /variable/CMAKE_OPTIMIZE_DEPENDENCIES
    /variable/CMAKE_OSX_ARCHITECTURES
    /variable/CMAKE_OSX_DEPLOYMENT_TARGET
    /variable/CMAKE_OSX_SYSROOT

+ 38 - 0
Help/prop_tgt/OPTIMIZE_DEPENDENCIES.rst

@@ -0,0 +1,38 @@
+OPTIMIZE_DEPENDENCIES
+---------------------
+
+Activates dependency optimization of static and object libraries.
+
+When this property is set to true, some dependencies for a static or object
+library may be removed at generation time if they are not necessary to build
+the library, since static and object libraries don't actually link against
+anything.
+
+If a static or object library has dependency optimization enabled, it first
+discards all dependencies. Then, it looks through all of the direct and
+indirect dependencies that it initially had, and adds them back if they meet
+any of the following criteria:
+
+* The dependency was added to the library by :command:`add_dependencies`.
+* The dependency was added to the library through a source file in the library
+  generated by a custom command that uses the dependency.
+* The dependency has any ``PRE_BUILD``, ``PRE_LINK``, or ``POST_BUILD`` custom
+  commands associated with it.
+* The dependency contains any source files that were generated by a custom
+  command.
+* The dependency contains any languages which produce side effects that are
+  relevant to the library. Currently, all languages except C, C++, Objective-C,
+  Objective-C++, assembly, and CUDA are assumed to produce side effects.
+  However, side effects from one language are assumed not to be relevant to
+  another (for example, a Fortran library is assumed to not have any side
+  effects that are relevant for a Swift library.)
+
+As an example, assume you have a static Fortran library which depends on a
+static C library, which in turn depends on a static Fortran library. The
+top-level Fortran library has optimization enabled, but the middle C library
+does not. If you build the top Fortran library, the bottom Fortran library will
+also build, but not the middle C library, since the C library does not have any
+side effects that are relevant for the Fortran library. However, if you build
+the middle C library, the bottom Fortran library will also build, even though
+it does not have any side effects that are relevant to the C library, since the
+C library does not have optimization enabled.

+ 7 - 0
Help/release/dev/optimize-link-dependencies.rst

@@ -0,0 +1,7 @@
+optimize-link-dependencies
+--------------------------
+
+* A new target property, :prop_tgt:`OPTIMIZE_DEPENDENCIES`, was added to
+  avoid unnecessarily building dependencies for a static library.
+* A new variable, :variable:`CMAKE_OPTIMIZE_DEPENDENCIES`, was added to
+  initialize the :prop_tgt:`OPTIMIZE_DEPENDENCIES` target property.

+ 4 - 0
Help/variable/CMAKE_OPTIMIZE_DEPENDENCIES.rst

@@ -0,0 +1,4 @@
+CMAKE_OPTIMIZE_DEPENDENCIES
+---------------------------
+
+Initializes the :prop_tgt:`OPTIMIZE_DEPENDENCIES` target property.

+ 156 - 7
Source/cmComputeTargetDepends.cxx

@@ -17,10 +17,12 @@
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmPolicies.h"
+#include "cmProperty.h"
 #include "cmRange.h"
 #include "cmSourceFile.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTargetDepend.h"
@@ -115,19 +117,32 @@ bool cmComputeTargetDepends::Compute()
   if (this->DebugMode) {
     this->DisplayGraph(this->InitialGraph, "initial");
   }
+  cmComputeComponentGraph ccg1(this->InitialGraph);
+  ccg1.Compute();
+  if (!this->CheckComponents(ccg1)) {
+    return false;
+  }
+
+  // Compute the intermediate graph.
+  this->CollectSideEffects();
+  this->ComputeIntermediateGraph();
+  if (this->DebugMode) {
+    this->DisplaySideEffects();
+    this->DisplayGraph(this->IntermediateGraph, "intermediate");
+  }
 
   // Identify components.
-  cmComputeComponentGraph ccg(this->InitialGraph);
-  ccg.Compute();
+  cmComputeComponentGraph ccg2(this->IntermediateGraph);
+  ccg2.Compute();
   if (this->DebugMode) {
-    this->DisplayComponents(ccg);
+    this->DisplayComponents(ccg2, "intermediate");
   }
-  if (!this->CheckComponents(ccg)) {
+  if (!this->CheckComponents(ccg2)) {
     return false;
   }
 
   // Compute the final dependency graph.
-  if (!this->ComputeFinalDepends(ccg)) {
+  if (!this->ComputeFinalDepends(ccg2)) {
     return false;
   }
   if (this->DebugMode) {
@@ -382,6 +397,111 @@ void cmComputeTargetDepends::AddTargetDepend(
   }
 }
 
+void cmComputeTargetDepends::CollectSideEffects()
+{
+  this->SideEffects.resize(0);
+  this->SideEffects.resize(this->InitialGraph.size());
+
+  int n = static_cast<int>(this->InitialGraph.size());
+  std::set<int> visited;
+  for (int i = 0; i < n; ++i) {
+    this->CollectSideEffectsForTarget(visited, i);
+  }
+}
+
+void cmComputeTargetDepends::CollectSideEffectsForTarget(
+  std::set<int>& visited, int depender_index)
+{
+  if (!visited.count(depender_index)) {
+    auto& se = this->SideEffects[depender_index];
+    visited.insert(depender_index);
+    this->Targets[depender_index]->AppendCustomCommandSideEffects(
+      se.CustomCommandSideEffects);
+    this->Targets[depender_index]->AppendLanguageSideEffects(
+      se.LanguageSideEffects);
+
+    for (auto const& edge : this->InitialGraph[depender_index]) {
+      this->CollectSideEffectsForTarget(visited, edge);
+      auto const& dse = this->SideEffects[edge];
+      se.CustomCommandSideEffects.insert(dse.CustomCommandSideEffects.cbegin(),
+                                         dse.CustomCommandSideEffects.cend());
+      for (auto const& it : dse.LanguageSideEffects) {
+        se.LanguageSideEffects[it.first].insert(it.second.cbegin(),
+                                                it.second.cend());
+      }
+    }
+  }
+}
+
+void cmComputeTargetDepends::ComputeIntermediateGraph()
+{
+  this->IntermediateGraph.resize(0);
+  this->IntermediateGraph.resize(this->InitialGraph.size());
+
+  int n = static_cast<int>(this->InitialGraph.size());
+  for (int i = 0; i < n; ++i) {
+    auto const& initialEdges = this->InitialGraph[i];
+    auto& intermediateEdges = this->IntermediateGraph[i];
+    cmGeneratorTarget const* gt = this->Targets[i];
+    if (gt->GetType() != cmStateEnums::STATIC_LIBRARY &&
+        gt->GetType() != cmStateEnums::OBJECT_LIBRARY) {
+      intermediateEdges = initialEdges;
+    } else {
+      if (cmProp optimizeDependencies =
+            gt->GetProperty("OPTIMIZE_DEPENDENCIES")) {
+        if (cmIsOn(optimizeDependencies)) {
+          this->OptimizeLinkDependencies(gt, intermediateEdges, initialEdges);
+        } else {
+          intermediateEdges = initialEdges;
+        }
+      } else {
+        intermediateEdges = initialEdges;
+      }
+    }
+  }
+}
+
+void cmComputeTargetDepends::OptimizeLinkDependencies(
+  cmGeneratorTarget const* gt, cmGraphEdgeList& outputEdges,
+  cmGraphEdgeList const& inputEdges)
+{
+  std::set<int> emitted;
+  for (auto const& edge : inputEdges) {
+    if (edge.IsStrong()) {
+      // Preserve strong edges
+      outputEdges.push_back(edge);
+    } else {
+      auto const& dse = this->SideEffects[edge];
+
+      // Add edges that have custom command side effects
+      for (cmGeneratorTarget const* dep : dse.CustomCommandSideEffects) {
+        auto index = this->TargetIndex[dep];
+        if (!emitted.count(index)) {
+          emitted.insert(index);
+          outputEdges.push_back(
+            cmGraphEdge(index, false, edge.IsCross(), edge.GetBacktrace()));
+        }
+      }
+
+      // Add edges that have language side effects for languages we
+      // care about
+      for (auto const& lang : gt->GetAllConfigCompileLanguages()) {
+        auto it = dse.LanguageSideEffects.find(lang);
+        if (it != dse.LanguageSideEffects.end()) {
+          for (cmGeneratorTarget const* dep : it->second) {
+            auto index = this->TargetIndex[dep];
+            if (!emitted.count(index)) {
+              emitted.insert(index);
+              outputEdges.push_back(cmGraphEdge(index, false, edge.IsCross(),
+                                                edge.GetBacktrace()));
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
 void cmComputeTargetDepends::DisplayGraph(Graph const& graph,
                                           const std::string& name)
 {
@@ -402,10 +522,39 @@ void cmComputeTargetDepends::DisplayGraph(Graph const& graph,
   fprintf(stderr, "\n");
 }
 
+void cmComputeTargetDepends::DisplaySideEffects()
+{
+  fprintf(stderr, "The side effects are:\n");
+  int n = static_cast<int>(SideEffects.size());
+  for (int depender_index = 0; depender_index < n; ++depender_index) {
+    cmGeneratorTarget const* depender = this->Targets[depender_index];
+    fprintf(stderr, "target %d is [%s]\n", depender_index,
+            depender->GetName().c_str());
+    if (!this->SideEffects[depender_index].CustomCommandSideEffects.empty()) {
+      fprintf(stderr, "  custom commands\n");
+      for (auto const* gt :
+           this->SideEffects[depender_index].CustomCommandSideEffects) {
+        fprintf(stderr, "    from target %d [%s]\n", this->TargetIndex[gt],
+                gt->GetName().c_str());
+      }
+    }
+    for (auto const& it :
+         this->SideEffects[depender_index].LanguageSideEffects) {
+      fprintf(stderr, "  language %s\n", it.first.c_str());
+      for (auto const* gt : it.second) {
+        fprintf(stderr, "    from target %d [%s]\n", this->TargetIndex[gt],
+                gt->GetName().c_str());
+      }
+    }
+  }
+  fprintf(stderr, "\n");
+}
+
 void cmComputeTargetDepends::DisplayComponents(
-  cmComputeComponentGraph const& ccg)
+  cmComputeComponentGraph const& ccg, const std::string& name)
 {
-  fprintf(stderr, "The strongly connected components are:\n");
+  fprintf(stderr, "The strongly connected components for the %s graph are:\n",
+          name.c_str());
   std::vector<NodeList> const& components = ccg.GetComponents();
   int n = static_cast<int>(components.size());
   for (int c = 0; c < n; ++c) {

+ 18 - 1
Source/cmComputeTargetDepends.h

@@ -42,6 +42,13 @@ public:
                               cmTargetDependSet& deps);
 
 private:
+  struct TargetSideEffects
+  {
+    std::set<cmGeneratorTarget const*> CustomCommandSideEffects;
+    std::map<std::string, std::set<cmGeneratorTarget const*>>
+      LanguageSideEffects;
+  };
+
   void CollectTargets();
   void CollectDepends();
   void CollectTargetDepends(int depender_index);
@@ -50,6 +57,12 @@ private:
   void AddTargetDepend(int depender_index, cmGeneratorTarget const* dependee,
                        cmListFileBacktrace const& dependee_backtrace,
                        bool linking, bool cross);
+  void CollectSideEffects();
+  void CollectSideEffectsForTarget(std::set<int>& visited, int depender_index);
+  void ComputeIntermediateGraph();
+  void OptimizeLinkDependencies(cmGeneratorTarget const* gt,
+                                cmGraphEdgeList& outputEdges,
+                                cmGraphEdgeList const& inputEdges);
   bool ComputeFinalDepends(cmComputeComponentGraph const& ccg);
   void AddInterfaceDepends(int depender_index, cmLinkItem const& dependee_name,
                            const std::string& config,
@@ -74,11 +87,15 @@ private:
   using EdgeList = cmGraphEdgeList;
   using Graph = cmGraphAdjacencyList;
   Graph InitialGraph;
+  Graph IntermediateGraph;
   Graph FinalGraph;
+  std::vector<TargetSideEffects> SideEffects;
   void DisplayGraph(Graph const& graph, const std::string& name);
+  void DisplaySideEffects();
 
   // Deal with connected components.
-  void DisplayComponents(cmComputeComponentGraph const& ccg);
+  void DisplayComponents(cmComputeComponentGraph const& ccg,
+                         const std::string& name);
   bool CheckComponents(cmComputeComponentGraph const& ccg);
   void ComplainAboutBadComponent(cmComputeComponentGraph const& ccg, int c,
                                  bool strong = false);

+ 31 - 0
Source/cmGeneratorTarget.cxx

@@ -1084,6 +1084,37 @@ std::vector<cmCustomCommand> const& cmGeneratorTarget::GetPostBuildCommands()
   return this->Target->GetPostBuildCommands();
 }
 
+void cmGeneratorTarget::AppendCustomCommandSideEffects(
+  std::set<cmGeneratorTarget const*>& sideEffects) const
+{
+  if (!this->GetPreBuildCommands().empty() ||
+      !this->GetPreLinkCommands().empty() ||
+      !this->GetPostBuildCommands().empty()) {
+    sideEffects.insert(this);
+  } else {
+    for (auto const& source : this->GetAllConfigSources()) {
+      if (source.Source->GetCustomCommand() != nullptr) {
+        sideEffects.insert(this);
+        break;
+      }
+    }
+  }
+}
+
+void cmGeneratorTarget::AppendLanguageSideEffects(
+  std::map<std::string, std::set<cmGeneratorTarget const*>>& sideEffects) const
+{
+  static const std::set<cm::string_view> LANGS_WITH_NO_SIDE_EFFECTS = {
+    "C"_s, "CXX"_s, "OBJC"_s, "OBJCXX"_s, "ASM"_s, "CUDA"_s,
+  };
+
+  for (auto const& lang : this->GetAllConfigCompileLanguages()) {
+    if (!LANGS_WITH_NO_SIDE_EFFECTS.count(lang)) {
+      sideEffects[lang].insert(this);
+    }
+  }
+}
+
 bool cmGeneratorTarget::IsInBuildSystem() const
 {
   if (this->IsImported()) {

+ 6 - 0
Source/cmGeneratorTarget.h

@@ -55,6 +55,12 @@ public:
   std::vector<cmCustomCommand> const& GetPreLinkCommands() const;
   std::vector<cmCustomCommand> const& GetPostBuildCommands() const;
 
+  void AppendCustomCommandSideEffects(
+    std::set<cmGeneratorTarget const*>& sideEffects) const;
+  void AppendLanguageSideEffects(
+    std::map<std::string, std::set<cmGeneratorTarget const*>>& sideEffects)
+    const;
+
 #define DECLARE_TARGET_POLICY(POLICY)                                         \
   cmPolicies::PolicyStatus GetPolicyStatus##POLICY() const                    \
   {                                                                           \

+ 1 - 0
Source/cmTarget.cxx

@@ -373,6 +373,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     initProp("VS_JUST_MY_CODE_DEBUGGING");
     initProp("DISABLE_PRECOMPILE_HEADERS");
     initProp("UNITY_BUILD");
+    initProp("OPTIMIZE_DEPENDENCIES");
     initPropValue("UNITY_BUILD_BATCH_SIZE", "8");
     initPropValue("UNITY_BUILD_MODE", "BATCH");
     initPropValue("PCH_WARN_INVALID", "ON");

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -401,6 +401,7 @@ add_RunCMake_test(no_install_prefix)
 add_RunCMake_test(configure_file)
 add_RunCMake_test(CTestTimeout -DTIMEOUT=${CTestTestTimeout_TIME})
 add_RunCMake_test(CTestTimeoutAfterMatch)
+add_RunCMake_test(DependencyGraph -DCMAKE_Fortran_COMPILER=${CMAKE_Fortran_COMPILER})
 
 # ctresalloc links against CMakeLib and CTestLib, which means it can't be built
 # if CMake_TEST_EXTERNAL_CMAKE is activated (the compiler might be different.)

+ 3 - 0
Tests/RunCMake/DependencyGraph/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.18)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 40 - 0
Tests/RunCMake/DependencyGraph/OptimizeCommon.cmake

@@ -0,0 +1,40 @@
+enable_language(C)
+
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY out)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY out)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY out)
+
+add_library(SharedTop SHARED mylib.c)
+add_library(StaticTop STATIC mylib.c)
+add_library(StaticMiddle STATIC mylib.c)
+
+add_library(StaticNone STATIC mylib.c)
+add_library(StaticPreBuild STATIC mylib.c)
+add_library(StaticPreLink STATIC mylib.c)
+add_library(StaticPostBuild STATIC mylib.c)
+add_library(StaticCc STATIC mylibcc.c)
+
+add_custom_command(TARGET StaticPreBuild PRE_BUILD
+  COMMAND ${CMAKE_COMMAND} -E true)
+add_custom_command(TARGET StaticPreLink PRE_LINK
+  COMMAND ${CMAKE_COMMAND} -E true)
+add_custom_command(TARGET StaticPostBuild POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E true)
+add_custom_command(OUTPUT mylibcc.c
+  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/mylib.c ${CMAKE_BINARY_DIR}/mylibcc.c)
+
+target_link_libraries(SharedTop PRIVATE StaticMiddle)
+target_link_libraries(StaticTop PRIVATE StaticMiddle)
+target_link_libraries(StaticMiddle PRIVATE StaticNone StaticPreBuild StaticPreLink StaticPostBuild StaticCc)
+
+if(OPTIMIZE_TOP)
+  set_target_properties(SharedTop StaticTop PROPERTIES
+    OPTIMIZE_DEPENDENCIES TRUE)
+endif()
+if(OPTIMIZE_MIDDLE)
+  set_target_properties(StaticMiddle PROPERTIES
+    OPTIMIZE_DEPENDENCIES TRUE)
+endif()
+
+include(WriteTargets.cmake)
+write_targets()

+ 5 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-both-build-check.cmake

@@ -0,0 +1,5 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${FortranTop_TARGET_FILE}
+  ${FortranBottom_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-both-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-both.cmake

@@ -0,0 +1 @@
+include(OptimizeFortranCommon.cmake)

+ 6 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-middle-build-check.cmake

@@ -0,0 +1,6 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${FortranTop_TARGET_FILE}
+  ${CMiddle_TARGET_FILE}
+  ${FortranBottom_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-middle-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-middle.cmake

@@ -0,0 +1 @@
+include(OptimizeFortranCommon.cmake)

+ 6 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-none-build-check.cmake

@@ -0,0 +1,6 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${FortranTop_TARGET_FILE}
+  ${CMiddle_TARGET_FILE}
+  ${FortranBottom_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-none-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-none.cmake

@@ -0,0 +1 @@
+include(OptimizeFortranCommon.cmake)

+ 5 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-top-build-check.cmake

@@ -0,0 +1,5 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${FortranTop_TARGET_FILE}
+  ${FortranBottom_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-top-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortran-top.cmake

@@ -0,0 +1 @@
+include(OptimizeFortranCommon.cmake)

+ 25 - 0
Tests/RunCMake/DependencyGraph/OptimizeFortranCommon.cmake

@@ -0,0 +1,25 @@
+enable_language(C)
+enable_language(Fortran)
+
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY out)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY out)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY out)
+
+add_library(FortranTop STATIC mylib.f90)
+add_library(CMiddle STATIC mylib.c)
+add_library(FortranBottom STATIC mylib.f90)
+
+target_link_libraries(FortranTop PRIVATE CMiddle)
+target_link_libraries(CMiddle PRIVATE FortranBottom)
+
+if(OPTIMIZE_TOP)
+  set_target_properties(FortranTop PROPERTIES
+    OPTIMIZE_DEPENDENCIES TRUE)
+endif()
+if(OPTIMIZE_MIDDLE)
+  set_target_properties(CMiddle PROPERTIES
+    OPTIMIZE_DEPENDENCIES TRUE)
+endif()
+
+include(WriteTargets.cmake)
+write_targets()

+ 11 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-both-build-check.cmake

@@ -0,0 +1,11 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${SharedTop_TARGET_FILE}
+  ${SharedTop_TARGET_LINKER_FILE}
+  ${StaticMiddle_TARGET_FILE}
+  ${StaticNone_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-both.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 11 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-middle-build-check.cmake

@@ -0,0 +1,11 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${SharedTop_TARGET_FILE}
+  ${SharedTop_TARGET_LINKER_FILE}
+  ${StaticMiddle_TARGET_FILE}
+  ${StaticNone_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-middle.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 11 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-none-build-check.cmake

@@ -0,0 +1,11 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${SharedTop_TARGET_FILE}
+  ${SharedTop_TARGET_LINKER_FILE}
+  ${StaticMiddle_TARGET_FILE}
+  ${StaticNone_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-none.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 11 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-top-build-check.cmake

@@ -0,0 +1,11 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${SharedTop_TARGET_FILE}
+  ${SharedTop_TARGET_LINKER_FILE}
+  ${StaticMiddle_TARGET_FILE}
+  ${StaticNone_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeShared-top.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 8 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-both-build-check.cmake

@@ -0,0 +1,8 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${StaticTop_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-both.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 10 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-middle-build-check.cmake

@@ -0,0 +1,10 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${StaticTop_TARGET_FILE}
+  ${StaticMiddle_TARGET_FILE}
+  ${StaticNone_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-middle.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 10 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-none-build-check.cmake

@@ -0,0 +1,10 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${StaticTop_TARGET_FILE}
+  ${StaticMiddle_TARGET_FILE}
+  ${StaticNone_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-none.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 8 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-top-build-check.cmake

@@ -0,0 +1,8 @@
+include(${RunCMake_TEST_BINARY_DIR}/target_files.cmake)
+check_files(${RunCMake_TEST_BINARY_DIR}/out
+  ${StaticTop_TARGET_FILE}
+  ${StaticPreBuild_TARGET_FILE}
+  ${StaticPreLink_TARGET_FILE}
+  ${StaticPostBuild_TARGET_FILE}
+  ${StaticCc_TARGET_FILE}
+  )

+ 1 - 0
Tests/RunCMake/DependencyGraph/OptimizeStatic-top.cmake

@@ -0,0 +1 @@
+include(OptimizeCommon.cmake)

+ 24 - 0
Tests/RunCMake/DependencyGraph/Property.cmake

@@ -0,0 +1,24 @@
+enable_language(C)
+
+add_library(Unset STATIC mylib.c)
+
+set(CMAKE_OPTIMIZE_DEPENDENCIES TRUE)
+add_library(SetTrue STATIC mylib.c)
+
+set(CMAKE_OPTIMIZE_DEPENDENCIES FALSE)
+add_library(SetFalse STATIC mylib.c)
+
+get_property(_set TARGET Unset PROPERTY OPTIMIZE_DEPENDENCIES SET)
+if(_set)
+  message(SEND_ERROR "OPTIMIZE_DEPENDENCIES property should not be set on Unset target")
+endif()
+
+get_property(_true TARGET SetTrue PROPERTY OPTIMIZE_DEPENDENCIES)
+if(NOT _true STREQUAL "TRUE")
+  message(SEND_ERROR "OPTIMIZE_DEPENDENCIES property should be TRUE on SetTrue target")
+endif()
+
+get_property(_false TARGET SetFalse PROPERTY OPTIMIZE_DEPENDENCIES)
+if(NOT _false STREQUAL "FALSE")
+  message(SEND_ERROR "OPTIMIZE_DEPENDENCIES property should be FALSE on SetFalse target")
+endif()

+ 60 - 0
Tests/RunCMake/DependencyGraph/RunCMakeTest.cmake

@@ -0,0 +1,60 @@
+include(RunCMake)
+
+function(check_files dir)
+  set(expected ${ARGN})
+  list(FILTER expected EXCLUDE REGEX "^$")
+  list(REMOVE_DUPLICATES expected)
+  list(SORT expected)
+
+  file(GLOB_RECURSE glob "${dir}/*")
+  set(actual)
+  foreach(i IN LISTS glob)
+    if(NOT i MATCHES "(\\.manifest$)|(\\.exp$)|(\\.tds$)")
+      list(APPEND actual ${i})
+    endif()
+  endforeach()
+  list(REMOVE_DUPLICATES actual)
+  list(SORT actual)
+
+  if(NOT "${expected}" STREQUAL "${actual}")
+    string(REPLACE ";" "\n  " expected_formatted "${expected}")
+    string(REPLACE ";" "\n  " actual_formatted "${actual}")
+    string(APPEND RunCMake_TEST_FAILED "Actual files did not match expected\nExpected:\n  ${expected_formatted}\nActual:\n  ${actual_formatted}\n")
+  endif()
+
+  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+function(run_cmake_build name)
+  set(RunCMake_TEST_NO_CLEAN TRUE)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
+  file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR})
+  if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)
+  endif()
+  run_cmake(${name})
+  set(RunCMake_TEST_OPTIONS)
+  run_cmake_command(${name}-build ${CMAKE_COMMAND}
+    --build ${RunCMake_TEST_BINARY_DIR}
+    --config Release
+    --target ${ARGN})
+endfunction()
+
+function(run_optimize_test name)
+  set(RunCMake_TEST_OPTIONS)
+  run_cmake_build(${name}-none ${ARGN})
+  set(RunCMake_TEST_OPTIONS -DOPTIMIZE_TOP=TRUE)
+  run_cmake_build(${name}-top ${ARGN})
+  set(RunCMake_TEST_OPTIONS -DOPTIMIZE_MIDDLE=TRUE)
+  run_cmake_build(${name}-middle ${ARGN})
+  set(RunCMake_TEST_OPTIONS -DOPTIMIZE_TOP=TRUE -DOPTIMIZE_MIDDLE=TRUE)
+  run_cmake_build(${name}-both ${ARGN})
+endfunction()
+
+run_cmake(Property)
+
+run_optimize_test(OptimizeShared SharedTop)
+run_optimize_test(OptimizeStatic StaticTop)
+if(CMAKE_Fortran_COMPILER)
+  run_optimize_test(OptimizeFortran FortranTop)
+endif()

+ 16 - 0
Tests/RunCMake/DependencyGraph/WriteTargets.cmake

@@ -0,0 +1,16 @@
+function(write_targets)
+  set(_input "")
+
+  get_property(_targets DIRECTORY . PROPERTY BUILDSYSTEM_TARGETS)
+  foreach(_t IN LISTS _targets)
+    get_property(_type TARGET "${_t}" PROPERTY TYPE)
+    if(_type STREQUAL "SHARED_LIBRARY")
+      string(APPEND _input "set(${_t}_TARGET_FILE [==[$<TARGET_FILE:${_t}>]==])\n")
+      string(APPEND _input "set(${_t}_TARGET_LINKER_FILE [==[$<TARGET_LINKER_FILE:${_t}>]==])\n")
+    elseif(_type STREQUAL "STATIC_LIBRARY")
+      string(APPEND _input "set(${_t}_TARGET_FILE [==[$<TARGET_FILE:${_t}>]==])\n")
+    endif()
+  endforeach()
+
+  file(GENERATE OUTPUT target_files.cmake CONTENT "${_input}" CONDITION $<CONFIG:Release>)
+endfunction()

+ 6 - 0
Tests/RunCMake/DependencyGraph/mylib.c

@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void mylib(void)
+{
+}

+ 3 - 0
Tests/RunCMake/DependencyGraph/mylib.f90

@@ -0,0 +1,3 @@
+function mylib_fortran()
+  mylib_fortran = 42
+end function mylib_fortran