|
| 1 | +use std::{ |
| 2 | + fmt, io, |
| 3 | + path::{Path, PathBuf}, |
| 4 | + process::{self, Command}, |
| 5 | +}; |
| 6 | + |
| 7 | +pub struct Builder { |
| 8 | + kernel_manifest_path: PathBuf, |
| 9 | + kernel_metadata: cargo_metadata::Metadata, |
| 10 | +} |
| 11 | + |
| 12 | +impl Builder { |
| 13 | + pub fn new(manifest_path: Option<PathBuf>) -> Result<Self, BuilderError> { |
| 14 | + let kernel_manifest_path = |
| 15 | + manifest_path.unwrap_or(locate_cargo_manifest::locate_manifest()?); |
| 16 | + let kernel_metadata = cargo_metadata::MetadataCommand::new() |
| 17 | + .manifest_path(&kernel_manifest_path) |
| 18 | + .exec()?; |
| 19 | + Ok(Builder { |
| 20 | + kernel_manifest_path, |
| 21 | + kernel_metadata, |
| 22 | + }) |
| 23 | + } |
| 24 | + |
| 25 | + pub fn kernel_manifest_path(&self) -> &Path { |
| 26 | + &self.kernel_manifest_path |
| 27 | + } |
| 28 | + |
| 29 | + pub fn kernel_root(&self) -> &Path { |
| 30 | + self.kernel_manifest_path |
| 31 | + .parent() |
| 32 | + .expect("kernel manifest has no parent directory") |
| 33 | + } |
| 34 | + |
| 35 | + pub fn kernel_metadata(&self) -> &cargo_metadata::Metadata { |
| 36 | + &self.kernel_metadata |
| 37 | + } |
| 38 | + |
| 39 | + pub fn kernel_package(&self) -> Result<&cargo_metadata::Package, String> { |
| 40 | + let mut packages = self.kernel_metadata.packages.iter(); |
| 41 | + let kernel_package = packages.find(|p| &p.manifest_path == &self.kernel_manifest_path); |
| 42 | + kernel_package.ok_or(format!( |
| 43 | + "packages[manifest_path = `{}`]", |
| 44 | + &self.kernel_manifest_path.display() |
| 45 | + )) |
| 46 | + } |
| 47 | + |
| 48 | + pub fn build_kernel(&self, args: &[String], quiet: bool) -> Result<(), BuildKernelError> { |
| 49 | + if !quiet { |
| 50 | + println!("Building kernel"); |
| 51 | + } |
| 52 | + |
| 53 | + let cargo = std::env::var("CARGO").unwrap_or("cargo".to_owned()); |
| 54 | + let mut cmd = process::Command::new(cargo); |
| 55 | + cmd.arg("xbuild"); |
| 56 | + cmd.args(args); |
| 57 | + if !quiet { |
| 58 | + cmd.stdout(process::Stdio::inherit()); |
| 59 | + cmd.stderr(process::Stdio::inherit()); |
| 60 | + } |
| 61 | + let output = cmd.output().map_err(|err| BuildKernelError::Io { |
| 62 | + message: "failed to execute kernel build", |
| 63 | + error: err, |
| 64 | + })?;; |
| 65 | + if !output.status.success() { |
| 66 | + let mut help_command = process::Command::new("cargo"); |
| 67 | + help_command.arg("xbuild").arg("--help"); |
| 68 | + help_command.stdout(process::Stdio::null()); |
| 69 | + help_command.stderr(process::Stdio::null()); |
| 70 | + if let Ok(help_exit_status) = help_command.status() { |
| 71 | + if !help_exit_status.success() { |
| 72 | + return Err(BuildKernelError::XbuildNotFound); |
| 73 | + } |
| 74 | + } |
| 75 | + return Err(BuildKernelError::XbuildFailed { |
| 76 | + stderr: output.stderr, |
| 77 | + }); |
| 78 | + } |
| 79 | + |
| 80 | + Ok(()) |
| 81 | + } |
| 82 | + |
| 83 | + pub fn create_bootimage( |
| 84 | + &self, |
| 85 | + kernel_bin_path: &Path, |
| 86 | + output_bin_path: &Path, |
| 87 | + quiet: bool, |
| 88 | + ) -> Result<(), CreateBootimageError> { |
| 89 | + let metadata = self.kernel_metadata(); |
| 90 | + |
| 91 | + let bootloader_name = { |
| 92 | + let kernel_package = self |
| 93 | + .kernel_package() |
| 94 | + .map_err(|key| CreateBootimageError::CargoMetadataIncomplete { key })?; |
| 95 | + let mut dependencies = kernel_package.dependencies.iter(); |
| 96 | + let bootloader_package = dependencies |
| 97 | + .find(|p| p.rename.as_ref().unwrap_or(&p.name) == "bootloader") |
| 98 | + .ok_or(CreateBootimageError::BootloaderNotFound)?; |
| 99 | + bootloader_package.name.clone() |
| 100 | + }; |
| 101 | + let target_dir = metadata |
| 102 | + .target_directory |
| 103 | + .join("bootimage") |
| 104 | + .join(&bootloader_name); |
| 105 | + |
| 106 | + let bootloader_pkg = metadata |
| 107 | + .packages |
| 108 | + .iter() |
| 109 | + .find(|p| p.name == bootloader_name) |
| 110 | + .ok_or(CreateBootimageError::CargoMetadataIncomplete { |
| 111 | + key: format!("packages[name = `{}`", &bootloader_name), |
| 112 | + })?; |
| 113 | + let bootloader_root = bootloader_pkg.manifest_path.parent().ok_or( |
| 114 | + CreateBootimageError::BootloaderInvalid( |
| 115 | + "bootloader manifest has no target directory".into(), |
| 116 | + ), |
| 117 | + )?; |
| 118 | + let bootloader_features = |
| 119 | + { |
| 120 | + let resolve = metadata.resolve.as_ref().ok_or( |
| 121 | + CreateBootimageError::CargoMetadataIncomplete { |
| 122 | + key: "resolve".into(), |
| 123 | + }, |
| 124 | + )?; |
| 125 | + let bootloader_resolve = resolve |
| 126 | + .nodes |
| 127 | + .iter() |
| 128 | + .find(|n| n.id == bootloader_pkg.id) |
| 129 | + .ok_or(CreateBootimageError::CargoMetadataIncomplete { |
| 130 | + key: format!("resolve[\"{}\"]", bootloader_name), |
| 131 | + })?; |
| 132 | + bootloader_resolve.features.clone() |
| 133 | + }; |
| 134 | + let bootloader_target_triple = |
| 135 | + crate::cargo_config::default_target_triple(&bootloader_root, false) |
| 136 | + .map_err(CreateBootimageError::BootloaderInvalid)? |
| 137 | + .ok_or(CreateBootimageError::BootloaderInvalid(format!( |
| 138 | + "bootloader must have a default target" |
| 139 | + )))?; |
| 140 | + |
| 141 | + // build bootloader |
| 142 | + if !quiet { |
| 143 | + println!("Building bootloader"); |
| 144 | + } |
| 145 | + |
| 146 | + let cargo = std::env::var("CARGO").unwrap_or("cargo".to_owned()); |
| 147 | + let mut cmd = process::Command::new(cargo); |
| 148 | + cmd.arg("xbuild"); |
| 149 | + cmd.arg("--manifest-path"); |
| 150 | + cmd.arg(&bootloader_pkg.manifest_path); |
| 151 | + cmd.arg("--target-dir").arg(&target_dir); |
| 152 | + cmd.arg("--features") |
| 153 | + .arg(bootloader_features.as_slice().join(" ")); |
| 154 | + cmd.arg("--release"); |
| 155 | + cmd.current_dir(bootloader_root); |
| 156 | + cmd.env("KERNEL", kernel_bin_path); |
| 157 | + cmd.env_remove("RUSTFLAGS"); |
| 158 | + if !quiet { |
| 159 | + cmd.stdout(process::Stdio::inherit()); |
| 160 | + cmd.stderr(process::Stdio::inherit()); |
| 161 | + } |
| 162 | + let output = cmd.output().map_err(|err| CreateBootimageError::Io { |
| 163 | + message: "failed to execute bootloader build command", |
| 164 | + error: err, |
| 165 | + })?; |
| 166 | + if !output.status.success() { |
| 167 | + return Err(CreateBootimageError::BootloaderBuildFailed { |
| 168 | + stderr: output.stderr, |
| 169 | + }); |
| 170 | + } |
| 171 | + |
| 172 | + let bootloader_elf_path = target_dir |
| 173 | + .join(&bootloader_target_triple) |
| 174 | + .join("release") |
| 175 | + .join(&bootloader_name); |
| 176 | + |
| 177 | + let llvm_tools = llvm_tools::LlvmTools::new()?; |
| 178 | + let objcopy = llvm_tools |
| 179 | + .tool(&llvm_tools::exe("llvm-objcopy")) |
| 180 | + .ok_or(CreateBootimageError::LlvmObjcopyNotFound)?; |
| 181 | + |
| 182 | + // convert bootloader to binary |
| 183 | + let mut cmd = Command::new(objcopy); |
| 184 | + cmd.arg("-I").arg("elf64-x86-64"); |
| 185 | + cmd.arg("-O").arg("binary"); |
| 186 | + cmd.arg("--binary-architecture=i386:x86-64"); |
| 187 | + cmd.arg(&bootloader_elf_path); |
| 188 | + cmd.arg(&output_bin_path); |
| 189 | + let output = cmd.output().map_err(|err| CreateBootimageError::Io { |
| 190 | + message: "failed to execute llvm-objcopy command", |
| 191 | + error: err, |
| 192 | + })?; |
| 193 | + if !output.status.success() { |
| 194 | + return Err(CreateBootimageError::ObjcopyFailed { |
| 195 | + stderr: output.stderr, |
| 196 | + }); |
| 197 | + } |
| 198 | + |
| 199 | + Ok(()) |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +#[derive(Debug)] |
| 204 | +pub enum BuilderError { |
| 205 | + /// Failed to locate cargo manifest |
| 206 | + LocateCargoManifest(locate_cargo_manifest::LocateManifestError), |
| 207 | + /// Error while running `cargo metadata` |
| 208 | + CargoMetadata(cargo_metadata::Error), |
| 209 | +} |
| 210 | + |
| 211 | +impl fmt::Display for BuilderError { |
| 212 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 213 | + match self { |
| 214 | + BuilderError::LocateCargoManifest(err) => writeln!( |
| 215 | + f, |
| 216 | + "Could not find Cargo.toml file starting from current folder: {:?}", |
| 217 | + err |
| 218 | + ), |
| 219 | + BuilderError::CargoMetadata(err) => writeln!( |
| 220 | + f, |
| 221 | + "Error while running `cargo metadata` for current project: {:?}", |
| 222 | + err |
| 223 | + ), |
| 224 | + } |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +#[derive(Debug)] |
| 229 | +pub enum BuildKernelError { |
| 230 | + /// Could not find kernel package in cargo metadata, required for retrieving kernel crate name |
| 231 | + KernelPackageNotFound, |
| 232 | + /// An unexpected I/O error occurred |
| 233 | + Io { |
| 234 | + /// Desciption of the failed I/O operation |
| 235 | + message: &'static str, |
| 236 | + /// The I/O error that occured |
| 237 | + error: io::Error, |
| 238 | + }, |
| 239 | + XbuildNotFound, |
| 240 | + XbuildFailed { |
| 241 | + stderr: Vec<u8>, |
| 242 | + }, |
| 243 | + CargoConfigInvalid { |
| 244 | + path: PathBuf, |
| 245 | + error: String, |
| 246 | + }, |
| 247 | +} |
| 248 | + |
| 249 | +impl fmt::Display for BuildKernelError { |
| 250 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 251 | + match self { |
| 252 | + BuildKernelError::KernelPackageNotFound => { |
| 253 | + writeln!(f, "Could not find kernel package in cargo metadata, required for retrieving kernel crate name") |
| 254 | + } |
| 255 | + BuildKernelError::Io {message, error} => { |
| 256 | + writeln!(f, "I/O error: {}: {}", message, error) |
| 257 | + } |
| 258 | + BuildKernelError::XbuildNotFound => { |
| 259 | + writeln!(f, "Failed to run `cargo xbuild`. Perhaps it is not installed?\n\ |
| 260 | + Run `cargo install cargo-xbuild` to install it.") |
| 261 | + } |
| 262 | + BuildKernelError::XbuildFailed{stderr} => { |
| 263 | + writeln!(f, "Kernel build failed: {}", String::from_utf8_lossy(stderr)) |
| 264 | + } |
| 265 | + BuildKernelError::CargoConfigInvalid{path,error} => { |
| 266 | + writeln!(f, "Failed to read cargo config at {}: {}", path.display(), error) |
| 267 | + }, |
| 268 | + } |
| 269 | + } |
| 270 | +} |
| 271 | + |
| 272 | +#[derive(Debug)] |
| 273 | +pub enum CreateBootimageError { |
| 274 | + /// Could not find some required information in the `cargo metadata` output |
| 275 | + CargoMetadataIncomplete { |
| 276 | + /// The required key that was not found |
| 277 | + key: String, |
| 278 | + }, |
| 279 | + /// Bootloader dependency not found |
| 280 | + BootloaderNotFound, |
| 281 | + /// Bootloader dependency has not the right format |
| 282 | + BootloaderInvalid(String), |
| 283 | + BootloaderBuildFailed { |
| 284 | + stderr: Vec<u8>, |
| 285 | + }, |
| 286 | + /// An unexpected I/O error occurred |
| 287 | + Io { |
| 288 | + /// Desciption of the failed I/O operation |
| 289 | + message: &'static str, |
| 290 | + /// The I/O error that occured |
| 291 | + error: io::Error, |
| 292 | + }, |
| 293 | + /// There was a problem retrieving the `llvm-tools-preview` rustup component |
| 294 | + LlvmTools(llvm_tools::Error), |
| 295 | + /// The llvm-tools component did not contain the required `llvm-objcopy` executable |
| 296 | + LlvmObjcopyNotFound, |
| 297 | + /// The `llvm-objcopy` command failed |
| 298 | + ObjcopyFailed { |
| 299 | + stderr: Vec<u8>, |
| 300 | + }, |
| 301 | +} |
| 302 | + |
| 303 | +impl fmt::Display for CreateBootimageError { |
| 304 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 305 | + match self { |
| 306 | + CreateBootimageError::CargoMetadataIncomplete { key } => writeln!( |
| 307 | + f, |
| 308 | + "Could not find required key `{}` in cargo metadata output", |
| 309 | + key |
| 310 | + ), |
| 311 | + CreateBootimageError::BootloaderNotFound => { |
| 312 | + writeln!(f, "Bootloader dependency not found\n\n\ |
| 313 | + You need to add a dependency on a crate named `bootloader` in your Cargo.toml.") |
| 314 | + } |
| 315 | + CreateBootimageError::BootloaderInvalid(err) => writeln!( |
| 316 | + f, |
| 317 | + "The `bootloader` dependency has not the right format: {}", |
| 318 | + err |
| 319 | + ), |
| 320 | + CreateBootimageError::BootloaderBuildFailed { stderr } => writeln!( |
| 321 | + f, |
| 322 | + "Bootloader build failed:\n\n{}", |
| 323 | + String::from_utf8_lossy(stderr) |
| 324 | + ), |
| 325 | + CreateBootimageError::Io { message, error } => { |
| 326 | + writeln!(f, "I/O error: {}: {}", message, error) |
| 327 | + } |
| 328 | + CreateBootimageError::LlvmTools(err) => match err { |
| 329 | + llvm_tools::Error::NotFound => writeln!( |
| 330 | + f, |
| 331 | + "Could not find the `llvm-tools-preview` rustup component.\n\n\ |
| 332 | + You can install by executing `rustup component add llvm-tools-preview`." |
| 333 | + ), |
| 334 | + err => writeln!( |
| 335 | + f, |
| 336 | + "Failed to locate the `llvm-tools-preview` rustup component: {:?}", |
| 337 | + err |
| 338 | + ), |
| 339 | + }, |
| 340 | + CreateBootimageError::LlvmObjcopyNotFound => writeln!( |
| 341 | + f, |
| 342 | + "Could not find `llvm-objcopy` in the `llvm-tools-preview` rustup component." |
| 343 | + ), |
| 344 | + CreateBootimageError::ObjcopyFailed { stderr } => writeln!( |
| 345 | + f, |
| 346 | + "Failed to run `llvm-objcopy`: {}", |
| 347 | + String::from_utf8_lossy(stderr) |
| 348 | + ), |
| 349 | + } |
| 350 | + } |
| 351 | +} |
| 352 | + |
| 353 | +// from implementations |
| 354 | + |
| 355 | +impl From<locate_cargo_manifest::LocateManifestError> for BuilderError { |
| 356 | + fn from(err: locate_cargo_manifest::LocateManifestError) -> Self { |
| 357 | + BuilderError::LocateCargoManifest(err) |
| 358 | + } |
| 359 | +} |
| 360 | + |
| 361 | +impl From<cargo_metadata::Error> for BuilderError { |
| 362 | + fn from(err: cargo_metadata::Error) -> Self { |
| 363 | + BuilderError::CargoMetadata(err) |
| 364 | + } |
| 365 | +} |
| 366 | + |
| 367 | +impl From<llvm_tools::Error> for CreateBootimageError { |
| 368 | + fn from(err: llvm_tools::Error) -> Self { |
| 369 | + CreateBootimageError::LlvmTools(err) |
| 370 | + } |
| 371 | +} |
0 commit comments