[CMake] Defer setting link options until all libraries are declared.

Fixes https://github.com/google/iree/issues/653.

Note: this bumps the minimum CMake version from 3.12 to 3.13.

Closes https://github.com/google/iree/pull/655

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/iree/pull/655 from ScottTodd:cmake-alwayslink-ordering d629ad981aa726a4935c2e85181142aaf39cc165
PiperOrigin-RevId: 293035338
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1c38314..9ce1f94 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,10 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.13)
 if(POLICY CMP0077)
   cmake_policy(SET CMP0077 NEW)
 endif()
+# Allow target_link_libraries() from other directories (since 3.13):
+#   https://cmake.org/cmake/help/v3.13/policy/CMP0079.html
+if(POLICY CMP0079)
+  cmake_policy(SET CMP0079 NEW)
+endif()
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 
 #-------------------------------------------------------------------------------
@@ -192,3 +197,6 @@
 if(${IREE_BUILD_EXPERIMENTAL})
   add_subdirectory(experimental)
 endif()
+
+# Note: this must be called after all libraries have been declared.
+iree_complete_binary_link_options()
diff --git a/build_tools/cmake/iree_cc_binary.cmake b/build_tools/cmake/iree_cc_binary.cmake
index 75f8228..538222c 100644
--- a/build_tools/cmake/iree_cc_binary.cmake
+++ b/build_tools/cmake/iree_cc_binary.cmake
@@ -14,6 +14,10 @@
 

 include(CMakeParseArguments)

 

+if (NOT DEFINED _IREE_CC_BINARY_NAMES)

+  set(_IREE_CC_BINARY_NAMES "")

+endif()

+

 # iree_cc_binary()

 #

 # CMake function to imitate Bazel's cc_binary rule.

@@ -99,73 +103,17 @@
   # Replace dependencies passed by ::name with ::iree::package::name

   list(TRANSFORM _RULE_DEPS REPLACE "^::" "${_PACKAGE_NS}::")

 

-  # List all dependencies, including transitive dependencies, then split the

-  # dependency list into one for whole archive (ALWAYSLINK) and one for

-  # standard linking (which only links in symbols that are directly used).

-  _iree_transitive_dependencies("${_RULE_DEPS}" _TRANSITIVE_DEPS)

-  set(_ALWAYS_LINK_DEPS "")

-  set(_STANDARD_DEPS "")

-  foreach(_DEP ${_TRANSITIVE_DEPS})

-    # Check if _DEP is a library with the ALWAYSLINK property set.

-    set(_DEP_IS_ALWAYSLINK OFF)

-    if (TARGET ${_DEP})

-      get_target_property(_DEP_TYPE ${_DEP} TYPE)

-      if(${_DEP_TYPE} STREQUAL "INTERFACE_LIBRARY")

-        # Can't be ALWAYSLINK since it's an INTERFACE library.

-        # We also can't even query for the property, since it isn't whitelisted.

-      else()

-        get_target_property(_DEP_IS_ALWAYSLINK ${_DEP} ALWAYSLINK)

-      endif()

-    endif()

-

-    # Append to the corresponding list of deps.

-    if(_DEP_IS_ALWAYSLINK)

-      list(APPEND _ALWAYS_LINK_DEPS ${_DEP})

-

-      # For MSVC, also add a `-WHOLEARCHIVE:` version of the dep.

-      # CMake treats -WHOLEARCHIVE[:lib] as a link flag and will not actually

-      # try to link the library in, so we need the flag *and* the dependency.

-      if(MSVC)

-        get_target_property(_ALIASED_TARGET ${_DEP} ALIASED_TARGET)

-        if (_ALIASED_TARGET)

-          list(APPEND _ALWAYS_LINK_DEPS "-WHOLEARCHIVE:${_ALIASED_TARGET}")

-        else()

-          list(APPEND _ALWAYS_LINK_DEPS "-WHOLEARCHIVE:${_DEP}")

-        endif()

-      endif()

-    else()

-      list(APPEND _STANDARD_DEPS ${_DEP})

-    endif()

-  endforeach(_DEP)

-

-  # Call into target_link_libraries with the lists of deps.

-  # TODO(scotttodd): `-Wl,-force_load` version

-  if(MSVC)

-    target_link_libraries(${_NAME}

-      PUBLIC

-        ${_ALWAYS_LINK_DEPS}

-        ${_STANDARD_DEPS}

-      PRIVATE

-        ${_RULE_LINKOPTS}

-    )

-  else()

-    target_link_libraries(${_NAME}

-      PUBLIC

-        "-Wl,--whole-archive"

-        ${_ALWAYS_LINK_DEPS}

-        "-Wl,--no-whole-archive"

-        ${_STANDARD_DEPS}

-      PRIVATE

-        ${_RULE_LINKOPTS}

-    )

-  endif()

-

   # Add all IREE targets to a folder in the IDE for organization.

   set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER}/binaries)

 

   set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${IREE_CXX_STANDARD})

   set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)

 

+  # Defer computing transitive dependencies and calling target_link_libraries()

+  # until all libraries have been declared.

+  # Track target and deps, use in iree_complete_binary_link_options() later.

+  set_property(GLOBAL APPEND PROPERTY _IREE_CC_BINARY_NAMES "${_NAME}")

+  set_property(TARGET ${_NAME} PROPERTY DIRECT_DEPS ${_RULE_DEPS})

 endfunction()

 

 # Lists all transitive dependencies of DIRECT_DEPS in TRANSITIVE_DEPS.

@@ -183,7 +131,7 @@
 # Performs a depth-first search through the dependency graph, appending all

 # dependencies of TARGET to the TRANSITIVE_DEPS list.

 function(_iree_transitive_dependencies_helper TARGET TRANSITIVE_DEPS)

-  if (NOT TARGET ${TARGET})

+  if (NOT TARGET "${TARGET}")

     # Excluded from the project, or invalid name? Just ignore.

     return()

   endif()

@@ -229,3 +177,74 @@
   # Propagate the augmented list up to the parent scope.

   set(${TRANSITIVE_DEPS} "${_RESULT}" PARENT_SCOPE)

 endfunction()

+

+# Sets target_link_libraries() on all registered binaries.

+# This must be called after all libraries have been declared.

+function(iree_complete_binary_link_options)

+  get_property(_NAMES GLOBAL PROPERTY _IREE_CC_BINARY_NAMES)

+

+  foreach(_NAME ${_NAMES})

+    get_target_property(_DIRECT_DEPS ${_NAME} DIRECT_DEPS)

+

+    # List all dependencies, including transitive dependencies, then split the

+    # dependency list into one for whole archive (ALWAYSLINK) and one for

+    # standard linking (which only links in symbols that are directly used).

+    _iree_transitive_dependencies("${_DIRECT_DEPS}" _TRANSITIVE_DEPS)

+    set(_ALWAYS_LINK_DEPS "")

+    set(_STANDARD_DEPS "")

+    foreach(_DEP ${_TRANSITIVE_DEPS})

+      # Check if _DEP is a library with the ALWAYSLINK property set.

+      set(_DEP_IS_ALWAYSLINK OFF)

+      if (TARGET ${_DEP})

+        get_target_property(_DEP_TYPE ${_DEP} TYPE)

+        if(${_DEP_TYPE} STREQUAL "INTERFACE_LIBRARY")

+          # Can't be ALWAYSLINK since it's an INTERFACE library.

+          # We also can't even query for the property, since it isn't whitelisted.

+        else()

+          get_target_property(_DEP_IS_ALWAYSLINK ${_DEP} ALWAYSLINK)

+        endif()

+      endif()

+

+      # Append to the corresponding list of deps.

+      if(_DEP_IS_ALWAYSLINK)

+        list(APPEND _ALWAYS_LINK_DEPS ${_DEP})

+

+        # For MSVC, also add a `-WHOLEARCHIVE:` version of the dep.

+        # CMake treats -WHOLEARCHIVE[:lib] as a link flag and will not actually

+        # try to link the library in, so we need the flag *and* the dependency.

+        if(MSVC)

+          get_target_property(_ALIASED_TARGET ${_DEP} ALIASED_TARGET)

+          if (_ALIASED_TARGET)

+            list(APPEND _ALWAYS_LINK_DEPS "-WHOLEARCHIVE:${_ALIASED_TARGET}")

+          else()

+            list(APPEND _ALWAYS_LINK_DEPS "-WHOLEARCHIVE:${_DEP}")

+          endif()

+        endif()

+      else()

+        list(APPEND _STANDARD_DEPS ${_DEP})

+      endif()

+    endforeach(_DEP)

+

+    # Call into target_link_libraries with the lists of deps.

+    # TODO(scotttodd): `-Wl,-force_load` version

+    if(MSVC)

+      target_link_libraries(${_NAME}

+        PUBLIC

+          ${_ALWAYS_LINK_DEPS}

+          ${_STANDARD_DEPS}

+        PRIVATE

+          ${_RULE_LINKOPTS}

+      )

+    else()

+      target_link_libraries(${_NAME}

+        PUBLIC

+          "-Wl,--whole-archive"

+          ${_ALWAYS_LINK_DEPS}

+          "-Wl,--no-whole-archive"

+          ${_STANDARD_DEPS}

+        PRIVATE

+          ${_RULE_LINKOPTS}

+      )

+    endif()

+  endforeach(_NAME)

+endfunction()