# 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 'uri' require 'net/http' require 'rake/clean' require 'open3' require 'etc' require_relative 'shared' GCC_VERSION = "14.2.0" BINUTILS_VERSION = '2.43.1' GLIBC_VERSION = '2.40' KERNEL_VERSION = '5.15.166' CLOBBER.include TMP class BuildTarget attr_accessor(:build, :gcc, :target, :tmp) def gxx @gcc.gsub 'c', '+' end def sysroot tmp + 'sysroot' end def rootfs tmp + 'rootfs' end def tools tmp + 'tools' end end def gcc_verbose(gcc_binary) read, write = IO.pipe sh({'LANG' => 'C'}, gcc_binary, '--verbose', err: write) write.close output = read.read read.close output end def find_build_target(gcc_version, task) gcc_binary = 'gcc' output = gcc_verbose gcc_binary if output.start_with? 'Apple clang' gcc_binary = "gcc-#{gcc_version.split('.').first}" output = gcc_verbose gcc_binary end result = output .lines .each_with_object(BuildTarget.new) do |line, accumulator| if line.start_with? 'Target: ' accumulator.build = line.split(' ').last.strip elsif line.start_with? 'COLLECT_GCC' accumulator.gcc = line.split('=').last.strip end end result.tmp = TMP task.with_defaults target: 'riscv32-unknown-linux-gnu' result.target = task[:target] result end def download_and_unarchive(url, target) case File.extname url.path when '.bz2' archive_type = '-j' root_directory = File.basename url.path, '.tar.bz2' when '.xz' archive_type = '-J' root_directory = File.basename url.path, '.tar.xz' else raise "Unsupported archive type #{url.path}." end Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http| request = Net::HTTP::Get.new url.request_uri http.request request do |response| case response when Net::HTTPRedirection download_and_unarchive URI.parse(response['location']) when Net::HTTPSuccess Open3.popen2 'tar', '-C', target.to_path, archive_type, '-xv' do |stdin, stdout, wait_thread| Thread.new do stdout.each { |line| puts line } end response.read_body do |chunk| stdin.write chunk end stdin.close wait_thread.value end else response.error! end end end target + root_directory end namespace :cross do desc 'Build cross binutils' task :binutils, [:target] do |_, args| options = find_build_target GCC_VERSION, args options.tools.mkpath source_directory = download_and_unarchive( URI.parse("https://ftp.gnu.org/gnu/binutils/binutils-#{BINUTILS_VERSION}.tar.xz"), options.tools) cwd = source_directory.dirname + 'build-binutils' cwd.mkpath options.rootfs.mkpath env = { 'CC' => options.gcc, 'CXX' => options.gxx } configure_options = [ "--prefix=#{options.rootfs.realpath}", "--target=#{options.target}", '--disable-nls', '--enable-gprofng=no', '--disable-werror', '--enable-default-hash-style=gnu', '--disable-libquadmath' ] configure = source_directory.relative_path_from(cwd) + 'configure' sh env, configure.to_path, *configure_options, chdir: cwd.to_path sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path sh env, 'make', 'install', chdir: cwd.to_path end desc 'Build stage 1 GCC' task :gcc1, [:target] do |_, args| options = find_build_target GCC_VERSION, args options.tools.mkpath source_directory = download_and_unarchive( URI.parse("https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz"), options.tools) cwd = source_directory.dirname + 'build-gcc' cwd.mkpath options.rootfs.mkpath options.sysroot.mkpath sh 'contrib/download_prerequisites', chdir: source_directory.to_path configure_options = [ "--prefix=#{options.rootfs.realpath}", "--with-sysroot=#{options.sysroot.realpath}", '--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}" ] flags = '-O2 -fPIC' env = { 'CC' => options.gcc, 'CXX' => options.gxx, 'CFLAGS' => flags, 'CXXFLAGS' => flags, 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" } configure = source_directory.relative_path_from(cwd) + 'configure' sh env, configure.to_path, *configure_options, chdir: cwd.to_path sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path sh env, 'make', 'install', chdir: cwd.to_path end desc 'Copy glibc headers' task :headers, [:target] do |_, args| options = find_build_target GCC_VERSION, args options.tools.mkpath source_directory = download_and_unarchive( URI.parse("https://ftp.gnu.org/gnu/glibc/glibc-#{GLIBC_VERSION}.tar.xz"), options.tools) include_directory = options.tools + 'include' include_directory.mkpath cp (source_directory + 'elf/elf.h'), (include_directory + 'elf.h') end desc 'Build linux kernel' task :kernel, [:target] do |_, args| options = find_build_target GCC_VERSION, args options.tools.mkpath cwd = download_and_unarchive( URI.parse("https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-#{KERNEL_VERSION}.tar.xz"), options.tools) env = { 'CROSS_COMPILE' => "#{options.target}-", 'ARCH' => 'riscv', 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}", 'HOSTCFLAGS' => "-D_UUID_T -D__GETHOSTUUID_H -I#{options.tools.realpath + 'include'}" } sh env, 'make', 'rv32_defconfig', chdir: cwd.to_path sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path sh env, 'make', 'headers', chdir: cwd.to_path user_directory = options.sysroot + 'usr' user_directory.mkpath cp_r (cwd + 'usr/include'), (user_directory + 'include') end desc 'Build glibc' task :glibc, [:target] do |_, args| options = find_build_target GCC_VERSION, args source_directory = options.tools + "glibc-#{GLIBC_VERSION}" configure_options = [ '--prefix=/usr', "--host=#{options.target}", "--target=#{options.target}", "--build=#{options.build}", "--enable-kernel=#{KERNEL_VERSION}", "--with-headers=#{options.sysroot.realpath + 'usr/include'}", '--disable-nscd', '--disable-libquadmath', '--disable-libitm', '--disable-werror', 'libc_cv_forced_unwind=yes' ] bin = options.rootfs.realpath + 'bin' env = { 'PATH' => "#{bin}:#{ENV['PATH']}", 'MAKE' => 'make' # Otherwise it uses gnumake which can be different and too old. } cwd = source_directory.dirname + 'build-glibc' cwd.mkpath configure = source_directory.relative_path_from(cwd) +'./configure' sh env, configure.to_path, *configure_options, chdir: cwd.to_path sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path sh env, 'make', "install_root=#{options.sysroot.realpath}", 'install', chdir: cwd.to_path end desc 'Build stage 2 GCC' task :gcc2, [:target] do |_, args| options = find_build_target GCC_VERSION, args source_directory = options.tools + "gcc-#{GCC_VERSION}" cwd = options.tools + 'build-gcc' rm_rf cwd cwd.mkpath configure_options = [ "--prefix=#{options.rootfs.realpath}", "--with-sysroot=#{options.sysroot.realpath}", '--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}" ] flags = '-O2 -fPIC' env = { 'CFLAGS' => flags, 'CXXFLAGS' => flags, 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" } configure = source_directory.relative_path_from(cwd) + 'configure' sh env, configure.to_path, *configure_options, chdir: cwd.to_path sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path sh env, 'make', 'install', chdir: cwd.to_path end task :init, [:target] do |_, args| options = find_build_target GCC_VERSION, args 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 desc 'Build cross toolchain' task cross: [ 'cross:binutils', 'cross:gcc1', 'cross:headers', 'cross:kernel', 'cross:glibc', 'cross:gcc2', 'cross:init' ] do end