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 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 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 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 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}