musikcube/script/relink-dynamic-libraries.js
2023-09-10 00:06:43 -07:00

137 lines
4.5 KiB
JavaScript

/* 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 unlink = promisify(fs.unlink);
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.60.dylib',
'libavformat-musikcube.60.dylib',
'libavutil-musikcube.58.dylib',
'libcrypto.3.dylib',
'libcurl.4.dylib',
'libmicrohttpd.12.dylib',
'libmp3lame.0.dylib',
'libogg.0.dylib',
'libopenmpt.0.dylib',
'libopus.0.dylib',
'libssl.3.dylib',
'libswresample-musikcube.4.dylib',
'libtag.1.19.0.dylib',
'libvorbis.0.dylib',
'libvorbisenc.2.dylib',
'libgme.0.6.3.dylib',
];
/* after updating libraries, re-establish symlinks */
const symlinks = [
['libavcodec-musikcube.60.dylib', 'libavcodec-musikcube.dylib'],
['libavformat-musikcube.60.dylib', 'libavformat-musikcube.dylib'],
['libavutil-musikcube.58.dylib', 'libavutil-musikcube.dylib'],
['libcrypto.3.dylib', 'libcrypto.dylib'],
['libcurl.4.dylib', 'libcurl.dylib'],
['libmicrohttpd.12.dylib', 'libmicrohttpd.dylib'],
['libmp3lame.0.dylib', 'libmp3lame.dylib'],
['libogg.0.dylib', 'libogg.dylib'],
['libopenmpt.0.dylib', 'libopenmpt.dylib'],
['libopus.0.dylib', 'libopus.dylib'],
['libssl.3.dylib', 'libssl.dylib'],
['libswresample-musikcube.4.dylib', 'libswresample-musikcube.dylib'],
['libtag.1.19.0.dylib', 'libtag.dylib'],
['libvorbis.0.dylib', 'libvorbis.dylib'],
['libvorbisenc.2.dylib', 'libvorbisenc.dylib'],
['libgme.0.6.3.dylib', 'libgme.dylib'],
];
const path = process.argv[2];
if (!path) {
console.log('\n\nusage: node relink-dynamic-libraries.js <path>\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 "<name> (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];
console.log('removing symlink:', `${path}/${dst}`);
try {
await exec(`rm ${path}/${dst}`);
} catch (e) {}
try {
await unlink(`${path}/${dst}`);
} catch (e) {}
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();