Skip to content

Commit 050afcf

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 96b08ff commit 050afcf

File tree

5 files changed

+187
-129
lines changed

5 files changed

+187
-129
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: 24 additions & 129 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,137 +14,32 @@
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-
23-
function cleanup() {
24-
if [[ ${#CLEANUP_FILES[@]} -gt 0 ]]; then
25-
rm -f "${CLEANUP_FILES[@]}"
26-
fi
27-
}
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-
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-
if [[ "${BASH_SOURCE[0]}" == "/"* ]]; then
72-
bash_source_abs="$(realpath "${BASH_SOURCE[0]}")"
73-
pwd_abs="$(realpath ".")"
74-
bash_source_rel=${bash_source_abs#"${pwd_abs}/"}
75-
else
76-
bash_source_rel="${BASH_SOURCE[0]}"
22+
# Attempt #1: /bin/bash -- present on FHS-compliant systems, but notably absent
23+
# on others, including NixOS.
24+
if /bin/bash true; then
25+
/bin/bash "${SCRIPT_DIR}"/cc_wrapper_inner.sh "$@"
26+
exit $?
7727
fi
78-
script_dir=$(dirname_shim "${bash_source_rel}")
79-
toolchain_path_prefix="%{toolchain_path_prefix}"
8028

81-
# Sometimes this path may be an absolute path in which case we dont do anything because
82-
# This is using the host toolchain to build.
83-
if [[ ${toolchain_path_prefix} != /* ]]; then
84-
# shellcheck disable=SC2312
85-
toolchain_path_prefix="$(dirname_shim "$(dirname_shim "${script_dir}")")/${toolchain_path_prefix#external/}"
29+
# Attempt #2: /usr/bin/env bash -- /usr/bin/env is required by POSIX, but some
30+
# callers to the LLVM toolchain, such as rules_rust, clear $PATH and leave
31+
# nothing for /usr/bin/env to search for.
32+
# if /usr/bin/env bash true; then
33+
# /usr/bin/env bash "${SCRIPT_DIR}"/cc_wrapper_inner.sh "$@"
34+
# exit $?
35+
# fi
36+
37+
# Attempt #3: Try `command -v`.
38+
if command -v bash; then
39+
CMD=$(command -v bash)
40+
"${CMD}" "${SCRIPT_DIR}"/cc_wrapper_inner.sh "$@"
41+
exit $?
8642
fi
8743

88-
if [[ ! -f ${toolchain_path_prefix}bin/clang ]]; then
89-
echo >&2 "ERROR: could not find clang; PWD=\"${PWD}\"; PATH=\"${PATH}\"; toolchain_path_prefix=${toolchain_path_prefix}."
90-
exit 5
91-
fi
92-
93-
OUTPUT=
94-
95-
function parse_option() {
96-
local -r opt="$1"
97-
if [[ "${OUTPUT}" = "1" ]]; then
98-
OUTPUT=${opt}
99-
elif [[ "${opt}" = "-o" ]]; then
100-
# output is coming
101-
OUTPUT=1
102-
fi
103-
}
104-
105-
function sanitize_option() {
106-
local -r opt=$1
107-
if [[ ${opt} == */cc_wrapper.sh ]]; then
108-
printf "%s" "${toolchain_path_prefix}bin/clang"
109-
elif [[ ${opt} =~ ^-fsanitize-(ignore|black)list=[^/] ]] && [[ ${script_dir} == /* ]]; then
110-
# shellcheck disable=SC2206
111-
parts=(${opt/=/ }) # Split flag name and value into array.
112-
# shellcheck disable=SC2312
113-
printf "%s" "${parts[0]}=$(dirname_shim "$(dirname_shim "$(dirname_shim "${script_dir}")")")/${parts[1]}"
114-
else
115-
printf "%s" "${opt}"
116-
fi
117-
}
118-
119-
cmd=()
120-
for ((i = 0; i <= $#; i++)); do
121-
if [[ ${!i} == @* && -r "${i:1}" ]]; then
122-
# Create a new, sanitized file.
123-
tmpfile=$(mktemp)
124-
CLEANUP_FILES+=("${tmpfile}")
125-
while IFS= read -r opt; do
126-
opt="$(
127-
set -e
128-
sanitize_option "${opt}"
129-
)"
130-
parse_option "${opt}"
131-
echo "${opt}" >>"${tmpfile}"
132-
done <"${!i:1}"
133-
cmd+=("@${tmpfile}")
134-
else
135-
opt="$(
136-
set -e
137-
sanitize_option "${!i}"
138-
)"
139-
parse_option "${opt}"
140-
cmd+=("${opt}")
141-
fi
142-
done
143-
144-
# Call the C++ compiler.
145-
"${cmd[@]}"
146-
147-
# Generate an empty file if header processing succeeded.
148-
if [[ "${OUTPUT}" == *.h.processed ]]; then
149-
echo -n >"${OUTPUT}"
150-
fi
44+
echo >&2 'Failed to find bash at /bin/bash or in PATH.'
45+
exit 1

toolchain/cc_wrapper_inner.sh.tpl

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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_innerr.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+
if [[ "${BASH_SOURCE[0]}" == "/"* ]]; then
70+
bash_source_abs="$(realpath "${BASH_SOURCE[0]}")"
71+
pwd_abs="$(realpath ".")"
72+
bash_source_rel=${bash_source_abs#"${pwd_abs}/"}
73+
else
74+
bash_source_rel="${BASH_SOURCE[0]}"
75+
fi
76+
script_dir=$(dirname_shim "${bash_source_rel}")
77+
toolchain_path_prefix="%{toolchain_path_prefix}"
78+
79+
# Sometimes this path may be an absolute path in which case we dont do anything because
80+
# This is using the host toolchain to build.
81+
if [[ ${toolchain_path_prefix} != /* ]]; then
82+
# shellcheck disable=SC2312
83+
toolchain_path_prefix="$(dirname_shim "$(dirname_shim "${script_dir}")")/${toolchain_path_prefix#external/}"
84+
fi
85+
86+
if [[ ! -f ${toolchain_path_prefix}bin/clang ]]; then
87+
echo >&2 "ERROR: could not find clang; PWD=\"${PWD}\"; PATH=\"${PATH}\"; toolchain_path_prefix=${toolchain_path_prefix}."
88+
exit 5
89+
fi
90+
91+
OUTPUT=
92+
93+
function parse_option() {
94+
local -r opt="$1"
95+
if [[ "${OUTPUT}" = "1" ]]; then
96+
OUTPUT=${opt}
97+
elif [[ "${opt}" = "-o" ]]; then
98+
# output is coming
99+
OUTPUT=1
100+
fi
101+
}
102+
103+
function sanitize_option() {
104+
local -r opt=$1
105+
if [[ ${opt} == */cc_wrapper.sh ]]; then
106+
printf "%s" "${toolchain_path_prefix}bin/clang"
107+
elif [[ ${opt} =~ ^-fsanitize-(ignore|black)list=[^/] ]] && [[ ${script_dir} == /* ]]; then
108+
# shellcheck disable=SC2206
109+
parts=(${opt/=/ }) # Split flag name and value into array.
110+
# shellcheck disable=SC2312
111+
printf "%s" "${parts[0]}=$(dirname_shim "$(dirname_shim "$(dirname_shim "${script_dir}")")")/${parts[1]}"
112+
else
113+
printf "%s" "${opt}"
114+
fi
115+
}
116+
117+
cmd=()
118+
for ((i = 0; i <= $#; i++)); do
119+
if [[ ${!i} == @* && -r "${i:1}" ]]; then
120+
# Create a new, sanitized file.
121+
tmpfile=$(mktemp)
122+
CLEANUP_FILES+=("${tmpfile}")
123+
while IFS= read -r opt; do
124+
opt="$(
125+
set -e
126+
sanitize_option "${opt}"
127+
)"
128+
parse_option "${opt}"
129+
echo "${opt}" >>"${tmpfile}"
130+
done <"${!i:1}"
131+
cmd+=("@${tmpfile}")
132+
else
133+
opt="$(
134+
set -e
135+
sanitize_option "${!i}"
136+
)"
137+
parse_option "${opt}"
138+
cmd+=("${opt}")
139+
fi
140+
done
141+
142+
# Call the C++ compiler.
143+
"${cmd[@]}"
144+
145+
# Generate an empty file if header processing succeeded.
146+
if [[ "${OUTPUT}" == *.h.processed ]]; then
147+
echo -n >"${OUTPUT}"
148+
fi

toolchain/internal/configure.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,17 @@ def llvm_config_impl(rctx):
249249
},
250250
)
251251

252+
# Inner CC wrapper script (redirect used for shell compatibility on Linux
253+
# platforms).
254+
if os != "darwin":
255+
rctx.template(
256+
"bin/cc_wrapper_inner.sh",
257+
rctx.attr._cc_wrapper_inner_sh_tpl,
258+
{
259+
"%{toolchain_path_prefix}": llvm_dist_path_prefix,
260+
},
261+
)
262+
252263
if hasattr(rctx, "repo_metadata"):
253264
return rctx.repo_metadata(reproducible = True)
254265
else:

toolchain/internal/repo.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,9 @@ llvm_config_attrs.update({
414414
"_cc_wrapper_sh_tpl": attr.label(
415415
default = "//toolchain:cc_wrapper.sh.tpl",
416416
),
417+
"_cc_wrapper_inner_sh_tpl": attr.label(
418+
default = "//toolchain:cc_wrapper_inner.sh.tpl",
419+
),
417420
})
418421

419422
def llvm_repo_impl(rctx):

0 commit comments

Comments
 (0)