1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
//! Provides functions to create a bootable OS image from a kernel binary.
//!
//! This crate is mainly built as a binary tool. Run `cargo install bootimage` to install it.

#![warn(missing_docs)]

use args::{Args, RunnerArgs};
use std::{fmt, process};

mod args;
pub mod builder;
pub mod config;
mod help;

mod subcommand;

enum Command {
    NoSubcommand,
    Build(Args),
    Run(Args),
    Test(Args),
    Runner(RunnerArgs),
    Help,
    BuildHelp,
    RunHelp,
    TestHelp,
    CargoBootimageHelp,
    RunnerHelp,
    Version,
}

/// The entry point for the binaries.
///
/// We support two binaries, `bootimage` and `cargo-bootimage` that both just
/// call into this function.
///
/// This function is just a small wrapper around [`run`] that prints error messages
/// and exits with the correct exit code.
pub fn lib_main() {
    match run() {
        Err(err) => {
            eprintln!("Error: {}", err.message);
            process::exit(1);
        }
        Ok(Some(exit_code)) => {
            process::exit(exit_code);
        }
        Ok(None) => {}
    }
}

/// Run the invoked command.
///
/// This function parses the arguments and invokes the chosen subcommand.
///
/// On success, it optionally returns an exit code. This feature is used by the
/// `run` and `runner` subcommand to pass through the exit code of the invoked
/// run command.
pub fn run() -> Result<Option<i32>, ErrorMessage> {
    let command = args::parse_args()?;
    let none = |()| None;
    match command {
        Command::Build(args) => subcommand::build::build(args).map(none),
        Command::Run(args) => subcommand::run::run(args).map(Some),
        Command::Test(args) => subcommand::test::test(args).map(none),
        Command::Runner(args) => subcommand::runner::runner(args).map(Some),
        Command::Help => Ok(help::help()).map(none),
        Command::BuildHelp => Ok(help::build_help()).map(none),
        Command::CargoBootimageHelp => Ok(help::cargo_bootimage_help()).map(none),
        Command::RunHelp => Ok(help::run_help()).map(none),
        Command::RunnerHelp => Ok(help::runner_help()).map(none),
        Command::TestHelp => Ok(help::test_help()).map(none),
        Command::Version => Ok(println!("bootimage {}", env!("CARGO_PKG_VERSION"))).map(none),
        Command::NoSubcommand => Err(help::no_subcommand()),
    }
}

/// A simple error message that can be created from every type that implements `fmt::Display`.
///
/// We use this error type for the CLI interface, where text based, human readable error messages
/// make sense. For the library part of this crate, we use custom error enums.
pub struct ErrorMessage {
    /// The actual error message
    pub message: Box<dyn fmt::Display + Send>,
}

impl fmt::Debug for ErrorMessage {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.message.fmt(f)
    }
}

impl<T> From<T> for ErrorMessage
where
    T: fmt::Display + Send + 'static,
{
    fn from(err: T) -> Self {
        ErrorMessage {
            message: Box::new(err),
        }
    }
}