Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/macos-build+deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,23 @@ jobs:
name: Build Boost
shell: bash
run: ./macos/${{ matrix.version }}/build-boost.bash
-
name: Normalise install names
shell: bash
run: ./macos/${{ matrix.version }}/fix-install-names.bash
-
name: Create tarball
shell: bash
run: |
cd ${DESTDIR}
tar zcf ${OUTPUTDIR}/macos-${{ matrix.version }}.tar.gz *
-
name: Verify bundle is self-describing
shell: bash
run: |
mkdir -p /tmp/VERIFY
tar xzf ${OUTPUTDIR}/macos-${{ matrix.version }}.tar.gz -C /tmp/VERIFY
./macos/${{ matrix.version }}/verify-bundle.bash /tmp/VERIFY
-
name: Push tarball to ghcr.io
shell: bash
Expand Down
54 changes: 54 additions & 0 deletions macos/arm64/fix-install-names.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
set -eo pipefail
set -x

LIBDIR="${DESTDIR}${PREFIX}/lib"

# b2 install (Boost) does not implement DESTDIR the way autotools/CMake do: it leaves
# libboost_*.dylib with a bare LC_ID_DYLIB (or, for libboost_filesystem and the like,
# an @rpath one) and bare/@rpath intra-bundle LC_LOAD_DYLIB references, with no
# LC_RPATH anywhere to resolve them. GSL/yaml-cpp/FFTW3 got their own install name
# right, but not necessarily every reference to a sibling library, so the same loop
# below is applied to every dylib in the bundle rather than special-cased to Boost.
for dylib in "${LIBDIR}"/*.dylib ; do
# The various libfoo.dylib -> libfoo.N.dylib symlinks carry no load commands of
# their own and need no rewriting.
[ -L "${dylib}" ] && continue

name=$(basename "${dylib}")

# The install name is what every future consumer (including the eos build itself)
# embeds as its own dependency reference, so it must always be the final absolute
# path -- regardless of whether it is currently bare, @rpath-based, or (were it to
# happen) DESTDIR-prefixed.
install_name_tool -id "${PREFIX}/lib/${name}" "${dylib}"

# Rewrite every dependency on another dylib shipped in this same bundle -- bare,
# @rpath/-based, or DESTDIR-prefixed -- to its final absolute path. System
# libraries (/usr/lib/..., /System/...) are left untouched.
otool -L "${dylib}" | tail -n +2 | awk '{print $1}' | while read -r dep ; do
case "${dep}" in
/usr/lib/*|/System/*)
continue
;;
esac
depname=$(basename "${dep}")
if [ -e "${LIBDIR}/${depname}" ] && [ "${dep}" != "${PREFIX}/lib/${depname}" ] ; then
install_name_tool -change "${dep}" "${PREFIX}/lib/${depname}" "${dylib}"
fi
done

# install_name_tool invalidates the code signature, and arm64 macOS refuses to
# load an unsigned (or invalidated) Mach-O. Do not rely on install_name_tool's
# occasional automatic ad-hoc re-signing -- do it explicitly.
codesign --force --sign - "${dylib}"

# Fail here, immediately, rather than waiting for the end-to-end verification
# step to notice a rewrite that didn't stick. `otool -L` always echoes the path
# it was given as its first line, and that path is itself inside $DESTDIR at
# this point in the build -- skip it with `tail -n +2`, or this check would
# fail on every single file regardless of what its load commands actually say.
if otool -L "${dylib}" | tail -n +2 | grep -q "${DESTDIR}" ; then
echo "error: ${dylib} still references DESTDIR (${DESTDIR}) after normalisation" >&2
exit 1
fi
done
87 changes: 87 additions & 0 deletions macos/arm64/verify-bundle.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
set -eo pipefail
set -x

# End-to-end gate: run against the extracted contents of the tarball that is about to
# be pushed (not the raw $DESTDIR staging tree), so a packaging mistake is caught too,
# not just a build-script one. Usage: verify-bundle.bash <extracted-tarball-root>
CHECKDIR="$1"
LIBDIR="${CHECKDIR}${PREFIX}/lib"
BINDIR="${CHECKDIR}${PREFIX}/bin"

status=0

fail() {
echo "error: $1" >&2
status=1
}

# Every real (non-symlink) dylib must have an absolute ${PREFIX}/lib/<name> install
# name, every non-system dependency must likewise be an absolute ${PREFIX}/lib/...
# path (no bare names, no @rpath/@loader_path/@executable_path), no LC_RPATH may be
# present at all (the bundle is meant to be self-describing via absolute paths, not
# via rpath resolution), no load command may mention the DESTDIR staging path, and the
# ad-hoc signature install_name_tool's rewriting invalidated must have been restored.
for dylib in "${LIBDIR}"/*.dylib ; do
[ -L "${dylib}" ] && continue
name=$(basename "${dylib}")

id=$(otool -D "${dylib}" | tail -n +2)
if [ "${id}" != "${PREFIX}/lib/${name}" ] ; then
fail "${dylib}: install name is '${id}', expected '${PREFIX}/lib/${name}'"
fi

# `otool -L`'s first entry after the header is always the file's own LC_ID_DYLIB
# (already checked above, and reported precisely there) -- skip a further line
# with `tail -n +3` so a broken id isn't also reported a second time here as a
# generic "dependency" failure.
while read -r dep ; do
case "${dep}" in
/usr/lib/*|/System/*|"${PREFIX}"/lib/*)
continue
;;
esac
fail "${dylib}: dependency '${dep}' is not an absolute ${PREFIX}/lib path"
done < <(otool -L "${dylib}" | tail -n +3 | awk '{print $1}')

if otool -l "${dylib}" | grep -q "cmd LC_RPATH" ; then
fail "${dylib}: carries an LC_RPATH -- the bundle must resolve via absolute paths only"
fi

# `otool -D`/`-L` echo the path they were given as their first line, which is
# itself under $CHECKDIR here -- skip it with `tail -n +2` so this only inspects
# the actual load-command values, not the argument echoed back.
if otool -D "${dylib}" | tail -n +2 | grep -q "${DESTDIR}" || otool -L "${dylib}" | tail -n +2 | grep -q "${DESTDIR}" ; then
fail "${dylib}: references the DESTDIR staging path (${DESTDIR})"
fi

if ! codesign --verify --strict "${dylib}" ; then
fail "${dylib}: does not carry a valid code signature"
fi
done

# The bundle also ships a handful of Mach-O executables (gsl-histogram, gsl-randist,
# fftw-wisdom); they carry no LC_ID_DYLIB but the same dependency/DESTDIR/signature
# invariants apply, and a future dependency bump could break them just as easily.
for exe in "${BINDIR}"/* ; do
[ -L "${exe}" ] && continue
file "${exe}" | grep -q "Mach-O" || continue

while read -r dep ; do
case "${dep}" in
/usr/lib/*|/System/*|"${PREFIX}"/lib/*)
continue
;;
esac
fail "${exe}: dependency '${dep}' is not an absolute ${PREFIX}/lib path"
done < <(otool -L "${exe}" | tail -n +2 | awk '{print $1}')

if otool -L "${exe}" | tail -n +2 | grep -q "${DESTDIR}" ; then
fail "${exe}: references the DESTDIR staging path (${DESTDIR})"
fi

if ! codesign --verify --strict "${exe}" ; then
fail "${exe}: does not carry a valid code signature"
fi
done

exit "${status}"
Loading