use crate::config::Config;
use cargo_metadata::Metadata;
use error::{BootloaderError, BuildKernelError, BuilderError, CreateBootimageError};
use std::{
path::{Path, PathBuf},
process,
};
mod bootloader;
mod disk_image;
pub mod error;
pub struct Builder {
manifest_path: PathBuf,
project_metadata: Option<Metadata>,
}
impl Builder {
pub fn new(manifest_path: Option<PathBuf>) -> Result<Self, BuilderError> {
let manifest_path = match manifest_path.or_else(|| {
std::env::var("CARGO_MANIFEST_DIR")
.ok()
.map(|dir| Path::new(&dir).join("Cargo.toml"))
}) {
Some(path) => path,
None => {
println!("WARNING: `CARGO_MANIFEST_DIR` env variable not set");
locate_cargo_manifest::locate_manifest()?
}
};
Ok(Builder {
manifest_path,
project_metadata: None,
})
}
pub fn manifest_path(&self) -> &Path {
&self.manifest_path
}
pub fn build_kernel(
&mut self,
args: &[String],
config: &Config,
quiet: bool,
) -> Result<Vec<PathBuf>, BuildKernelError> {
if !quiet {
println!("Building kernel");
}
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned());
let mut cmd = process::Command::new(&cargo);
cmd.args(&config.build_command);
cmd.args(args);
if !quiet {
cmd.stdout(process::Stdio::inherit());
cmd.stderr(process::Stdio::inherit());
}
let output = cmd.output().map_err(|err| BuildKernelError::Io {
message: "failed to execute kernel build",
error: err,
})?;
if !output.status.success() {
if config.build_command.starts_with(&["xbuild".into()]) {
let mut help_command = process::Command::new("cargo");
help_command.arg("xbuild").arg("--help");
help_command.stdout(process::Stdio::null());
help_command.stderr(process::Stdio::null());
if let Ok(help_exit_status) = help_command.status() {
if !help_exit_status.success() {
return Err(BuildKernelError::XbuildNotFound);
}
}
}
return Err(BuildKernelError::BuildFailed {
stderr: output.stderr,
});
}
let mut cmd = process::Command::new(cargo);
cmd.args(&config.build_command);
cmd.args(args);
cmd.arg("--message-format").arg("json");
let output = cmd.output().map_err(|err| BuildKernelError::Io {
message: "failed to execute kernel build with json output",
error: err,
})?;
if !output.status.success() {
return Err(BuildKernelError::BuildFailed {
stderr: output.stderr,
});
}
let mut executables = Vec::new();
for line in String::from_utf8(output.stdout)
.map_err(BuildKernelError::BuildJsonOutputInvalidUtf8)?
.lines()
{
let mut artifact =
json::parse(line).map_err(BuildKernelError::BuildJsonOutputInvalidJson)?;
if let Some(executable) = artifact["executable"].take_string() {
executables.push(PathBuf::from(executable));
}
}
Ok(executables)
}
pub fn create_bootimage(
&mut self,
kernel_manifest_path: &Path,
bin_path: &Path,
output_bin_path: &Path,
quiet: bool,
) -> Result<(), CreateBootimageError> {
let bootloader_build_config = bootloader::BuildConfig::from_metadata(
self.project_metadata()?,
kernel_manifest_path,
bin_path,
)?;
if !quiet {
println!("Building bootloader");
}
let mut cmd = bootloader_build_config.build_command();
if !quiet {
cmd.stdout(process::Stdio::inherit());
cmd.stderr(process::Stdio::inherit());
}
let output = cmd.output().map_err(|err| CreateBootimageError::Io {
message: "failed to execute bootloader build command",
error: err,
})?;
if !output.status.success() {
return Err(CreateBootimageError::BootloaderBuildFailed {
stderr: output.stderr,
});
}
let mut cmd = bootloader_build_config.build_command();
cmd.arg("--message-format").arg("json");
let output = cmd.output().map_err(|err| CreateBootimageError::Io {
message: "failed to execute bootloader build command with json output",
error: err,
})?;
if !output.status.success() {
return Err(CreateBootimageError::BootloaderBuildFailed {
stderr: output.stderr,
});
}
let mut bootloader_elf_path = None;
for line in String::from_utf8(output.stdout)
.map_err(CreateBootimageError::BuildJsonOutputInvalidUtf8)?
.lines()
{
let mut artifact =
json::parse(line).map_err(CreateBootimageError::BuildJsonOutputInvalidJson)?;
if let Some(executable) = artifact["executable"].take_string() {
if bootloader_elf_path
.replace(PathBuf::from(executable))
.is_some()
{
return Err(BootloaderError::BootloaderInvalid(
"bootloader has multiple executables".into(),
)
.into());
}
}
}
let bootloader_elf_path = bootloader_elf_path.ok_or_else(|| {
BootloaderError::BootloaderInvalid("bootloader has no executable".into())
})?;
disk_image::create_disk_image(&bootloader_elf_path, output_bin_path)?;
Ok(())
}
pub fn kernel_package_for_bin(
&mut self,
kernel_bin_name: &str,
) -> Result<Option<&cargo_metadata::Package>, cargo_metadata::Error> {
Ok(self.project_metadata()?.packages.iter().find(|p| {
p.targets
.iter()
.any(|t| t.name == kernel_bin_name && t.kind.iter().any(|k| k == "bin"))
}))
}
fn project_metadata(&mut self) -> Result<&Metadata, cargo_metadata::Error> {
if let Some(ref metadata) = self.project_metadata {
return Ok(metadata);
}
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(&self.manifest_path)
.exec()?;
Ok(self.project_metadata.get_or_insert(metadata))
}
}