···11+use std::{path::{PathBuf, Path}, env, fs::{create_dir_all, remove_file}, io::{self, ErrorKind}, os::unix::fs::symlink, process::{Stdio, Command}};
22+33+fn main() {
44+ // This is the folder where a build script (this file) should place its output
55+ let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
66+ // This is the `runner` folder
77+ let runner_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
88+ // This folder contains Limine files such as `BOOTX64.EFI`
99+ let limine_dir = PathBuf::from(env::var("LIMINE_PATH").unwrap());
1010+1111+ // Symlink the out dir so we get a constant path to it
1212+ ensure_symlink(&out_dir, runner_dir.join("out_dir")).unwrap();
1313+1414+ // We will create an ISO file for our OS
1515+ // First we create a folder which will be used to generate the ISO
1616+ // We will use symlinks instead of copying to avoid unnecessary disk space used
1717+ let iso_dir = out_dir.join("iso_root");
1818+ create_dir_all(&iso_dir).unwrap();
1919+2020+ // Limine config will be in `limine.conf`
2121+ let limine_conf = iso_dir.join("limine.conf");
2222+ ensure_symlink(runner_dir.join("limine.conf"), limine_conf).unwrap();
2323+2424+ let boot_dir = iso_dir.join("boot");
2525+ create_dir_all(&boot_dir).unwrap();
2626+2727+ // Copy files from the Limine packaeg into `boot/limine`
2828+ let out_limine_dir = boot_dir.join("limine");
2929+ create_dir_all(&out_limine_dir).unwrap();
3030+3131+ for path in [
3232+ "limine-bios.sys",
3333+ "limine-bios-cd.bin",
3434+ "limine-uefi-cd.bin",
3535+ ] {
3636+ let from = limine_dir.join(path);
3737+ let to = out_limine_dir.join(path);
3838+ ensure_symlink(from, to).unwrap();
3939+ }
4040+4141+ // EFI/BOOT/BOOTX64.EFI is the executable loaded by UEFI firmware
4242+ // We will also copy BOOTIA32.EFI because xorisso will complain if it's not there
4343+ let efi_boot_dir = iso_dir.join("EFI/BOOT");
4444+ create_dir_all(&efi_boot_dir).unwrap();
4545+4646+ for efi_file in ["BOOTX64.EFI", "BOOTIA32.EFI"] {
4747+ ensure_symlink(limine_dir.join(efi_file), efi_boot_dir.join(efi_file)).unwrap();
4848+ }
4949+5050+ // We'll call the output iso `os.iso`
5151+ let output_iso = out_dir.join("os.iso");
5252+ // This command creates an ISO file from our `iso_root` folder.
5353+ // Symlinks will be read (the contents will be copied into the ISO file)
5454+ let status = Command::new("xorriso")
5555+ .arg("-as")
5656+ .arg("mkisofs")
5757+ .arg("--follow-links")
5858+ .arg("-b")
5959+ .arg(
6060+ out_limine_dir
6161+ .join("limine-bios-cd.bin")
6262+ .strip_prefix(&iso_dir)
6363+ .unwrap(),
6464+ )
6565+ .arg("-no-emul-boot")
6666+ .arg("-boot-load-size")
6767+ .arg("4")
6868+ .arg("-boot-info-table")
6969+ .arg("--efi-boot")
7070+ .arg(
7171+ out_limine_dir
7272+ .join("limine-uefi-cd.bin")
7373+ .strip_prefix(&iso_dir)
7474+ .unwrap(),
7575+ )
7676+ .arg("-efi-boot-part")
7777+ .arg("--efi-boot-image")
7878+ .arg("--protective-msdos-label")
7979+ .arg(iso_dir)
8080+ .arg("-o")
8181+ .arg(&output_iso)
8282+ .stderr(Stdio::inherit())
8383+ .stdout(Stdio::inherit())
8484+ .status()
8585+ .unwrap();
8686+8787+ assert!(status.success() || status.code().unwrap_or(-1) == 32);
8888+8989+ // This is needed to create a hybrid ISO that boots on both BIOS and UEFI. See https://github.com/limine-bootloader/limine/blob/v9.x/USAGE.md#biosuefi-hybrid-iso-creation
9090+ let status = Command::new("limine")
9191+ .arg("bios-install")
9292+ .arg(&output_iso)
9393+ .stderr(Stdio::inherit())
9494+ .stdout(Stdio::inherit())
9595+ .status()
9696+ .unwrap();
9797+9898+ assert!(status.success());
9999+100100+ let output_iso = output_iso.display();
101101+ println!("cargo:rustc-env=ISO={output_iso}");
102102+}
103103+104104+pub fn ensure_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
105105+ match remove_file(&link) {
106106+ Ok(()) => Ok(()),
107107+ Err(error) => match error.kind() {
108108+ ErrorKind::NotFound => Ok(()),
109109+ _ => Err(error),
110110+ },
111111+ }?;
112112+113113+ symlink(original, link)?;
114114+ Ok(())
115115+}
+6
runner/src/main.rs
···11+use std::env;
22+33+fn main() {
44+ let iso = env::var("ISO").unwrap();
55+ println!("ISO path: {iso:?}");
66+}