nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh
Colin Putney 234bb31f61
Fix venv creation in Python environments
The way we build python environments is subtly broken. A python
environment should be semantically identical to a vanilla Python
installation in, say, /usr/local. The current implementation, however,
differs in two important ways. The first is that it's impossible to use
python packages from the environment in python virtual environments. The
second is that the nix-generated environment appears to be a venv, but
it's not.

This commit changes the way python environments are built:

  * When generating wrappers for python executables, we inherit argv[0]
    from the wrapper. This causes python to initialize its configuration
    in the environment with all the correct paths.
  * We remove the sitecustomize.py file from the base python package.
    This file was used tweak the python configuration after it was
    incorrectly initialized. That's no longer necessary.

The end result is that python environments no longer appear to be venvs,
and behave more like a vanilla python installation. In addition it's
possible to create a venv using an environment and use packages from
both the environment and the venv.
2024-03-21 19:26:57 -06:00

226 lines
9.1 KiB
Bash

# Assert that FILE exists and is executable
#
# assertExecutable FILE
assertExecutable() {
local file="$1"
[[ -f "$file" && -x "$file" ]] || \
die "Cannot wrap '$file' because it is not an executable file"
}
# construct an executable file that wraps the actual executable
# makeWrapper EXECUTABLE OUT_PATH ARGS
# ARGS:
# --argv0 NAME : set the name of the executed process to NAME
# (if unset or empty, defaults to EXECUTABLE)
# --inherit-argv0 : the executable inherits argv0 from the wrapper.
# (use instead of --argv0 '$0')
# --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH
# --set VAR VAL : add VAR with value VAL to the executable's environment
# --set-default VAR VAL : like --set, but only adds VAR if not already set in
# the environment
# --unset VAR : remove VAR from the environment
# --chdir DIR : change working directory (use instead of --run "cd DIR")
# --run COMMAND : run command before the executable
# --add-flags ARGS : prepend ARGS to the invocation of the executable
# (that is, *before* any arguments passed on the command line)
# --append-flags ARGS : append ARGS to the invocation of the executable
# (that is, *after* any arguments passed on the command line)
# --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP
# --suffix
# --prefix-each ENV SEP VALS : like --prefix, but VALS is a list
# --suffix-each ENV SEP VALS : like --suffix, but VALS is a list
# --prefix-contents ENV SEP FILES : like --suffix-each, but contents of FILES
# are read first and used as VALS
# --suffix-contents
makeWrapper() { makeShellWrapper "$@"; }
makeShellWrapper() {
local original="$1"
local wrapper="$2"
local params varName value command separator n fileNames
local argv0 flagsBefore flagsAfter flags
assertExecutable "$original"
# Write wrapper code which adds `value` to the beginning or end of
# the list variable named by `varName`, depending on the `mode`
# specified.
#
# A value which is already part of the list will not be added
# again. If this is the case and the `suffix` mode is used, the
# list won't be touched at all. The `prefix` mode will however
# move the last matching instance of the value to the beginning
# of the list. Any remaining duplicates of the value will be left
# as-is.
addValue() {
local mode="$1" # `prefix` or `suffix` to add to the beginning or end respectively
local varName="$2" # name of list variable to add to
local separator="$3" # character used to separate elements of list
local value="$4" # one value, or multiple values separated by `separator`, to add to list
# Disable file globbing, since bash will otherwise try to find
# filenames matching the the value to be prefixed/suffixed if
# it contains characters considered wildcards, such as `?` and
# `*`. We want the value as is, except we also want to split
# it on on the separator; hence we can't quote it.
local reenableGlob=0
if [[ ! -o noglob ]]; then
reenableGlob=1
fi
set -o noglob
if [[ -n "$value" ]]; then
local old_ifs=$IFS
IFS=$separator
if [[ "$mode" == '--prefix'* ]]; then
# Keep the order of the components as written when
# prefixing; normally, they would be added in the
# reverse order.
local tmp=
for v in $value; do
tmp=$v${tmp:+$separator}$tmp
done
value="$tmp"
fi
for v in $value; do
{
echo "$varName=\${$varName:+${separator@Q}\$$varName${separator@Q}}" # add separators on both ends unless empty
if [[ "$mode" == '--prefix'* ]]; then # -- in prefix mode --
echo "$varName=\${$varName/${separator@Q}${v@Q}${separator@Q}/${separator@Q}}" # remove the first instance of the value (if any)
echo "$varName=${v@Q}\$$varName" # prepend the value
elif [[ "$mode" == '--suffix'* ]]; then # -- in suffix mode --
echo "if [[ \$$varName != *${separator@Q}${v@Q}${separator@Q}* ]]; then" # if the value isn't already in the list
echo " $varName=\$$varName${v@Q}" # append the value
echo "fi"
else
echo "unknown mode $mode!" 1>&2
exit 1
fi
echo "$varName=\${$varName#${separator@Q}}" # remove leading separator
echo "$varName=\${$varName%${separator@Q}}" # remove trailing separator
echo "export $varName"
} >> "$wrapper"
done
IFS=$old_ifs
fi
if (( reenableGlob )); then
set +o noglob
fi
}
mkdir -p "$(dirname "$wrapper")"
echo "#! @shell@ -e" > "$wrapper"
params=("$@")
for ((n = 2; n < ${#params[*]}; n += 1)); do
p="${params[$n]}"
if [[ "$p" == "--set" ]]; then
varName="${params[$((n + 1))]}"
value="${params[$((n + 2))]}"
n=$((n + 2))
echo "export $varName=${value@Q}" >> "$wrapper"
elif [[ "$p" == "--set-default" ]]; then
varName="${params[$((n + 1))]}"
value="${params[$((n + 2))]}"
n=$((n + 2))
echo "export $varName=\${$varName-${value@Q}}" >> "$wrapper"
elif [[ "$p" == "--unset" ]]; then
varName="${params[$((n + 1))]}"
n=$((n + 1))
echo "unset $varName" >> "$wrapper"
elif [[ "$p" == "--chdir" ]]; then
dir="${params[$((n + 1))]}"
n=$((n + 1))
echo "cd ${dir@Q}" >> "$wrapper"
elif [[ "$p" == "--run" ]]; then
command="${params[$((n + 1))]}"
n=$((n + 1))
echo "$command" >> "$wrapper"
elif [[ ("$p" == "--suffix") || ("$p" == "--prefix") ]]; then
varName="${params[$((n + 1))]}"
separator="${params[$((n + 2))]}"
value="${params[$((n + 3))]}"
n=$((n + 3))
addValue "$p" "$varName" "$separator" "$value"
elif [[ ("$p" == "--suffix-each") || ("$p" == "--prefix-each") ]]; then
varName="${params[$((n + 1))]}"
separator="${params[$((n + 2))]}"
values="${params[$((n + 3))]}"
n=$((n + 3))
for value in $values; do
addValue "$p" "$varName" "$separator" "$value"
done
elif [[ ("$p" == "--suffix-contents") || ("$p" == "--prefix-contents") ]]; then
varName="${params[$((n + 1))]}"
separator="${params[$((n + 2))]}"
fileNames="${params[$((n + 3))]}"
n=$((n + 3))
for fileName in $fileNames; do
contents="$(cat "$fileName")"
addValue "$p" "$varName" "$separator" "$contents"
done
elif [[ "$p" == "--add-flags" ]]; then
flags="${params[$((n + 1))]}"
n=$((n + 1))
flagsBefore="${flagsBefore-} $flags"
elif [[ "$p" == "--append-flags" ]]; then
flags="${params[$((n + 1))]}"
n=$((n + 1))
flagsAfter="${flagsAfter-} $flags"
elif [[ "$p" == "--argv0" ]]; then
argv0="${params[$((n + 1))]}"
n=$((n + 1))
elif [[ "$p" == "--inherit-argv0" ]]; then
# Whichever comes last of --argv0 and --inherit-argv0 wins
argv0='$0'
elif [[ "$p" == "--resolve-argv0" ]]; then
# this is noop in shell wrappers, since bash will always resolve $0
resolve_argv0=1
else
die "makeWrapper doesn't understand the arg $p"
fi
done
echo exec ${argv0:+-a \"$argv0\"} \""$original"\" \
"${flagsBefore-}" '"$@"' "${flagsAfter-}" >> "$wrapper"
chmod +x "$wrapper"
}
addSuffix() {
suffix="$1"
shift
for name in "$@"; do
echo "$name$suffix"
done
}
filterExisting() {
for fn in "$@"; do
if test -e "$fn"; then
echo "$fn"
fi
done
}
# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>
wrapProgram() { wrapProgramShell "$@"; }
wrapProgramShell() {
local prog="$1"
local hidden
assertExecutable "$prog"
hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped
while [ -e "$hidden" ]; do
hidden="${hidden}_"
done
mv "$prog" "$hidden"
makeShellWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"
}