Skip to content

Commit 9aa034c

Browse files
committed
Add a run subcommand (builds and invokes QEMU by default)
1 parent b7f87d6 commit 9aa034c

File tree

9 files changed

+140
-23
lines changed

9 files changed

+140
-23
lines changed

Readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ Configuration is done through a through a `[package.metadata.bootimage]` table i
2727
default-target = "" # This target is used if no `--target` is passed
2828
output = "bootimage.bin" # The output file name
2929
minimum-image-size = 0 # The minimum output file size (in MiB)
30+
# The command invoked on `bootimage run`
31+
# (the "{}" will be replaced with the path to the bootable disk image)
32+
run-command = ["qemu-system-x86_64", "-drive", "format=raw,file={}"]
3033

3134
[package.metadata.bootimage.bootloader]
3235
name = "bootloader" # The bootloader crate name

src/args.rs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,28 @@ pub(crate) fn parse_args() -> Command {
77
let first = args.next();
88
match first.as_ref().map(|s| s.as_str()) {
99
Some("build") => parse_build_args(args),
10+
Some("run") => match parse_build_args(args) {
11+
Command::Build(args) => Command::Run(args),
12+
Command::BuildHelp => Command::RunHelp,
13+
cmd => cmd,
14+
},
1015
Some("--help") | Some("-h") => Command::Help,
1116
Some("--version") => Command::Version,
1217
_ => Command::NoSubcommand,
1318
}
1419
}
1520

16-
fn parse_build_args<A>(args: A) -> Command where A: Iterator<Item=String>{
21+
fn parse_build_args<A>(args: A) -> Command
22+
where
23+
A: Iterator<Item = String>,
24+
{
1725
let mut manifest_path: Option<PathBuf> = None;
1826
let mut target: Option<String> = None;
1927
let mut release: Option<bool> = None;
2028
let mut update_bootloader: Option<bool> = None;
2129
let mut cargo_args = Vec::new();
30+
let mut run_args = Vec::new();
31+
let mut run_args_started = false;
2232
{
2333
fn set<T>(arg: &mut Option<T>, value: Option<T>) {
2434
let previous = mem::replace(arg, value);
@@ -30,6 +40,10 @@ fn parse_build_args<A>(args: A) -> Command where A: Iterator<Item=String>{
3040

3141
let mut arg_iter = args.into_iter();
3242
while let Some(arg) = arg_iter.next() {
43+
if run_args_started {
44+
run_args.push(arg);
45+
continue;
46+
}
3347
match arg.as_ref() {
3448
"--help" | "-h" => {
3549
return Command::BuildHelp;
@@ -54,10 +68,7 @@ fn parse_build_args<A>(args: A) -> Command where A: Iterator<Item=String>{
5468
}
5569
"--manifest-path" => {
5670
let next = arg_iter.next();
57-
set(
58-
&mut manifest_path,
59-
next.as_ref().map(|p| PathBuf::from(&p)),
60-
);
71+
set(&mut manifest_path, next.as_ref().map(|p| PathBuf::from(&p)));
6172
cargo_args.push(arg);
6273
if let Some(next) = next {
6374
cargo_args.push(next);
@@ -71,10 +82,13 @@ fn parse_build_args<A>(args: A) -> Command where A: Iterator<Item=String>{
7182
"--release" => {
7283
set(&mut release, Some(true));
7384
cargo_args.push(arg);
74-
},
85+
}
7586
"--update-bootloader" => {
7687
set(&mut update_bootloader, Some(true));
7788
}
89+
"--" => {
90+
run_args_started = true;
91+
}
7892
_ => {
7993
cargo_args.push(arg);
8094
}
@@ -83,7 +97,8 @@ fn parse_build_args<A>(args: A) -> Command where A: Iterator<Item=String>{
8397
}
8498

8599
Command::Build(Args {
86-
all_cargo: cargo_args,
100+
cargo_args,
101+
run_args,
87102
target,
88103
manifest_path,
89104
release: release.unwrap_or(false),
@@ -93,14 +108,16 @@ fn parse_build_args<A>(args: A) -> Command where A: Iterator<Item=String>{
93108

94109
pub struct Args {
95110
/// All arguments that are passed to cargo.
96-
pub all_cargo: Vec<String>,
97-
/// The manifest path (also present in `all_cargo`).
111+
pub cargo_args: Vec<String>,
112+
/// All arguments that are passed to the runner.
113+
pub run_args: Vec<String>,
114+
/// The manifest path (also present in `cargo_args`).
98115
manifest_path: Option<PathBuf>,
99-
/// The target triple (also present in `all_cargo`).
116+
/// The target triple (also present in `cargo_args`).
100117
target: Option<String>,
101-
/// The release flag (also present in `all_cargo`).
118+
/// The release flag (also present in `cargo_args`).
102119
release: bool,
103-
/// Whether the bootloader should be updated (not present in `all_cargo`).
120+
/// Whether the bootloader should be updated (not present in `cargo_args`).
104121
update_bootloader: bool,
105122
}
106123

@@ -124,7 +141,7 @@ impl Args {
124141
pub fn set_target(&mut self, target: String) {
125142
assert!(self.target.is_none());
126143
self.target = Some(target.clone());
127-
self.all_cargo.push("--target".into());
128-
self.all_cargo.push(target);
144+
self.cargo_args.push("--target".into());
145+
self.cargo_args.push(target);
129146
}
130147
}

src/build.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,20 @@ use xmas_elf;
1111
const BLOCK_SIZE: usize = 512;
1212
type KernelInfoBlock = [u8; BLOCK_SIZE];
1313

14-
pub(crate) fn build(mut args: Args) -> Result<(), Error> {
14+
pub(crate) fn build(args: Args) -> Result<(), Error> {
15+
let (args, config, metadata, out_dir) = common_setup(args)?;
16+
17+
build_impl(&args, &config, &metadata, &out_dir)
18+
}
19+
20+
pub(crate) fn run(args: Args) -> Result<(), Error> {
21+
let (args, config, metadata, out_dir) = common_setup(args)?;
22+
23+
build_impl(&args, &config, &metadata, &out_dir)?;
24+
run_impl(&args, &config)
25+
}
26+
27+
fn common_setup(mut args: Args) -> Result<(Args, Config, CargoMetadata, PathBuf), Error> {
1528
fn out_dir(args: &Args, metadata: &CargoMetadata) -> PathBuf {
1629
let target_dir = PathBuf::from(&metadata.target_directory);
1730
let mut out_dir = target_dir;
@@ -43,13 +56,22 @@ pub(crate) fn build(mut args: Args) -> Result<(), Error> {
4356

4457
let out_dir = out_dir(&args, &metadata);
4558

59+
Ok((args, config, metadata, out_dir))
60+
}
61+
62+
fn build_impl(
63+
args: &Args,
64+
config: &Config,
65+
metadata: &CargoMetadata,
66+
out_dir: &Path,
67+
) -> Result<(), Error> {
4668
let kernel = build_kernel(&out_dir, &args, &config, &metadata)?;
4769

4870
let kernel_size = kernel.metadata()?.len();
4971
let kernel_info_block = create_kernel_info_block(kernel_size);
5072

5173
if args.update_bootloader() {
52-
let mut bootloader_cargo_lock = out_dir.clone();
74+
let mut bootloader_cargo_lock = PathBuf::from(out_dir);
5375
bootloader_cargo_lock.push("bootloader");
5476
bootloader_cargo_lock.push("Cargo.lock");
5577

@@ -63,6 +85,25 @@ pub(crate) fn build(mut args: Args) -> Result<(), Error> {
6385
Ok(())
6486
}
6587

88+
fn run_impl(args: &Args, config: &Config) -> Result<(), Error> {
89+
let command = &config.run_command[0];
90+
let mut command = process::Command::new(command);
91+
for arg in &config.run_command[1..] {
92+
command.arg(
93+
arg.replace(
94+
"{}",
95+
config
96+
.output
97+
.to_str()
98+
.expect("output must be valid unicode"),
99+
),
100+
);
101+
}
102+
command.args(&args.run_args);
103+
command.status()?;
104+
Ok(())
105+
}
106+
66107
fn read_cargo_metadata(args: &Args) -> Result<CargoMetadata, cargo_metadata::Error> {
67108
cargo_metadata::metadata(args.manifest_path().as_ref().map(PathBuf::as_path))
68109
}
@@ -82,7 +123,7 @@ fn build_kernel(
82123

83124
// compile kernel
84125
println!("Building kernel");
85-
let exit_status = run_xargo_build(&env::current_dir()?, &args.all_cargo)?;
126+
let exit_status = run_xargo_build(&env::current_dir()?, &args.cargo_args)?;
86127
if !exit_status.success() {
87128
process::exit(1)
88129
}

src/config.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub struct Config {
88
pub output: PathBuf,
99
pub bootloader: BootloaderConfig,
1010
pub minimum_image_size: Option<u64>,
11+
pub run_command: Vec<String>,
1112
}
1213

1314
pub struct BootloaderConfig {
@@ -89,6 +90,18 @@ pub(crate) fn read_config(manifest_path: PathBuf) -> Result<Config, Error> {
8990
)))?
9091
}
9192
}
93+
("run-command", Value::Array(array)) => {
94+
let mut command = Vec::new();
95+
for value in array {
96+
match value {
97+
Value::String(s) => command.push(s),
98+
_ => Err(Error::Config(format!(
99+
"run-command must be a list of strings"
100+
)))?,
101+
}
102+
}
103+
config.run_command = Some(command);
104+
}
92105
(key, value) => Err(Error::Config(format!(
93106
"unexpected `package.metadata.bootimage` \
94107
key `{}` with value `{}`",
@@ -106,6 +119,7 @@ struct ConfigBuilder {
106119
output: Option<PathBuf>,
107120
bootloader: Option<BootloaderConfigBuilder>,
108121
minimum_image_size: Option<u64>,
122+
run_command: Option<Vec<String>>,
109123
}
110124

111125
#[derive(Default)]
@@ -131,6 +145,11 @@ impl Into<Config> for ConfigBuilder {
131145
output: self.output.unwrap_or(PathBuf::from("bootimage.bin")),
132146
bootloader: self.bootloader.unwrap_or(default_bootloader_config).into(),
133147
minimum_image_size: self.minimum_image_size,
148+
run_command: self.run_command.unwrap_or(vec![
149+
"qemu-system-x86_64".into(),
150+
"-drive".into(),
151+
"format=raw,file={}".into(),
152+
]),
134153
}
135154
}
136155
}

src/help/build_help.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Creates a bootable disk image from a Rust kernel
22

33
USAGE:
4-
bootimage build [BUILD_OPTIONS] Create a bootable disk image (see below)
4+
bootimage build [BUILD_OPTS] Create a bootable disk image
55

66
(for other forms of usage see `bootimage --help`)
77

8-
BUILD_OPTIONS:
8+
BUILD_OPTS:
99
--update-bootloader Update the bootloader dependency.
1010

1111
Any additional options are directly passed to `cargo build` (see
@@ -16,7 +16,7 @@ BUILD_OPTIONS:
1616
CONFIGURATION:
1717
The bootloader and the behavior of `bootimage build` can be configured
1818
through a `[package.metadata.bootimage]` table in the `Cargo.toml`. The
19-
following options are available:
19+
following options are available to configure the build:
2020

2121
[package.metadata.bootimage]
2222
default-target = "" This target is used if no `--target` is passed

src/help/help.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
Creates a bootable disk image from a Rust kernel
22

33
USAGE:
4-
bootimage [OPTIONS] Display help and version information
5-
bootimage build [BUILD_OPTIONS] Create a bootable disk image (see below)
4+
bootimage [OPTIONS] Help and version information
5+
bootimage build [BUILD_OPTS] Create a bootable disk image
6+
bootimage run [BUILD_OPTS] -- [RUN_OPTS] Build and run a disk image
67

78
OPTIONS:
89
-h, --help Prints help information and exit
910
---version Prints version information and exit
1011

11-
BUILD_OPTIONS:
12+
BUILD_OPTS:
1213
--update-bootloader Update the bootloader dependency.
1314

1415
Any additional options are directly passed to `cargo build` (see
@@ -17,3 +18,9 @@ BUILD_OPTIONS:
1718
disk image.
1819

1920
For configuration options see `bootimage build --help`.
21+
22+
RUN_OPTS:
23+
Any options are directly passed to the run command. Note that the run
24+
options must be separated from the build options by a "--".
25+
26+
For configuration options see `bootimage run --help`.

src/help/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::process;
22

33
const HELP: &str = include_str!("help.txt");
44
const BUILD_HELP: &str = include_str!("build_help.txt");
5+
const RUN_HELP: &str = include_str!("run_help.txt");
56

67
pub(crate) fn help() {
78
print!("{}", HELP);
@@ -11,6 +12,10 @@ pub(crate) fn build_help() {
1112
print!("{}", BUILD_HELP);
1213
}
1314

15+
pub(crate) fn run_help() {
16+
print!("{}", RUN_HELP);
17+
}
18+
1419
pub(crate) fn no_subcommand() -> ! {
1520
println!("Please invoke `bootimage` with a subcommand (e.g. `bootimage build`).");
1621
println!();

src/help/run_help.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Creates a bootable disk image from a Rust kernel
2+
3+
USAGE:
4+
bootimage run [BUILD_OPTS] -- [RUN_OPTS] Build and run a disk image
5+
6+
(for other forms of usage see `bootimage --help`)
7+
(for BUILD_OPTS see `bootimage build --help`)
8+
9+
RUN_OPTS:
10+
Any options are directly passed to the run command. Note that the run
11+
options must be separated from the build options by a "--".
12+
13+
CONFIGURATION:
14+
The behavior of `bootimage run` can be configured through a
15+
`[package.metadata.bootimage]` table in the `Cargo.toml`. The
16+
following options are available to configure run behavior:
17+
18+
[package.metadata.bootimage]
19+
# The command invoked on `bootimage run`
20+
# (the "{}" will be replaced with the path to the bootable disk image)
21+
run-command = ["qemu-system-x86_64", "-drive", "format=raw,file={}"]

src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ mod help;
1414
enum Command {
1515
NoSubcommand,
1616
Build(Args),
17+
Run(Args),
1718
Help,
1819
BuildHelp,
20+
RunHelp,
1921
Version,
2022
}
2123

@@ -59,8 +61,10 @@ fn run() -> Result<(), Error> {
5961
match command {
6062
Command::NoSubcommand => help::no_subcommand(),
6163
Command::Build(args) => build::build(args),
64+
Command::Run(args) => build::run(args),
6265
Command::Help => Ok(help::help()),
6366
Command::BuildHelp => Ok(help::build_help()),
67+
Command::RunHelp => Ok(help::run_help()),
6468
Command::Version => Ok(println!("bootimage {}", env!("CARGO_PKG_VERSION"))),
6569
}
6670
}

0 commit comments

Comments
 (0)