diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9ed6fec..0000000 --- a/.travis.yml +++ /dev/null @@ -1,46 +0,0 @@ -language: rust - -sudo: false - -notifications: - email: - on_success: never - on_failure: change - -rust: - - nightly - - stable - - beta - -os: - - linux - - osx - - windows - -cache: - cargo: true - directories: - - $HOME/Library/Caches/Homebrew - -addons: - apt: - packages: - - qemu-system-x86 - homebrew: - packages: - - qemu - -install: - - if [ $TRAVIS_OS_NAME = windows ]; then choco install qemu; export PATH="/c/Program Files/qemu:$PATH"; fi - -before_script: - - rustup component add rust-src - - (test -x $HOME/.cargo/bin/cargo-update-installed || cargo install cargo-update-installed) - - (test -x $HOME/.cargo/bin/cargo-xbuild || cargo install cargo-xbuild) - - cargo update-installed - -script: -- cargo test -- cargo install --debug --force -- git clone https://github.com/phil-opp/blog_os.git --branch post-10 -- if [ $TRAVIS_RUST_VERSION = nightly ]; then bootimage test --manifest-path blog_os/Cargo.toml; fi diff --git a/Cargo.lock b/Cargo.lock index 5d402a4..56481c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,45 +8,19 @@ dependencies = [ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "backtrace" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace-sys 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "bootimage" version = "0.6.6" dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_metadata 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "json 0.11.13 (registry+https://github.com/rust-lang/crates.io-index)", + "llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "locate-cargo-manifest 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "byteorder" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cargo_metadata" version = "0.7.4" @@ -59,11 +33,6 @@ dependencies = [ "serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "cc" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cfg-if" version = "0.1.4" @@ -116,16 +85,13 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "failure" -version = "0.1.5" +name = "itoa" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "itoa" -version = "0.4.2" +name = "json" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -138,6 +104,19 @@ name = "libc" version = "0.2.42" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "locate-cargo-manifest" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "json 0.11.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memoffset" version = "0.2.1" @@ -193,11 +172,6 @@ dependencies = [ "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rustc-demangle" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "scopeguard" version = "0.3.3" @@ -273,45 +247,9 @@ dependencies = [ "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "winapi" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "xmas-elf" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "zero" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [metadata] "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" -"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" -"checksum backtrace-sys 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "bff67d0c06556c0b8e6b5f090f0eac52d950d9dfd1d35ba04e4ca3543eaf6a7e" -"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum cargo_metadata 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "178d62b240c34223f265a4c1e275e37d62da163d421fc8d7f7e3ee340f803c57" -"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275" "checksum cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efe5c877e17a9c717a0bf3613b2709f723202c4e4675cc8f12926ded29bcb17e" "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" @@ -319,10 +257,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" "checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" -"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606" +"checksum json 0.11.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9ad0485404155f45cce53a40d4b2d6ac356418300daed05273d9e26f91c390be" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" +"checksum llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" +"checksum locate-cargo-manifest 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d87d2c1ca6d2636268f961e70b024866db2d8244a46687527e7ed1a9360b8de" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" @@ -330,7 +270,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035" "checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473" "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" -"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" @@ -341,8 +280,3 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum toml 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c5890a989fa47ecdc7bcb4c63a77a82c18f306714104b1decfd722db17b39e" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" -"checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/Cargo.toml b/Cargo.toml index ff9eb3f..efc90fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,19 +5,16 @@ license = "MIT/Apache-2.0" name = "bootimage" version = "0.6.6" repository = "https://github.com/rust-osdev/bootimage" +edition = "2018" [dependencies] -byteorder = "1.3.1" rayon = "1.0" toml = "0.5.0" wait-timeout = "0.2" -xmas-elf = "0.6.2" +llvm-tools = "0.1.1" +locate-cargo-manifest = "0.1.0" +json = "0.11.13" [dependencies.cargo_metadata] version = "0.7.4" default-features = false - -[dependencies.failure] -version = "0.1.5" -default-features = false -features = ["std"] diff --git a/Changelog.md b/Changelog.md index f0fa753..2b3c486 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,29 @@ + +## Breaking + +- Rewrite for new bootloader build system + - Compatible with bootloader 0.5.0+ +- Remove the following config options: `output`, `bootloader.*`, `minimum_image_size`, and `package_filepath` + - The bootloader is now fully controlled through cargo dependencies. + - For using a bootloader crate with name different than `bootloader` use [cargo's rename feature](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml). +- Remove support for `bootloader_precompiled` + - The `bootloader` crate compiles fine on all architectures for some time and should be prefered +- Require the `llvm-tools-preview` rustup component +- Pass the QEMU exit code in `bootimage run` + +## Other + +- Add support for default targets declared in `.cargo/config` files +- Add a `cargo-bootimage` executable that is equivalent to `bootimage build` and can be used as cargo subcommand (`cargo bootimage`) +- Add a new `bootimage runner` subcommand that can be used as `target.[…].runner` in `.cargo/config` files +- Make test timeout configurable and increase default to 5 minutes +- Move crate to 2018 edition +- Refactor and cleanup the code +- Remove the dependency on `failure` + - Use a custom `ErrorMessage` type instead +- Add a new `run-args` config key +- Add a new `--quiet` argument to suppress output + # 0.6.6 - Update dependencies diff --git a/Readme.md b/Readme.md index 817eb30..03fa576 100644 --- a/Readme.md +++ b/Readme.md @@ -10,40 +10,83 @@ Creates a bootable disk image from a Rust OS kernel. ## Usage -First you need to add a dependency on the `bootloader` crate: +First you need to add a dependency on the [`bootloader`](https://github.com/rust-osdev/bootloader) crate: ```toml # in your Cargo.toml [dependencies] -bootloader = "0.2.0-alpha" +bootloader = "0.5.0" ``` +**Note**: At least bootloader version `0.5.0` is required. + +If you want to use a custom bootloader with a different name, you can use Cargo's [rename functionality](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml). + +### Building + Now you can build the kernel project and create a bootable disk image from it by running: ``` -> bootimage build --target your_custom_target.json [other_args] +bootimage build --target your_custom_target.json [other_args] +``` + +The command will invoke [`cargo xbuild`](https://github.com/rust-osdev/cargo-xbuild), forwarding all passed options. Then it will build the specified bootloader together with the kernel to create a bootable disk image. + +If you prefer a cargo subcommand, you can use the equivalent `cargo bootimage` command: + +``` +cargo bootimage --target your_custom_target.json [other_args] +``` + +### Running + +To run your kernel in QEMU, you can use `bootimage run`: + +``` +bootimage run --target your_custom_target.json [other_args] -- [qemu args] +``` + +All arguments after `--` are passed to QEMU. If you want to use a custom run command, see the _Configuration_ section below. + +If you prefer working directly with cargo, you can use `bootimage runner` as a custom runner in your `.cargo/config`: + +```toml +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" ``` -The command will invoke [`cargo xbuild`](https://github.com/rust-osdev/cargo-xbuild), forwarding all passed options. Then it will download and build a bootloader, by default the [rust-osdev/bootloader](https://github.com/rust-osdev/bootloader). Finally, it combines the kernel and the bootloader into a bootable disk image. +Now you can run your kernel through `cargo xrun --target […]`. ## Configuration -Configuration is done through a through a `[package.metadata.bootimage]` table in the `Cargo.toml`. The following options are available: +Configuration is done through a through a `[package.metadata.bootimage]` table in the `Cargo.toml` of your kernel. The following options are available: ```toml [package.metadata.bootimage] - default-target = "" # This target is used if no `--target` is passed - output = "bootimage.bin" # The output file name - minimum-image-size = 0 # The minimum output file size (in MiB) - # The command invoked on `bootimage run` + # This target is used if no `--target` is passed + default-target = "" + + # The command invoked on `bootimage run` or `bootimage runner` # (the "{}" will be replaced with the path to the bootable disk image) run-command = ["qemu-system-x86_64", "-drive", "format=raw,file={}"] - [package.metadata.bootimage.bootloader] - name = "bootloader" # The bootloader crate name - target = "x86_64-bootloader.json" # Target triple for compiling the bootloader + # Additional arguments passed to the runner on `bootimage run` or `bootimage runner` + # (this is useful when you want to add some arguments to the default QEMU command) + run-args = [] + + # The timeout for running an integration test through `bootimage test` in seconds + test-timeout = 300 ``` ## License -Dual-licensed under MIT or the Apache License (Version 2.0). + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..f750360 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,200 @@ +# Documentation: https://aka.ms/yaml + +trigger: + batch: true + branches: + include: + # This is where pull requests from "bors r+" are built. + - staging + # This is where pull requests from "bors try" are built. + - trying + # Build pull requests. + - master + +jobs: +- job: build + displayName: Build + strategy: + matrix: + linux: + image_name: 'ubuntu-16.04' + rustup_toolchain: stable + mac: + image_name: 'macos-10.13' + rustup_toolchain: stable + windows: + image_name: 'vs2017-win2016' + rustup_toolchain: stable + + pool: + vmImage: $(image_name) + + steps: + - bash: | + echo "Hello world from $AGENT_NAME running on $AGENT_OS" + echo "Reason: $BUILD_REASON" + case "$BUILD_REASON" in + "Manual") echo "$BUILD_REQUESTEDFOR manually queued the build." ;; + "PullRequest") echo "This is a CI build for a pull request on $BUILD_REQUESTEDFOR." ;; + "IndividualCI") echo "This is a CI build for $BUILD_REQUESTEDFOR." ;; + "BatchedCI") echo "This is a batched CI build for $BUILD_REQUESTEDFOR." ;; + *) "$BUILD_REASON" ;; + esac + displayName: 'Build Info' + continueOnError: true + + - script: | + set -euxo pipefail + curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN + echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" + condition: or(eq( variables['Agent.OS'], 'Linux' ), eq( variables['Agent.OS'], 'Darwin' )) + displayName: 'Install Rust (Linux/macOS)' + + - script: curl -sSf -o rustup-init.exe https://win.rustup.rs && rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + displayName: 'Install Rust (Windows)' + + - script: | + echo ##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + displayName: 'Add ~/.cargo/bin to PATH (Windows)' + + - script: | + rustc -Vv + cargo -V + displayName: 'Print Rust Version' + continueOnError: true + + - script: cargo build + displayName: 'Build' + + - script: cargo test + displayName: 'Test' + + +- job: test + displayName: Test + + strategy: + matrix: + linux: + image_name: 'ubuntu-16.04' + rustup_toolchain: nightly + mac: + image_name: 'macos-10.13' + rustup_toolchain: nightly + windows: + image_name: 'vs2017-win2016' + rustup_toolchain: nightly + + pool: + vmImage: $(image_name) + + steps: + - bash: | + echo "Hello world from $AGENT_NAME running on $AGENT_OS" + echo "Reason: $BUILD_REASON" + case "$BUILD_REASON" in + "Manual") echo "$BUILD_REQUESTEDFOR manually queued the build." ;; + "PullRequest") echo "This is a CI build for a pull request on $BUILD_REQUESTEDFOR." ;; + "IndividualCI") echo "This is a CI build for $BUILD_REQUESTEDFOR." ;; + "BatchedCI") echo "This is a batched CI build for $BUILD_REQUESTEDFOR." ;; + *) "$BUILD_REASON" ;; + esac + displayName: 'Build Info' + continueOnError: true + + - script: | + set -euxo pipefail + curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN + echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" + condition: or(eq( variables['Agent.OS'], 'Linux' ), eq( variables['Agent.OS'], 'Darwin' )) + displayName: 'Install Rust (Linux/macOS)' + + - script: curl -sSf -o rustup-init.exe https://win.rustup.rs && rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + displayName: 'Install Rust (Windows)' + + - script: | + echo ##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + displayName: 'Add ~/.cargo/bin to PATH (Windows)' + + - script: | + rustc -Vv + cargo -V + displayName: 'Print Rust Version' + continueOnError: true + + - script: rustup component add rust-src llvm-tools-preview + displayName: 'Install Rustup Components' + + - script: cargo install cargo-xbuild --debug + displayName: 'Install cargo-xbuild' + + - script: sudo apt update && sudo apt install qemu-system-x86 + condition: eq( variables['Agent.OS'], 'Linux' ) + displayName: 'Install QEMU (Linux)' + + - script: | + set -euxo pipefail + export HOMEBREW_NO_AUTO_UPDATE=1 + export HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK=1 + export HOMEBREW_NO_INSTALL_CLEANUP=1 + brew install qemu + condition: eq( variables['Agent.OS'], 'Darwin' ) + displayName: 'Install QEMU (macOS)' + + - script: | + choco install qemu --limit-output --no-progress + echo ##vso[task.setvariable variable=PATH;]%PATH%;C:\Program Files\qemu + set PATH=%PATH%;C:\Program Files\qemu + qemu-system-x86_64 --version + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + failOnStderr: true + displayName: 'Install QEMU (Windows)' + + - script: cargo install --path . --force --debug + displayName: 'Install this bootimage version' + + - script: bootimage build --target ../x86_64-bootimage-example-kernels.json + workingDirectory: example-kernels/basic + displayName: 'Build "basic" Kernel' + + - bash: | + qemu-system-x86_64 -drive format=raw,file=target/x86_64-bootimage-example-kernels/debug/bootimage-basic.bin -device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none + if [ $? -eq 103 ]; then (exit 0); else (exit 1); fi + workingDirectory: example-kernels + displayName: 'Run QEMU with "basic" Kernel' + + - bash: | + bootimage run --target ../x86_64-bootimage-example-kernels.json -- -device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none + if [ $? -eq 103 ]; then (exit 0); else (exit 1); fi + workingDirectory: example-kernels/basic + displayName: 'Check Exit Code of `bootimage run` for "basic" kernel' + + - bash: | + bootimage run -- -device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none + if [ $? -eq 105 ]; then (exit 0); else (exit 1); fi + workingDirectory: example-kernels/default-target-bootimage + displayName: 'Check Exit Code of `bootimage run` for "default-target-bootimage" kernel' + + - bash: | + bootimage run -- -device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none + if [ $? -eq 107 ]; then (exit 0); else (exit 1); fi + workingDirectory: example-kernels/default-target-cargo + displayName: 'Check Exit Code of `bootimage run` for "default-target-cargo" kernel' + + - script: bootimage test + workingDirectory: example-kernels/testing-serial-result + displayName: 'Run `bootimage test` for "testing-serial-result" kernel' + + - script: bootimage test + workingDirectory: example-kernels/testing-qemu-exit-code + displayName: 'Run `bootimage test` for "testing-qemu-exit-code" kernel' + + - bash: | + cargo xrun + if [ $? -eq 109 ]; then (exit 0); else (exit 1); fi + workingDirectory: example-kernels/runner + displayName: 'Run `cargo xrun` for "runner" kernel' diff --git a/bors.toml b/bors.toml index 574c563..9c47331 100644 --- a/bors.toml +++ b/bors.toml @@ -1,4 +1,4 @@ status = [ - "continuous-integration/travis-ci/push", + "rust-osdev.bootimage", ] delete_merged_branches = true diff --git a/example-kernels/.gitignore b/example-kernels/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/example-kernels/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/example-kernels/Cargo.lock b/example-kernels/Cargo.lock new file mode 100644 index 0000000..01c75f4 --- /dev/null +++ b/example-kernels/Cargo.lock @@ -0,0 +1,386 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "basic" +version = "0.1.0" +dependencies = [ + "bootloader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit_field" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bootloader" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "font8x8 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "default-target-bootimage" +version = "0.1.0" +dependencies = [ + "bootloader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "default-target-cargo" +version = "0.1.0" +dependencies = [ + "bootloader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fixedvec" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "font8x8" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "getopts" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "os_bootinfo" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pulldown-cmark" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "raw-cpuid" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "runner" +version = "0.1.0" +dependencies = [ + "bootloader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "skeptic" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "spin" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "spin" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "testing-qemu-exit-code" +version = "0.1.0" +dependencies = [ + "bootloader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "testing-serial-result" +version = "0.1.0" +dependencies = [ + "bootloader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "uart_16550" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "usize_conversions" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ux" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "x86_64" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "x86_64" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "x86_64" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "xmas-elf" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "zero" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +"checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum bootloader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8668a7d724534aba1c3f2509eea122fcf611c31101662ecb947d502cc3cb96ae" +"checksum cc 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ad0daef304fa0b4238f5f7ed7178774b43b06f6a9b6509f6642bef4ff1f7b9b2" +"checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0" +"checksum font8x8 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b81d84c3c978af7d05d31a2198af4b9ba956d819d15d8f6d58fc150e33f8dc1f" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" +"checksum llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a" +"checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30a9d219c32c9132f7be513c18be77c9881c7107d2ab5569d205a6a0f0e6dc7d" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "061203a849117b0f7090baf8157aa91dac30545208fbb85166ac58b4ca33d89c" +"checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" +"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" +"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +"checksum uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "269f953d8de3226f7c065c589c7b4a3e83d10a419c7c3b5e2e0f197e6acc966e" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" +"checksum ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd647af1614659e1febec1d681231aea4ebda4818bf55a578aff02f3e4db4b4" +"checksum x86_64 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f9258d7e2dd25008d69e8c9e9ee37865887a5e1e3d06a62f1cb3f6c209e6f177" +"checksum x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8f7e92e985f4052118fd69f2b366c67e91288c0f01f4ae52610dce236425dfa0" +"checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" +"checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/example-kernels/Cargo.toml b/example-kernels/Cargo.toml new file mode 100644 index 0000000..ede58ed --- /dev/null +++ b/example-kernels/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = [ + "basic", + "default-target-bootimage", + "default-target-cargo", + "runner", + "testing-qemu-exit-code", + "testing-serial-result", +] \ No newline at end of file diff --git a/example-kernels/basic/.gitignore b/example-kernels/basic/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/example-kernels/basic/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/example-kernels/basic/Cargo.toml b/example-kernels/basic/Cargo.toml new file mode 100644 index 0000000..331b235 --- /dev/null +++ b/example-kernels/basic/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "basic" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.5.0" +x86_64 = "0.5.3" diff --git a/example-kernels/basic/src/main.rs b/example-kernels/basic/src/main.rs new file mode 100644 index 0000000..0acc995 --- /dev/null +++ b/example-kernels/basic/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use core::panic::PanicInfo; + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start` by default + + // exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) + unsafe { exit_qemu(); } + + loop {} +} + +pub unsafe fn exit_qemu() { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(51); // exit code is (51 << 1) | 1 = 103 +} diff --git a/example-kernels/default-target-bootimage/.gitignore b/example-kernels/default-target-bootimage/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/example-kernels/default-target-bootimage/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/example-kernels/default-target-bootimage/Cargo.toml b/example-kernels/default-target-bootimage/Cargo.toml new file mode 100644 index 0000000..5825f0a --- /dev/null +++ b/example-kernels/default-target-bootimage/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "default-target-bootimage" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.5.0" +x86_64 = "0.5.3" + +[package.metadata.bootimage] +default-target = "../x86_64-bootimage-example-kernels.json" \ No newline at end of file diff --git a/example-kernels/default-target-bootimage/src/main.rs b/example-kernels/default-target-bootimage/src/main.rs new file mode 100644 index 0000000..44b77a9 --- /dev/null +++ b/example-kernels/default-target-bootimage/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use core::panic::PanicInfo; + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start` by default + + // exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) + unsafe { exit_qemu(); } + + loop {} +} + +pub unsafe fn exit_qemu() { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(52); // exit code is (52 << 1) | 1 = 105 +} diff --git a/example-kernels/default-target-cargo/.cargo/config b/example-kernels/default-target-cargo/.cargo/config new file mode 100644 index 0000000..79fdf34 --- /dev/null +++ b/example-kernels/default-target-cargo/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "../x86_64-bootimage-example-kernels.json" \ No newline at end of file diff --git a/example-kernels/default-target-cargo/.gitignore b/example-kernels/default-target-cargo/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/example-kernels/default-target-cargo/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/example-kernels/default-target-cargo/Cargo.toml b/example-kernels/default-target-cargo/Cargo.toml new file mode 100644 index 0000000..737b944 --- /dev/null +++ b/example-kernels/default-target-cargo/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "default-target-cargo" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.5.0" +x86_64 = "0.5.3" diff --git a/example-kernels/default-target-cargo/src/main.rs b/example-kernels/default-target-cargo/src/main.rs new file mode 100644 index 0000000..14aead9 --- /dev/null +++ b/example-kernels/default-target-cargo/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use core::panic::PanicInfo; + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start` by default + + // exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) + unsafe { exit_qemu(); } + + loop {} +} + +pub unsafe fn exit_qemu() { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(53); // exit code is (53 << 1) | 1 = 107 +} diff --git a/example-kernels/runner/.cargo/config b/example-kernels/runner/.cargo/config new file mode 100644 index 0000000..3b4d89e --- /dev/null +++ b/example-kernels/runner/.cargo/config @@ -0,0 +1,5 @@ +[build] +target = "../x86_64-bootimage-example-kernels.json" + +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" diff --git a/example-kernels/runner/.gitignore b/example-kernels/runner/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/example-kernels/runner/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/example-kernels/runner/Cargo.toml b/example-kernels/runner/Cargo.toml new file mode 100644 index 0000000..966cce4 --- /dev/null +++ b/example-kernels/runner/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "runner" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.5.0" +x86_64 = "0.5.3" + +[package.metadata.bootimage] +run-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-display", "none"] diff --git a/example-kernels/runner/src/main.rs b/example-kernels/runner/src/main.rs new file mode 100644 index 0000000..033f446 --- /dev/null +++ b/example-kernels/runner/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use core::panic::PanicInfo; + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start` by default + + // exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) + unsafe { exit_qemu(); } + + loop {} +} + +pub unsafe fn exit_qemu() { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(54); // exit code is (54 << 1) | 1 = 109 +} diff --git a/example-kernels/rust-toolchain b/example-kernels/rust-toolchain new file mode 100644 index 0000000..07ade69 --- /dev/null +++ b/example-kernels/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/example-kernels/testing-qemu-exit-code/.gitignore b/example-kernels/testing-qemu-exit-code/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/example-kernels/testing-qemu-exit-code/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/example-kernels/testing-qemu-exit-code/Cargo.toml b/example-kernels/testing-qemu-exit-code/Cargo.toml new file mode 100644 index 0000000..6efb450 --- /dev/null +++ b/example-kernels/testing-qemu-exit-code/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "testing-qemu-exit-code" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.5.0" +x86_64 = "0.5.3" + +[package.metadata.bootimage] +default-target = "../x86_64-bootimage-example-kernels.json" \ No newline at end of file diff --git a/example-kernels/testing-qemu-exit-code/src/bin/test-basic-boot.rs b/example-kernels/testing-qemu-exit-code/src/bin/test-basic-boot.rs new file mode 100644 index 0000000..1a506f1 --- /dev/null +++ b/example-kernels/testing-qemu-exit-code/src/bin/test-basic-boot.rs @@ -0,0 +1,27 @@ +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points +#![cfg_attr(test, allow(unused_imports))] + +use testing_qemu_exit_code::{exit_qemu, ExitCode}; +use core::panic::PanicInfo; + +/// This function is the entry point, since the linker looks for a function +/// named `_start` by default. +#[cfg(not(test))] +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + unsafe { + exit_qemu(ExitCode::Success); + } + loop {} +} + +/// This function is called on panic. +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + unsafe { + exit_qemu(ExitCode::Failure); + } + loop {} +} diff --git a/example-kernels/testing-qemu-exit-code/src/bin/test-panic.rs b/example-kernels/testing-qemu-exit-code/src/bin/test-panic.rs new file mode 100644 index 0000000..a1c05f8 --- /dev/null +++ b/example-kernels/testing-qemu-exit-code/src/bin/test-panic.rs @@ -0,0 +1,21 @@ +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] +#![cfg_attr(test, allow(unused_imports))] + +use testing_qemu_exit_code::{exit_qemu, ExitCode}; +use core::panic::PanicInfo; + +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn _start() -> ! { + panic!(); +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { + exit_qemu(ExitCode::Success); + } + loop {} +} diff --git a/example-kernels/testing-qemu-exit-code/src/lib.rs b/example-kernels/testing-qemu-exit-code/src/lib.rs new file mode 100644 index 0000000..cdc0ba8 --- /dev/null +++ b/example-kernels/testing-qemu-exit-code/src/lib.rs @@ -0,0 +1,21 @@ +#![cfg_attr(not(test), no_std)] +#![feature(abi_x86_interrupt)] + +#[repr(u32)] +pub enum ExitCode { + Success = 2, + Failure = 3, +} + +pub unsafe fn exit_qemu(exit_code: ExitCode) { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(exit_code as u32); +} + +pub fn hlt_loop() -> ! { + loop { + x86_64::instructions::hlt(); + } +} diff --git a/example-kernels/testing-serial-result/.gitignore b/example-kernels/testing-serial-result/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/example-kernels/testing-serial-result/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/example-kernels/testing-serial-result/Cargo.toml b/example-kernels/testing-serial-result/Cargo.toml new file mode 100644 index 0000000..bbe84a2 --- /dev/null +++ b/example-kernels/testing-serial-result/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "testing-serial-result" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.5.0" +x86_64 = "0.5.3" +spin = "0.4.9" +uart_16550 = "0.1.0" + +[dependencies.lazy_static] +version = "1.3.0" +features = ["spin_no_std"] + +[package.metadata.bootimage] +default-target = "../x86_64-bootimage-example-kernels.json" diff --git a/example-kernels/testing-serial-result/src/bin/test-basic-boot-serial.rs b/example-kernels/testing-serial-result/src/bin/test-basic-boot-serial.rs new file mode 100644 index 0000000..d6982d0 --- /dev/null +++ b/example-kernels/testing-serial-result/src/bin/test-basic-boot-serial.rs @@ -0,0 +1,33 @@ +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points +#![cfg_attr(test, allow(unused_imports))] + +use testing_serial_result::{exit_qemu, serial_println}; +use core::panic::PanicInfo; + +/// This function is the entry point, since the linker looks for a function +/// named `_start` by default. +#[cfg(not(test))] +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + serial_println!("ok"); + + unsafe { + exit_qemu(); + } + loop {} +} + +/// This function is called on panic. +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + serial_println!("failed"); + + serial_println!("{}", info); + + unsafe { + exit_qemu(); + } + loop {} +} diff --git a/example-kernels/testing-serial-result/src/bin/test-panic-serial.rs b/example-kernels/testing-serial-result/src/bin/test-panic-serial.rs new file mode 100644 index 0000000..324a28f --- /dev/null +++ b/example-kernels/testing-serial-result/src/bin/test-panic-serial.rs @@ -0,0 +1,23 @@ +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] +#![cfg_attr(test, allow(unused_imports))] + +use testing_serial_result::{exit_qemu, serial_println}; +use core::panic::PanicInfo; + +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn _start() -> ! { + panic!(); +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + serial_println!("ok"); + + unsafe { + exit_qemu(); + } + loop {} +} diff --git a/example-kernels/testing-serial-result/src/lib.rs b/example-kernels/testing-serial-result/src/lib.rs new file mode 100644 index 0000000..6bd2578 --- /dev/null +++ b/example-kernels/testing-serial-result/src/lib.rs @@ -0,0 +1,58 @@ +#![cfg_attr(not(test), no_std)] +#![feature(abi_x86_interrupt)] + +pub unsafe fn exit_qemu() { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(0); +} + +pub fn hlt_loop() -> ! { + loop { + x86_64::instructions::hlt(); + } +} + +pub mod serial { + use lazy_static::lazy_static; + use spin::Mutex; + use uart_16550::SerialPort; + + lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = SerialPort::new(0x3F8); + serial_port.init(); + Mutex::new(serial_port) + }; + } + + #[doc(hidden)] + pub fn _print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + SERIAL1 + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); + }); + } + + /// Prints to the host through the serial interface. + #[macro_export] + macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; + } + + /// Prints to the host through the serial interface, appending a newline. + #[macro_export] + macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(concat!($fmt, "\n"), $($arg)*)); + } +} \ No newline at end of file diff --git a/example-kernels/testing-serial-result/src/main.rs b/example-kernels/testing-serial-result/src/main.rs new file mode 100644 index 0000000..f0352e2 --- /dev/null +++ b/example-kernels/testing-serial-result/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use core::panic::PanicInfo; + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start` by default + + // exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) + unsafe { exit_qemu(); } + + loop {} +} + +pub unsafe fn exit_qemu() { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(61); // exit code is (61 << 1) | 1 = 123 +} diff --git a/example-kernels/x86_64-bootimage-example-kernels.json b/example-kernels/x86_64-bootimage-example-kernels.json new file mode 100644 index 0000000..9afe809 --- /dev/null +++ b/example-kernels/x86_64-bootimage-example-kernels.json @@ -0,0 +1,15 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "panic-strategy": "abort", + "disable-redzone": true, + "features": "-mmx,-sse,+soft-float" + } diff --git a/src/args.rs b/src/args.rs index 7fcc5b9..05ea511 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,18 +1,26 @@ +//! Parses command line arguments. + +use crate::{config::Config, Command, ErrorMessage}; use std::path::{Path, PathBuf}; use std::{env, mem}; -use Command; -pub(crate) fn parse_args() -> Command { - let mut args = env::args().skip(1); +pub(crate) fn parse_args() -> Result { + let mut args = env::args(); + let executable_name = args.next().ok_or("no first argument (executable name)")?; let first = args.next(); match first.as_ref().map(|s| s.as_str()) { Some("build") => parse_build_args(args), - Some("run") => match parse_build_args(args) { + Some("bootimage") if executable_name.ends_with("cargo-bootimage") => parse_build_args(args) + .map(|cmd| match cmd { + Command::BuildHelp => Command::CargoBootimageHelp, + cmd => cmd, + }), + Some("run") => parse_build_args(args).map(|cmd| match cmd { Command::Build(args) => Command::Run(args), Command::BuildHelp => Command::RunHelp, cmd => cmd, - }, - Some("test") => match parse_build_args(args) { + }), + Some("test") => parse_build_args(args).map(|cmd| match cmd { Command::Build(args) => { assert_eq!( args.bin_name, None, @@ -22,14 +30,15 @@ pub(crate) fn parse_args() -> Command { } Command::BuildHelp => Command::TestHelp, cmd => cmd, - }, - Some("--help") | Some("-h") => Command::Help, - Some("--version") => Command::Version, - _ => Command::NoSubcommand, + }), + Some("runner") => parse_runner_args(args), + Some("--help") | Some("-h") => Ok(Command::Help), + Some("--version") => Ok(Command::Version), + _ => Ok(Command::NoSubcommand), } } -fn parse_build_args(args: A) -> Command +fn parse_build_args(args: A) -> Result where A: Iterator, { @@ -40,13 +49,14 @@ where let mut cargo_args = Vec::new(); let mut run_args = Vec::new(); let mut run_args_started = false; + let mut quiet = false; { - fn set(arg: &mut Option, value: Option) { + fn set(arg: &mut Option, value: Option) -> Result<(), ErrorMessage> { let previous = mem::replace(arg, value); - assert!( - previous.is_none(), - "multiple arguments of same type provided" - ) + if previous.is_some() { + Err("multiple arguments of same type provided")? + } + Ok(()) }; let mut arg_iter = args.into_iter(); @@ -57,14 +67,17 @@ where } match arg.as_ref() { "--help" | "-h" => { - return Command::BuildHelp; + return Ok(Command::BuildHelp); } "--version" => { - return Command::Version; + return Ok(Command::Version); + } + "--quiet" => { + quiet = true; } "--bin" => { let next = arg_iter.next(); - set(&mut bin_name, next.clone()); + set(&mut bin_name, next.clone())?; cargo_args.push(arg); if let Some(next) = next { cargo_args.push(next); @@ -74,12 +87,12 @@ where set( &mut bin_name, Some(String::from(arg.trim_start_matches("--bin="))), - ); + )?; cargo_args.push(arg); } "--target" => { let next = arg_iter.next(); - set(&mut target, next.clone()); + set(&mut target, next.clone())?; cargo_args.push(arg); if let Some(next) = next { cargo_args.push(next); @@ -89,7 +102,7 @@ where set( &mut target, Some(String::from(arg.trim_start_matches("--target="))), - ); + )?; cargo_args.push(arg); } "--manifest-path" => { @@ -101,7 +114,7 @@ where .canonicalize() .expect("--manifest-path invalid") }), - ); + )?; cargo_args.push(arg); if let Some(next) = next { cargo_args.push(next); @@ -111,11 +124,11 @@ where let path = Path::new(arg.trim_start_matches("--manifest-path=")) .canonicalize() .expect("--manifest-path invalid"); - set(&mut manifest_path, Some(path)); + set(&mut manifest_path, Some(path))?; cargo_args.push(arg); } "--release" => { - set(&mut release, Some(true)); + set(&mut release, Some(true))?; cargo_args.push(arg); } "--" => { @@ -128,14 +141,15 @@ where } } - Command::Build(Args { + Ok(Command::Build(Args { cargo_args, run_args, bin_name, target, manifest_path, release: release.unwrap_or(false), - }) + quiet, + })) } #[derive(Debug, Clone)] @@ -144,6 +158,8 @@ pub struct Args { pub cargo_args: Vec, /// All arguments that are passed to the runner. pub run_args: Vec, + /// Suppress any output to stdout. + pub quiet: bool, /// The manifest path (also present in `cargo_args`). manifest_path: Option, /// The name of the binary (passed `--bin` argument) (also present in `cargo_args`). @@ -159,18 +175,10 @@ impl Args { &self.manifest_path } - pub fn bin_name(&self) -> &Option { - &self.bin_name - } - pub fn target(&self) -> &Option { &self.target } - pub fn release(&self) -> bool { - self.release - } - pub fn set_target(&mut self, target: String) { assert!(self.target.is_none()); self.target = Some(target.clone()); @@ -184,4 +192,62 @@ impl Args { self.cargo_args.push("--bin".into()); self.cargo_args.push(bin_name); } + + pub fn apply_default_target(&mut self, config: &Config, crate_root: &Path) { + if self.target().is_none() { + if let Some(ref target) = config.default_target { + let canonicalized_target = crate_root.join(target); + self.set_target(canonicalized_target.to_string_lossy().into_owned()); + } + } + } +} + +fn parse_runner_args(args: A) -> Result +where + A: Iterator, +{ + let mut executable = None; + let mut quiet = false; + + let mut arg_iter = args.into_iter().fuse(); + + loop { + match arg_iter.next().as_ref().map(|s| s.as_str()) { + Some("--help") | Some("-h") => { + return Ok(Command::RunnerHelp); + } + Some("--version") => { + return Ok(Command::Version); + } + Some("--quiet") => { + quiet = true; + } + Some(exe) if executable.is_none() => { + let path = Path::new(exe); + let path_canonicalized = path.canonicalize().map_err(|err| { + format!( + "Failed to canonicalize executable path `{}`: {}", + path.display(), + err + ) + })?; + executable = Some(path_canonicalized); + } + Some(arg) => Err(format!("unexpected argument `{}`", arg))?, + None => break, + } + } + + Ok(Command::Runner(RunnerArgs { + executable: executable.ok_or("excepted path to kernel executable as first argument")?, + quiet, + })) +} + +#[derive(Debug, Clone)] +pub struct RunnerArgs { + pub executable: PathBuf, + /// Suppress any output to stdout. + pub quiet: bool, } diff --git a/src/bin/cargo-bootimage.rs b/src/bin/cargo-bootimage.rs new file mode 100644 index 0000000..5349675 --- /dev/null +++ b/src/bin/cargo-bootimage.rs @@ -0,0 +1,3 @@ +pub fn main() { + bootimage::lib_main(); +} diff --git a/src/build.rs b/src/build.rs deleted file mode 100644 index f1bc258..0000000 --- a/src/build.rs +++ /dev/null @@ -1,459 +0,0 @@ -use args::{self, Args}; -use byteorder::{ByteOrder, LittleEndian}; -use cargo_metadata::{self, Metadata as CargoMetadata}; -use config::{self, Config}; -use failure::{self, Error, ResultExt}; -use std::fs::File; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::{fmt, io, process}; -use xmas_elf; - -const BLOCK_SIZE: usize = 512; -type KernelInfoBlock = [u8; BLOCK_SIZE]; - -pub(crate) fn build(args: Args) -> Result<(), Error> { - let (args, config, metadata, root_dir, out_dir) = common_setup(args)?; - - build_impl(&args, &config, &metadata, &root_dir, &out_dir, true)?; - Ok(()) -} - -pub(crate) fn run(args: Args) -> Result<(), Error> { - let (args, config, metadata, root_dir, out_dir) = common_setup(args)?; - - let output_path = build_impl(&args, &config, &metadata, &root_dir, &out_dir, true)?; - run_impl(&args, &config, &output_path) -} - -pub(crate) fn common_setup( - mut args: Args, -) -> Result<(Args, Config, CargoMetadata, PathBuf, PathBuf), Error> { - fn out_dir(args: &Args, metadata: &CargoMetadata) -> PathBuf { - let target_dir = PathBuf::from(&metadata.target_directory); - let mut out_dir = target_dir; - if let &Some(ref target) = args.target() { - out_dir.push(Path::new(target).file_stem().unwrap().to_str().unwrap()); - } - if args.release() { - out_dir.push("release"); - } else { - out_dir.push("debug"); - } - out_dir - } - - let metadata = read_cargo_metadata(&args)?; - let manifest_path = args - .manifest_path() - .as_ref() - .map(Clone::clone) - .unwrap_or(Path::new("Cargo.toml").canonicalize().unwrap()); - let crate_root = manifest_path.parent().unwrap().to_path_buf(); - let config = config::read_config(manifest_path)?; - - if args.target().is_none() { - if let Some(ref target) = config.default_target { - let mut canonicalized_target = crate_root.clone(); - canonicalized_target.push(target); - args.set_target(canonicalized_target.to_string_lossy().into_owned()); - } - } - - if let &Some(ref target) = args.target() { - if !target.ends_with(".json") { - use std::io::{self, Write}; - use std::process; - - writeln!( - io::stderr(), - "Please pass a path to `--target` (with `.json` extension`): `--target {}.json`", - target - ) - .unwrap(); - process::exit(1); - } - } - - let out_dir = out_dir(&args, &metadata); - - Ok((args, config, metadata, crate_root, out_dir)) -} - -pub(crate) fn build_impl( - args: &Args, - config: &Config, - metadata: &CargoMetadata, - root_dir: &Path, - out_dir: &Path, - verbose: bool, -) -> Result { - let crate_ = metadata - .packages - .iter() - .find(|p| { - Path::new(&p.manifest_path) - .canonicalize() - .map(|path| path == config.manifest_path) - .unwrap_or(false) - }) - .expect("Could not read crate name from cargo metadata"); - let bin_name: String = args.bin_name().as_ref().unwrap_or(&crate_.name).clone(); - - let kernel = build_kernel(&out_dir, &bin_name, &args, verbose)?; - - let maybe_package = if let Some(ref path) = config.package_filepath { - Some( - File::open(path) - .with_context(|e| format!("Unable to open specified package file: {}", e))?, - ) - } else { - None - }; - - let maybe_package_size = if let Some(ref file) = maybe_package { - Some( - file.metadata() - .with_context(|e| format!("Failed to read specified package file: {}", e))? - .len(), - ) - } else { - None - }; - - let kernel_size = kernel - .metadata() - .with_context(|e| format!("Failed to read kernel output file: {}", e))? - .len(); - let kernel_info_block = create_kernel_info_block(kernel_size, maybe_package_size); - - let bootloader = build_bootloader(&metadata, &config) - .with_context(|e| format!("Failed to build bootloader: {}", e))?; - - create_disk_image( - root_dir, - out_dir, - &bin_name, - &config, - kernel, - maybe_package, - kernel_info_block, - &bootloader, - verbose, - ) -} - -fn run_impl(args: &Args, config: &Config, output_path: &Path) -> Result<(), Error> { - let command = &config.run_command[0]; - let mut command = process::Command::new(command); - for arg in &config.run_command[1..] { - command.arg(arg.replace( - "{}", - output_path.to_str().expect("output must be valid unicode"), - )); - } - command.args(&args.run_args); - command - .status() - .with_context(|e| format!("Failed to execute run `{:?}`: {}", command, e))?; - Ok(()) -} - -#[derive(Debug)] -pub struct CargoMetadataError { - error: String, -} - -impl fmt::Display for CargoMetadataError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.error) - } -} - -impl failure::Fail for CargoMetadataError {} - -fn read_cargo_metadata(args: &Args) -> Result { - run_cargo_fetch(args); - let metadata = { - let mut cmd = cargo_metadata::MetadataCommand::new(); - if let Some(ref path) = args.manifest_path() { - cmd.manifest_path(path.as_path()); - } - cmd.exec().map_err(|e| CargoMetadataError { - error: format!("{}", e), - })? - }; - Ok(metadata) -} - -fn build_kernel( - out_dir: &Path, - bin_name: &str, - args: &args::Args, - verbose: bool, -) -> Result { - // compile kernel - if verbose { - println!("Building kernel"); - } - let exit_status = run_xbuild(&args.cargo_args) - .with_context(|e| format!("Failed to run `cargo xbuild`: {}", e))?; - if !exit_status.success() { - process::exit(1) - } - - let mut kernel_path = out_dir.to_owned(); - kernel_path.push(bin_name); - let kernel = File::open(kernel_path) - .with_context(|e| format!("Failed to open kernel output file: {}", e))?; - Ok(kernel) -} - -fn run_xbuild(args: &[String]) -> io::Result { - let mut command = process::Command::new("cargo"); - command.arg("xbuild"); - command.args(args); - let exit_status = command.status()?; - - if !exit_status.success() { - 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() { - let mut stderr = io::stderr(); - writeln!( - stderr, - "Failed to run `cargo xbuild`. Perhaps it is not installed?" - )?; - writeln!(stderr, "Run `cargo install cargo-xbuild` to install it.")?; - } - } - } - - Ok(exit_status) -} - -fn run_cargo_fetch(args: &Args) { - let mut command = process::Command::new("cargo"); - command.arg("fetch"); - if let Some(manifest_path) = args.manifest_path() { - command.arg("--manifest-path"); - command.arg(manifest_path); - } - if !command.status().map(|s| s.success()).unwrap_or(false) { - process::exit(1); - } -} - -fn create_kernel_info_block(kernel_size: u64, maybe_package_size: Option) -> KernelInfoBlock { - let kernel_size = if kernel_size <= u64::from(u32::max_value()) { - kernel_size as u32 - } else { - panic!("Kernel can't be loaded by BIOS bootloader because is too big") - }; - - let package_size = if let Some(size) = maybe_package_size { - if size <= u64::from(u32::max_value()) { - size as u32 - } else { - panic!("Package can't be loaded by BIOS bootloader because is too big") - } - } else { - 0 - }; - - let mut kernel_info_block = [0u8; BLOCK_SIZE]; - LittleEndian::write_u32(&mut kernel_info_block[0..4], kernel_size); - LittleEndian::write_u32(&mut kernel_info_block[8..12], package_size); - - kernel_info_block -} - -fn build_bootloader(metadata: &CargoMetadata, config: &Config) -> Result, Error> { - use std::io::Read; - - let bootloader_metadata = metadata.packages.iter().find(|p| { - if let Some(name) = config.bootloader.name.as_ref() { - p.name == name.as_str() - } else { - p.name == "bootloader" || p.name == "bootloader_precompiled" - } - }); - let bootloader_metadata = - match bootloader_metadata { - Some(package_metadata) => package_metadata.clone(), - None => Err(format_err!("Bootloader dependency not found\n\n\ - You need to add a dependency on the `bootloader` or `bootloader_precompiled` crates \ - in your Cargo.toml.\n\nIn case you just updated bootimage from an earlier version, \ - check out the migration guide at https://github.com/rust-osdev/bootimage/pull/16. \ - Alternatively, you can downgrade to bootimage 0.4 again by executing \ - `cargo install bootimage --version {} --force`.", r#""^0.4""# - ))?, - }; - - let bootloader_manifest_path = Path::new(&bootloader_metadata.manifest_path); - let bootloader_dir = bootloader_manifest_path.parent().unwrap(); - - let mut bootloader_target_path = PathBuf::from(bootloader_dir); - bootloader_target_path.push(&config.bootloader.target); - - let bootloader_elf_path = if bootloader_metadata.name == "bootloader_precompiled" { - let mut bootloader_elf_path = bootloader_dir.to_path_buf(); - bootloader_elf_path.push("bootloader"); - bootloader_elf_path - } else { - let mut args = vec![ - String::from("--manifest-path"), - bootloader_metadata - .manifest_path - .as_os_str() - .to_os_string() - .into_string() - .expect("manifest path not valid unicode"), - String::from("--target"), - bootloader_target_path.display().to_string(), - String::from("--release"), - String::from("--features"), - config - .bootloader - .features - .iter() - .fold(String::new(), |i, j| i + " " + j), - ]; - - if !config.bootloader.default_features { - args.push(String::from("--no-default-features")); - } - - println!("Building bootloader v{}", bootloader_metadata.version); - let exit_status = - run_xbuild(&args).with_context(|e| format!("Failed to run `cargo xbuild`: {}", e))?; - if !exit_status.success() { - process::exit(1) - } - - let bootloader_metadata = { - let mut cmd = cargo_metadata::MetadataCommand::new(); - cmd.manifest_path(&bootloader_manifest_path); - cmd.no_deps(); - cmd.exec().map_err(|e| CargoMetadataError { - error: format!("{}", e), - })? - }; - let mut bootloader_elf_path = PathBuf::from(bootloader_metadata.target_directory); - bootloader_elf_path.push(config.bootloader.target.file_stem().unwrap()); - bootloader_elf_path.push("release"); - bootloader_elf_path.push("bootloader"); - bootloader_elf_path - }; - - let mut bootloader_elf_bytes = Vec::new(); - let mut bootloader = File::open(&bootloader_elf_path) - .with_context(|e| format!("Could not open bootloader: {}", e))?; - bootloader - .read_to_end(&mut bootloader_elf_bytes) - .with_context(|e| format!("Could not read bootloader: {}", e))?; - - // copy bootloader section of ELF file to bootloader_path - let elf_file = xmas_elf::ElfFile::new(&bootloader_elf_bytes).unwrap(); - xmas_elf::header::sanity_check(&elf_file).unwrap(); - let bootloader_section = elf_file - .find_section_by_name(".bootloader") - .expect("bootloader must have a .bootloader section"); - - Ok(Vec::from(bootloader_section.raw_data(&elf_file)).into_boxed_slice()) -} - -fn create_disk_image( - root_dir: &Path, - out_dir: &Path, - bin_name: &str, - config: &Config, - mut kernel: File, - mut maybe_package: Option, - kernel_info_block: KernelInfoBlock, - bootloader_data: &[u8], - verbose: bool, -) -> Result { - use std::io::{Read, Write}; - - let mut output_path = PathBuf::from(out_dir); - let file_name = format!("bootimage-{}.bin", bin_name); - output_path.push(file_name); - - if let Some(ref output) = config.output { - output_path = output.clone(); - } - - if verbose { - println!( - "Creating disk image at {}", - output_path - .strip_prefix(root_dir) - .unwrap_or(output_path.as_path()) - .display() - ); - } - let mut output = File::create(&output_path) - .with_context(|e| format!("Could not create output bootimage file: {}", e))?; - output - .write_all(&bootloader_data) - .with_context(|e| format!("Could not write output bootimage file: {}", e))?; - output - .write_all(&kernel_info_block) - .with_context(|e| format!("Could not write output bootimage file: {}", e))?; - - fn write_file_to_file(output: &mut File, datafile: &mut File) -> Result { - let data_size = datafile.metadata()?.len(); - let mut buffer = [0u8; 1024]; - let mut acc = 0; - loop { - let (n, interrupted) = match datafile.read(&mut buffer) { - Ok(0) => break, - Ok(n) => (n, false), - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => (0, true), - Err(e) => Err(e)?, - }; - if !interrupted { - acc += n; - output.write_all(&buffer[..n])? - } - } - - assert!(data_size == acc as u64); - - Ok(acc) - } - - fn pad_file(output: &mut File, written_size: usize, padding: &[u8]) -> Result<(), Error> { - let padding_size = (padding.len() - (written_size % padding.len())) % padding.len(); - output - .write_all(&padding[..padding_size]) - .with_context(|e| format!("Could not write to output file: {}", e))?; - Ok(()) - } - - // write out kernel elf file - - let kernel_size = write_file_to_file(&mut output, &mut kernel)?; - - pad_file(&mut output, kernel_size, &[0; 512])?; - - if let Some(ref mut package) = maybe_package { - println!("Writing specified package to output"); - let package_size = write_file_to_file(&mut output, package)?; - pad_file(&mut output, package_size, &[0; 512])?; - } - - if let Some(min_size) = config.minimum_image_size { - // we already wrote to output successfully, - // both metadata and set_len should succeed. - if output.metadata()?.len() < min_size { - output.set_len(min_size)?; - } - } - - Ok(output_path) -} diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..f0e78ad --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,508 @@ +//! Provides functions to build the kernel and the bootloader. + +use std::{ + fmt, fs, io, + path::{Path, PathBuf}, + process::{self, Command}, +}; + +/// Abstracts a build environment and provides methods for building the kernel and creating a +/// bootimage. +pub struct Builder { + kernel_manifest_path: PathBuf, + kernel_metadata: cargo_metadata::Metadata, +} + +impl Builder { + /// Creates a new Builder by searching for the kernel's Cargo manifest and running + /// `cargo metadata` on it. + pub fn new(manifest_path: Option) -> Result { + let kernel_manifest_path = + manifest_path.unwrap_or(locate_cargo_manifest::locate_manifest()?); + let kernel_metadata = cargo_metadata::MetadataCommand::new() + .manifest_path(&kernel_manifest_path) + .exec()?; + Ok(Builder { + kernel_manifest_path, + kernel_metadata, + }) + } + + /// Returns the path to the `Cargo.toml` file of the kernel. + pub fn kernel_manifest_path(&self) -> &Path { + &self.kernel_manifest_path + } + + /// Returns the directory that contains the `Cargo.toml` of the kernel. + pub fn kernel_root(&self) -> &Path { + self.kernel_manifest_path + .parent() + .expect("kernel manifest has no parent directory") + } + + /// Returns a reference to the cargo metadata object. + pub fn kernel_metadata(&self) -> &cargo_metadata::Metadata { + &self.kernel_metadata + } + + /// Returns a reference to the kernel package in the `cargo metadata` output. + pub fn kernel_package(&self) -> Result<&cargo_metadata::Package, String> { + let mut packages = self.kernel_metadata.packages.iter(); + let kernel_package = packages.find(|p| &p.manifest_path == &self.kernel_manifest_path); + kernel_package.ok_or(format!( + "packages[manifest_path = `{}`]", + &self.kernel_manifest_path.display() + )) + } + + /// Builds the kernel by executing `cargo xbuild` with the given arguments. + /// + /// Returns a list of paths to all built executables. For crates with only a single binary, + /// the returned list contains only a single element. + /// + /// If the quiet argument is set to true, all output to stdout is suppressed. + pub fn build_kernel( + &self, + args: &[String], + quiet: bool, + ) -> Result, BuildKernelError> { + if !quiet { + println!("Building kernel"); + } + + let cargo = std::env::var("CARGO").unwrap_or("cargo".to_owned()); + let mut cmd = process::Command::new(&cargo); + cmd.arg("xbuild"); + 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() { + 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::XbuildFailed { + stderr: output.stderr, + }); + } + + // Retrieve binary paths + let mut cmd = process::Command::new(cargo); + cmd.arg("xbuild"); + 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::XbuildFailed { + stderr: output.stderr, + }); + } + let mut executables = Vec::new(); + for line in String::from_utf8(output.stdout) + .map_err(BuildKernelError::XbuildJsonOutputInvalidUtf8)? + .lines() + { + let mut artifact = + json::parse(line).map_err(BuildKernelError::XbuildJsonOutputInvalidJson)?; + if let Some(executable) = artifact["executable"].take_string() { + executables.push(PathBuf::from(executable)); + } + } + + Ok(executables) + } + + /// Creates a bootimage by combining the given kernel binary with the bootloader. + /// + /// Places the resulting bootable disk image at the given `output_bin_path`. + /// + /// If the quiet argument is set to true, all output to stdout is suppressed. + pub fn create_bootimage( + &self, + kernel_bin_path: &Path, + output_bin_path: &Path, + quiet: bool, + ) -> Result<(), CreateBootimageError> { + let metadata = self.kernel_metadata(); + + let bootloader_name = { + let kernel_package = self + .kernel_package() + .map_err(|key| CreateBootimageError::CargoMetadataIncomplete { key })?; + let mut dependencies = kernel_package.dependencies.iter(); + let bootloader_package = dependencies + .find(|p| p.rename.as_ref().unwrap_or(&p.name) == "bootloader") + .ok_or(CreateBootimageError::BootloaderNotFound)?; + bootloader_package.name.clone() + }; + let target_dir = metadata + .target_directory + .join("bootimage") + .join(&bootloader_name); + + let bootloader_pkg = metadata + .packages + .iter() + .find(|p| p.name == bootloader_name) + .ok_or(CreateBootimageError::CargoMetadataIncomplete { + key: format!("packages[name = `{}`", &bootloader_name), + })?; + let bootloader_root = bootloader_pkg.manifest_path.parent().ok_or( + CreateBootimageError::BootloaderInvalid( + "bootloader manifest has no target directory".into(), + ), + )?; + let bootloader_target = { + let cargo_config_content = match fs::read_to_string( + bootloader_root.join(".cargo").join("config"), + ) { + Err(ref err) if err.kind() == io::ErrorKind::NotFound => { + return Err(CreateBootimageError::BootloaderInvalid("No `.cargo/config` file found in bootloader crate\n\n\ + (If you're using the official bootloader crate, you need at least version 0.5.0.)".into())); + } + Err(err) => { + return Err(CreateBootimageError::Io { + message: "Failed to read `cargo/config` file of bootloader crate", + error: err, + }); + } + Ok(content) => content, + }; + let cargo_config: toml::Value = cargo_config_content.parse().map_err(|err| { + CreateBootimageError::BootloaderInvalid(format!( + "The `.cargo/config` file of the bootloader crate is not valid TOML: {}", + err + )) + })?; + let target = cargo_config.get("build").and_then(|v| v.get("target")).and_then(|v| v.as_str()).ok_or(CreateBootimageError::BootloaderInvalid("The `.cargo/config` file of the bootloader crate contains no build.target key or it is not valid".into()))?; + bootloader_root.join(target) + }; + let bootloader_features = + { + let resolve = metadata.resolve.as_ref().ok_or( + CreateBootimageError::CargoMetadataIncomplete { + key: "resolve".into(), + }, + )?; + let bootloader_resolve = resolve + .nodes + .iter() + .find(|n| n.id == bootloader_pkg.id) + .ok_or(CreateBootimageError::CargoMetadataIncomplete { + key: format!("resolve[\"{}\"]", bootloader_name), + })?; + bootloader_resolve.features.clone() + }; + + // build bootloader + if !quiet { + println!("Building bootloader"); + } + + let cargo = std::env::var("CARGO").unwrap_or("cargo".to_owned()); + let build_command = || { + let mut cmd = process::Command::new(&cargo); + cmd.arg("xbuild"); + cmd.arg("--manifest-path"); + cmd.arg(&bootloader_pkg.manifest_path); + cmd.arg("--target-dir").arg(&target_dir); + cmd.arg("--features") + .arg(bootloader_features.as_slice().join(" ")); + cmd.arg("--target").arg(&bootloader_target); + cmd.arg("--release"); + cmd.env("KERNEL", kernel_bin_path); + cmd.env_remove("RUSTFLAGS"); + cmd.env("SYSROOT_DIR", target_dir.join("sysroot")); // for cargo-xbuild + cmd + }; + + let mut cmd = 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, + }); + } + + // Retrieve binary path + let mut cmd = 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::XbuildJsonOutputInvalidUtf8)? + .lines() + { + let mut artifact = + json::parse(line).map_err(CreateBootimageError::XbuildJsonOutputInvalidJson)?; + if let Some(executable) = artifact["executable"].take_string() { + if bootloader_elf_path + .replace(PathBuf::from(executable)) + .is_some() + { + return Err(CreateBootimageError::BootloaderInvalid( + "bootloader has multiple executables".into(), + )); + } + } + } + let bootloader_elf_path = bootloader_elf_path.ok_or( + CreateBootimageError::BootloaderInvalid("bootloader has no executable".into()), + )?; + + let llvm_tools = llvm_tools::LlvmTools::new()?; + let objcopy = llvm_tools + .tool(&llvm_tools::exe("llvm-objcopy")) + .ok_or(CreateBootimageError::LlvmObjcopyNotFound)?; + + // convert bootloader to binary + let mut cmd = Command::new(objcopy); + cmd.arg("-I").arg("elf64-x86-64"); + cmd.arg("-O").arg("binary"); + cmd.arg("--binary-architecture=i386:x86-64"); + cmd.arg(&bootloader_elf_path); + cmd.arg(&output_bin_path); + let output = cmd.output().map_err(|err| CreateBootimageError::Io { + message: "failed to execute llvm-objcopy command", + error: err, + })?; + if !output.status.success() { + return Err(CreateBootimageError::ObjcopyFailed { + stderr: output.stderr, + }); + } + + Ok(()) + } +} + +/// Represents an error that occurred while creating a new `Builder`. +#[derive(Debug)] +pub enum BuilderError { + /// Failed to locate cargo manifest + LocateCargoManifest(locate_cargo_manifest::LocateManifestError), + /// Error while running `cargo metadata` + CargoMetadata(cargo_metadata::Error), +} + +impl fmt::Display for BuilderError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + BuilderError::LocateCargoManifest(err) => writeln!( + f, + "Could not find Cargo.toml file starting from current folder: {:?}", + err + ), + BuilderError::CargoMetadata(err) => writeln!( + f, + "Error while running `cargo metadata` for current project: {:?}", + err + ), + } + } +} + +/// Represents an error that occurred when building the kernel. +#[derive(Debug)] +pub enum BuildKernelError { + /// Could not find kernel package in cargo metadata, required for retrieving kernel crate name + KernelPackageNotFound, + /// An unexpected I/O error occurred + Io { + /// Desciption of the failed I/O operation + message: &'static str, + /// The I/O error that occured + error: io::Error, + }, + /// Could not find the `cargo xbuild` tool. Perhaps it is not installed? + XbuildNotFound, + /// Running `cargo xbuild` failed. + XbuildFailed { + /// The standard error output. + stderr: Vec, + }, + /// The output of `cargo xbuild --message-format=json` was not valid UTF-8 + XbuildJsonOutputInvalidUtf8(std::string::FromUtf8Error), + /// The output of `cargo xbuild --message-format=json` was not valid JSON + XbuildJsonOutputInvalidJson(json::Error), + #[doc(hidden)] + __NonExhaustive, +} + +impl fmt::Display for BuildKernelError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + BuildKernelError::KernelPackageNotFound => { + writeln!(f, "Could not find kernel package in cargo metadata, required for retrieving kernel crate name") + } + BuildKernelError::Io {message, error} => { + writeln!(f, "I/O error: {}:\n{}", message, error) + } + BuildKernelError::XbuildNotFound => { + writeln!(f, "Failed to run `cargo xbuild`. Perhaps it is not installed?\n\ + Run `cargo install cargo-xbuild` to install it.") + } + BuildKernelError::XbuildFailed{stderr} => { + writeln!(f, "Kernel build failed:\n{}", String::from_utf8_lossy(stderr)) + } + BuildKernelError::XbuildJsonOutputInvalidUtf8(err) => { + writeln!(f, "Output of kernel build with --message-format=json is not valid UTF-8:\n{}", err) + } + BuildKernelError::XbuildJsonOutputInvalidJson(err) => { + writeln!(f, "Output of kernel build with --message-format=json is not valid JSON:\n{}", err) + } + BuildKernelError::__NonExhaustive => panic!("__NonExhaustive variant constructed"), + } + } +} + +/// Represents an error that occurred when creating a bootimage. +#[derive(Debug)] +pub enum CreateBootimageError { + /// Could not find some required information in the `cargo metadata` output + CargoMetadataIncomplete { + /// The required key that was not found + key: String, + }, + /// Bootloader dependency not found + BootloaderNotFound, + /// Bootloader dependency has not the right format + BootloaderInvalid(String), + /// Building the bootloader failed + BootloaderBuildFailed { + /// The `cargo xbuild` output to standard error + stderr: Vec, + }, + /// An unexpected I/O error occurred + Io { + /// Desciption of the failed I/O operation + message: &'static str, + /// The I/O error that occured + error: io::Error, + }, + /// There was a problem retrieving the `llvm-tools-preview` rustup component + LlvmTools(llvm_tools::Error), + /// The llvm-tools component did not contain the required `llvm-objcopy` executable + LlvmObjcopyNotFound, + /// The `llvm-objcopy` command failed + ObjcopyFailed { + /// The output of `llvm-objcopy` to standard error + stderr: Vec, + }, + /// The output of `cargo xbuild --message-format=json` was not valid UTF-8 + XbuildJsonOutputInvalidUtf8(std::string::FromUtf8Error), + /// The output of `cargo xbuild --message-format=json` was not valid JSON + XbuildJsonOutputInvalidJson(json::Error), + #[doc(hidden)] + __NonExhaustive, +} + +impl fmt::Display for CreateBootimageError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + CreateBootimageError::CargoMetadataIncomplete { key } => writeln!( + f, + "Could not find required key `{}` in cargo metadata output", + key + ), + CreateBootimageError::BootloaderNotFound => { + writeln!(f, "Bootloader dependency not found\n\n\ + You need to add a dependency on a crate named `bootloader` in your Cargo.toml.") + } + CreateBootimageError::BootloaderInvalid(err) => writeln!( + f, + "The `bootloader` dependency has not the right format: {}", + err + ), + CreateBootimageError::BootloaderBuildFailed { stderr } => writeln!( + f, + "Bootloader build failed:\n\n{}", + String::from_utf8_lossy(stderr) + ), + CreateBootimageError::Io { message, error } => { + writeln!(f, "I/O error: {}: {}", message, error) + } + CreateBootimageError::LlvmTools(err) => match err { + llvm_tools::Error::NotFound => writeln!( + f, + "Could not find the `llvm-tools-preview` rustup component.\n\n\ + You can install by executing `rustup component add llvm-tools-preview`." + ), + err => writeln!( + f, + "Failed to locate the `llvm-tools-preview` rustup component: {:?}", + err + ), + }, + CreateBootimageError::LlvmObjcopyNotFound => writeln!( + f, + "Could not find `llvm-objcopy` in the `llvm-tools-preview` rustup component." + ), + CreateBootimageError::ObjcopyFailed { stderr } => writeln!( + f, + "Failed to run `llvm-objcopy`: {}", + String::from_utf8_lossy(stderr) + ), + CreateBootimageError::XbuildJsonOutputInvalidUtf8(err) => writeln!( + f, + "Output of bootloader build with --message-format=json is not valid UTF-8:\n{}", + err + ), + CreateBootimageError::XbuildJsonOutputInvalidJson(err) => writeln!( + f, + "Output of bootloader build with --message-format=json is not valid JSON:\n{}", + err + ), + CreateBootimageError::__NonExhaustive => panic!("__NonExhaustive variant constructed"), + } + } +} + +// from implementations + +impl From for BuilderError { + fn from(err: locate_cargo_manifest::LocateManifestError) -> Self { + BuilderError::LocateCargoManifest(err) + } +} + +impl From for BuilderError { + fn from(err: cargo_metadata::Error) -> Self { + BuilderError::CargoMetadata(err) + } +} + +impl From for CreateBootimageError { + fn from(err: llvm_tools::Error) -> Self { + CreateBootimageError::LlvmTools(err) + } +} diff --git a/src/config.rs b/src/config.rs index fad966a..6c80bfd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,37 +1,46 @@ -use failure::{Error, ResultExt}; -use std::path::PathBuf; +//! Parses the `package.metadata.bootimage` configuration table + +use crate::ErrorMessage; +use std::path::Path; use toml::Value; +/// Represents the `package.metadata.bootimage` configuration table +/// +/// The bootimage crate can be configured through a `package.metadata.bootimage` table +/// in the `Cargo.toml` file of the kernel. This struct represents the parsed configuration +/// options. #[derive(Debug, Clone)] pub struct Config { - pub manifest_path: PathBuf, + /// This target is used if no `--target` argument is passed pub default_target: Option, - pub output: Option, - pub bootloader: BootloaderConfig, - pub minimum_image_size: Option, + /// The run command that is invoked on `bootimage run` or `bootimage runner` + /// + /// The substring "{}" will be replaced with the path to the bootable disk image. pub run_command: Vec, - pub package_filepath: Option, + /// Additional arguments passed to the runner on `bootimage run` or `bootimage runner` + pub run_args: Option>, + /// The timeout for running an integration test through `bootimage test` in seconds + pub test_timeout: u32, + non_exhaustive: (), } -#[derive(Debug, Clone)] -pub struct BootloaderConfig { - pub name: Option, - pub target: PathBuf, - pub default_features: bool, - pub features: Vec, +pub(crate) fn read_config(manifest_path: &Path) -> Result { + let config = read_config_inner(manifest_path) + .map_err(|err| format!("Failed to read bootimage configuration: {:?}", err))?; + Ok(config) } -pub(crate) fn read_config(manifest_path: PathBuf) -> Result { +pub(crate) fn read_config_inner(manifest_path: &Path) -> Result { use std::{fs::File, io::Read}; let cargo_toml: Value = { let mut content = String::new(); - File::open(&manifest_path) - .with_context(|e| format!("Failed to open Cargo.toml: {}", e))? + File::open(manifest_path) + .map_err(|e| format!("Failed to open Cargo.toml: {}", e))? .read_to_string(&mut content) - .with_context(|e| format!("Failed to read Cargo.toml: {}", e))?; + .map_err(|e| format!("Failed to read Cargo.toml: {}", e))?; content .parse::() - .with_context(|e| format!("Failed to parse Cargo.toml: {}", e))? + .map_err(|e| format!("Failed to parse Cargo.toml: {}", e))? }; let metadata = cargo_toml @@ -40,143 +49,48 @@ pub(crate) fn read_config(manifest_path: PathBuf) -> Result { .and_then(|table| table.get("bootimage")); let metadata = match metadata { None => { - return Ok(ConfigBuilder { - manifest_path: Some(manifest_path), - ..Default::default() - } - .into()); + return Ok(ConfigBuilder::default().into()); } - Some(metadata) => metadata.as_table().ok_or(format_err!( - "Bootimage configuration invalid: {:?}", - metadata - ))?, + Some(metadata) => metadata + .as_table() + .ok_or(format!("Bootimage configuration invalid: {:?}", metadata))?, }; - /* - * The user shouldn't specify any features if they're using a precompiled bootloader, as we - * don't actually compile it. - */ - if cargo_toml - .get("dependencies") - .and_then(|table| table.get("bootloader_precompiled")) - .and_then(|table| { - table - .get("features") - .or_else(|| table.get("default-features")) - }) - .is_some() - { - return Err(format_err!( - "Can't change features of precompiled bootloader!" - )); - } - - let bootloader_dependency = cargo_toml - .get("dependencies") - .and_then(|table| table.get("bootloader")); - let bootloader_default_features = - match bootloader_dependency.and_then(|table| table.get("default-features")) { - None => None, - Some(Value::Boolean(default_features)) => Some(*default_features), - Some(_) => { - return Err(format_err!( - "Bootloader 'default-features' field should be a bool!" - )); - } - }; - - let bootloader_features = match cargo_toml - .get("dependencies") - .and_then(|table| table.get("bootloader")) - .and_then(|table| table.get("features")) - { - None => None, - Some(Value::Array(array)) => { - let mut features = Vec::new(); - - for feature_string in array { - match feature_string { - Value::String(feature) => features.push(feature.clone()), - _ => return Err(format_err!("Bootloader features are malformed!")), - } - } - - Some(features) - } - Some(_) => return Err(format_err!("Bootloader features are malformed!")), - }; - - let mut config = ConfigBuilder { - manifest_path: Some(manifest_path), - bootloader: BootloaderConfigBuilder { - features: bootloader_features, - default_features: bootloader_default_features, - ..Default::default() - }, - ..Default::default() - }; + let mut config = ConfigBuilder::default(); for (key, value) in metadata { match (key.as_str(), value.clone()) { ("default-target", Value::String(s)) => config.default_target = From::from(s), - ("output", Value::String(s)) => config.output = Some(PathBuf::from(s)), - ("bootloader", Value::Table(t)) => { - for (key, value) in t { - match (key.as_str(), value) { - ("name", Value::String(s)) => config.bootloader.name = From::from(s), - ("target", Value::String(s)) => { - config.bootloader.target = Some(PathBuf::from(s)) - } - (k @ "precompiled", _) - | (k @ "version", _) - | (k @ "git", _) - | (k @ "branch", _) - | (k @ "path", _) => Err(format_err!( - "the \ - `package.metadata.bootimage.bootloader` key `{}` was deprecated\n\n\ - In case you just updated bootimage from an earlier version, \ - check out the migration guide at \ - https://github.com/rust-osdev/bootimage/pull/16.", - k - ))?, - (key, value) => Err(format_err!( - "unexpected \ - `package.metadata.bootimage.bootloader` key `{}` with value `{}`", - key, - value - ))?, - } - } + ("test-timeout", Value::Integer(timeout)) if timeout.is_negative() => { + Err(format!("test-timeout must not be negative"))? } - ("minimum-image-size", Value::Integer(x)) => { - if x >= 0 { - config.minimum_image_size = Some((x * 1024 * 1024) as u64); // MiB -> Byte - } else { - Err(format_err!( - "unexpected `package.metadata.bootimage` \ - key `minimum-image-size` with negative value `{}`", - value - ))? - } + ("test-timeout", Value::Integer(timeout)) => { + config.test_timeout = Some(timeout as u32); } ("run-command", Value::Array(array)) => { let mut command = Vec::new(); for value in array { match value { Value::String(s) => command.push(s), - _ => Err(format_err!("run-command must be a list of strings"))?, + _ => Err(format!("run-command must be a list of strings"))?, } } config.run_command = Some(command); } - ("package-file", Value::String(path)) => { - config.package_filepath = Some(PathBuf::from(path)); + ("run-args", Value::Array(array)) => { + let mut args = Vec::new(); + for value in array { + match value { + Value::String(s) => args.push(s), + _ => Err(format!("run-args must be a list of strings"))?, + } + } + config.run_args = Some(args); } - (key, value) => Err(format_err!( + (key, value) => Err(format!( "unexpected `package.metadata.bootimage` \ key `{}` with value `{}`", - key, - value + key, value ))?, } } @@ -185,50 +99,24 @@ pub(crate) fn read_config(manifest_path: PathBuf) -> Result { #[derive(Default)] struct ConfigBuilder { - manifest_path: Option, default_target: Option, - output: Option, - bootloader: BootloaderConfigBuilder, - minimum_image_size: Option, run_command: Option>, - package_filepath: Option, -} - -#[derive(Default)] -struct BootloaderConfigBuilder { - name: Option, - target: Option, - features: Option>, - default_features: Option, + run_args: Option>, + test_timeout: Option, } impl Into for ConfigBuilder { fn into(self) -> Config { Config { - manifest_path: self.manifest_path.expect("manifest path must be set"), default_target: self.default_target, - output: self.output, - bootloader: self.bootloader.into(), - minimum_image_size: self.minimum_image_size, run_command: self.run_command.unwrap_or(vec![ "qemu-system-x86_64".into(), "-drive".into(), "format=raw,file={}".into(), ]), - package_filepath: self.package_filepath, - } - } -} - -impl Into for BootloaderConfigBuilder { - fn into(self) -> BootloaderConfig { - BootloaderConfig { - name: self.name, - target: self - .target - .unwrap_or(PathBuf::from("x86_64-bootloader.json")), - features: self.features.unwrap_or(Vec::with_capacity(0)), - default_features: self.default_features.unwrap_or(true), + run_args: self.run_args, + test_timeout: self.test_timeout.unwrap_or(60 * 5), + non_exhaustive: (), } } } diff --git a/src/help/build_help.txt b/src/help/build_help.txt index 5477d81..e623053 100644 --- a/src/help/build_help.txt +++ b/src/help/build_help.txt @@ -12,15 +12,9 @@ BUILD_OPTS: disk image. CONFIGURATION: - The bootloader and the behavior of `bootimage build` can be configured - through a `[package.metadata.bootimage]` table in the `Cargo.toml`. The - following options are available to configure the build: + The behavior of `bootimage build` can be configured through a + `[package.metadata.bootimage]` table in the `Cargo.toml`. The following + options are available to configure the build: [package.metadata.bootimage] default-target = "" This target is used if no `--target` is passed - output = "bootimage.bin" The output file name - minimum-image-size = 0 The minimum output file size (in MiB) - - [package.metadata.bootimage.bootloader] - name = "bootloader" The bootloader crate name - target = "x86_64-bootloader.json" Target triple for compiling the bootloader diff --git a/src/help/cargo_bootimage_help.txt b/src/help/cargo_bootimage_help.txt new file mode 100644 index 0000000..b257c10 --- /dev/null +++ b/src/help/cargo_bootimage_help.txt @@ -0,0 +1,20 @@ +Creates a bootable disk image from a Rust kernel + +USAGE: + cargo bootimage [BUILD_OPTS] Create a bootable disk image + + (for other forms of usage see `bootimage --help`) + +BUILD_OPTS: + Any options are directly passed to `cargo build` (see + `cargo build --help` for possible options). After building, a bootloader + is downloaded and built, and then combined with the kernel into a bootable + disk image. + +CONFIGURATION: + The behavior of `cargo bootimage` can be configured through a + `[package.metadata.bootimage]` table in the `Cargo.toml`. The following + options are available to configure the build: + + [package.metadata.bootimage] + default-target = "" This target is used if no `--target` is passed diff --git a/src/help/help.txt b/src/help/help.txt index 4d594ea..3d265b9 100644 --- a/src/help/help.txt +++ b/src/help/help.txt @@ -5,6 +5,10 @@ USAGE: bootimage build [BUILD_OPTS] Create a bootable disk image bootimage run [BUILD_OPTS] -- [RUN_OPTS] Build and run a disk image bootimage test [BUILD_OPTS] Runs integration tests + bootimage runner EXECUTABLE Convert and run an executable + + cargo bootimage [BUILD_OPTS] Create a bootable disk image + (equivalent to bootimage build) For more information about a subcommand run `bootimage [subcommand] --help`. diff --git a/src/help/mod.rs b/src/help/mod.rs index 63bd8a1..9af62e7 100644 --- a/src/help/mod.rs +++ b/src/help/mod.rs @@ -1,8 +1,10 @@ -use std::process; +use crate::ErrorMessage; const HELP: &str = include_str!("help.txt"); const BUILD_HELP: &str = include_str!("build_help.txt"); +const CARGO_BOOTIMAGE_HELP: &str = include_str!("cargo_bootimage_help.txt"); const RUN_HELP: &str = include_str!("run_help.txt"); +const RUNNER_HELP: &str = include_str!("runner_help.txt"); const TEST_HELP: &str = include_str!("test_help.txt"); pub(crate) fn help() { @@ -13,17 +15,23 @@ pub(crate) fn build_help() { print!("{}", BUILD_HELP); } +pub(crate) fn cargo_bootimage_help() { + print!("{}", CARGO_BOOTIMAGE_HELP); +} + pub(crate) fn run_help() { print!("{}", RUN_HELP); } +pub(crate) fn runner_help() { + print!("{}", RUNNER_HELP); +} + pub(crate) fn test_help() { print!("{}", TEST_HELP); } -pub(crate) fn no_subcommand() -> ! { - println!("Please invoke `bootimage` with a subcommand (e.g. `bootimage build`)."); - println!(); - println!("See `bootimage --help` for more information."); - process::exit(1); +pub(crate) fn no_subcommand() -> ErrorMessage { + "Please invoke `bootimage` with a subcommand (e.g. `bootimage build`).\n\n\ + See `bootimage --help` for more information.".into() } diff --git a/src/help/run_help.txt b/src/help/run_help.txt index 09dfdce..4056ae3 100644 --- a/src/help/run_help.txt +++ b/src/help/run_help.txt @@ -16,6 +16,9 @@ CONFIGURATION: following options are available to configure run behavior: [package.metadata.bootimage] - # The command invoked on `bootimage run` + # The command invoked on `bootimage run` or `bootimage runner` # (the "{}" will be replaced with the path to the bootable disk image) run-command = ["qemu-system-x86_64", "-drive", "format=raw,file={}"] + # Additional arguments passed to the runner on `bootimage run` or `bootimage runner` + # (this is useful when you want to add some arguments to the default QEMU command) + run-args = [] \ No newline at end of file diff --git a/src/help/runner_help.txt b/src/help/runner_help.txt new file mode 100644 index 0000000..d2bf3ed --- /dev/null +++ b/src/help/runner_help.txt @@ -0,0 +1,25 @@ +Creates a bootable disk image from a Rust kernel and launches it in QEMU + +USAGE: + bootimage runner EXECUTABLE Convert and run the given EXECUTABLE + + (for other forms of usage see `bootimage --help`) + + This subcommand can be used as a target runner in a `.cargo/config` file: + ``` + [target.'cfg(target_os = "none")'] + runner = "bootimage runner" + ``` + +CONFIGURATION: + The behavior of `bootimage runner` can be configured through a + `[package.metadata.bootimage]` table in the `Cargo.toml`. The + following options are available to configure run behavior: + + [package.metadata.bootimage] + # The command invoked on `bootimage run` or `bootimage runner` + # (the "{}" will be replaced with the path to the bootable disk image) + run-command = ["qemu-system-x86_64", "-drive", "format=raw,file={}"] + # Additional arguments passed to the runner on `bootimage run` or `bootimage runner` + # (this is useful when you want to add some arguments to the default QEMU command) + run-args = [] \ No newline at end of file diff --git a/src/help/test_help.txt b/src/help/test_help.txt index 18c5b91..a9fa8e1 100644 --- a/src/help/test_help.txt +++ b/src/help/test_help.txt @@ -15,3 +15,12 @@ USAGE: (for other forms of usage see `bootimage --help`) (for BUILD_OPTS see `bootimage build --help`) + +CONFIGURATION: + The behavior of `bootimage test` can be configured through a + `[package.metadata.bootimage]` table in the `Cargo.toml`. The + following options are available to configure test behavior: + + [package.metadata.bootimage] + # The timeout for running an integration test in seconds + test-timeout = 300 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6d6ce73 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,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, 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, +} + +impl fmt::Debug for ErrorMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.message.fmt(f) + } +} + +impl From for ErrorMessage +where + T: fmt::Display + Send + 'static, +{ + fn from(err: T) -> Self { + ErrorMessage { + message: Box::new(err), + } + } +} diff --git a/src/main.rs b/src/main.rs index 4064403..5349675 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,52 +1,3 @@ -extern crate byteorder; -extern crate cargo_metadata; -extern crate rayon; -extern crate toml; -extern crate wait_timeout; -extern crate xmas_elf; -#[macro_use] -extern crate failure; - -use args::Args; -use std::{io, process}; - -mod args; -mod build; -mod config; -mod help; -mod test; - -enum Command { - NoSubcommand, - Build(Args), - Run(Args), - Test(Args), - Help, - BuildHelp, - RunHelp, - TestHelp, - Version, -} - pub fn main() { - use std::io::Write; - if let Err(err) = run() { - writeln!(io::stderr(), "Error: {}", err).unwrap(); - process::exit(1); - } -} - -fn run() -> Result<(), failure::Error> { - let command = args::parse_args(); - match command { - Command::NoSubcommand => help::no_subcommand(), - Command::Build(args) => build::build(args), - Command::Run(args) => build::run(args), - Command::Test(args) => test::test(args), - Command::Help => Ok(help::help()), - Command::BuildHelp => Ok(help::build_help()), - Command::RunHelp => Ok(help::run_help()), - Command::TestHelp => Ok(help::test_help()), - Command::Version => Ok(println!("bootimage {}", env!("CARGO_PKG_VERSION"))), - } + bootimage::lib_main(); } diff --git a/src/subcommand.rs b/src/subcommand.rs new file mode 100644 index 0000000..f848c46 --- /dev/null +++ b/src/subcommand.rs @@ -0,0 +1,4 @@ +pub mod build; +pub mod run; +pub mod runner; +pub mod test; diff --git a/src/subcommand/build.rs b/src/subcommand/build.rs new file mode 100644 index 0000000..1cc118e --- /dev/null +++ b/src/subcommand/build.rs @@ -0,0 +1,55 @@ +use crate::{args::Args, builder::Builder, config, ErrorMessage}; +use std::{path::PathBuf, process}; + +pub(crate) fn build(mut args: Args) -> Result<(), ErrorMessage> { + let builder = Builder::new(args.manifest_path().clone())?; + let config = config::read_config(builder.kernel_manifest_path())?; + args.apply_default_target(&config, builder.kernel_root()); + + let quiet = args.quiet; + build_impl(&builder, &mut args, quiet).map(|_| ()) +} + +pub(crate) fn build_impl( + builder: &Builder, + args: &Args, + quiet: bool, +) -> Result, ErrorMessage> { + run_cargo_fetch(&args).map_err(|()| "cargo fetch failed")?; + + let executables = builder.build_kernel(&args.cargo_args, quiet)?; + if executables.len() == 0 { + Err("no executables built")?; + } + + let mut bootimages = Vec::new(); + + for executable in executables { + let out_dir = executable.parent().ok_or("executable has no parent path")?; + let file_stem = executable + .file_stem() + .ok_or("executable has no file stem")? + .to_str() + .ok_or("executable file stem not valid utf8")?; + + let bootimage_path = out_dir.join(format!("bootimage-{}.bin", file_stem)); + builder.create_bootimage(&executable, &bootimage_path, quiet)?; + bootimages.push(bootimage_path); + } + + Ok(bootimages) +} + +fn run_cargo_fetch(args: &Args) -> Result<(), ()> { + let mut command = process::Command::new("cargo"); + command.arg("fetch"); + if let Some(manifest_path) = args.manifest_path() { + command.arg("--manifest-path"); + command.arg(manifest_path); + } + if command.status().map(|s| s.success()).unwrap_or(false) { + Ok(()) + } else { + Err(()) + } +} diff --git a/src/subcommand/run.rs b/src/subcommand/run.rs new file mode 100644 index 0000000..e83b8dd --- /dev/null +++ b/src/subcommand/run.rs @@ -0,0 +1,41 @@ +use crate::{args::Args, builder::Builder, config, ErrorMessage}; +use std::process; + +pub(crate) fn run(mut args: Args) -> Result { + use crate::subcommand::build; + + let builder = Builder::new(args.manifest_path().clone())?; + let config = config::read_config(builder.kernel_manifest_path())?; + args.apply_default_target(&config, builder.kernel_root()); + + let quiet = args.quiet; + let bootimages = build::build_impl(&builder, &mut args, quiet)?; + let bootimage_path = bootimages.first().ok_or("no bootimages created")?; + if bootimages.len() > 1 { + Err("more than one bootimage created")?; + } + + let command = &config.run_command[0]; + let mut command = process::Command::new(command); + for arg in &config.run_command[1..] { + command.arg( + arg.replace( + "{}", + bootimage_path + .to_str() + .ok_or(ErrorMessage::from("bootimage path is not valid unicode"))?, + ), + ); + } + if let Some(run_args) = config.run_args { + command.args(run_args); + } + command.args(&args.run_args); + let exit_status = command.status().map_err(|err| { + ErrorMessage::from(format!( + "Failed to execute run command `{:?}`: {}", + command, err + )) + })?; + Ok(exit_status.code().unwrap_or(1)) +} diff --git a/src/subcommand/runner.rs b/src/subcommand/runner.rs new file mode 100644 index 0000000..f00e4de --- /dev/null +++ b/src/subcommand/runner.rs @@ -0,0 +1,39 @@ +use crate::{args::RunnerArgs, builder::Builder, config, ErrorMessage}; +use std::process; + +pub(crate) fn runner(args: RunnerArgs) -> Result { + let builder = Builder::new(None)?; + let config = config::read_config(builder.kernel_manifest_path())?; + + let bootimage_bin = { + let parent = args + .executable + .parent() + .ok_or("kernel executable has no parent")?; + let file_stem = args + .executable + .file_stem() + .ok_or("kernel executable has no file stem")? + .to_str() + .ok_or("kernel executable file stem is not valid UTF-8")?; + parent.join(format!("bootimage-{}.bin", file_stem)) + }; + + builder.create_bootimage(&args.executable, &bootimage_bin, args.quiet)?; + + let mut command = process::Command::new(&config.run_command[0]); + for arg in &config.run_command[1..] { + command.arg(arg.replace("{}", &format!("{}", bootimage_bin.display()))); + } + if let Some(run_args) = config.run_args { + command.args(run_args); + } + + println!("Running: {:?}", command); + + let output = command + .output() + .map_err(|e| format!("Failed to execute `{:?}`: {}", command, e))?; + + Ok(output.status.code().unwrap_or(1)) +} diff --git a/src/test.rs b/src/subcommand/test.rs similarity index 70% rename from src/test.rs rename to src/subcommand/test.rs index b173e4a..7bf06b1 100644 --- a/src/test.rs +++ b/src/subcommand/test.rs @@ -1,56 +1,41 @@ -use args::Args; -use build; -use failure::{Error, ResultExt}; +use crate::{args::Args, builder::Builder, config, subcommand::build, ErrorMessage}; use rayon::prelude::*; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::time::Duration; -use std::{fs, io, process}; +use std::{fs, io, io::Write, process, time::Duration}; use wait_timeout::ChildExt; -pub(crate) fn test(args: Args) -> Result<(), Error> { - let (args, config, metadata, root_dir, out_dir) = build::common_setup(args)?; +pub(crate) fn test(mut args: Args) -> Result<(), ErrorMessage> { + let builder = Builder::new(args.manifest_path().clone())?; + let config = config::read_config(builder.kernel_manifest_path())?; + args.apply_default_target(&config, builder.kernel_root()); let test_args = args.clone(); - let test_config = { - let mut test_config = config.clone(); - test_config.output = None; - test_config - }; - - let test_targets = metadata - .packages - .iter() - .find(|p| { - Path::new(&p.manifest_path) - .canonicalize() - .map(|path| path == config.manifest_path) - .unwrap_or(false) - }) - .expect("Could not read crate name from cargo metadata") + let kernel_package = builder + .kernel_package() + .map_err(|key| format!("Kernel package not found it cargo metadata (`{}`)", key))?; + let test_target_iter = kernel_package .targets .iter() - .filter(|t| t.kind == ["bin"] && t.name.starts_with("test-")) - .map(|target| { - println!("BUILD: {}", target.name); - - let mut target_args = test_args.clone(); - target_args.set_bin_name(target.name.clone()); - let test_path = build::build_impl( - &target_args, - &test_config, - &metadata, - &root_dir, - &out_dir, - false, - ) + .filter(|t| t.kind == ["bin"] && t.name.starts_with("test-")); + + let mut test_targets = Vec::new(); + for target in test_target_iter { + println!("BUILD: {}", target.name); + + let mut target_args = test_args.clone(); + target_args.set_bin_name(target.name.clone()); + let executables = build::build_impl(&builder, &mut target_args, true) .expect(&format!("Failed to build test: {}", target.name)); - println!(""); + let test_bin_path = executables + .first() + .ok_or("no test executable built")? + .to_owned(); + if executables.len() > 1 { + Err("more than one test executables built")?; + } - (target, test_path) - }) - .collect::>(); + test_targets.push((target, test_bin_path)); + } let tests = test_targets .par_iter() @@ -72,25 +57,25 @@ pub(crate) fn test(args: Args) -> Result<(), Error> { command.stderr(process::Stdio::null()); let mut child = command .spawn() - .with_context(|e| format_err!("Failed to launch QEMU: {:?}\n{}", command, e))?; - let timeout = Duration::from_secs(60); + .map_err(|e| format!("Failed to launch QEMU: {:?}\n{}", command, e))?; + let timeout = Duration::from_secs(config.test_timeout.into()); match child .wait_timeout(timeout) - .with_context(|e| format!("Failed to wait with timeout: {}", e))? + .map_err(|e| format!("Failed to wait with timeout: {}", e))? { None => { child .kill() - .with_context(|e| format!("Failed to kill QEMU: {}", e))?; + .map_err(|e| format!("Failed to kill QEMU: {}", e))?; child .wait() - .with_context(|e| format!("Failed to wait for QEMU process: {}", e))?; + .map_err(|e| format!("Failed to wait for QEMU process: {}", e))?; test_result = TestResult::TimedOut; writeln!(io::stderr(), "Timed Out")?; } Some(exit_status) => { - let output = fs::read_to_string(&output_file).with_context(|e| { - format_err!("Failed to read test output file {}: {}", output_file, e) + let output = fs::read_to_string(&output_file).map_err(|e| { + format!("Failed to read test output file {}: {}", output_file, e) })?; test_result = handle_exit_status(exit_status, &output, &target.name)?; } @@ -98,7 +83,7 @@ pub(crate) fn test(args: Args) -> Result<(), Error> { Ok((target.name.clone(), test_result)) }) - .collect::, Error>>()?; + .collect::, ErrorMessage>>()?; println!(""); if tests.iter().all(|t| t.1 == TestResult::Ok) { @@ -109,7 +94,7 @@ pub(crate) fn test(args: Args) -> Result<(), Error> { for test in tests.iter().filter(|t| t.1 != TestResult::Ok) { writeln!(io::stderr(), " {}: {:?}", test.0, test.1)?; } - process::exit(1); + Err("Some tests failed".into()) } } @@ -117,7 +102,7 @@ fn handle_exit_status( exit_status: process::ExitStatus, output: &str, target_name: &str, -) -> Result { +) -> Result { match exit_status.code() { None => { writeln!(io::stderr(), "FAIL: No Exit Code.")?;