cc-wrapper: Make hygienic

See the added comments for what exactly has been done.
This commit is contained in:
John Ericson 2017-07-06 17:19:53 -04:00
parent 9f1e009975
commit 42f35503b5
3 changed files with 189 additions and 15 deletions

View file

@ -1,3 +1,77 @@
# N.B. It may be a surprise that the derivation-specific variables are exported,
# since this is just sourced by the wrapped binaries---the end consumers. This
# is because one wrapper binary may invoke another (e.g. cc invoking ld). In
# that case, it is cheaper/better to not repeat this step and let the forked
# wrapped binary just inherit the work of the forker's wrapper script.
# Accumulate prefixes for taking in the right input parameters. See setup-hook
# for details.
declare -a role_prefixes=()
if [[ -n "${NIX_CC_WRAPPER_@infixSalt@_TARGET_BUILD:-}" ]]; then
role_prefixes+=(_BUILD)
fi
if [[ -n "${NIX_CC_WRAPPER_@infixSalt@_TARGET_HOST:-}" ]]; then
role_prefixes+=('')
fi
if [[ -n "${NIX_CC_WRAPPER_@infixSalt@_TARGET_TARGET:-}" ]]; then
role_prefixes+=(_TARGET)
fi
# For each role we serve, we accumulate the input parameters into our own
# cc-wrapper-derivation-specific environment variables.
for pre in "${role_prefixes[@]}"; do
# We need to mangle names for hygiene, but also take parameters/overrides
# from the environment.
slurpUnsalted () {
case "$1" in
CC_WRAPPER_*)
local firstPre=NIX_CC_WRAPPER_
local varname="${1#CC_WRAPPER_}"
;;
LD_WRAPPER_*)
local firstPre=NIX_LD_WRAPPER_
local varname="${1#LD_WRAPPER_}"
;;
*)
local firstPre=NIX_
local varname="$1"
;;
esac
local inputVar="${firstPre}${pre}${varname}"
local outputVar="${firstPre}@infixSalt@_${varname}"
local delimiter=''
if [[ -n "${!outputVar:-}" && -n "${!inputVar:-}" ]]; then
delimiter=' '
fi
# Easiest to just do this to deal with either the input or (old) output.
set +u
export ${outputVar}+="${delimiter}${!inputVar}"
set -u
}
slurpUnsalted CC_WRAPPER_START_HOOK
slurpUnsalted CC_WRAPPER_EXEC_HOOK
slurpUnsalted LD_WRAPPER_START_HOOK
slurpUnsalted LD_WRAPPER_EXEC_HOOK
slurpUnsalted CFLAGS_COMPILE
slurpUnsalted CFLAGS_LINK
slurpUnsalted CXXSTDLIB_COMPILE
slurpUnsalted CXXSTDLIB_LINK
slurpUnsalted DONT_SET_RPATH
slurpUnsalted GNATFLAGS_COMPILE
slurpUnsalted IGNORE_LD_THROUGH_GCC
slurpUnsalted LDFLAGS
slurpUnsalted LDFLAGS_BEFORE
slurpUnsalted LDFLAGS_AFTER
slurpUnsalted LDFLAGS_HARDEN
slurpUnsalted SET_BUILD_ID
slurpUnsalted DONT_SET_RPATH
slurpUnsalted ENFORCE_NO_NATIVE
done
unset -f slurpUnsalted
# `-B@out@/bin' forces cc to use ld-wrapper.sh when calling ld.
export NIX_@infixSalt@_CFLAGS_COMPILE="-B@out@/bin/ $NIX_@infixSalt@_CFLAGS_COMPILE"
@ -34,4 +108,5 @@ if [ -e @out@/nix-support/libc-ldflags-before ]; then
NIX_@infixSalt@_LDFLAGS_BEFORE="$(< @out@/nix-support/libc-ldflags-before) $NIX_@infixSalt@_LDFLAGS_BEFORE"
fi
# That way forked processes don't againt extend these environment variables
export NIX_CC_WRAPPER_@infixSalt@_FLAGS_SET=1

View file

@ -1,21 +1,111 @@
# CC Wrapper hygiene
#
# For at least cross compilation, we need to depend on multiple cc-wrappers at
# once---specifically up to one per sort of dependency. This follows from having
# different tools targeting different platforms, and different flags for those
# tools. For example:
#
# # Flags for compiling (whether or not linking) C code for the...
# NIX_BUILD_CFLAGS_COMPILE # ...build platform
# NIX_CFLAGS_COMPILE # ...host platform
# NIX_TARGET_CFLAGS_COMPILE # ...target platform
#
# Notice that these platforms are the 3 *relative* to the package using
# cc-wrapper, not absolute like `x86_64-pc-linux-gnu`.
#
# The simplest solution would be to have separate cc-wrappers per (3 intended
# use-cases * n absolute concrete platforms). For the use-case axis, we would
# @-splice in 'BUILD_' '' 'TARGET_' to use the write environment variables when
# building the cc-wrapper, and likewise prefix the binaries' names so they didn't
# clobber each other on the PATH. But the need for 3x cc-wrappers, along with
# non-standard name prefixes, is annoying and liable to break packages' build
# systems.
#
# Instead, we opt to have just one cc-wrapper per absolute platform. Matching
# convention, the binaries' names can just be prefixed with their target
# platform. On the other hand, that means packages will depend on not just
# multiple cc-wrappers, but the exact same cc-wrapper derivation multiple ways.
# That means the exact same cc-wrapper derivation must be able to avoid
# conflicting with itself, despite the fact that `setup-hook.sh`, the `addCvars`
# function, and `add-flags.sh` are all communicating with each other with
# environment variables. Yuck.
#
# The basic strategy is:
#
# - Everyone exclusively *adds information* to relative-platform-specific
# environment variables, like `NIX_TARGET_CFLAGS_COMPILE`, to communicate
# with the wrapped binaries.
#
# - The wrapped binaries will exclusively *read* cc-wrapper-derivation-specific
# environment variables distinguished with with `infixSalt`, like
# `NIX_@infixSalt@_CFLAGS_COMPILE`.
#
# - `add-flags`, beyond its old task of reading extra flags stuck inside the
# cc-wrapper derivation, will convert the relative-platform-specific
# variables to cc-wrapper-derivation-specific variables. This conversion is
# the only time all but one of the cc-wrapper-derivation-specific variables
# are set.
#
# This ensures the flow of information is exclusive from
# relative-platform-specific variables to cc-wrapper-derivation-specific
# variables. This allows us to support the general case of a many--many relation
# between relative platforms and cc-wrapper derivations.
#
# For more details, read the individual files where the mechanisms used to
# accomplish this will be individually documented.
# It's fine that any other cc-wrapper will redefine this. Bash functions close
# over no state, and there's no @-substitutions within, so any redefined
# function is guaranteed to be exactly the same.
ccWrapper_addCVars () {
# The `depOffset` describes how the platforms of the dependencies are slid
# relative to the depending package. It is brought into scope of the
# environment hook defined as the role of the dependency being applied.
case $depOffset in
-1) local role='BUILD_' ;;
0) local role='' ;;
1) local role='TARGET_' ;;
*) echo "cc-wrapper: Error: Cannot be used with $depOffset-offset deps, " >2;
return 1 ;;
esac
if [[ -d "$1/include" ]]; then
export NIX_CFLAGS_COMPILE+=" ${ccIncludeFlag:--isystem} $1/include"
export NIX_${role}CFLAGS_COMPILE+=" ${ccIncludeFlag:--isystem} $1/include"
fi
if [[ -d "$1/lib64" && ! -L "$1/lib64" ]]; then
export NIX_LDFLAGS+=" -L$1/lib64"
export NIX_${role}LDFLAGS+=" -L$1/lib64"
fi
if [[ -d "$1/lib" ]]; then
export NIX_LDFLAGS+=" -L$1/lib"
export NIX_${role}LDFLAGS+=" -L$1/lib"
fi
if [[ -d "$1/Library/Frameworks" ]]; then
export NIX_CFLAGS_COMPILE+=" -F$1/Library/Frameworks"
export NIX_${role}CFLAGS_COMPILE+=" -F$1/Library/Frameworks"
fi
}
# Since the same cc-wrapper derivation can be depend on in multiple ways, we
# need to accumulate *each* role (i.e. target platform relative the depending
# derivation) in which the cc-wrapper derivation is used.
# `NIX_CC_WRAPPER_@infixSalt@_TARGET_*` tracks this (needs to be an exported env
# var so can't use fancier data structures).
#
# We also need to worry about what role is being added on *this* invocation of
# setup-hook, which `role` tracks.
if [ -n "${crossConfig:-}" ]; then
export NIX_CC_WRAPPER_@infixSalt@_TARGET_BUILD=1
role="BUILD_"
else
export NIX_CC_WRAPPER_@infixSalt@_TARGET_HOST=1
role=""
fi
# Eventually the exact sort of env-hook we create will depend on the role. This
# is because based on what relative platform we are targeting, we use different
# dependencies.
envHooks+=(ccWrapper_addCVars)
# Note 1: these come *after* $out in the PATH (see setup.sh).
@ -41,16 +131,12 @@ if [ -n "@coreutils_bin@" ]; then
addToSearchPath _PATH @coreutils_bin@/bin
fi
if [ -z "${crossConfig:-}" ]; then
ENV_PREFIX=""
else
ENV_PREFIX="BUILD_"
fi
# Export tool environment variables so various build systems use the right ones.
export NIX_${ENV_PREFIX}CC=@out@
export NIX_${role}CC=@out@
export ${ENV_PREFIX}CC=@named_cc@
export ${ENV_PREFIX}CXX=@named_cxx@
export ${role}CC=@named_cc@
export ${role}CXX=@named_cxx@
for CMD in \
cpp \
@ -59,9 +145,9 @@ do
if
PATH=$_PATH type -p "@binPrefix@$CMD" > /dev/null
then
export "${ENV_PREFIX}$(echo "$CMD" | tr "[:lower:]" "[:upper:]")=@binPrefix@${CMD}";
export "${role}$(echo "$CMD" | tr "[:lower:]" "[:upper:]")=@binPrefix@${CMD}";
fi
done
# No local scope available for sourced files
unset ENV_PREFIX
# No local scope in sourced file
unset role

View file

@ -336,8 +336,20 @@ fi
# Set the relevant environment variables to point to the build inputs
# found above.
#
# These `depOffset`s tell the env hook what sort of dependency
# (ignoring propagatedness) is being passed to the env hook. In a real
# language, we'd append a closure with this information to the
# relevant env hook array, but bash doesn't have closures, so it's
# easier to just pass this in.
_addToNativeEnv() {
local pkg="$1"
if [[ -n "${crossConfig:-}" ]]; then
local -i depOffset=-1
else
local -i depOffset=0
fi
# Run the package-specific hooks set by the setup-hook scripts.
runHook envHook "$pkg"
@ -349,6 +361,7 @@ done
_addToCrossEnv() {
local pkg="$1"
local -i depOffset=0
# Run the package-specific hooks set by the setup-hook scripts.
runHook crossEnvHook "$pkg"