diff --git a/crosstool/osx_cc_configure.bzl b/crosstool/osx_cc_configure.bzl index 30528ca..a1a20d5 100644 --- a/crosstool/osx_cc_configure.bzl +++ b/crosstool/osx_cc_configure.bzl @@ -163,7 +163,7 @@ def configure_osx_toolchain(repository_ctx): # https://github.com/bazelbuild/bazel/blob/ab71a1002c9c53a8061336e40f91204a2a32c38e/tools/cpp/lib_cc_configure.bzl#L17-L38 # for more info xcode_locator = Label("@bazel_tools//tools/osx:xcode_locator.m") - osx_cc_wrapper = Label("@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl") + osx_cc_wrapper = Label("@build_bazel_apple_support//crosstool:osx_cc_wrapper.sh.tpl") xcrunwrapper = Label("@build_bazel_apple_support//crosstool:xcrunwrapper.sh") libtool = Label("@build_bazel_apple_support//crosstool:libtool.sh") make_hashed_objlist = Label("@build_bazel_apple_support//crosstool:make_hashed_objlist.py") diff --git a/crosstool/osx_cc_wrapper.sh.tpl b/crosstool/osx_cc_wrapper.sh.tpl new file mode 100755 index 0000000..e40a98b --- /dev/null +++ b/crosstool/osx_cc_wrapper.sh.tpl @@ -0,0 +1,138 @@ +#!/bin/bash +# +# Copyright 2015 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# OS X relpath is not really working. This is a wrapper script around gcc +# to simulate relpath behavior. +# +# This wrapper uses install_name_tool to replace all paths in the binary +# (bazel-out/.../path/to/original/library.so) by the paths relative to +# the binary. It parses the command line to behave as rpath is supposed +# to work. +# +# See https://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac +# on how to set those paths for Mach-O binaries. +# +set -eu + +LIBS= +LIB_PATHS= +LIB_DIRS= +RPATHS= +OUTPUT= + +function parse_option() { + local -r opt="$1" + if [[ "${OUTPUT}" = "1" ]]; then + OUTPUT=$opt + elif [[ "$opt" =~ ^-l(.*)$ ]]; then + LIBS="${BASH_REMATCH[1]} $LIBS" + elif [[ "$opt" =~ ^(.*)\.so$ ]]; then + LIB_PATHS="${opt} $LIB_PATHS" + elif [[ "$opt" =~ ^(.*)\.dylib$ ]]; then + LIB_PATHS="${opt} $LIB_PATHS" + elif [[ "$opt" =~ ^-L(.*)$ ]]; then + LIB_DIRS="${BASH_REMATCH[1]} $LIB_DIRS" + elif [[ "$opt" =~ ^\@loader_path/(.*)$ ]]; then + RPATHS="${BASH_REMATCH[1]} ${RPATHS}" + elif [[ "$opt" = "-o" ]]; then + # output is coming + OUTPUT=1 + fi +} + +# let parse the option list +for i in "$@"; do + if [[ "$i" = @* && -r "${i:1}" ]]; then + while IFS= read -r opt + do + parse_option "$opt" + done < "${i:1}" || exit 1 + else + parse_option "$i" + fi +done + +# Set-up the environment +%{env} + +# Call the C++ compiler +%{cc} "$@" + +# Generate an empty file if header processing succeeded. +if [[ "${OUTPUT}" == *.h.processed ]]; then + echo -n > "${OUTPUT}" +fi + +function get_library_path() { + for libdir in ${LIB_DIRS}; do + if [ -f ${libdir}/lib$1.so ]; then + echo "${libdir}/lib$1.so" + elif [ -f ${libdir}/lib$1.dylib ]; then + echo "${libdir}/lib$1.dylib" + fi + done +} + +# A convenient method to return the actual path even for non symlinks +# and multi-level symlinks. +function get_realpath() { + local previous="$1" + local next=$(readlink "${previous}") + while [ -n "${next}" ]; do + previous="${next}" + next=$(readlink "${previous}") + done + echo "${previous}" +} + +# Get the path of a lib inside a tool +function get_otool_path() { + # the lib path is the path of the original lib relative to the workspace + get_realpath $1 | sed 's|^.*/bazel-out/|bazel-out/|' +} + +function call_install_name() { + /usr/bin/xcrun install_name_tool -change $(get_otool_path "$1") \ + "@loader_path/$2/$3" "${OUTPUT}" +} + +# Do replacements in the output +for rpath in ${RPATHS}; do + for lib in ${LIBS}; do + unset libname + if [ -f "$(dirname ${OUTPUT})/${rpath}/lib${lib}.so" ]; then + libname="lib${lib}.so" + elif [ -f "$(dirname ${OUTPUT})/${rpath}/lib${lib}.dylib" ]; then + libname="lib${lib}.dylib" + fi + # ${libname-} --> return $libname if defined, or undefined otherwise. This is to make + # this set -e friendly + if [[ -n "${libname-}" ]]; then + libpath=$(get_library_path ${lib}) + if [ -n "${libpath}" ]; then + call_install_name "${libpath}" "${rpath}" "${libname}" + fi + fi + done + for libpath in ${LIB_PATHS}; do + if [ -f "$libpath" ]; then + libname=$(basename "$libpath") + if [ -f "$(dirname ${OUTPUT})/${rpath}/${libname}" ]; then + call_install_name "${libpath}" "${rpath}" "${libname}" + fi + fi + done +done diff --git a/test/rpaths/BUILD b/test/rpaths/BUILD new file mode 100644 index 0000000..faf03d1 --- /dev/null +++ b/test/rpaths/BUILD @@ -0,0 +1,34 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +cc_library( + name = "foo", + srcs = ["foo.cc"], +) + +cc_binary( + name = "libbar.so", + srcs = ["bar.cc"], + linkshared = True, +) + +cc_binary( + name = "libbaz.dylib", + srcs = ["baz.cc"], + linkshared = True, +) + +cc_test( + name = "test", + srcs = [ + "test.cc", + ":libbar.so", + ":libbaz.dylib", + ], + deps = [":foo"], +) + +sh_test( + name = "test-portable", + srcs = ["test-portable.sh"], + data = [":test"], +) diff --git a/test/rpaths/bar.cc b/test/rpaths/bar.cc new file mode 100644 index 0000000..2ca1145 --- /dev/null +++ b/test/rpaths/bar.cc @@ -0,0 +1 @@ +int bar() { return 12; } diff --git a/test/rpaths/baz.cc b/test/rpaths/baz.cc new file mode 100644 index 0000000..d9071f8 --- /dev/null +++ b/test/rpaths/baz.cc @@ -0,0 +1 @@ +int baz() { return 42; } diff --git a/test/rpaths/foo.cc b/test/rpaths/foo.cc new file mode 100644 index 0000000..a0fd902 --- /dev/null +++ b/test/rpaths/foo.cc @@ -0,0 +1 @@ +int foo() { return 2; } diff --git a/test/rpaths/test-portable.sh b/test/rpaths/test-portable.sh new file mode 100755 index 0000000..56dd94a --- /dev/null +++ b/test/rpaths/test-portable.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euo pipefail + +# Without valid rpaths the binary will only work from a specific PWD + +./test/rpaths/test +cd ./test +./rpaths/test +cd ./rpaths +./test diff --git a/test/rpaths/test.cc b/test/rpaths/test.cc new file mode 100644 index 0000000..8e7e495 --- /dev/null +++ b/test/rpaths/test.cc @@ -0,0 +1,11 @@ +int foo(); +int bar(); +int baz(); +int main() { + int result = foo() + bar() + baz(); + if (result == 56) { + return 0; + } else { + return result; + } +}