limage/
builder.rs

1use crate::config::Config;
2use std::{path::{Path, PathBuf}, process::Stdio};
3use cargo_metadata::Metadata;
4use thiserror::Error;
5
6pub fn build(config: &Config) -> Result<i32, BuildError> {
7    let mut builder = Builder::new(None)?;
8    builder.build(&config, &None)
9}
10
11pub struct Builder {
12    manifest_path: PathBuf,
13    project_metadata: Option<Metadata>
14}
15
16impl Builder {
17    pub fn new(manifest_path: Option<PathBuf>) -> Result<Self, BuildError> {
18        let manifest_path = match manifest_path.or_else(|| {
19            std::env::var("CARGO_MANIFEST_DIR")
20                .ok()
21                .map(|dir| Path::new(&dir).join("Cargo.toml"))
22        }) {
23            Some(path) => path,
24            None => {
25                println!("WARNING: `CARGO_MANIFEST_DIR` env variable not set");
26                locate_cargo_manifest::locate_manifest()?
27            }
28        };
29
30        Ok(Builder {
31            manifest_path,
32            project_metadata: None,
33        })
34    }
35
36    pub fn manifest_path(&self) -> &Path {
37        &self.manifest_path
38    }
39
40    pub fn project_metadata(&mut self) -> Result<&Metadata, cargo_metadata::Error> {
41        if let Some(ref metadata) = self.project_metadata {
42            return Ok(metadata);
43        }
44        let metadata = cargo_metadata::MetadataCommand::new()
45            .manifest_path(&self.manifest_path)
46            .exec()?;
47        Ok(self.project_metadata.get_or_insert(metadata))
48    }
49    
50    pub fn build(&mut self, config: &Config, bin_path: &Option<PathBuf>) -> Result<i32, BuildError> {
51        self.prepare_ovmf_files()?;
52        self.prepare_limine_files()?;
53        self.copy_kernel(&bin_path)?;
54        self.create_limine_iso(&config)?;
55
56        Ok(0)
57    }
58
59    fn prepare_ovmf_files(&self) -> Result<(), BuildError> {
60        // println!("BUILD STEP 1/4: Preparing OVMF firmware files");
61
62        std::fs::create_dir_all("./target/ovmf").unwrap();
63        
64        self.prepare_ovmf_file(
65            &format!("https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/ovmf-code-{}.fd", "x86_64"), 
66            &format!("target/ovmf/ovmf-code-{}.fd", "x86_64")
67        )?;
68        self.prepare_ovmf_file(
69            &format!("https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/ovmf-vars-{}.fd", "x86_64"),
70            &format!("target/ovmf/ovmf-vars-{}.fd", "x86_64")
71        )?;
72        Ok(())
73    }
74
75    fn prepare_ovmf_file(&self, url: &str, path: &str) -> Result<(), BuildError> {
76        std::process::Command::new("curl")
77            .arg("-Lo")
78            .arg(path)
79            .arg(url)
80            .stdout(Stdio::piped())
81            .output()
82            .map_err(|_| BuildError::DownloadOvmfFirmwareFailed)?;
83        Ok(())
84    }
85
86    fn prepare_limine_files(&self) -> Result<(), BuildError> {
87        // println!("BUILD STEP 2/4: Preparing Limine bootloader files");
88
89        std::fs::create_dir_all("./target").unwrap();
90        
91        self.clone_limine_binary()?;
92        self.copy_limine_config()?;
93        self.copy_limine_binary()?;
94        Ok(())
95    }
96
97    fn clone_limine_binary(&self) -> Result<(), BuildError> {
98        if !std::path::Path::new("./target/limine").exists() {
99            std::fs::create_dir_all("./target/limine").unwrap();
100            
101            std::process::Command::new("git")
102                .arg("clone")
103                .arg("https://github.com/limine-bootloader/limine.git")
104                .arg("--branch=v8.x-binary")
105                .arg("--depth=1")
106                .arg("target/limine")
107                .stdout(Stdio::piped())
108                .output()
109                .map_err(|_| BuildError::CloneLimineBinaryFailed)?;
110        }
111        Ok(())
112    }
113
114    fn copy_limine_config(&self) -> Result<(), BuildError> {
115        std::fs::create_dir_all("./target/iso_root/boot/limine").unwrap();
116        std::fs::copy("./limine.conf", "./target/iso_root/boot/limine/limine.conf")
117            .map_err(|_| BuildError::CopyLimineConfigFailed)?;
118        Ok(())
119    }
120
121    fn copy_limine_binary(&self) -> Result<(), BuildError> {
122        std::fs::create_dir_all("./target/iso_root/EFI/BOOT").unwrap();
123        
124        std::fs::copy("target/limine/limine-bios.sys", "target/iso_root/boot/limine/limine-bios.sys")
125            .map_err(|_| BuildError::CopyLimineBinaryFailed)?;
126        std::fs::copy("target/limine/limine-bios-cd.bin", "target/iso_root/boot/limine/limine-bios-cd.bin")
127            .map_err(|_| BuildError::CopyLimineBinaryFailed)?;
128        std::fs::copy("target/limine/limine-uefi-cd.bin", "target/iso_root/boot/limine/limine-uefi-cd.bin")
129            .map_err(|_| BuildError::CopyLimineBinaryFailed)?;
130        
131        std::fs::copy("target/limine/BOOTX64.EFI", "target/iso_root/EFI/BOOT/BOOTX64.EFI")
132            .map_err(|_| BuildError::CopyLimineBinaryFailed)?;
133        std::fs::copy("target/limine/BOOTIA32.EFI", "target/iso_root/EFI/BOOT/BOOTIA32.EFI")
134            .map_err(|_| BuildError::CopyLimineBinaryFailed)?;
135        Ok(())
136    }
137
138    fn copy_kernel(&mut self, bin_path: &Option<PathBuf>) -> Result<(), BuildError> {
139        // println!("BUILD STEP 3/4: Copying kernel binary to the ISO directory");
140
141        std::fs::create_dir_all("target/iso_root/boot/kernel")
142            .map_err(|_| BuildError::CreateDirectoryFailed)?;
143
144        let kernel_binary = if let Some(path) = bin_path {
145            path.clone()
146        } else {
147            PathBuf::from("target/x86_64-unknown-none/debug/kernel")
148        };
149        
150        std::fs::copy(
151            &kernel_binary,
152            "target/iso_root/boot/kernel/kernel"
153        ).map_err(|_| BuildError::CopyKernelBinaryFailed)?;
154
155        Ok(())
156    }
157
158    fn create_limine_iso(&self, config: &Config) -> Result<(), BuildError> {
159        // println!("BUILD STEP 4/4: Creating the Limine ISO");
160
161        if let Some(parent) = Path::new(&config.image_path).parent() {
162            std::fs::create_dir_all(parent)
163                .map_err(|_| BuildError::CreateDirectoryFailed)?;
164        }
165
166        self.create_raw_iso(&config)?;
167        self.install_limine_to_iso(&config)?;
168        Ok(())
169    }
170
171    fn create_raw_iso(&self, config: &Config) -> Result<(), BuildError> {
172        std::process::Command::new("xorriso")
173            .arg("-as")
174            .arg("mkisofs")
175            .arg("-b").arg("boot/limine/limine-bios-cd.bin")
176            .arg("-no-emul-boot").arg("-boot-load-size").arg("4").arg("-boot-info-table")
177            .arg("--efi-boot").arg("boot/limine/limine-uefi-cd.bin")
178            .arg("-efi-boot-part").arg("--efi-boot-image").arg("--protective-msdos-label")
179            .arg("target/iso_root")
180            .arg("-o").arg(config.image_path.clone())
181            .stdout(Stdio::piped())
182            .output()
183            .map_err(|_| BuildError::CreateLimineIsoFailed)?;
184        Ok(())
185    }
186
187    fn install_limine_to_iso(&self, config: &Config) -> Result<(), BuildError> {
188        std::process::Command::new("target/limine/limine")
189            .arg("bios-install")
190            .arg(config.image_path.clone())
191            .stdout(Stdio::piped())
192            .output()
193            .map_err(|_| BuildError::InstallLimineToIsoFailed)?;
194        Ok(())
195    }
196}
197
198#[derive(Debug, Error)]
199pub enum BuildError {
200    #[error("Failed to download OVMF firmware")]
201    DownloadOvmfFirmwareFailed,
202
203    #[error("Failed to copy limine.conf")]
204    CopyLimineConfigFailed,
205
206    #[error("Failed to copy limine binary file(s)")]
207    CopyLimineBinaryFailed,
208
209    #[error("Could not find Cargo.toml file starting from current folder: {0:?}")]
210    LocateCargoManifest(#[from] locate_cargo_manifest::LocateManifestError),
211    
212    #[error("Failed to build the kernel through cargo")]
213    CargoBuildFailed,
214
215    #[error("Failed to create the Limine ISO")]
216    CreateLimineIsoFailed,
217
218    #[error("Failed to clone Limine binary repository")]
219    CloneLimineBinaryFailed,
220
221    #[error("Failed to copy kernel binary")]
222    CopyKernelBinaryFailed,
223
224    #[error("Failed to create directory")]
225    CreateDirectoryFailed,
226
227    #[error("Failed to install Limine to the ISO")]
228    InstallLimineToIsoFailed,
229
230    #[error("Failed to retrieve cargo metadata")]
231    CargoMetadataFailed(#[from] cargo_metadata::Error),
232}