Skip to content

Commit 0732989

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 bazel-contrib#543.
1 parent 283c024 commit 0732989

File tree

5 files changed

+189
-121
lines changed

5 files changed

+189
-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: 16 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,23 @@
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+
command -v bash && exec $(command -v bash) "${SCRIPT_DIR}"/cc_wrapper_inner.sh "$@"
3034

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
35+
echo >&2 'Failed to find bash at /bin/bash or in PATH.'
36+
exit 1

toolchain/cc_wrapper_inner.sh.tpl

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/bin/sh
2+
#
3+
# Copyright 2021 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+
# shellcheck disable=SC1083
18+
19+
set -euo
20+
21+
cleanup() {
22+
while read -r f; do
23+
rm -f "${f}"
24+
done <"${CLEANUP_FILES}"
25+
}
26+
27+
CLEANUP_FILES=""
28+
29+
trap cleanup EXIT
30+
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+
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 "$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+
case "${toolchain_path_prefix}" in
77+
/*) ;;
78+
*) toolchain_path_prefix="${script_dir}/../../${toolchain_path_prefix#external/}" ;;
79+
esac
80+
81+
if [ ! -f "${toolchain_path_prefix}bin/clang" ]; then
82+
echo >&2 "ERROR: could not find clang; PWD=\"${PWD}\"; PATH=\"${PATH}\"; toolchain_path_prefix=${toolchain_path_prefix}."
83+
exit 5
84+
fi
85+
86+
OUTPUT=
87+
88+
parse_option() {
89+
po_opt="$1"
90+
if [ "${OUTPUT}" = "1" ]; then
91+
OUTPUT=${po_opt}
92+
elif [ "${po_opt}" = "-o" ]; then
93+
# output is coming
94+
OUTPUT=1
95+
fi
96+
}
97+
98+
sanitize_option() {
99+
so_opt="$1"
100+
case ${so_opt} in
101+
*/cc_wrapper.sh) printf "%s" "${toolchain_path_prefix}bin/clang" ;;
102+
*)
103+
if eval "case ${so_opt} in -fsanitize-ignorelist=*|-fsanitize-blacklist=*) [ ${script_dir} == /* ] ;; esac"; then
104+
# Split flag name and value.
105+
#
106+
# shellcheck disable=SC2206
107+
part0=$(echo "${so_opt}" | cut -d '=' -f 1)
108+
part1=$(echo "${so_opt}" | cut -d '=' -f 2)
109+
printf "%s" "${part0}=${script_dir}/../../../${part1}"
110+
else
111+
printf "%s" "${so_opt}"
112+
fi
113+
;;
114+
esac
115+
}
116+
117+
COUNT=$#
118+
i=0
119+
while [ "${i}" -le "${COUNT}" ]; do
120+
temp=""
121+
eval "temp=\${${i}}"
122+
substr="${temp#?}"
123+
if eval "case ${temp} in @*) [ -r \"{substr}\" ] ;; esac"; then
124+
# Create a new, sanitized file.
125+
tmpfile=$(mktemp)
126+
# POSIX shell does not support arrays, so we write the cleanup files as an
127+
# array-separated list. We do not need to worry about spaces in filenames,
128+
# because `mktemp` cannot use them when using the default template.
129+
CLEANUP_FILES="${CLEANUP_FILES} ${tmpfile}"
130+
while IFS= read -r opt; do
131+
opt="$(
132+
set -e
133+
sanitize_option "${opt}"
134+
)"
135+
parse_option "${opt}"
136+
echo "${opt}" >>"${tmpfile}"
137+
done <"${substr}"
138+
cmd="${cmd} ${tmpfile}"
139+
else
140+
opt="$(
141+
set -e
142+
sanitize_option "${temp}"
143+
)"
144+
parse_option "${opt}"
145+
# The items within $cmd also cannot contain spaces, because of how
146+
# `sanitize_option` behaves.
147+
cmd="${cmd} ${opt}"
148+
fi
149+
i=$((i + 1))
150+
done
151+
152+
# Call the C++ compiler.
153+
eval \""${cmd}"\"
154+
155+
# Generate an empty file if header processing succeeded.
156+
if [ "${OUTPUT}" = "*.h.processed" ]; then
157+
true >"${OUTPUT}"
158+
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)