Skip to content
Merged
87 changes: 83 additions & 4 deletions scripts/cmake/z_vcpkg_fixup_rpath_macho.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ function(z_vcpkg_calculate_corrected_macho_rpath)
set("${arg_OUT_NEW_RPATH_VAR}" "${new_rpath}" PARENT_SCOPE)
endfunction()

function(z_vcpkg_regex_escape)
cmake_parse_arguments(PARSE_ARGV 0 "arg"
""
"STRING;OUT_REGEX_ESCAPED_STRING_VAR"
"")
string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" regex_escaped "${arg_STRING}")
set("${arg_OUT_REGEX_ESCAPED_STRING_VAR}" "${regex_escaped}" PARENT_SCOPE)
endfunction()

function(z_vcpkg_fixup_macho_rpath_in_dir)
# We need to iterate through everything because we
# can't predict where a Mach-O file will be located
Expand Down Expand Up @@ -95,6 +104,8 @@ function(z_vcpkg_fixup_macho_rpath_in_dir)
continue()
endif()

list(APPEND macho_executables_and_shared_libs "${macho_file}")

get_filename_component(macho_file_dir "${macho_file}" DIRECTORY)
get_filename_component(macho_file_name "${macho_file}" NAME)

Expand All @@ -106,15 +117,40 @@ function(z_vcpkg_fixup_macho_rpath_in_dir)
if("${file_type}" STREQUAL "shared")
# Set the install name for shared libraries
execute_process(
COMMAND "${install_name_tool_cmd}" -id "@rpath/${macho_file_name}" "${macho_file}"
COMMAND "${otool_cmd}" -D "${macho_file}"
OUTPUT_VARIABLE get_id_ov
RESULT_VARIABLE get_id_rv
)
if(NOT get_id_rv EQUAL 0)
message(FATAL_ERROR "Could not obtain install name id from '${macho_file}'")
endif()
set(macho_new_id "@rpath/${macho_file_name}")
message(STATUS "Setting install name id of '${macho_file}' to '@rpath/${macho_file_name}'")
execute_process(
COMMAND "${install_name_tool_cmd}" -id "${macho_new_id}" "${macho_file}"
OUTPUT_QUIET
ERROR_VARIABLE set_id_error
RESULT_VARIABLE set_id_exit_code
)
message(STATUS "Set install name id of '${macho_file}' to '@rpath/${macho_file_name}'")
if(NOT "${set_id_error}" STREQUAL "")
if(NOT "${set_id_error}" STREQUAL "" AND NOT set_id_exit_code EQUAL 0)
message(WARNING "Couldn't adjust install name of '${macho_file}': ${set_id_error}")
continue()
endif()

# otool -D <macho_file> typically returns lines like:

# <macho_file>:
# <id>

# But also with ARM64 binaries, it can return:
# <macho_file> (architecture arm64):
# <id>

# Either way we need to remove the first line and trim the trailing newline char.
string(REGEX REPLACE "[^\n]+:\n" "" get_id_ov "${get_id_ov}")
string(REGEX REPLACE "\n.*" "" get_id_ov "${get_id_ov}")
list(APPEND adjusted_shared_lib_old_ids "${get_id_ov}")
list(APPEND adjusted_shared_lib_new_ids "${macho_new_id}")
endif()

# List all existing rpaths
Expand Down Expand Up @@ -151,14 +187,57 @@ function(z_vcpkg_fixup_macho_rpath_in_dir)
COMMAND "${install_name_tool_cmd}" ${rpath_args} "${macho_file}"
OUTPUT_QUIET
ERROR_VARIABLE set_rpath_error
RESULT_VARIABLE set_rpath_exit_code
)

if(NOT "${set_rpath_error}" STREQUAL "")
if(NOT "${set_rpath_error}" STREQUAL "" AND NOT set_rpath_exit_code EQUAL 0)
message(WARNING "Couldn't adjust RPATH of '${macho_file}': ${set_rpath_error}")
continue()
endif()

message(STATUS "Adjusted RPATH of '${macho_file}' to '${new_rpath}'")
endforeach()
endforeach()

# Check for dependent libraries in executables and shared libraries that
# need adjusting after id change
list(LENGTH adjusted_shared_lib_old_ids last_adjusted_index)
if(NOT last_adjusted_index EQUAL 0)
math(EXPR last_adjusted_index "${last_adjusted_index} - 1")
foreach(macho_file IN LISTS macho_executables_and_shared_libs)
execute_process(
COMMAND "${otool_cmd}" -L "${macho_file}"
OUTPUT_VARIABLE get_deps_ov
RESULT_VARIABLE get_deps_rv
)
if(NOT get_deps_rv EQUAL 0)
message(FATAL_ERROR "Could not obtain dependencies list from '${macho_file}'")
endif()
# change adjusted_shared_lib_old_ids[i] -> adjusted_shared_lib_new_ids[i]
foreach(i RANGE ${last_adjusted_index})
list(GET adjusted_shared_lib_old_ids ${i} adjusted_old_id)
z_vcpkg_regex_escape(
STRING "${adjusted_old_id}"
OUT_REGEX_ESCAPED_STRING_VAR regex
)
if(NOT get_deps_ov MATCHES "[ \t]${regex} ")
continue()
endif()
list(GET adjusted_shared_lib_new_ids ${i} adjusted_new_id)

# Replace the old id with the new id
execute_process(
COMMAND "${install_name_tool_cmd}" -change "${adjusted_old_id}" "${adjusted_new_id}" "${macho_file}"
OUTPUT_QUIET
ERROR_VARIABLE change_id_error
Copy link
Member

Choose a reason for hiding this comment

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

Check exit code? (Or COMMAND_ERROR_IS_FATAL ?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry I'm new to using COMMAND_ERROR_IS_FATAL - if I add COMMAND_ERROR_IS_FATAL ANY would that make the check for change_id_error potentially redundant or I should have both checks in-case install_name_tool returns a 0 exit code but still prints something out to stderr?

Copy link
Member

Choose a reason for hiding this comment

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

To be honest I haven't used COMMAND_ERROR_IS_FATAL yet either 😅. I was just looking at the docs to make sure I understood what you were doing and saw that there.

I don't know what "${install_name_tool_cmd}"'s behavior with respect to stderr and errors is; despite the name, stderr does not always mean 'all errors go here'. It originates from big iron in the 70s where you would have machine readable bits on and input and output tape, and anything you wanted to show to a human operator would be sent to a separate physical printer.

My comment is more like 'some of these commands' exit code is being checked and some of them aren't being checked, it seems like they should all be checked unless there is a specific reason not to'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

applied nitpicks.patch to f41bc39 (one of the nitpicks already applied via code suggestion in another comment).

RESULT_VARIABLE change_id_exit_code
)
if(NOT "${change_id_error}" STREQUAL "" AND NOT change_id_exit_code EQUAL 0)
message(WARNING "Couldn't adjust dependent shared library install name in '${macho_file}': ${change_id_error}")
continue()
endif()
message(STATUS "Adjusted dependent shared library install name in '${macho_file}' (From '${adjusted_old_id}' -> To '${adjusted_new_id}')")
endforeach()
endforeach()
endif()
endfunction()
46 changes: 46 additions & 0 deletions scripts/test_ports/rpath-macho-test-binaries/portfile.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
set(VCPKG_POLICY_EMPTY_INCLUDE_FOLDER enabled)
vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY)

vcpkg_cmake_configure(
SOURCE_PATH "${CURRENT_PORT_DIR}/project"
OPTIONS_RELEASE
-DTEST_STRING=release
OPTIONS_DEBUG
-DTEST_STRING=debug
)
vcpkg_cmake_install()

function(make_rpath_absolute lib_dir)
string(REPLACE "/" "_" logname "make_rpath_absolute-${lib_dir}")
vcpkg_execute_required_process(
COMMAND "install_name_tool" -id ${CURRENT_INSTALLED_DIR}/${lib_dir}/librpath-macho-backend-lib++.dylib ${CURRENT_PACKAGES_DIR}/${lib_dir}/librpath-macho-backend-lib++.dylib
WORKING_DIRECTORY "${CURRENT_PACKAGES_DIR}"
LOGNAME "${logname}-id"
)

vcpkg_execute_required_process(
COMMAND "install_name_tool" -change @rpath/librpath-macho-backend-lib++.dylib ${CURRENT_INSTALLED_DIR}/${lib_dir}/librpath-macho-backend-lib++.dylib ${CURRENT_PACKAGES_DIR}/${lib_dir}/librpath-macho-test-lib.dylib
WORKING_DIRECTORY "${CURRENT_PACKAGES_DIR}"
LOGNAME "${logname}-change"
)
endfunction()

if(NOT VCPKG_BUILD_TYPE)
vcpkg_copy_tools(TOOL_NAMES rpath-macho-test-tool
SEARCH_DIR "${CURRENT_PACKAGES_DIR}/debug/bin"
DESTINATION "${CURRENT_PACKAGES_DIR}/debug/tools/${PORT}"
)
vcpkg_copy_tools(TOOL_NAMES rpath-macho-test-tool
SEARCH_DIR "${CURRENT_PACKAGES_DIR}/debug/bin"
DESTINATION "${CURRENT_PACKAGES_DIR}/manual-tools/${PORT}/debug"
)
vcpkg_copy_tools(TOOL_NAMES rpath-macho-test-tool
SEARCH_DIR "${CURRENT_PACKAGES_DIR}/debug/bin"
DESTINATION "${CURRENT_PACKAGES_DIR}/tools/${PORT}/debug"
)
make_rpath_absolute("debug/lib")
endif()
make_rpath_absolute("lib")
vcpkg_copy_tools(TOOL_NAMES rpath-macho-test-tool DESTINATION "${CURRENT_PACKAGES_DIR}/manual-tools/${PORT}")
vcpkg_copy_tools(TOOL_NAMES rpath-macho-test-tool AUTO_CLEAN)
file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" "This test port is part of vcpkg.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.7)
project(rpath-macho-test CXX)

set(TEST_STRING "" CACHE STRING "")

set(CMAKE_SKIP_INSTALL_RPATH TRUE)

add_library(rpath-macho-backend-lib++ transitive.cpp)
target_compile_definitions(rpath-macho-backend-lib++ PRIVATE "TEST_STRING=\"${TEST_STRING}\"")

add_library(rpath-macho-test-lib lib.cpp)
target_link_libraries(rpath-macho-test-lib PRIVATE rpath-macho-backend-lib++)

add_executable(rpath-macho-test-tool main.cpp)
target_link_libraries(rpath-macho-test-tool PRIVATE rpath-macho-test-lib)

install(TARGETS rpath-macho-backend-lib++ rpath-macho-test-lib rpath-macho-test-tool)
6 changes: 6 additions & 0 deletions scripts/test_ports/rpath-macho-test-binaries/project/lib.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extern const char* getTestStringBackend();

const char* getTestString()
{
return getTestStringBackend();
}
8 changes: 8 additions & 0 deletions scripts/test_ports/rpath-macho-test-binaries/project/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <stdio.h>

extern const char* getTestString();

int main()
{
puts(getTestString());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const char* getTestStringBackend()
{
return TEST_STRING;
}
12 changes: 12 additions & 0 deletions scripts/test_ports/rpath-macho-test-binaries/vcpkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "rpath-macho-test-binaries",
"version-string": "ci",
"description": "Provides installed binaries for rpath macho fixup test",
"supports": "native & osx",
"dependencies": [
{
"name": "vcpkg-cmake",
"host": true
}
]
}
62 changes: 62 additions & 0 deletions scripts/test_ports/rpath-macho-test/portfile.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
set(VCPKG_POLICY_EMPTY_PACKAGE enabled)

foreach(dir IN ITEMS tools/rpath-macho-test-binaries manual-tools/rpath-macho-test-binaries)
string(REPLACE "/" "_" logname "execute-rel-${dir}")
vcpkg_execute_required_process(
COMMAND "${CURRENT_INSTALLED_DIR}/${dir}/rpath-macho-test-tool"
WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
LOGNAME "${logname}"
)
if(NOT output STREQUAL "release")
message(SEND_ERROR "${dir}: $Actual: '${output}', expected: 'release'")
endif()
endforeach()

if(NOT VCPKG_BUILD_TYPE)
foreach(dir IN ITEMS tools/rpath-macho-test-binaries/debug manual-tools/rpath-macho-test-binaries/debug debug/tools/rpath-macho-test-binaries)
string(REPLACE "/" "_" logname "execute-dbg-${dir}")
vcpkg_execute_required_process(
COMMAND "${CURRENT_INSTALLED_DIR}/${dir}/rpath-macho-test-tool"
WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
LOGNAME "${logname}"
)
if(NOT output STREQUAL "debug")
message(SEND_ERROR "${dir}: Actual: '${output}', expected: 'debug'")
endif()
endforeach()
endif()

function(check_proper_rpath macho_lib)
vcpkg_execute_required_process(
COMMAND "otool" "-L" "${macho_lib}"
WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
LOGNAME "${logname}"
)

set(found_rpath_backend_lib OFF)

string(REPLACE "\n" ";" output_lines "${output}")
# Ignore first line, it contains the path to the lib which we are checking
list(REMOVE_AT output_lines 0)
foreach(line IN LISTS output_lines)
if("${line}" MATCHES "\\s+/.*librpath-macho-backend-lib\\+\\+\\.dylib")
message(SEND_ERROR "${line} contains an absolute path")
endif()
if("${line}" MATCHES "@rpath/librpath-macho-backend-lib\\+\\+.dylib")
set(found_rpath_backend_lib ON)
endif()
endforeach()

if(NOT found_rpath_backend_lib)
message(SEND_ERROR "@rpath/librpath-macho-backend-lib++.dylib not found in ${output}")
endif()
endfunction()

check_proper_rpath("${CURRENT_INSTALLED_DIR}/lib/librpath-macho-test-lib.dylib")
check_proper_rpath("${CURRENT_INSTALLED_DIR}/debug/lib/librpath-macho-test-lib.dylib")
8 changes: 8 additions & 0 deletions scripts/test_ports/rpath-macho-test/vcpkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "rpath-macho-test",
"version-string": "ci",
"description": "Test rpath macho fixup",
"dependencies": [
"rpath-macho-test-binaries"
]
}