require "tmpdir" project_name "HOS" path_prepend "x86_64-elf-gcc/bin" configure do rscons "x86_64-elf-gcc.rb", "-b", "#{build_dir}/x86_64-elf-gcc" check_d_compiler "ldc2", use: "ldc2" check_c_compiler "x86_64-w64-mingw32-gcc", use: "x86_64-w64-mingw32-gcc" check_c_compiler "x86_64-elf-gcc", use: "x86_64-elf-gcc" check_c_compiler check_program "mformat", on_fail: "Install the mtools package" check_program "parted" check_cfg package: "freetype2", on_fail: "Install libfreetype-dev", use: "freetype" sh %w[git submodule update --init] end # Kernel default font size. KFONT_SIZE = 18 # One kilobyte. KB = 1024 # One megabyte. MB = 1024 * 1024 class HulkBinObj < Builder def run(options) @cache.mkdir_p(File.dirname(@target)) File.write(@target, <#{@target}", nil) @cache.register_build(@target, nil, @sources, @env) end true end end # Create a GPT disk image with an EFI partition containing the EFI image. class Image < Builder def run(options) unless @cache.up_to_date?(@target, nil, @sources, @env) print_run_message("Creating disk image #{@target}", nil) efi_image_size = File.stat(@sources.first).size efi_image_size_mb = (efi_image_size + MB - 1) / MB partition_size_mb = efi_image_size_mb + 1 empty_mb = "\0".b * MB File.binwrite(@target, empty_mb * partition_size_mb) system(*%W[mformat -i #{@target} ::]) system(*%W[mmd -i #{@target} ::/EFI]) system(*%W[mmd -i #{@target} ::/EFI/BOOT]) system(*%W[mcopy -i #{@target} #{@sources.first} ::/EFI/BOOT/BOOTX64.EFI]) partition_contents = File.binread(@target) disk_image = empty_mb + partition_contents + empty_mb File.binwrite(@target, disk_image) system(*%W[parted --script #{@target} mklabel gpt mkpart HOS fat32 1MiB #{partition_size_mb + 1}MiB]) @cache.register_build(@target, nil, @sources, @env) end true end end class CheckThreadLocal < Builder def run(options) map_file = File.binread(@sources.first) if map_file =~ /\.(tdata|tbss)\b/ $stderr.puts "Error: found thread-local data in #{@sources.first}" false else true end end end class FontGen < Builder def run(options) if @command finalize_command else fontgen = @vars["fontgen"] @sources += [fontgen] command = %W[#{fontgen} #{@sources.first} #{KFONT_SIZE} #{@target}] standard_command("FontGen #{@target}", command, {}) end end end fontgen_env = env "fontgen", use: "freetype" do |env| env.Program("^/fontgen", glob("src/fontgen/**/*.c")) end hulk_env = env "hulk", use: %w[ldc2 x86_64-elf-gcc] do |env| env.add_builder(FontGen) env.add_builder(CheckThreadLocal) env.FontGen("^/src/hulk/kfont.d", "font/Hack-Regular.ttf", "fontgen" => fontgen_env.expand("^/fontgen")) env["sources"] = glob("src/hulk/**/*.d") env["sources"] << "^/src/hulk/kfont.d" cpu_attrs = %w[ -avx -avx2 -avx512bf16 -avx512bitalg -avx512bw -avx512cd -avx512dq -avx512er -avx512f -avx512ifma -avx512pf -avx512vbmi -avx512vbmi2 -avx512vl -avx512vnni -avx512vp2intersect -avx512vpopcntdq -sse -sse-unaligned-mem -sse2 -sse3 -sse4.1 -sse4.2 -sse4a -ssse3 ] env["DFLAGS"] += %W[-mtriple=x86_64-unknown-elf -mattr=#{cpu_attrs.join(",")} --betterC -release -O3 --wi --enable-cross-module-inlining -code-model=large] env["D_IMPORT_PATH"] += %w[src] env["D_IMPORT_PATH"] << env.expand("^/src") env["LD"] = "x86_64-elf-gcc" env["LDFLAGS"] += %w[-nostdlib -Tsrc/hulk/hulk.ld -Wl,-Map,${_TARGET}.map] env["LDCMD"] = %w[${LD} -o ${_TARGET} ${LDFLAGS} ${_SOURCES} ${LIBDIRPREFIX}${LIBPATH} ${LIBLINKPREFIX}${LIBS}] env["OBJDUMP"] = "x86_64-elf-objdump" env["OBJCOPY"] = "x86_64-elf-objcopy" env.Program("^/hulk.elf", "${sources}") env.produces("^/hulk.elf", "^/hulk.elf.map") env.CheckThreadLocal(:hulk_map_check, "^/hulk.elf.map") env.depends("^/hulk.elf", "src/hulk/hulk.ld") env["SIZE"] = "x86_64-elf-size" env.Size("^/hulk.size", "^/hulk.elf") env.Disassemble("^/hulk.txt", "^/hulk.elf") env.Command("^/hulk.bin", "^/hulk.elf", "CMD" => %W[${OBJCOPY} -O binary ${_SOURCES} ${_TARGET}], "CMD_DESC" => "Convert ELF to binary:") env.add_build_hook do |builder| if builder.target.end_with?(".o") env.Disassemble("#{builder.target}.txt", builder.target) end end end hello_env = env "hello", use: %w[ldc2 x86_64-w64-mingw32-gcc] do |env| env.add_builder(Image) env.add_builder(CheckThreadLocal) env.add_builder(HulkBinObj) env["sources"] = glob("src/hello/**/*.d") env["sources"] += %w[ src/hulk/serial.d src/hulk/writef.d ] env["sources"] += glob("uefi-d/source/**/*.d") env.HulkBinObj("^/hulk_bin.S", hulk_env.expand("^/hulk.bin")) env.Object("^/hulk_bin.o", "^/hulk_bin.S") env.depends("^/hulk_bin.o", hulk_env.expand("^/hulk.bin")) env["sources"] << "^/hulk_bin.o" env["DFLAGS"] += %w[-mtriple=x86_64-unknown-windows-coff --betterC -release -O3 --wi --enable-cross-module-inlining] env["D_IMPORT_PATH"] += %w[src uefi-d/source] env["LD"] = "x86_64-w64-mingw32-gcc" env["LDFLAGS"] += %w[-nostdlib -Wl,-dll -shared -Wl,--subsystem,10 -e efi_main -Wl,-Map,${_TARGET}.map] env["LDCMD"] = %w[${LD} -o ${_TARGET} ${LDFLAGS} ${_SOURCES} ${LIBDIRPREFIX}${LIBPATH} ${LIBLINKPREFIX}${LIBS}] env["OBJDUMP"] = "x86_64-w64-mingw32-objdump" env.Program("^/HOS.EFI", "${sources}") env.produces("^/HOS.EFI", "^/HOS.EFI.map") env.CheckThreadLocal(:hos_map_check, "^/HOS.EFI.map") env["SIZE"] = "x86_64-w64-mingw32-size" env.Size("^/HOS.size", "^/HOS.EFI") env.Disassemble("^/HOS.txt", "^/HOS.EFI") env.Image("^/HOS.img", "^/HOS.EFI") end task "run", desc: "Run HOS in QEMU" do FileUtils.rm_rf("qemu") FileUtils.mkdir_p("qemu") img = hello_env.expand("^/HOS.img") FileUtils.cp(img, "qemu") ovmf = "OVMF.fd" if File.exist?("/usr/share/edk2/x64/OVMF.fd") ovmf = "/usr/share/edk2/x64/OVMF.fd" end sh %W[ qemu-system-x86_64 -machine q35 -cpu max -serial file:qemu/serial.out -bios #{ovmf} -drive file=qemu/HOS.img,format=raw -device qemu-xhci -device usb-tablet] end # See README.md for how to set up VirtualBox for HOS. task "mk-vmdk", desc: "Create VirtualBox VMDK virtual drive for HOS" do sh %W[VBoxManage internalcommands createrawvmdk -filename HOS.vmdk -rawdisk #{File.expand_path(hello_env.expand("^/HOS.img"))}] end # See README.md for how to set up VirtualBox for HOS. task "run-vb", desc: "Run HOS in VirtualBox" do sh %W[VBoxManage startvm HOS] end