blob: 484c55e3872956a4dcf68cc6dfa8eae44aa929d8 [file] [log] [blame]
Brian Silverman7d89e282021-11-17 17:36:54 -08001#!/bin/bash
2#
3# Copyright 2015 The Bazel Authors. All rights reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17# OS X relpath is not really working. This is a wrapper script around gcc
18# to simulate relpath behavior.
19#
20# This wrapper uses install_name_tool to replace all paths in the binary
21# (bazel-out/.../path/to/original/library.so) by the paths relative to
22# the binary. It parses the command line to behave as rpath is supposed
23# to work.
24#
25# See https://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac
26# on how to set those paths for Mach-O binaries.
27#
28set -eu
29
30INSTALL_NAME_TOOL="/usr/bin/install_name_tool"
31
32LIBS=
33LIB_DIRS=
34RPATHS=
35OUTPUT=
36
37function parse_option() {
38 local -r opt="$1"
39 if [[ "${OUTPUT}" = "1" ]]; then
40 OUTPUT=$opt
41 elif [[ "$opt" =~ ^-l(.*)$ ]]; then
42 LIBS="${BASH_REMATCH[1]} $LIBS"
43 elif [[ "$opt" =~ ^-L(.*)$ ]]; then
44 LIB_DIRS="${BASH_REMATCH[1]} $LIB_DIRS"
45 elif [[ "$opt" =~ ^-Wl,-rpath,\@loader_path/(.*)$ ]]; then
46 RPATHS="${BASH_REMATCH[1]} ${RPATHS}"
47 elif [[ "$opt" = "-o" ]]; then
48 # output is coming
49 OUTPUT=1
50 fi
51}
52
53# let parse the option list
54for i in "$@"; do
55 if [[ "$i" = @* ]]; then
56 while IFS= read -r opt
57 do
58 parse_option "$opt"
59 done < "${i:1}" || exit 1
60 else
61 parse_option "$i"
62 fi
63done
64
65# On macOS, we use ld as the linker for single-platform builds (i.e., when not
66# cross-compiling). Some applications may remove /usr/bin from PATH before
67# calling this script, which would make /usr/bin/ld unreachable. For example,
68# rules_rust does not set PATH (unless the user explicitly sets PATH in env
69# through attributes) [1] when calling rustc, and rustc does not replace an
70# unset PATH with a reasonable default either ([2], [3]), which results in CC
71# being called with PATH={sysroot}/{rust_lib}/bin. Note that rules_cc [4] and
72# rules_go [5] do ensure that /usr/bin is in PATH.
73# [1]: https://github.com/bazelbuild/rules_rust/blob/e589105b4e8181dd1d0d8ccaa0cf3267efb06e86/cargo/cargo_build_script.bzl#L66-L68
74# [2]: https://github.com/rust-lang/rust/blob/1c03f0d0ba4fee54b7aa458f4d3ad989d8bf7b34/compiler/rustc_session/src/session.rs#L804-L813
75# [3]: https://github.com/rust-lang/rust/blob/1c03f0d0ba4fee54b7aa458f4d3ad989d8bf7b34/compiler/rustc_codegen_ssa/src/back/link.rs#L640-L645
76# [4]: https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java;l=529;drc=72caead7b428fd50164079956ec368fc54a9567c
77# [5]: https://github.com/bazelbuild/rules_go/blob/63dfd99403076331fef0775d52a8039d502d4115/go/private/context.bzl#L434
78# Let's restore /usr/bin to PATH in such cases. Note that /usr/bin is not a
79# writeable directory on macOS even with sudo privileges, so it should be safe
80# to add it to PATH even when the application wants to use a very strict PATH.
81if [[ ":${PATH}:" != *":/usr/bin:"* ]]; then
82 PATH="${PATH}:/usr/bin"
83fi
84
85# Call the C++ compiler.
86if [[ -f %{toolchain_path_prefix}bin/clang ]]; then
87 %{toolchain_path_prefix}bin/clang "$@"
88elif [[ "${BASH_SOURCE[0]}" == "/"* ]]; then
89 # Some consumers of `CcToolchainConfigInfo` (e.g. `cmake` from rules_foreign_cc)
90 # change CWD and call $CC (this script) with its absolute path.
91 # the execroot (i.e. `cmake` from `rules_foreign_cc`) and call CC . For cases like this,
92 # we'll try to find `clang` relative to this script.
93 # This script is at _execroot_/external/_repo_name_/bin/cc_wrapper.sh
94 execroot_path="${BASH_SOURCE[0]%/*/*/*/*}"
95 clang="${execroot_path}/%{toolchain_path_prefix}bin/clang"
96 "${clang}" "${@}"
97else
98 >&2 echo "ERROR: could not find clang; PWD=\"$(pwd)\"; PATH=\"${PATH}\"."
99 exit 5
100fi
101
102function get_library_path() {
103 for libdir in ${LIB_DIRS}; do
104 if [ -f ${libdir}/lib$1.so ]; then
105 echo "${libdir}/lib$1.so"
106 elif [ -f ${libdir}/lib$1.dylib ]; then
107 echo "${libdir}/lib$1.dylib"
108 fi
109 done
110}
111
112# A convenient method to return the actual path even for non symlinks
113# and multi-level symlinks.
114function get_realpath() {
115 local previous="$1"
116 local next=$(readlink "${previous}")
117 while [ -n "${next}" ]; do
118 previous="${next}"
119 next=$(readlink "${previous}")
120 done
121 echo "${previous}"
122}
123
124# Get the path of a lib inside a tool
125function get_otool_path() {
126 # the lib path is the path of the original lib relative to the workspace
127 get_realpath $1 | sed 's|^.*/bazel-out/|bazel-out/|'
128}
129
130# Do replacements in the output
131for rpath in ${RPATHS}; do
132 for lib in ${LIBS}; do
133 unset libname
134 if [ -f "$(dirname ${OUTPUT})/${rpath}/lib${lib}.so" ]; then
135 libname="lib${lib}.so"
136 elif [ -f "$(dirname ${OUTPUT})/${rpath}/lib${lib}.dylib" ]; then
137 libname="lib${lib}.dylib"
138 fi
139 # ${libname-} --> return $libname if defined, or undefined otherwise. This is to make
140 # this set -e friendly
141 if [[ -n "${libname-}" ]]; then
142 libpath=$(get_library_path ${lib})
143 if [ -n "${libpath}" ]; then
144 ${INSTALL_NAME_TOOL} -change $(get_otool_path "${libpath}") \
145 "@loader_path/${rpath}/${libname}" "${OUTPUT}"
146 fi
147 fi
148 done
149done