musikcube/script/create-crosscompile-sysroot.js
casey langen 4f44829cda
Improve cross-compile support (#634)
* Update minimum CMake version requirement to get rid of warning.
* Include CMake compile commands for easier diagnostics.
* More improvements to arm toolchain selection while cross-compiling third-party dependencies.
* Use x-tools provided cmake toolchains
* Add a script to download and extract deb dependencies for crosscompile.
* Link against libstdc++ statically when cross-compiling to ARM to improve portability.
* Update GeneratePackage.cmake to generate better filenames.
* Ensure symbols are stripped properly when cross-compiling
* Remove old scripts that are no longer required
* Add script to install x-tools
* Add some docs that describe how to setup a crosscompile environment.
* Add docs for building standlone on Linux
* Update CHANGELOG
* Update version hash.
2023-09-07 22:05:43 -07:00

197 lines
5.4 KiB
JavaScript

const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
const fs = require('fs');
const path = require('path');
/**
* This script will download and extract all transitive dependencies
* for the packages specified by PACKAGE_NAMES. This is used to populate
* a cross-compile sysroot with the libraries required to compile the
* app itself.
*/
const PACKAGE_NAMES = [
'libasound2-dev',
'libev-dev',
'libncurses-dev',
'libopus-dev',
'libpulse-dev',
'libsndio-dev',
'libsystemd-dev',
'libvorbis-dev',
'portaudio19-dev',
'zlib1g-dev',
];
/**
* These are packages that are either provided by the cross-compiler
* tools, or otherwise introduce conflicts while building and linking.
* If any of the packages depend on these, we explicitly ignore them.
*/
const EXCLUDE = [
'debconf',
'dpkg',
'gcc-10-base',
'gcc-13-base',
'gcc-8-base',
'install-info',
'libc-dev-bin',
'libc6-dev',
'libc6',
'libcrypt1',
'libdebian-installer4',
'libdpkg-perl',
'libgcc-s1',
'libgcc1',
'libselinux1-dev',
'libselinux1',
'libstdc++6',
'linux-libc-dev',
];
/**
* A list of files known to be provided by deb archives that we don't
* want or need; we'll delete these after we're done downloading and
* extracting files.
*/
const CLEANUP_FILES = [
'control.tar.gz',
'control.tar.bz2',
'control.tar.xz',
'control.tar.zst',
'data.tar.gz',
'data.tar.bz2',
'data.tar.xz',
'data.tar.zst',
'debian-binary',
];
/**
* Uses `apt-cache` to resolve a list of package dependencies for
* the specified package.
*/
const getPackageDependencies = async (packageName) => {
const rawOutput = (
await exec(
`apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances ${packageName}`
)
).stdout.split('\n');
const packages = rawOutput
.filter((e) => e.indexOf('Depends:') >= 0)
.map((e) => e.replace('Depends:', '').trim())
.filter(
(e) => !(e.startsWith('Pre ') || e.startsWith('<') || e.startsWith('|'))
);
return packages;
};
/**
* Uses `apt download` to get a list of download URLs from
* the specified list of packages.
*/
const getPackageDownloadUrls = async (packages) => {
const rawOutput = (
await exec(`apt download --print-uris ${packages.join(' ')}`)
).stdout.split('\n');
const downloadUrls = rawOutput
.filter((e) => e.trim().length > 0)
.map((e) => e.split(' ')[0].trim().replace(/'/g, ''));
return downloadUrls;
};
/**
* Downloads and extracts a list of deb URLs
*/
const downloadAndExtract = async (downloadUrls) => {
for (let i = 0; i < downloadUrls.length; i++) {
const fn = decodeURIComponent(downloadUrls[i].split('/').pop());
console.log(
`processing [${i + 1}/${downloadUrls.length}]`,
downloadUrls[i]
);
await exec(`wget ${downloadUrls[i]}`);
await exec(`ar x ./${fn}`);
if (fs.existsSync('data.tar.zst')) {
await exec(`tar --use-compress-program=unzstd -xvf data.tar.zst`);
} else if (fs.existsSync('data.tar.xz')) {
await exec(`tar -xvf data.tar.xz`);
} else if (fs.existsSync('data.tar.gz')) {
await exec(`tar xvfz data.tar.gz`);
} else if (fs.existsSync('data.tar.bz2')) {
await exec(`tar xvfj data.tar.bz2`);
} else {
console.error('unknown file type');
process.exit(-1);
}
for (let j = 0; j < CLEANUP_FILES.length; j++) {
if (fs.existsSync(CLEANUP_FILES[j])) {
await exec(`rm ${CLEANUP_FILES[j]}`);
}
}
}
};
/**
* Finds all symlinks with absolute paths in our generated
* sysroot, and converts them to relative paths. This ensures
* the sysroot is "relocatable" and can be used with
* crosscompile toolchains.
*/
const convertAbsoluteToRelativeSymlinks = async () => {
const root = process.cwd();
const symlinks = (await exec('find . -type l')).stdout
.split('\n')
.filter((e) => e.length > 0);
for (let i = 0; i < symlinks.length; i++) {
const dest = fs.readlinkSync(symlinks[i]);
if (dest[0] === '/') {
const absoluteTo = path.join(root, dest);
const absoluteFrom = path.join(root, symlinks[i]);
const relative = path.relative(path.dirname(absoluteFrom), absoluteTo);
console.log(
`relinking\n * fn: ${absoluteFrom}\n * from: ${absoluteTo}\n * to: ${relative}`
);
fs.unlinkSync(symlinks[i]);
fs.symlinkSync(relative, symlinks[i]);
}
}
};
/**
* Delete any debs we downloaded
*/
const rmDebs = async () => {
try {
console.log('cleaning up downloads');
await exec('rm *.deb');
} catch (e) {
/* nothing */
}
};
const main = async () => {
await rmDebs();
let dependencies = [];
for (let i = 0; i < PACKAGE_NAMES.length; i++) {
console.log(
`scanning [${i + 1}/${PACKAGE_NAMES.length}]`,
PACKAGE_NAMES[i]
);
dependencies = [
...dependencies,
...(await getPackageDependencies(PACKAGE_NAMES[i])),
];
}
const deduped = new Set([...PACKAGE_NAMES, ...dependencies]);
for (let i = 0; i < EXCLUDE.length; i++) {
deduped.delete(EXCLUDE[i]);
}
console.log(`resolved ${deduped.size} transitive dependencies`);
const downloadUrls = await getPackageDownloadUrls(Array.from(deduped));
await downloadAndExtract(downloadUrls);
await convertAbsoluteToRelativeSymlinks();
await rmDebs();
await exec('tar cvf sysroot.tar .', { maxBuffer: 1024 * 1024 * 8 }); /* 8mb */
};
main();