diff --git a/script/build-vendor-libraries.sh b/script/build-vendor-libraries.sh index 613941356..ec91060e9 100755 --- a/script/build-vendor-libraries.sh +++ b/script/build-vendor-libraries.sh @@ -26,6 +26,7 @@ RPATH="@rpath" OS=$(uname) ARCH=$(uname -m) +SCRIPTDIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) BOOST_VERSION_URL_PATH="1.76.0" BOOST_VERSION="1_76_0" OPENSSL_VERSION="1.1.1m" @@ -438,154 +439,44 @@ function build_libopenmpt() { # macOS dylib rpaths # -function stage_opus_ogg_vorbis() { +function stage_prebuilt_libraries() { if [[ $OS == "Darwin" ]]; then - # instead of building opus, ogg and vorbis from source we snag them - # from brew, update their dylib ids with @rpath, re-sign them, then create - # new pkg-config files to point towards this directory. that way ffmpeg - # will pick them up automatically. - - mkdir -p bin/lib/ - cd bin/lib/ - export PKG_CONFIG_PATH=$(pwd) - BREW=$(brew --prefix) - - # create pkg-config files to point towards this dir - cp $BREW/opt/opus/lib/pkgconfig/opus.pc . - cp $BREW/opt/libogg/lib/pkgconfig/ogg.pc . - cp $BREW/opt/libvorbis/lib/pkgconfig/vorbis.pc . - cp $BREW/opt/libvorbis/lib/pkgconfig/vorbisenc.pc . - chmod 644 *.pc - perl -i.bak -0pe "s|libdir.*\n|libdir=$(pwd)\n|" opus.pc - perl -i.bak -0pe "s|libdir.*\n|libdir=$(pwd)\n|" ogg.pc - perl -i.bak -0pe "s|libdir.*\n|libdir=$(pwd)\n|" vorbis.pc - perl -i.bak -0pe "s|libdir.*\n|libdir=$(pwd)\n|" vorbisenc.pc - rm *.bak - - # copy libs, update their ids, then resign LIBOPUS="$BREW/opt/opus/lib/libopus.0.dylib" LIBOGG="$BREW/opt/libogg/lib/libogg.0.dylib" LIBVORBIS="$BREW/opt/libvorbis/lib/libvorbis.0.dylib" LIBVORBISENC="$BREW/opt/libvorbis/lib/libvorbisenc.2.dylib" - - cp ${LIBOPUS} ${LIBOGG} ${LIBVORBIS} ${LIBVORBISENC} . - chmod 755 *.dylib - - install_name_tool -id "$RPATH/libopus.0.dylib" ./libopus.0.dylib - codesign --remove-signature ./libopus.0.dylib - codesign --sign=- ./libopus.0.dylib - ln -s libopus.0.dylib libopus.dylib - - install_name_tool -id "$RPATH/libogg.0.dylib" ./libogg.0.dylib - codesign --remove-signature ./libogg.0.dylib - codesign --sign=- ./libogg.0.dylib - ln -s libogg.0.dylib libogg.dylib - - install_name_tool -id "$RPATH/libvorbis.0.dylib" ./libvorbis.0.dylib - install_name_tool -change "${LIBOGG}" "$RPATH/libogg.0.dylib" ./libvorbis.0.dylib - ln -s libvorbis.0.dylib libvorbis.dylib - - install_name_tool -id "$RPATH/libvorbisenc.2.dylib" ./libvorbisenc.2.dylib - install_name_tool -change "${LIBOGG}" "$RPATH/libogg.0.dylib" ./libvorbisenc.2.dylib - install_name_tool -change "${LIBVORBIS}" "$RPATH/libvorbis.0.dylib" ./libvorbisenc.2.dylib - # odd man out... not sure why this is this way... - LIBVORBIS_CELLAR="$BREW/Cellar/libvorbis/1.3.7/lib/libvorbis.0.dylib" - install_name_tool -change "${LIBVORBIS_CELLAR}" "$RPATH/libvorbis.0.dylib" ./libvorbisenc.2.dylib - #end weird hack - ln -s libvorbisenc.2.dylib libvorbisenc.dylib - - codesign --remove-signature ./libvorbis.0.dylib - codesign --remove-signature ./libvorbisenc.2.dylib - codesign --sign=- ./libvorbis.0.dylib - codesign --sign=- ./libvorbisenc.2.dylib - - cd ../.. + mkdir -p bin/lib/ + cp ${LIBOPUS} ${LIBOGG} ${LIBVORBIS} ${LIBVORBISENC} bin/lib/ + chmod 755 bin/lib/*.dylib fi } -function patch_dylib_rpaths() { - if [[ $OS == "Darwin" ]]; then - cd bin/lib - - install_name_tool -id "$RPATH/libavutil-musikcube.57.dylib" libavutil-musikcube.57.dylib - rm libavutil-musikcube.dylib - ln -s libavutil-musikcube.57.dylib libavutil-musikcube.dylib - - # ffmpeg - install_name_tool -id "$RPATH/libavformat-musikcube.59.dylib" libavformat-musikcube.59.dylib - install_name_tool -change "$LIBDIR/libswresample-musikcube.4.dylib" "$RPATH/libswresample-musikcube.4.dylib" libavformat-musikcube.59.dylib - install_name_tool -change "$LIBDIR/libavcodec-musikcube.59.dylib" "$RPATH/libavcodec-musikcube.59.dylib" libavformat-musikcube.59.dylib - install_name_tool -change "$LIBDIR/libavutil-musikcube.57.dylib" "$RPATH/libavutil-musikcube.57.dylib" libavformat-musikcube.59.dylib - install_name_tool -change "${LIBOPUS}" "$RPATH/libopus.0.dylib" libavformat-musikcube.59.dylib - install_name_tool -change "${LIBOGG}" "$RPATH/libogg.0.dylib" libavformat-musikcube.59.dylib - install_name_tool -change "${LIBVORBIS}" "$RPATH/libvorbis.0.dylib" libavformat-musikcube.59.dylib - install_name_tool -change "${LIBVORBISENC}" "$RPATH/libvorbisenc.2.dylib" libavformat-musikcube.59.dylib - rm libavformat-musikcube.dylib - ln -s libavformat-musikcube.59.dylib libavformat-musikcube.dylib - - install_name_tool -id "$RPATH/libavcodec-musikcube.59.dylib" libavcodec-musikcube.59.dylib - install_name_tool -change "$LIBDIR/libswresample-musikcube.4.dylib" "$RPATH/libswresample-musikcube.4.dylib" libavcodec-musikcube.59.dylib - install_name_tool -change "$LIBDIR/libavcodec-musikcube.59.dylib" "$RPATH/libavcodec-musikcube.59.dylib" libavcodec-musikcube.59.dylib - install_name_tool -change "$LIBDIR/libavutil-musikcube.57.dylib" "$RPATH/libavutil-musikcube.57.dylib" libavcodec-musikcube.59.dylib - install_name_tool -change "${LIBOPUS}" "$RPATH/libopus.0.dylib" libavcodec-musikcube.59.dylib - install_name_tool -change "${LIBOGG}" "$RPATH/libogg.0.dylib" libavcodec-musikcube.59.dylib - install_name_tool -change "${LIBVORBIS}" "$RPATH/libvorbis.0.dylib" libavcodec-musikcube.59.dylib - install_name_tool -change "${LIBVORBISENC}" "$RPATH/libvorbisenc.2.dylib" libavcodec-musikcube.59.dylib - rm libavcodec-musikcube.dylib - ln -s libavcodec-musikcube.59.dylib libavcodec-musikcube.dylib - - install_name_tool -id "$RPATH/libswresample-musikcube.4.dylib" libswresample-musikcube.4.dylib - install_name_tool -change "$LIBDIR/libavutil-musikcube.57.dylib" "$RPATH/libavutil-musikcube.57.dylib" libswresample-musikcube.4.dylib - rm libswresample-musikcube.dylib - ln -s libswresample-musikcube.4.dylib libswresample-musikcube.dylib - - # openssl - install_name_tool -id "$RPATH/libcrypto.1.1.dylib" libcrypto.1.1.dylib - rm libcrypto.dylib - ln -s libcrypto.1.1.dylib libcrypto.dylib - - install_name_tool -id "$RPATH/libssl.1.1.dylib" libssl.1.1.dylib - install_name_tool -change "${LIBDIR}/libcrypto.1.1.dylib" "$RPATH/libcrypto.1.1.dylib" libssl.1.1.dylib - rm libssl.dylib - ln -s libssl.1.1.dylib libssl.dylib - - # curl - install_name_tool -id "$RPATH/libcurl.4.dylib" libcurl.4.dylib - install_name_tool -change "${LIBDIR}/libcrypto.1.1.dylib" "$RPATH/libcrypto.1.1.dylib" libcurl.4.dylib - install_name_tool -change "${LIBDIR}/libssl.1.1.dylib" "$RPATH/libssl.1.1.dylib" libcurl.4.dylib - rm libcurl.dylib - ln -s libcurl.4.dylib libcurl.dylib - - # libmicrohttpd - install_name_tool -id "$RPATH/libmicrohttpd.12.dylib" libmicrohttpd.12.dylib - rm libmicrohttpd.dylib - ln -s libmicrohttpd.12.dylib libmicrohttpd.dylib - - # lame - install_name_tool -id "$RPATH/libmp3lame.0.dylib" libmp3lame.0.dylib - rm libmp3lame.dylib - ln -s libmp3lame.0.dylib libmp3lame.dylib - - # libopenmpt - install_name_tool -id "$RPATH/libopenmpt.0.dylib" libopenmpt.0.dylib - rm libopenmpt.dylib - ln -s libopenmpt.0.dylib libopenmpt.dylib - - cd ../../ - fi +function relink_dynamic_libraries() { + node ${SCRIPTDIR}/relink-dynamic-libraries.js bin/lib } clean + mkdir vendor cd vendor + +stage_prebuilt_libraries fetch_packages build_boost build_openssl build_curl build_libmicrohttpd -stage_opus_ogg_vorbis build_ffmpeg build_lame build_libopenmpt -patch_dylib_rpaths +relink_dynamic_libraries + +cd .. +if [[ $CROSSCOMPILE == "rpi" ]]; then + mv vendor vendor-${CROSSCOMPILE} +else + mv vendor vendor-$(uname -m) +fi + +printf "\n\ndone!\n\n" diff --git a/script/relink-dynamic-libraries.js b/script/relink-dynamic-libraries.js new file mode 100644 index 000000000..d50851684 --- /dev/null +++ b/script/relink-dynamic-libraries.js @@ -0,0 +1,137 @@ +/* this script is used to update all macOS dynamic libraries so they can discover +each other in the app's sandboxed directory; it removes absolute paths and replaces +them with @rpaths. it also ensures symlinks are setup properly. whenever we add +a new third-party dependency we need to update the `libraries` and `symlinks` +mapping so the script knows to process them. + +the result of the script will be later validated by the `scan-standalone.js` script +during app build-time. */ + +const { promisify } = require('util'); +const exec = promisify(require('child_process').exec); +const fs = require('fs'); +const rm = promisify(fs.rm); +const symlink = promisify(fs.symlink); + +const mac = process.platform === 'darwin'; + +if (!mac) { + console.log(`\n\n no need to relink libraries on '${process.platform}\n\n`); + process.exit(0); +} + +/* these are the libraries we'll scan, and update linked libraries from +absolute paths to "@rpath/filename" */ +const libraries = [ + 'libavcodec-musikcube.59.18.100.dylib', + 'libavcodec-musikcube.59.dylib', + 'libavformat-musikcube.59.16.100.dylib', + 'libavformat-musikcube.59.dylib', + 'libavutil-musikcube.57.17.100.dylib', + 'libavutil-musikcube.57.dylib', + 'libswresample-musikcube.4.3.100.dylib', + 'libswresample-musikcube.4.dylib', + 'libcrypto.1.1.dylib', + 'libssl.1.1.dylib', + 'libcurl.4.dylib', + 'libmicrohttpd.12.dylib', + 'libmp3lame.0.dylib', + 'libogg.0.dylib', + 'libvorbis.0.dylib', + 'libvorbisenc.2.dylib', + 'libopus.0.dylib', + 'libopenmpt.0.dylib', +]; + +/* after updating libraries, re-establish symlinks */ +const symlinks = [ + ['libavcodec-musikcube.59.18.100.dylib', 'libavcodec-musikcube.59.dylib'], + ['libavcodec-musikcube.59.dylib', 'libavcodec-musikcube.dylib'], + ['libavformat-musikcube.59.16.100.dylib', 'libavformat-musikcube.59.dylib'], + ['libavformat-musikcube.59.dylib', 'libavformat-musikcube.dylib'], + ['libavutil-musikcube.57.17.100.dylib', 'libavutil-musikcube.57.dylib'], + ['libavutil-musikcube.57.dylib', 'libavutil-musikcube.dylib'], + ['libswresample-musikcube.4.3.100.dylib', 'libswresample-musikcube.4.dylib'], + ['libswresample-musikcube.4.dylib', 'libswresample-musikcube.dylib'], + ['libcrypto.1.1.dylib', 'libcrypto.dylib'], + ['libssl.1.1.dylib', 'libssl.dylib'], + ['libcurl.4.dylib', 'libcurl.dylib'], + ['libmicrohttpd.12.dylib', 'libmicrohttpd.dylib'], + ['libmp3lame.0.dylib', 'libmp3lame.dylib'], + ['libogg.0.dylib', 'libogg.dylib'], + ['libvorbis.0.dylib', 'libvorbis.dylib'], + ['libvorbisenc.2.dylib', 'libvorbisenc.dylib'], + ['libopus.0.dylib', 'libopus.dylib'], + ['libopenmpt.0.dylib', 'libopenmpt.dylib'], +]; + +const path = process.argv[2]; + +if (!path) { + console.log('\n\nusage: node relink-dynamic-libraries.js \n\n'); + process.exit(1); +} + +const run = async (cmd) => { + console.log(cmd); + return await exec(cmd); +}; + +/* scans the specified dylib using `otool`, builds a list of dependencies +that need their absolute paths updated with relative paths, then uses +`install_name_tool` to update the paths */ +const relink = async (fn) => { + const output = await exec(`otool -L ${path}/${fn}`); + const relink = output.stdout + .split('\n') + .map((line) => line.trim()) + .filter((line) => !!line) + /* the first line of output is the path to the library; ignore. */ + .slice() + /* libraries are formatted as " (compatibility ...)", so split + and take the name */ + .map((line) => line.split(' (compatibility')[0].trim()) + /* grab the filename from the path, and see if it's one we want to + re-link relatively */ + .filter((line) => libraries.indexOf(line.split('/').pop()) !== -1); + await run(`install_name_tool -id "@rpath/${fn}" ${path}/${fn}`); + for (let i = 0; i < relink.length; i++) { + const entry = relink[i]; + const leaf = entry.split('/').pop(); + await run( + `install_name_tool -change "${entry}" "@rpath/${leaf}" ${path}/${fn}` + ); + } + /* changing the id and updating entries sometimes invalidates the code + signature. remove the old one, and re-sign */ + await run(`codesign --remove-signature ${path}/${fn}`); + await run(`codesign --sign=- ${path}/${fn}`); + return relink; +}; + +const rebuildSymlinks = async () => { + for (let i = 0; i < symlinks.length; i++) { + const [src, dst] = symlinks[i]; + if (fs.existsSync(`${path}/${dst}`)) { + console.log('removing symlink:', `${path}/${dst}`); + await rm(`${path}/${dst}`); + } + console.log('creating symlink:', src, dst); + await symlink(src, `${path}/${dst}`); + } +}; + +const main = async () => { + try { + await Promise.allSettled(libraries.map((library) => relink(library))); + await rebuildSymlinks(); + } catch (e) { + console.log(`\n\n failed to relink dynamic libraries!\n\n`); + console.log(e, '\n\n'); + process.exit(1); + } + console.log(`\n\n finished relinking dynamic libraries!\n\n`); + process.exit(0); +}; + +main(); diff --git a/script/stage-vendor-libraries.sh b/script/stage-vendor-libraries.sh index a8a821497..76e012098 100755 --- a/script/stage-vendor-libraries.sh +++ b/script/stage-vendor-libraries.sh @@ -10,13 +10,14 @@ ln -s ../lib/ popd PLATFORM=$(uname) + if [[ "$PLATFORM" == 'Darwin' ]]; then echo "[stage-static-vendor-libraries] staging macOS .dylib files..." - cp vendor/bin/lib/libavcodec-musikcube.59.dylib ./bin/lib - cp vendor/bin/lib/libavformat-musikcube.59.dylib ./bin/lib - cp vendor/bin/lib/libavutil-musikcube.57.dylib ./bin/lib - cp vendor/bin/lib/libswresample-musikcube.4.dylib ./bin/lib + cp vendor/bin/lib/libavcodec-musikcube*.dylib ./bin/lib + cp vendor/bin/lib/libavformat-musikcube*.dylib ./bin/lib + cp vendor/bin/lib/libavutil-musikcube*.dylib ./bin/lib + cp vendor/bin/lib/libswresample-musikcube*.dylib ./bin/lib cp vendor/bin/lib/libopus.0.dylib ./bin/lib cp vendor/bin/lib/libogg.0.dylib ./bin/lib cp vendor/bin/lib/libvorbis.0.dylib ./bin/lib