Hi Andrew,
I don't have a full answer, but I can tell you how we overcame the majority of the hermeticity problems.
Note that we're not currently using the latest bazel version. We're currently at 0.23 so I apologize if something doesn't apply.
The basic idea is that we have a tarball that contains a Python3.7 installation set as the "files" for the py_runtime rule.
Apologies for the wall of text, but I hope it gets the idea across. Now that I think about it, might have been better as a Github gist. Oh well.
Essentially, it let's us write sandboxed Python that ignores whatever the host has installed. This includes pip also.
The downside is that importing pip packages into the bazel sandbox is kind of a manual/tedious process.
rules_python works, but as far as I can tell has trouble (i.e. doesn't work) with Python3. Last time I tried to get it to work was a year ago.
- Phil
//:.bazelrc
build --python_top=//:python3
//:WORKSPACE:
//:python.BUILD:
filegroup(
name = "python3",
srcs = glob([
"usr/lib/python3.7/**/*.py",
"usr/lib/**/*.so",
"usr/lib/**/*.so.*",
"lib/**/*.so",
"lib/**/*.so.*",
"usr/bin/xz*",
]) + [
"usr/bin/python3",
"usr/bin/python3.7",
"usr/lib/python3.7/lib2to3/Grammar.txt",
"usr/lib/python3.7/lib2to3/PatternGrammar.txt",
],
visibility = ["//visibility:public"],
)
//:BUILD
py_runtime(
name = "python3",
files = [
"//tools:python3_binary",
"@python//:python3",
],
interpreter = "//tools:python3_binary",
visibility = ["//visibility:public"],
)
//tools:BUILD
filegroup(
name = "python3_binary",
srcs = ["python3_binary.sh"],
)
//tools:python3_binary.sh
#!/bin/bash
set -e
set -u
set -o pipefail
export PYTHONDONTWRITEBYTECODE=1
BASE_PATH=""
for path in ${PYTHONPATH//:/ }; do
if [[ "$path" == *.runfiles/python ]]; then
BASE_PATH="$path"
export LD_LIBRARY_PATH="$path"/lib/x86_64-linux-gnu:"$path"/usr/lib:"$path"/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}
break
done
if [[ -z "$BASE_PATH" ]]; then
echo "Could not find Python base path." >&2
exit 1
fi
# There are a few utilities (e.g. xz) for which a sandboxed Python application
# shouldn't use the host's versions. Instead, the application should use the
# bundled version.
export PATH="${BASE_PATH}/usr/bin${PATH+:${PATH}}"
# Python really likes to escape the sandbox by periodically dereferencing
# symlinks when importing modules. This breaks some of the RPATH entries that
# bazel adds to its cc_binary targets. To work around this, we make sure that
# all the shared libraries are accessible all the time.
# Here we find the runfiles directory (parent folder of Python's base path) and
# then look for all the solib folders in the child folders. One of the child
# folders could be "com_peloton_tech" or "com_google_protobuf".
shopt -s nullglob
for solib_folder in "${BASE_PATH%/*}"/*/_solib_*; do
for subfolder in "$solib_folder"/*/; do
export LD_LIBRARY_PATH="$(readlink -f "$subfolder"):${LD_LIBRARY_PATH}"
done
done
shopt -u nullglob
# Prevent adding the user's site-packages to the python path. For example, with
# the -s option you should no longer see
# /home/foo/.local/lib/python3.4/site-packages being added to the path.
# Also force everyone to get tk from @python3_tk_repo//
export TCL_LIBRARY="${BASE_PATH}/../python3_tk_repo/usr/share/tcltk/tcl8.6"
export TK_LIBRARY="${BASE_PATH}/../python3_tk_repo/usr/share/tcltk/tk8.6"
exec "$BASE_PATH"/usr/bin/python3 -s "$@"