8000 Rewrite bundle_static to be much more efficient. by alexreinking · Pull Request #8386 · halide/Halide · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Rewrite bundle_static to be much more efficient. #8386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ include(CheckCXXSymbolExists)

# Make our custom helpers available throughout the project via include().
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
include(BundleStatic)
include(HalideFeatures)
include(HalideGeneratorHelpers)
include(HalidePackageConfigHelpers)
Expand Down Expand Up @@ -213,6 +214,8 @@ Halide_feature(Halide_BUNDLE_STATIC "Bundle Halide's static dependencies" OFF AD
Halide_feature(Halide_ENABLE_EXCEPTIONS "Enable exceptions in Halide" ON)
Halide_feature(Halide_ENABLE_RTTI "Enable RTTI in Halide" ON
DEPENDS LLVM_ENABLE_RTTI)
Halide_feature(Halide_BUNDLE_STATIC "Bundle Halide's static dependencies" OFF ADVANCED
DEPENDS NOT BUILD_SHARED_LIBS)

Halide_feature(WITH_AUTOSCHEDULERS "Build the Halide autoschedulers" ON
DEPENDS BUILD_SHARED_LIBS)
Expand Down
2 changes: 1 addition & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"binaryDir": "static-Release",
"cacheVariables": {
"BUILD_SHARED_LIBS": "NO",
"Halide_BUNDLE_LLVM": "YES"
"Halide_BUNDLE_STATIC": "YES"
}
},
{
Expand Down
16 changes: 8 additions & 8 deletions README_cmake.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,14 +388,14 @@ Halide reads and understands several options that can configure the build. The
following are the most consequential and control how Halide is actually
compiled.

| Option | Default | Description |
|------------------------------------------|-----------------------|------------------------------------------------------------------------------------------------------------------|
| [`BUILD_SHARED_LIBS`][build_shared_libs] | `ON` | Standard CMake variable that chooses whether to build as a static or shared library. |
| `Halide_BUNDLE_LLVM` | `OFF` | When building Halide as a static library, unpack the LLVM static libraries and add those objects to libHalide.a. |
| `Halide_LLVM_SHARED_LIBS` | `OFF` | Link to the shared version of LLVM. Not available on Windows. |
| `Halide_ENABLE_RTTI` | _inherited from LLVM_ | Enable RTTI when building Halide. Recommended to be set to `ON` |
| `Halide_ENABLE_EXCEPTIONS` | `ON` | Enable exceptions when building Halide |
| `Halide_TARGET` | _empty_ | The default target triple to use for `add_halide_library` (and the generator tests, by extension) |
| Option | Default | Description |
|------------------------------------------|-----------------------|---------------------------------------------------------------------------------------------------|
| [`BUILD_SHARED_LIBS`][build_shared_libs] | `ON` | Standard CMake variable that chooses whether to build as a static or shared library. |
| `Halide_BUNDLE_STATIC` | `OFF` | When building Halide as a static library, merge static library dependencies into libHalide.a. |
| `Halide_LLVM_SHARED_LIBS` | `OFF` | Link to the shared version of LLVM. Not available on Windows. |
| `Halide_ENABLE_RTTI` | _inherited from LLVM_ | Enable RTTI when building Halide. Recommended to be set to `ON` |
| `Halide_ENABLE_EXCEPTIONS` | `ON` | Enable exceptions when building Halide |
| `Halide_TARGET` | _empty_ | The default target triple to use for `add_halide_library` (and the generator tests, by extension) |

The following options are _advanced_ and should not be required in typical workflows. Generally, these are used by
Halide's own CI infrastructure, or as escape hatches for third-party packagers.
Expand Down
302 changes: 112 additions & 190 deletions cmake/BundleStatic.cmake
Original file line number Diff line number Diff line change
@@ -1,201 +1,123 @@
cmake_minimum_required(VERSION 3.28)

##
# This module provides a utility for bundling a set of IMPORTED
# STATIC libraries together as a merged INTERFACE library that,
# due to CMake Issue #15415, requires manual propagation to its
# linkees, unfortunately.
#
# This is useful when a STATIC library produced by your project
# depends privately on some 3rd-party STATIC libraries that are
# tricky to distribute or for end-users to build. CMake handles
# this by assuming that imported libraries will be easy to find
# in an end-user's environment so a simple find_dependency call
# in the package config will suffice. Unfortunately, things are
# not so simple. Some libraries (eg. LLVM) can be built in many
# different configurations, and dependents can be built against
# one fixed configuration. If we have LLVM -> X -> Y where X is
# my library and Y is some other user's library, then Y must be
# very careful to build LLVM in _exactly_ the same way as X was
# configured to use. While this might be acceptable in a super-
# build, it fails when we want to release binary packages of X.
##

# All of the IMPORTED_ and INTERFACE_ properties should be accounted for below.
# https://cmake.org/cmake/help/v3.22/manual/cmake-properties.7.html#properties-on-targets

# Irrelevant properties:
# IMPORTED_IMPLIB(_<CONFIG>) # shared-only
# IMPORTED_LIBNAME(_<CONFIG>) # interface-only
# IMPORTED_LINK_DEPENDENT_LIBRARIES(_<CONFIG>) # shared-only
# IMPORTED_LINK_INTERFACE_LIBRARIES(_<CONFIG>) # deprecated
# IMPORTED_LINK_INTERFACE_MULTIPLICITY(_<CONFIG>) # static-only. irrelevant when all objects listed.
# IMPORTED_NO_SONAME(_<CONFIG>) # shared-only
# IMPORTED_SONAME(_<CONFIG>) # shared-only

function(bundle_static TARGET)
set(options)
set(oneValueArgs)
set(multiValueArgs LIBRARIES)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

set(interfaceLib ${TARGET})
set(objectLib ${interfaceLib}.obj)

add_library(${objectLib} OBJECT IMPORTED)
set_target_properties(${objectLib} PROPERTIES IMPORTED_GLOBAL TRUE)

target_sources(${interfaceLib} INTERFACE $<BUILD_INTERFACE:$<TARGET_OBJECTS:${objectLib}>>)

set(queue ${ARG_LIBRARIES})
while (queue)
list(POP_FRONT queue lib)
if (VISITED_${lib})
continue()
endif ()
set(VISITED_${lib} TRUE)

if (NOT TARGET ${lib})
target_link_libraries(${interfaceLib} INTERFACE ${lib})
continue()
endif ()

get_property(isImported TARGET ${lib} PROPERTY IMPORTED)
get_property(type TARGET ${lib} PROPERTY TYPE)

if (NOT isImported OR NOT "${type}" STREQUAL "STATIC_LIBRARY")
target_link_libraries(${interfaceLib} INTERFACE ${lib})
continue()
endif ()

transfer_same(PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE
FROM ${lib} TO ${interfaceLib})

transfer_append(PROPERTIES
INTERFACE_AUTOUIC_OPTIONS
INTERFACE_COMPILE_DEFINITIONS
INTERFACE_COMPILE_FEATURES
INTERFACE_COMPILE_OPTIONS
INTERFACE_INCLUDE_DIRECTORIES
INTERFACE_LINK_DEPENDS
INTERFACE_LINK_DIRECTORIES
INTERFACE_LINK_OPTIONS
INTERFACE_PRECOMPILE_HEADERS
INTERFACE_SOURCES
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
FROM ${lib} TO ${interfaceLib})

transfer_same(PROPERTIES IMPORTED_COMMON_LANGUAGE_RUNTIME
FROM ${lib} TO ${objectLib})

transfer_locations(FROM ${lib} TO ${objectLib})

get_property(deps TARGET ${lib} PROPERTY INTERFACE_LINK_LIBRARIES)
list(APPEND queue ${deps})
endwhile ()
# Merge all the static library dependencies of TARGET into the library as a
# POST_BUILD step.

function(_bundle_static_replace VAR BEFORE AFTER)
string(REPLACE "$<" "$\\\\<" AFTER "${AFTER}")
string(REPLACE ">" "$<ANGLE-R>" AFTER "${AFTER}")
string(REPLACE "," "$<COMMA>" AFTER "${AFTER}")
string(REPLACE ";" "$<SEMICOLON>" AFTER "${AFTER}")
set("${VAR}" "$<LIST:TRANSFORM,${${VAR}},REPLACE,${BEFORE},${AFTER}>")
set("${VAR}" "$<LIST:TRANSFORM,${${VAR}},REPLACE,\\\\<,<>")
set("${VAR}" "$<GENEX_EVAL:${${VAR}}>" PARENT_SCOPE)
endfunction()

function(transfer_same)
set(options)
set(oneValueArgs FROM TO PROPERTIES)
set(multiValueArgs)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

foreach (p IN LISTS ARG_PROPERTIES)
get_property(fromSet TARGET ${ARG_FROM} PROPERTY ${p} SET)
if (NOT fromSet)
continue()
endif ()
get_property(fromVal TARGET ${ARG_FROM} PROPERTY ${p})

get_property(toSet TARGET ${ARG_TO} PROPERTY ${p} SET)
if (NOT toSet)
set_property(TARGET ${ARG_TO} PROPERTY ${p} ${fromVal})
endif ()

get_property(toVal TARGET ${ARG_TO} PROPERTY ${p})
if (NOT "${fromVal}" STREQUAL "${toVal}")
message(WARNING "Property ${p} does not agree between ${ARG_FROM} [${fromVal}] and ${ARG_TO} [${toVal}]")
endif ()
endforeach ()
function(_bundle_static_check_output VAR)
execute_process(COMMAND ${ARGN} OUTPUT_VARIABLE "${VAR}" RESULT_VARIABLE "_${VAR}" ERROR_QUIET)
if (_${VAR})
set("${VAR}" "")
endif ()
set("${VAR}" "${${VAR}}" PARENT_SCOPE)
endfunction()

function(transfer_append)
set(options)
set(oneValueArgs FROM TO PROPERTIES)
set(multiValueArgs)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

foreach (p IN LISTS ARG_PROPERTIES)
get_property(fromSet TARGET ${ARG_FROM} PROPERTY ${p} SET)
if (fromSet)
get_property(fromVal TARGET ${ARG_FROM} PROPERTY ${p})
set_property(TARGET ${ARG_TO} APPEND PROPERTY ${p} ${fromVal})
endif ()
endforeach ()
function(_bundle_static_is_apple_libtool result item)
_bundle_static_check_output(version_info "${item}" -V)
if (version_info MATCHES "Apple, Inc.")
set(result 1 PARENT_SCOPE)
else ()
set(result 0 PARENT_SCOPE)
endif ()
endfunction()

function(transfer_locations)
set(options)
set(oneValueArgs FROM TO)
set(multiValueArgs)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

get_property(configs TARGET ${ARG_FROM} PROPERTY IMPORTED_CONFIGURATIONS)
foreach (cfg IN LISTS configs ITEMS "")
if (cfg)
string(TOUPPER "_${cfg}" cfg)
endif ()

get_property(lib TARGET ${ARG_FROM} PROPERTY "IMPORTED_LOCATION${cfg}")
if (lib)
cmake_path(GET lib STEM stage)
set(stage "${CMAKE_CURRENT_BINARY_DIR}/${stage}.obj")

if (NOT EXISTS "${stage}")
file(MAKE_DIRECTORY "${stage}")
if (MSVC)
execute_process(COMMAND "${CMAKE_AR}" /NOLOGO /LIST "${lib}"
WORKING_DIRECTORY "${stage}"
OUTPUT_VARIABLE objsInLib)

# Process the output to a list of internal objects
string(STRIP "${objsInLib}" objsInLib)
string(REGEX REPLACE "(\r|\n)+" ";" objsInLib "${objsInLib}")
list(TRANSFORM objsInLib STRIP)

foreach (obj IN LISTS objsInLib)
execute_process(COMMAND "${CMAKE_AR}" /NOLOGO "/EXTRACT:${obj}" "${lib}"
WORKING_DIRECTORY "${stage}")
endforeach ()
else ()
execute_process(COMMAND "${CMAKE_AR}" -x "${lib}"
WORKING_DIRECTORY "${stage}"
RESULT_VARIABLE error)
endif ()
endif ()

get_property(languages TARGET ${ARG_FROM} PROPERTY "IMPORTED_LINK_INTERFACE_LANGUAGES${cfg}")
if (NOT languages)
get_property(languages TARGET ${ARG_FROM} PROPERTY "IMPORTED_LINK_INTERFACE_LANGUAGES")
endif ()

message(VERBOSE "Transferring ${languages}[${cfg}] objects from ${lib} to ${ARG_TO}")

set(globs "")
foreach (lang IN LISTS languages)
if (DEFINED "CMAKE_${lang}_OUTPUT_EXTENSION")
list(APPEND globs "${stage}/*${CMAKE_${lang}_OUTPUT_EXTENSION}")
endif ()
endforeach ()

file(GLOB_RECURSE objects ${globs})

foreach (obj IN LISTS objects)
message(VERBOSE "... ${obj}")
endforeach ()

set_property(TARGET ${ARG_TO} APPEND PROPERTY "IMPORTED_OBJECTS${cfg}" ${objects})
endif ()
function(bundle_static TARGET)
get_property(type TARGET "${TARGET}" PROPERTY TYPE)
if (NOT type STREQUAL "STATIC_LIBRARY")
return()
endif ()

# The following code is quite subtle. First, it "recursively" (up to a depth
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent comment.

# limit) expands all the INTERFACE_LINK_LIBRARIES of the TARGET. Once the
# full set of library dependencies has been determined, it filters just
# the static libraries and replaces them with their on-disk locations.

# Start with the $<LINK_ONLY:$<BUILD_LOCAL_INTERFACE:...>> dependencies of
# the target. These are the privately-linked static and interface libraries
# that the user intends to delete upon export.
set(cmd "$<TARGET_PROPERTY:${TARGET},INTERFACE_LINK_LIBRARIES>")
set(cmd "$<FILTER:${cmd},INCLUDE,LINK_ONLY:..BUILD_LOCAL_INTERFACE>")

# Repeatedly expand and flatten: T ~> T, T.INTERFACE_LINK_LIBRARIES
foreach (i RANGE 5)
_bundle_static_replace(
cmd "(.+)" "\\1;$<$<TARGET_EXISTS:\\1>:$<TARGET_PROPERTY:\\1,INTERFACE_LINK_LIBRARIES>>"
)
set(cmd "$<LIST:REMOVE_DUPLICATES,$<GENEX_EVAL:${cmd}>>")
endforeach ()

# Rewrite T ~> T^T.TYPE -- we use ^ as a delimiter
_bundle_static_replace(cmd "(.+)" "\\1^$<TARGET_PROPERTY:\\1,TYPE>")
set(cmd "$<GENEX_EVAL:${cmd}>")

# Select exactly the set of static libraries
set(cmd "$<FILTER:${cmd},INCLUDE,\\^STATIC_LIBRARY$>")

# Rewrite T^... ~> $<TARGET_FILE:T>
_bundle_static_replace(cmd "^([^^]+)\\^.+$" "$<TARGET_FILE:\\1>")

# Rename the target to target.tmp
add_custom_command(
TARGET "${TARGET}" POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E rename "$<TARGET_FILE:${TARGET}>" "$<TARGET_FILE:${TARGET}>.tmp"
VERBATIM
)

# Finally merge everything together using the platform tool.
find_program(LIB lib.exe HINTS "${CMAKE_AR}")
if (WIN32 AND LIB)
add_custom_command(
TARGET "${TARGET}" POST_BUILD
COMMAND "${LIB}" "/out:$<TARGET_FILE:${TARGET}>" "$<TARGET_FILE:${TARGET}>.tmp" "${cmd}"
COMMAND_EXPAND_LISTS
VERBATIM
)
return()
endif ()

find_program(LIBTOOL libtool VALIDATOR _bundle_static_is_apple_libtool)
if (APPLE AND LIBTOOL)
add_custom_command(
TARGET "${TARGET}" POST_BUILD
COMMAND "${LIBTOOL}" -static -o "$<TARGET_FILE:${TARGET}>" "$<TARGET_FILE:${TARGET}>.tmp" "${cmd}"
COMMAND_EXPAND_LISTS
VERBATIM
)
return()
endif ()

_bundle_static_check_output(version_info "${CMAKE_AR}" V)
if (version_info MATCHES "GNU")
string(CONFIGURE [[
create $<TARGET_FILE:@TARGET@>
addlib $<TARGET_FILE:@TARGET@>.tmp
$<LIST:JOIN,$<LIST:TRANSFORM,@cmd@,PREPEND,addlib >,
>
save
end
]] mri_script)
string(REGEX REPLACE "(^|\n) +" "\\1" mri_script "${mri_script}")

file(GENERATE OUTPUT "fuse-${TARGET}.mri"
CONTENT "${mri_script}" TARGET "${TARGET}")

add_custom_command(
TARGET "${TARGET}" POST_BUILD
COMMAND "${CMAKE_AR}" -M < "${CMAKE_CURRENT_BINARY_DIR}/fuse-${TARGET}.mri"
VERBATIM
)
return()
endif ()

message(FATAL_ERROR "bundle_static_libs not implemented for the present toolchain")
endfunction()
Loading
0