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