import fs from 'fs/promises' import path from 'node:path' import childProcess from 'node:child_process' import process from 'process' import os from 'os' import { Buffer } from 'node:buffer' // Define constants. const tmp = path.resolve('./build/tools') const baseDirectory = path.resolve('./tools') const kernelVersion = '5.15.159' const gccVersion = '14.1.0' const binutilsVersion = '2.42' const glibcVersion = '2.39' async function gccVerbose (gccBinary) { const env = { ...process.env, LANG: 'C' } const gccV = childProcess.spawn(gccBinary, ['--verbose'], { stdio: ['ignore', 'ignore', 'pipe'], env }) const buffers = [] gccV.stderr.on('data', function (data) { buffers.push(data) }) return new Promise(function (resolve, reject) { gccV.on('exit', function () { resolve(Buffer.concat(buffers).toString()) }) }) } async function findBuildTarget (gccVersion) { let gccBinary = 'gcc' let output = await gccVerbose(gccBinary) if (output.startsWith('Apple clang')) { gccBinary = `gcc-${gccVersion.split('.')[0]}` output = await gccVerbose(gccBinary) } return output .split('\n') .reduce(function (accumulator, line) { if (line.startsWith('Target: ')) { return { ...accumulator, build: line.split(' ')[1] } } else if (line.startsWith('COLLECT_GCC')) { return { ...accumulator, gcc: line.split('=')[1] } } else { return accumulator } }, {}) } 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 copyGlibcHeaders () { const sourceDirectory = await downloadAndUnarchive( new URL(`https://ftp.gnu.org/gnu/glibc/glibc-${glibcVersion}.tar.xz`) ) const includeDirectory = path.join(tmp, 'include') await fs.mkdir(includeDirectory) await fs.cp(path.join(sourceDirectory, 'elf/elf.h'), path.join(includeDirectory, 'elf.h')) return sourceDirectory } async function buildKernel (rootfs, options) { 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: `${options.target}-`, ARCH: 'riscv', PATH: `${path.join(rootfs, 'bin')}:${process.env['PATH']}`, HOSTCFLAGS: `-D_UUID_T -D__GETHOSTUUID_H -I${path.join(tmp, 'include')}` } 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(options.sysroot, '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 (rootfs, options) { const env = { ...process.env, PATH: `${path.join(rootfs, 'bin')}:${process.env['PATH']}` } const compilerArguments = [ '-ffreestanding', '-static', '-o', path.join(tmp, 'init'), path.join(baseDirectory, 'init.c') ] childProcess.execFileSync('riscv32-unknown-linux-gnu-gcc', compilerArguments, { stdio: 'inherit', env }) } async function buildGlibc (sourceDirectory, rootfs, options) { const configureOptions = [ '--prefix=/usr', `--host=${options.target}`, `--target=${options.target}`, `--build=${options.build}`, `--enable-kernel=${kernelVersion}`, `--with-headers=${path.join(options.sysroot, 'usr/include')}`, '--disable-nscd', '--disable-libquadmath', '--disable-libitm', '--disable-werror', 'libc_cv_forced_unwind=yes' ] const bin = path.join(rootfs, 'bin') const env = { ...process.env, PATH: `${path.join(rootfs, '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', [`install_root=${options.sysroot}`, 'install'], { stdio: 'inherit', env, cwd }) } async function buildCrossBinutils (rootfs, options) { 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 env = { ...process.env, CC: options.gcc, CXX: options.gxx } const configureOptions = [ `--prefix=${rootfs}`, `--target=${options.target}`, '--disable-nls', '--enable-gprofng=no', '--disable-werror', '--enable-default-hash-style=gnu', '--disable-libquadmath' ] childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, { stdio: 'inherit', cwd, env }) childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', cwd, env }) childProcess.execFileSync('make', ['install'], { stdio: 'inherit', cwd, env }) return sourceDirectory } async function buildGCC1 (rootfs, options) { const sourceDirectory = await downloadAndUnarchive( new URL(`https://gcc.gnu.org/pub/gcc/releases/gcc-${gccVersion}/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=${rootfs}`, `--with-sysroot=${options.sysroot}`, '--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=${options.target}`, `--build=${options.build}`, `--host=${options.build}` ] const flags = '-O2 -fPIC' const env = { ...process.env, CC: options.gcc, CXX: options.gxx, CFLAGS: flags, CXXFLAGS: flags, PATH: `${path.join(rootfs, '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 buildGCC2 (sourceDirectory, rootfs, options) { const cwd = path.join(path.dirname(sourceDirectory), 'build-gcc'); await fs.rm(cwd, { recursive: true, force: true }) await fs.mkdir(cwd) const configureOptions = [ `--prefix=${rootfs}`, `--with-sysroot=${options.sysroot}`, '--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', '--disable-libquadmath', '--enable-threads=posix', '--with-default-libstdcxx-abi=new', '--disable-nls', `--target=${options.target}`, `--build=${options.build}`, `--host=${options.build}` ] const flags = '-O2 -fPIC' const env = { ...process.env, CFLAGS: flags, CXXFLAGS: flags, PATH: `${path.join(rootfs, '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 }) } const rootfs = path.join(tmp, 'rootfs') const options = await findBuildTarget(gccVersion) options.gxx = options.gcc.replaceAll('c', '+') options.sysroot = path.join(tmp, 'sysroot') options.target = 'riscv32-unknown-linux-gnu' for (const targetDirectory of [tmp, rootfs]) { await fs.rm(targetDirectory, { recursive: true, force: true }) await fs.mkdir(targetDirectory) } const binutilsSource = await buildCrossBinutils(rootfs, options) const gccSource = await buildGCC1(rootfs, options) const glibcSource = await copyGlibcHeaders() const kernelImage = await buildKernel(rootfs, options) await buildGlibc(glibcSource, rootfs, options) await buildGCC2(gccSource, rootfs, options) buildInit (rootfs, options) console.log(kernelImage)