import fs from 'fs/promises' import path from 'node:path' import childProcess from 'node:child_process' import process from 'process' import os from 'os' // Define constants. const tmp = path.resolve('./build/tools') const target = 'riscv32-unknown-linux-gnu' const build = 'x86_64-slackware-linux' const baseDirectory = path.resolve('./tools') const busyboxVersion = '1.36.1' const kernelVersion = '5.15.158' const gccVersion = '13.2.0' const binutilsVersion = '2.42' const glibcVersion = '2.39' function createImage (rootfs) { const rootExt4 = path.join(tmp, 'rootfs.ext4') childProcess.execFileSync('dd', ['if=/dev/zero', `of=${rootExt4}`, 'bs=1M', 'count=1024'], { stdio: 'inherit' }) childProcess.execFileSync('/sbin/mkfs.ext4', ['-d', rootfs, rootExt4], { stdio: 'inherit' }) } async function downloadAndUnarchive (url) { const response = await fetch(url) const bodyReader = response.body.getReader() const basename = path.basename(url.pathname) let archiveType = '' let rootDirectory = '' switch (path.extname(basename)) { case '.bz2': archiveType = '-j' rootDirectory = path.basename(basename, '.tar.bz2') break case '.xz': archiveType = '-J' rootDirectory = path.basename(basename, '.tar.xz') break default: break } return new Promise(async function (resolve, reject) { const untar = childProcess.spawn('tar', ['-C', tmp, archiveType, '-xv'], { stdio: ['pipe', 'inherit', 'inherit'] }) let done = false untar.on('exit', function () { resolve(path.join(tmp, rootDirectory)) }) do { const chunk = await bodyReader.read() done = chunk.done if (chunk.value !== undefined) { untar.stdin.write(chunk.value) } } while (!done) untar.stdin.end() }) } async function buildBusyBox (sysroot, rootfs) { const cwd = await downloadAndUnarchive(new URL(`https://busybox.net/downloads/busybox-${busyboxVersion}.tar.bz2`)) const env = { ...process.env, CROSS_COMPILE: path.join(sysroot, 'bin', `${target}-`) } const configuration = [ `CONFIG_PREFIX=${rootfs}`, `CONFIG_SYSROOT=${rootfs}` ] childProcess.execFileSync('make', ['defconfig'], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', [...configuration, 'install'], { stdio: 'inherit', env, cwd }) } async function buildKernel (sysroot, rootfs) { const cwd = await downloadAndUnarchive( new URL(`https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-${kernelVersion}.tar.xz`) ) const env = { ...process.env, CROSS_COMPILE: `${target}-`, ARCH: 'riscv', PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}` } childProcess.execFileSync('make', ['rv32_defconfig'], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['headers'], { stdio: 'inherit', env, cwd }) const userDirectory = path.join(rootfs, 'usr') await fs.cp(path.join(cwd, 'usr/include'), path.join(userDirectory, 'include'), { recursive: true }) return path.join(cwd, 'arch/riscv/boot/Image') } async function buildInit (sysroot) { const env = { ...process.env, PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}` } const compilerArguments = [ '-ffreestanding', '-static', '-o', path.join(tmp, 'init'), path.join(baseDirectory, 'init.cpp') ] childProcess.execFileSync('riscv32-unknown-linux-gnu-g++', compilerArguments, { stdio: 'inherit', env }) } async function buildGlibc (sysroot, rootfs) { const sourceDirectory = await downloadAndUnarchive( new URL(`https://ftp.gnu.org/gnu/glibc/glibc-${glibcVersion}.tar.xz`) ) const configureOptions = [ '--prefix=/usr', '--libdir=/usr/lib', `--host=${target}`, `--build=${build}`, `--enable-kernel=${kernelVersion}`, `--with-headers=${path.join(rootfs, 'usr/include')}`, '--disable-nscd' ] const env = { ...process.env, PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}` } const cwd = path.join(path.dirname(sourceDirectory), 'build-glibc'); await fs.mkdir(cwd) childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), './configure'), configureOptions, { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', [`DESTDIR=${rootfs}`, 'install'], { stdio: 'inherit', env, cwd }) } async function buildCrossBinutils (sysroot) { const sourceDirectory = await downloadAndUnarchive( new URL(`https://ftp.gnu.org/gnu/binutils/binutils-${binutilsVersion}.tar.xz`) ) const cwd = path.join(path.dirname(sourceDirectory), 'build-binutils'); await fs.mkdir(cwd) const configureOptions = [ `--prefix=${sysroot}`, `--target=${target}`, '--disable-nls', '--enable-gprofng=no', '--disable-werror', '--enable-default-hash-style=gnu' ] childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, { stdio: 'inherit', cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', cwd }) childProcess.execFileSync('make', ['install'], { stdio: 'inherit', cwd }) return sourceDirectory } async function buildGCC1 (sysroot, rootfs) { const sourceDirectory = await downloadAndUnarchive( new URL(`https://download.dlackware.com/slackware/slackware64-current/source/d/gcc/gcc-${gccVersion}.tar.xz`) ) const cwd = path.join(path.dirname(sourceDirectory), 'build-gcc'); await fs.mkdir(cwd) childProcess.execFileSync(path.join(sourceDirectory, 'contrib/download_prerequisites'), { stdio: 'inherit', cwd: sourceDirectory }) const configureOptions = [ `--prefix=${sysroot}`, `--with-sysroot=${rootfs}`, '--enable-languages=c,c++', '--disable-shared', '--with-arch=rv32imafdc', '--with-abi=ilp32d', '--with-tune=rocket', '--with-isa-spec=20191213', '--disable-bootstrap', '--disable-multilib', '--disable-libmudflap', '--disable-libssp', '--disable-libquadmath', '--disable-libsanitizer', '--disable-threads', '--disable-libatomic', '--disable-libgomp', '--disable-libvtv', '--disable-libstdcxx', '--disable-nls', '--with-newlib', '--without-headers', `--target=${target}`, `--build=${build}`, `--host=${build}` ] const flags = '-O2 -fPIC' const env = { ...process.env, CFLAGS: flags, CXXFLAGS: flags, PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}` } childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['install'], { stdio: 'inherit', env, cwd }) return sourceDirectory } async function buildBinutils (sourceDirectory, sysroot, rootfs) { const cwd = path.join(path.dirname(sourceDirectory), 'build-binutils'); await fs.rm(cwd, { recursive: true, force: true }) await fs.mkdir(cwd) const configureOptions = [ '--prefix=/usr', `--build=${build}`, `--host=${target}`, `--with-build-sysroot=${rootfs}`, '--disable-nls', '--enable-shared', '--enable-gprofng=no', '--disable-werror', '--enable-default-hash-style=gnu' ] const env = { ...process.env, PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}` } childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', [`DESTDIR=${rootfs}`, 'install'], { stdio: 'inherit', env, cwd }) return sourceDirectory } async function buildGCC2 (sourceDirectory, sysroot, rootfs) { const cwd = path.join(path.dirname(sourceDirectory), 'build-gcc'); await fs.rm(cwd, { recursive: true, force: true }) await fs.mkdir(cwd) const configureOptions = [ `--prefix=${sysroot}`, `--with-sysroot=${rootfs}`, '--enable-languages=c,c++,lto', '--enable-lto', '--enable-shared', '--with-arch=rv32imafdc', '--with-abi=ilp32d', '--with-tune=rocket', '--with-isa-spec=20191213', '--disable-bootstrap', '--disable-multilib', '--enable-checking=release', '--disable-libssp', '--enable-threads=posix', '--with-default-libstdcxx-abi=new', '--disable-nls', `--target=${target}`, `--build=${build}`, `--host=${build}` ] const flags = '-O2 -fPIC' const env = { ...process.env, CFLAGS: flags, CXXFLAGS: flags, PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}` } childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['install'], { stdio: 'inherit', env, cwd }) } async function buildGCC3 (sourceDirectory, sysroot, rootfs) { const cwd = path.join(path.dirname(sourceDirectory), 'build-gcc'); await fs.rm(cwd, { recursive: true, force: true }) await fs.mkdir(cwd) const configureOptions = [ `--prefix=/usr`, `--libdir=/usr/lib`, '--enable-languages=c,c++,lto', '--enable-lto', '--enable-shared', '--with-arch=rv32imafdc', '--with-abi=ilp32d', '--with-tune=rocket', '--with-isa-spec=20191213', '--disable-bootstrap', '--disable-multilib', '--enable-checking=release', '--disable-libssp', '--enable-threads=posix', '--with-default-libstdcxx-abi=new', '--disable-nls', `--target=${target}`, `--build=${build}`, `--host=${target}` ] const flags = '-O2 -fPIC' const env = { ...process.env, CFLAGS: flags, CXXFLAGS: flags, PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}` } childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd }) childProcess.execFileSync('make', [`DESTDIR=${rootfs}`, 'install'], { stdio: 'inherit', env, cwd }) } async function createRoot (rootfs) { for (const directory of ['proc', 'sys', 'dev', 'etc/init.d', 'usr/local', 'usr/local/bin']) { await fs.mkdir(path.join(rootfs, directory)) } await fs.cp(path.join(baseDirectory, 'files/fstab'), path.join(rootfs, 'etc/fstab')) await fs.cp(path.join(baseDirectory, 'files/rcS'), path.join(rootfs, 'etc/init.d/rcS')) await fs.cp(path.join(baseDirectory, 'files/rcK'), path.join(rootfs, 'etc/init.d/rcK')) await fs.cp(path.join(baseDirectory, 'files/inittab'), path.join(rootfs, 'etc/inittab')) } const sysroot = path.join(tmp, 'sysroot') const rootfs = path.join(tmp, 'rootfs') for (const targetDirectory of [tmp, sysroot, rootfs]) { await fs.rm(targetDirectory, { recursive: true, force: true }) await fs.mkdir(targetDirectory) } const binutilsSource = await buildCrossBinutils(sysroot) const gccSource = await buildGCC1(sysroot, rootfs) const kernelImage = await buildKernel(sysroot, rootfs) await buildGlibc(sysroot, rootfs) await buildBinutils(binutilsSource, sysroot, rootfs) await buildGCC2(gccSource, sysroot, rootfs) buildInit (sysroot) await buildBusyBox(sysroot, rootfs) await createRoot(rootfs) await buildGCC3(gccSource, sysroot, rootfs) createImage(rootfs) console.log(kernelImage)