# This Source Code Form is subject to the terms of the Mozilla Public License, # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. -} require 'pathname' require 'open3' require 'rake/clean' require_relative 'tools/support' # Dependencies. GCC_VERSION = "14.2.0" GCC_PATCH = 'https://raw.githubusercontent.com/Homebrew/formula-patches/f30c309442a60cfb926e780eae5d70571f8ab2cb/gcc/gcc-14.2.0-r2.diff' # Paths. HOST_GCC = TMP + 'host/gcc' HOST_INSTALL = TMP + 'host/install' CLOBBER.include TMP directory(TMP + 'tools') directory HOST_GCC directory HOST_INSTALL task default: [TMP + 'elna'] do sh (TMP + 'elna').to_path, '--tokenize', 'source.elna' end namespace :boot do desc 'Download and configure the bootstrap compiler' task configure: [TMP + 'tools', HOST_GCC, HOST_INSTALL] do url = URI.parse "https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz" options = find_build_target GCC_VERSION source_directory = TMP + "tools/gcc-#{GCC_VERSION}" frontend_link = source_directory + 'gcc' download_and_pipe url, source_directory.dirname, ['tar', '-Jxv'] download_and_pipe URI.parse(GCC_PATCH), source_directory, ['patch', '-p1'] sh 'contrib/download_prerequisites', chdir: source_directory.to_path File.symlink Pathname.new('.').relative_path_from(frontend_link), (frontend_link + 'elna') configure_options = [ "--prefix=#{HOST_INSTALL.realpath}", "--with-sysroot=#{options.sysroot.realpath}", '--enable-languages=c,c++,elna', '--disable-bootstrap', '--disable-multilib', "--target=#{options.build}", "--build=#{options.build}", "--host=#{options.build}" ] flags = '-O2 -fPIC -I/opt/homebrew/Cellar/flex/2.6.4_2/include' env = { 'CC' => options.gcc, 'CXX' => options.gxx, 'CFLAGS' => flags, 'CXXFLAGS' => flags, } configure = source_directory.relative_path_from(HOST_GCC) + 'configure' sh env, configure.to_path, *configure_options, chdir: HOST_GCC.to_path end desc 'Make and install the bootstrap compiler' task :make do cwd = HOST_GCC.to_path sh 'make', '-j', Etc.nprocessors.to_s, chdir: cwd sh 'make', 'install', chdir: cwd end end desc 'Build the bootstrap compiler' task boot: %w[boot:configure boot:make] file (TMP + 'elna').to_path => ['source.elna'] file (TMP + 'elna').to_path => [(HOST_INSTALL + 'bin/gelna').to_path] do |task| sh (HOST_INSTALL + 'bin/gelna').to_path, '-o', task.name, task.prerequisites.first end namespace :cross do desc 'Build cross toolchain' task :init, [:target] do |_, args| args.with_defaults target: 'riscv32-unknown-linux-gnu' options = find_build_target GCC_VERSION, args[:target] env = { 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" } sh env, 'riscv32-unknown-linux-gnu-gcc', '-ffreestanding', '-static', '-o', (options.tools + 'init').to_path, 'tools/init.c' end end namespace :test do test_sources = FileList['tests/vm/*.elna', 'tests/vm/*.s'] compiler = TMP + 'bin/elna' object_directory = TMP + 'riscv/tests' root_directory = TMP + 'riscv/root' executable_directory = root_directory + 'tests' expectation_directory = root_directory + 'expectations' init = TMP + 'riscv/root/init' builtin = TMP + 'riscv/builtin.o' directory root_directory directory object_directory directory executable_directory directory expectation_directory file builtin => ['tools/builtin.s', object_directory] do |task| sh AS, '-o', task.name, task.prerequisites.first end test_files = test_sources.flat_map do |test_source| test_basename = File.basename(test_source, '.*') test_object = object_directory + test_basename.ext('.o') file test_object => [test_source, object_directory] do |task| case File.extname(task.prerequisites.first) when '.s' sh AS, '-mno-relax', '-o', task.name, task.prerequisites.first when '.elna' sh compiler, '--output', task.name, task.prerequisites.first else raise "Unknown source file extension #{task.prerequisites.first}" end end test_executable = executable_directory + test_basename file test_executable => [test_object, executable_directory, builtin] do |task| objects = task.prerequisites.filter { |prerequisite| File.file? prerequisite } sh LINKER, '-o', test_executable.to_path, *objects end expectation_name = test_basename.ext '.txt' source_expectation = "tests/expectations/#{expectation_name}" target_expectation = expectation_directory + expectation_name file target_expectation => [source_expectation, expectation_directory] do cp source_expectation, target_expectation end [test_executable, target_expectation] end file init => [root_directory] do |task| cp (TMP + 'tools/init'), task.name end # Directories should come first. test_files.unshift executable_directory, expectation_directory, init file (TMP + 'riscv/root.cpio') => test_files do |task| root_files = task.prerequisites .map { |prerequisite| Pathname.new(prerequisite).relative_path_from(root_directory).to_path } File.open task.name, 'wb' do |cpio_file| cpio_options = { chdir: root_directory.to_path } cpio_stream = Open3.popen2 'cpio', '-o', '--format=newc', cpio_options do |stdin, stdout, wait_thread| stdin.write root_files.join("\n") stdin.close stdout.each { |chunk| cpio_file.write chunk } wait_thread.value end end end task :vm => (TMP + 'riscv/root.cpio') do |task| kernels = FileList.glob(TMP + 'tools/linux-*/arch/riscv/boot/Image') sh 'qemu-system-riscv32', '-nographic', '-M', 'virt', '-bios', 'default', '-kernel', kernels.first, '-append', 'quiet panic=1', '-initrd', task.prerequisites.first, '-no-reboot' end end