Skip to content

Commit 2ff4157

Browse files
author
Ryan Torok
committed
[wrapper] Make cc_wrapper.sh POSIX-compliant and use /bin/sh
Some Linux distributions, such as NixOS, do not provide /bin/bash. This commit aims to make the LLVM toolchain compatible with these platforms by only using POSIX-compatible features in the shell script and replacing the shebang with #!/bin/sh . This commit does not update the corresponding MacOS script, as all MacOS builds should have /bin/bash. Supercedes #543.
1 parent 283c024 commit 2ff4157

File tree

5 files changed

+171
-121
lines changed

5 files changed

+171
-121
lines changed

toolchain/BUILD.toolchain.tpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ filegroup(
3535
name = "internal-use-wrapped-tools",
3636
srcs = [
3737
"%{wrapper_bin_prefix}cc_wrapper.sh",
38+
"%{wrapper_bin_prefix}cc_wrapper_inner.sh",
3839
],
3940
visibility = ["//visibility:private"],
4041
)

toolchain/cc_wrapper.sh.tpl

Lines changed: 17 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/bin/sh
22
#
33
# Copyright 2021 The Bazel Authors. All rights reserved.
44
#
@@ -14,128 +14,24 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
# shellcheck disable=SC1083
17+
SCRIPT_DIR=$(dirname "$0")
1818

19-
set -euo pipefail
19+
# Search for `bash` on the system, and then execute cc_wrapper_inner.sh with
20+
# it.
2021

21-
CLEANUP_FILES=()
22+
# Attempt #1: /bin/bash -- present on FHS-compliant systems, but notably absent
23+
# on others, including NixOS.
24+
test -e /bin/bash && exec /bin/bash "${SCRIPT_DIR}"/cc_wrapper_inner.sh "$@"
2225

23-
function cleanup() {
24-
if [[ ${#CLEANUP_FILES[@]} -gt 0 ]]; then
25-
rm -f "${CLEANUP_FILES[@]}"
26-
fi
27-
}
26+
# Attempt #2: /usr/bin/env bash -- /usr/bin/env is required by POSIX, but some
27+
# callers to the LLVM toolchain, such as rules_rust, clear $PATH and leave
28+
# nothing for /usr/bin/env to search for.
29+
test -e /usr/bin/env && test /usr/bin/env bash true &&
30+
exec /usr/bin/env bash "${SCRIPT_DIR}"/cc_wrapper_inner.sh "$@"
2831

29-
trap cleanup EXIT
32+
# Attempt #3: Try `command -v`.
33+
CMD=$(command -v bash)
34+
$? && exec "${CMD}" "${SCRIPT_DIR}"/cc_wrapper_inner.sh "$@"
3035

31-
# See note in toolchain/internal/configure.bzl where we define
32-
# `wrapper_bin_prefix` for why this wrapper is needed.
33-
34-
# this script is located at either
35-
# - <execroot>/external/<repo_name>/bin/cc_wrapper.sh
36-
# - <runfiles>/<repo_name>/bin/cc_wrapper.sh
37-
# The clang is located at
38-
# - <execroot>/external/<repo_name2>/bin/clang
39-
# - <runfiles>/<repo_name2>/bin/clang
40-
#
41-
# In both cases, getting to clang can be done via
42-
# Finding the current dir of this script,
43-
# - <execroot>/external/<repo_name>/bin/
44-
# - <runfiles>/<repo_name>/bin/
45-
# going back 2 directories
46-
# - <execroot>/external
47-
# - <runfiles>
48-
#
49-
# Going into %{toolchain_path_prefix} without the `external/` prefix + `bin/clang`
50-
#
51-
52-
dirname_shim() {
53-
local path="$1"
54-
55-
# Remove trailing slashes
56-
path="${path%/}"
57-
58-
# If there's no slash, return "."
59-
if [[ "${path}" != */* ]]; then
60-
echo "."
61-
return
62-
fi
63-
64-
# Remove the last component after the final slash
65-
path="${path%/*}"
66-
67-
# If it becomes empty, it means root "/"
68-
echo "${path:-/}"
69-
}
70-
71-
script_dir=$(dirname_shim "${BASH_SOURCE[0]}")
72-
toolchain_path_prefix="%{toolchain_path_prefix}"
73-
74-
# Sometimes this path may be an absolute path in which case we dont do anything because
75-
# This is using the host toolchain to build.
76-
if [[ ${toolchain_path_prefix} != /* ]]; then
77-
toolchain_path_prefix="${script_dir}/../../${toolchain_path_prefix#external/}"
78-
fi
79-
80-
if [[ ! -f ${toolchain_path_prefix}bin/clang ]]; then
81-
echo >&2 "ERROR: could not find clang; PWD=\"${PWD}\"; PATH=\"${PATH}\"; toolchain_path_prefix=${toolchain_path_prefix}."
82-
exit 5
83-
fi
84-
85-
OUTPUT=
86-
87-
function parse_option() {
88-
local -r opt="$1"
89-
if [[ "${OUTPUT}" = "1" ]]; then
90-
OUTPUT=${opt}
91-
elif [[ "${opt}" = "-o" ]]; then
92-
# output is coming
93-
OUTPUT=1
94-
fi
95-
}
96-
97-
function sanitize_option() {
98-
local -r opt=$1
99-
if [[ ${opt} == */cc_wrapper.sh ]]; then
100-
printf "%s" "${toolchain_path_prefix}bin/clang"
101-
elif [[ ${opt} =~ ^-fsanitize-(ignore|black)list=[^/] ]] && [[ ${script_dir} == /* ]]; then
102-
# shellcheck disable=SC2206
103-
parts=(${opt/=/ }) # Split flag name and value into array.
104-
printf "%s" "${parts[0]}=${script_dir}/../../../${parts[1]}"
105-
else
106-
printf "%s" "${opt}"
107-
fi
108-
}
109-
110-
cmd=()
111-
for ((i = 0; i <= $#; i++)); do
112-
if [[ ${!i} == @* && -r "${i:1}" ]]; then
113-
# Create a new, sanitized file.
114-
tmpfile=$(mktemp)
115-
CLEANUP_FILES+=("${tmpfile}")
116-
while IFS= read -r opt; do
117-
opt="$(
118-
set -e
119-
sanitize_option "${opt}"
120-
)"
121-
parse_option "${opt}"
122-
echo "${opt}" >>"${tmpfile}"
123-
done <"${!i:1}"
124-
cmd+=("@${tmpfile}")
125-
else
126-
opt="$(
127-
set -e
128-
sanitize_option "${!i}"
129-
)"
130-
parse_option "${opt}"
131-
cmd+=("${opt}")
132-
fi
133-
done
134-
135-
# Call the C++ compiler.
136-
"${cmd[@]}"
137-
138-
# Generate an empty file if header processing succeeded.
139-
if [[ "${OUTPUT}" == *.h.processed ]]; then
140-
echo -n >"${OUTPUT}"
141-
fi
36+
echo >&2 'Failed to find bash at /bin/bash or in PATH.'
37+
exit 1

toolchain/cc_wrapper_inner.sh.tpl

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Copyright 2021 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# shellcheck disable=SC1083
16+
17+
set -euo pipefail
18+
19+
CLEANUP_FILES=()
20+
21+
function cleanup() {
22+
if [[ ${#CLEANUP_FILES[@]} -gt 0 ]]; then
23+
rm -f "${CLEANUP_FILES[@]}"
24+
fi
25+
}
26+
27+
trap cleanup EXIT
28+
29+
# See note in toolchain/internal/configure.bzl where we define
30+
# `wrapper_bin_prefix` for why this wrapper is needed.
31+
32+
# this script is located at either
33+
# - <execroot>/external/<repo_name>/bin/cc_wrapper_inner.sh
34+
# - <runfiles>/<repo_name>/bin/cc_wrapper_inner.sh
35+
# The clang is located at
36+
# - <execroot>/external/<repo_name2>/bin/clang
37+
# - <runfiles>/<repo_name2>/bin/clang
38+
#
39+
# In both cases, getting to clang can be done via
40+
# Finding the current dir of this script,
41+
# - <execroot>/external/<repo_name>/bin/
42+
# - <runfiles>/<repo_name>/bin/
43+
# going back 2 directories
44+
# - <execroot>/external
45+
# - <runfiles>
46+
#
47+
# Going into %{toolchain_path_prefix} without the `external/` prefix + `bin/clang`
48+
#
49+
50+
dirname_shim() {
51+
local path="$1"
52+
53+
# Remove trailing slashes
54+
path="${path%/}"
55+
56+
# If there's no slash, return "."
57+
if [[ "${path}" != */* ]]; then
58+
echo "."
59+
return
60+
fi
61+
62+
# Remove the last component after the final slash
63+
path="${path%/*}"
64+
65+
# If it becomes empty, it means root "/"
66+
echo "${path:-/}"
67+
}
68+
69+
script_dir=$(dirname_shim "${BASH_SOURCE[0]}")
70+
toolchain_path_prefix="%{toolchain_path_prefix}"
71+
72+
# Sometimes this path may be an absolute path in which case we dont do anything because
73+
# This is using the host toolchain to build.
74+
if [[ ${toolchain_path_prefix} != /* ]]; then
75+
toolchain_path_prefix="${script_dir}/../../${toolchain_path_prefix#external/}"
76+
fi
77+
78+
if [[ ! -f ${toolchain_path_prefix}bin/clang ]]; then
79+
echo >&2 "ERROR: could not find clang; PWD=\"${PWD}\"; PATH=\"${PATH}\"; toolchain_path_prefix=${toolchain_path_prefix}."
80+
exit 5
81+
fi
82+
83+
OUTPUT=
84+
85+
function parse_option() {
86+
local -r opt="$1"
87+
if [[ "${OUTPUT}" = "1" ]]; then
88+
OUTPUT=${opt}
89+
elif [[ "${opt}" = "-o" ]]; then
90+
# output is coming
91+
OUTPUT=1
92+
fi
93+
}
94+
95+
function sanitize_option() {
96+
local -r opt=$1
97+
if [[ ${opt} == */cc_wrapper.sh ]]; then
98+
printf "%s" "${toolchain_path_prefix}bin/clang"
99+
elif [[ ${opt} =~ ^-fsanitize-(ignore|black)list=[^/] ]] && [[ ${script_dir} == /* ]]; then
100+
# shellcheck disable=SC2206
101+
parts=(${opt/=/ }) # Split flag name and value into array.
102+
printf "%s" "${parts[0]}=${script_dir}/../../../${parts[1]}"
103+
else
104+
printf "%s" "${opt}"
105+
fi
106+
}
107+
108+
cmd=()
109+
for ((i = 0; i <= $#; i++)); do
110+
if [[ ${!i} == @* && -r "${i:1}" ]]; then
111+
# Create a new, sanitized file.
112+
tmpfile=$(mktemp)
113+
CLEANUP_FILES+=("${tmpfile}")
114+
while IFS= read -r opt; do
115+
opt="$(
116+
set -e
117+
sanitize_option "${opt}"
118+
)"
119+
parse_option "${opt}"
120+
echo "${opt}" >>"${tmpfile}"
121+
done <"${!i:1}"
122+
cmd+=("@${tmpfile}")
123+
else
124+
opt="$(
125+
set -e
126+
sanitize_option "${!i}"
127+
)"
128+
parse_option "${opt}"
129+
cmd+=("${opt}")
130+
fi
131+
done
132+
133+
# Call the C++ compiler.
134+
"${cmd[@]}"
135+
136+
# Generate an empty file if header processing succeeded.
137+
if [[ "${OUTPUT}" == *.h.processed ]]; then
138+
echo -n >"${OUTPUT}"
139+
fi

toolchain/internal/configure.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,17 @@ def llvm_config_impl(rctx):
228228
},
229229
)
230230

231+
# Inner CC wrapper script (redirect used for shell compatibility on Linux
232+
# platforms).
233+
if os != "darwin":
234+
rctx.template(
235+
"bin/cc_wrapper_inner.sh",
236+
rctx.attr._cc_wrapper_inner_sh_tpl,
237+
{
238+
"%{toolchain_path_prefix}": llvm_dist_path_prefix,
239+
},
240+
)
241+
231242
if hasattr(rctx, "repo_metadata"):
232243
return rctx.repo_metadata(reproducible = True)
233244
else:

toolchain/internal/repo.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,9 @@ llvm_config_attrs.update({
322322
"_cc_wrapper_sh_tpl": attr.label(
323323
default = "//toolchain:cc_wrapper.sh.tpl",
324324
),
325+
"_cc_wrapper_inner_sh_tpl": attr.label(
326+
default = "//toolchain:cc_wrapper_inner.sh.tpl",
327+
),
325328
})
326329

327330
def llvm_repo_impl(rctx):

0 commit comments

Comments
 (0)