diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fcf00c2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,142 @@ +name: Build + +on: + push: + branches: + - 'master' + tags: + - '*' + schedule: + - cron: '40 4 * * *' # every day at 4:40 + pull_request: + +jobs: + test: + name: "Test" + + strategy: + fail-fast: false + matrix: + platform: [ + ubuntu-latest, + macos-latest, + windows-latest + ] + + runs-on: ${{ matrix.platform }} + timeout-minutes: 15 + + steps: + - name: "Checkout Repository" + uses: actions/checkout@v1 + + - name: Set up Rustup + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + + - name: "Print Rust Version" + run: | + rustc -Vv + cargo -Vv + + - name: "Run cargo build" + uses: actions-rs/cargo@v1 + with: + command: build + + - name: "Run cargo test" + uses: actions-rs/cargo@v1 + with: + command: test + + - name: "Deny Warnings" + uses: actions-rs/cargo@v1 + with: + command: build + env: + RUSTFLAGS: "-D warnings" + + - name: "Install it" + run: cargo install --path . + + - name: "Switch to Rust nightly" + run: rustup default nightly + + - name: "Install Rustup Components" + run: rustup component add rust-src llvm-tools-preview + + # install QEMU + - name: Install QEMU (Linux) + run: | + sudo apt update + sudo apt install qemu-system-x86 + if: runner.os == 'Linux' + - name: Install QEMU (macOS) + run: brew install qemu + if: runner.os == 'macOS' + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 1 + HOMEBREW_NO_INSTALL_CLEANUP: 1 + - name: Install QEMU (Windows) + run: | + choco install qemu --version 2021.5.5 + echo "$Env:Programfiles\qemu" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + if: runner.os == 'Windows' + shell: pwsh + + - name: "Print QEMU Version" + run: qemu-system-x86_64 --version + + - name: 'Build "basic" Kernel' + run: cargo bootimage --target ../x86_64-bootimage-example-kernels.json + working-directory: example-kernels/basic + + - name: 'Run QEMU with "basic" Kernel' + run: | + 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 + shell: bash {0} + working-directory: example-kernels + + - name: 'Run `cargo run` for "runner" kernel' + run: | + cargo run + if [ $? -eq 109 ]; then (exit 0); else (exit 1); fi + shell: bash {0} + working-directory: example-kernels/runner + + - run: cargo test + working-directory: example-kernels/runner-test + name: 'Run `cargo test` for "runner-test" kernel' + + - run: cargo test -Z doctest-xcompile + working-directory: example-kernels/runner-doctest + name: 'Run `cargo test -Z doctest-xcompile` for "runner-doctest" kernel' + + - run: cargo test + working-directory: example-kernels/runner-fail-reboot + name: 'Run `cargo test` for "runner-fail-reboot" kernel' + + check_formatting: + name: "Check Formatting" + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - uses: actions/checkout@v1 + - run: rustup toolchain install nightly --profile minimal --component rustfmt + - run: cargo +nightly fmt -- --check + + clippy: + name: "Clippy" + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v1 + - run: rustup toolchain install nightly --profile minimal --component clippy + - name: "Run `cargo clippy`" + uses: actions-rs/cargo@v1 + with: + command: clippy diff --git a/Cargo.lock b/Cargo.lock index fc9a4fd..ec54604 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,282 +1,189 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "arrayvec" -version = "0.4.7" +name = "anyhow" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" [[package]] name = "bootimage" -version = "0.7.6" +version = "0.10.3" dependencies = [ - "cargo_metadata 0.7.4 (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)", + "anyhow", + "cargo_metadata", + "json", + "llvm-tools", + "locate-cargo-manifest", + "thiserror", + "toml", + "wait-timeout", ] [[package]] name = "cargo_metadata" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cfg-if" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "crossbeam-deque" -version = "0.2.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e3374c604fb39d1a2f35ed5e4a4e30e60d01fab49446e08f1b3e9a90aef202" dependencies = [ - "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "semver", + "serde", + "serde_derive", + "serde_json", ] -[[package]] -name = "crossbeam-epoch" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dtoa" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "either" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "error-chain" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "itoa" -version = "0.4.2" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "json" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "1.2.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] name = "libc" -version = "0.2.42" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" [[package]] name = "llvm-tools" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" [[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" -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 = "num_cpus" -version = "1.8.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db985b63431fe09e8d71f50aeceffcc31e720cb86be8dad2f38d084c5a328466" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "json", ] [[package]] name = "proc-macro2" -version = "0.4.8" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "quote" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rayon" -version = "1.0.3" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] -name = "rayon-core" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scopeguard" -version = "0.3.3" +name = "ryu" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "semver-parser", + "serde", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.89" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" [[package]] name = "serde_derive" -version = "1.0.89" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" dependencies = [ - "proc-macro2 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" -version = "1.0.24" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" dependencies = [ - "dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "syn" -version = "0.15.29" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" dependencies = [ - "proc-macro2 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "toml" -version = "0.5.0" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" dependencies = [ - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", ] [[package]] name = "unicode-xid" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] - -[metadata] -"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" -"checksum cargo_metadata 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "178d62b240c34223f265a4c1e275e37d62da163d421fc8d7f7e3ee340f803c57" -"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" -"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" -"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 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" -"checksum proc-macro2 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c65b1ea15bb859d922cade2d1765b4b88beac339cbfad545ef2d2ef8c8215ee6" -"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 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" -"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" -"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" -"checksum serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c6908c7b925cd6c590358a4034de93dbddb20c45e1d021931459fd419bf0e2" -"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" -"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" diff --git a/Cargo.toml b/Cargo.toml index 7f01bd3..f2fbf21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,18 +3,23 @@ authors = ["Philipp Oppermann "] description = "Tool to create a bootable OS image from a kernel binary." license = "MIT/Apache-2.0" name = "bootimage" -version = "0.7.6" +version = "0.10.3" repository = "https://github.com/rust-osdev/bootimage" edition = "2018" [dependencies] -rayon = "1.0" -toml = "0.5.0" -wait-timeout = "0.2" +toml = "0.5.6" +wait-timeout = "0.2.0" llvm-tools = "0.1.1" -locate-cargo-manifest = "0.1.0" -json = "0.11.13" +locate-cargo-manifest = "0.2.0" +json = "0.12.4" +anyhow = "1.0.28" +thiserror = "1.0.16" +cargo_metadata = "0.9.1" -[dependencies.cargo_metadata] -version = "0.7.4" -default-features = false +[package.metadata.release] +no-dev-version = true +pre-release-replacements = [ + { file="Changelog.md", search="# Unreleased", replace="# Unreleased\n\n# {{version}} – {{date}}", exactly=1 }, +] +pre-release-commit-message = "Release version {{version}}" diff --git a/Changelog.md b/Changelog.md index 4c21e12..63326a4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,50 @@ +# Unreleased + +# 0.10.3 – 2021-04-01 + +- Fix "unnnecessary trailing semicolon" warning on Rust 1.51 + +# 0.10.2 – 2020-12-10 + +- Fix nightly breakage of doctests in workspaces ([#69](https://github.com/rust-osdev/bootimage/pull/69)) + +# 0.10.1 – 2020-08-03 + +- Parse `--version` argument without subcommand (`bootimage --version`) ([#67](https://github.com/rust-osdev/bootimage/pull/67)) + +# 0.10.0 – 2020-08-03 + +- **Breaking:** Consider all other exit codes besides 'test-success-exit-code' as failures ([#65](https://github.com/rust-osdev/bootimage/pull/65)) + - Also runs tests with `-no-reboot` by default, configurable through a new `test-no-reboot` config key + +# 0.9.0 – 2020-07-17 + +- **Breaking:** Make `cargo bootimage` use `cargo build` instead of `cargo xbuild` ([#63](https://github.com/rust-osdev/bootimage/pull/63)) + +# 0.8.1 – 2020-07-17 + +- Add support for building bootloaders using `-Zbuild-std ([#62](https://github.com/rust-osdev/bootimage/pull/62)) + +# 0.8.0 + +- **Breaking:** Rewrite: Remove support for `bootimage {run, test}` ([#55](https://github.com/rust-osdev/bootimage/pull/55)) + +# 0.7.10 + +- Add support for doctests ([#52](https://github.com/rust-osdev/bootimage/pull/52)) + +# 0.7.9 + +- Set empty RUSTFLAGS to ensure that no .cargo/config applies ([#51](https://github.com/rust-osdev/bootimage/pull/51)) + +# 0.7.8 + +- Don't exit with expected exit code when failed to read QEMU exit code ([#47](https://github.com/rust-osdev/bootimage/pull/47)) + +# 0.7.7 + +- Pass location of kernel's Cargo.toml to bootloader ([#45](https://github.com/rust-osdev/bootimage/pull/45)) + # 0.7.6 - If the bootloader has a feature named `binary`, enable it ([#43](https://github.com/rust-osdev/bootimage/pull/43)) diff --git a/Readme.md b/Readme.md index 41099c7..852f25d 100644 --- a/Readme.md +++ b/Readme.md @@ -16,7 +16,7 @@ First you need to add a dependency on the [`bootloader`](https://github.com/rust # in your Cargo.toml [dependencies] -bootloader = "0.6.4" +bootloader = "0.9.8" ``` **Note**: At least bootloader version `0.5.1` is required since `bootimage 0.7.0`. For earlier bootloader versions, use `bootimage 0.6.6`. @@ -27,64 +27,64 @@ If you want to use a custom bootloader with a different name, you can use Cargo' 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] -``` - -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] -``` +The command will invoke `cargo build`, forwarding all passed options. Then it will build the specified bootloader together with the kernel to create a bootable disk image. -All arguments after `--` are passed to QEMU. If you want to use a custom run command, see the _Configuration_ section below. +### Running -If you prefer working directly with cargo, you can use `bootimage runner` as a custom runner in your `.cargo/config`: +To run your kernel in QEMU, you can set a `bootimage runner` as a custom runner in a `.cargo/config` file: ```toml [target.'cfg(target_os = "none")'] runner = "bootimage runner" ``` -Now you can run your kernel through `cargo xrun --target […]`. +Then you can run your kernel through: -## Configuration - -Configuration is done through a through a `[package.metadata.bootimage]` table in the `Cargo.toml` of your kernel. The following options are available: +``` +cargo xrun --target your_custom_target.json [other_args] -- [qemu args] +``` -```toml - [package.metadata.bootimage] - # This target is used if no `--target` is passed - default-target = "" +All arguments after `--` are passed to QEMU. If you want to use a custom run command, see the _Configuration_ section below. - # The command invoked with the created bootimage (the "{}" will be replaced - # with the path to the bootable disk image) - # Applies to `bootimage run` and `bootimage runner` - run-command = ["qemu-system-x86_64", "-drive", "format=raw,file={}"] +### Testing - # Additional arguments passed to the run command for non-test executables - # Applies to `bootimage run` and `bootimage runner` - run-args = [] +The `bootimage` has built-in support for running unit and integration tests of your kernel. For this, you need to use the `custom_tests_framework` feature of Rust as described [here](https://os.phil-opp.com/testing/#custom-test-frameworks). - # Additional arguments passed to the run command for test executables - # Applies to `bootimage runner` - test-args = [] +## Configuration - # An exit code that should be considered as success for test executables - test-success-exit-code = {integer} +Configuration is done through a `[package.metadata.bootimage]` table in the `Cargo.toml` of your kernel. The following options are available: - # The timeout for running a test through `bootimage test` or `bootimage runner` (in seconds) - test-timeout = 300 +```toml +[package.metadata.bootimage] +# The cargo subcommand that will be used for building the kernel. +# +# For building using the `cargo-xbuild` crate, set this to `xbuild`. +build-command = ["build"] +# The command invoked with the created bootimage (the "{}" will be replaced +# with the path to the bootable disk image) +# Applies to `bootimage run` and `bootimage runner` +run-command = ["qemu-system-x86_64", "-drive", "format=raw,file={}"] + +# Additional arguments passed to the run command for non-test executables +# Applies to `bootimage run` and `bootimage runner` +run-args = [] + +# Additional arguments passed to the run command for test executables +# Applies to `bootimage runner` +test-args = [] + +# An exit code that should be considered as success for test executables +test-success-exit-code = {integer} + +# The timeout for running a test through `bootimage test` or `bootimage runner` (in seconds) +test-timeout = 300 + +# Whether the `-no-reboot` flag should be passed to test executables +test-no-reboot = true ``` ## License diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 4e659e0..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,241 +0,0 @@ -# Documentation: https://aka.ms/yaml - -trigger: - branches: - include: - - '*' - exclude: - - 'staging.tmp' - -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)' - - - bash: rustup default $RUSTUP_TOOLCHAIN - condition: eq( variables['Agent.OS'], 'Windows_NT' ) - displayName: 'Set Rust Toolchain (Windows)' - - - bash: rustup update $RUSTUP_TOOLCHAIN - displayName: 'Run Rustup Update' - - - 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)' - - - bash: rustup default $RUSTUP_TOOLCHAIN - condition: eq( variables['Agent.OS'], 'Windows_NT' ) - displayName: 'Set Rust Toolchain (Windows)' - - - bash: rustup update $RUSTUP_TOOLCHAIN - displayName: 'Run Rustup Update' - - - 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' - - - script: cargo xtest - workingDirectory: example-kernels/runner-test - displayName: 'Run `cargo xtest` for "runner-test" kernel' - -- job: formatting - displayName: Check Formatting - - strategy: - matrix: - linux: - image_name: 'ubuntu-16.04' - 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' - - - script: | - rustc -Vv - cargo -V - displayName: 'Print Rust Version' - continueOnError: true - - - script: rustup component add rustfmt - displayName: 'Install Rustfmt' - - - script: cargo fmt -- --check - displayName: 'Check Formatting' diff --git a/bors.toml b/bors.toml deleted file mode 100644 index 9c47331..0000000 --- a/bors.toml +++ /dev/null @@ -1,4 +0,0 @@ -status = [ - "rust-osdev.bootimage", -] -delete_merged_branches = true diff --git a/example-kernels/.cargo/config.toml b/example-kernels/.cargo/config.toml new file mode 100644 index 0000000..92cee48 --- /dev/null +++ b/example-kernels/.cargo/config.toml @@ -0,0 +1,2 @@ +[unstable] +build-std = ["core", "compiler_builtins"] diff --git a/example-kernels/Cargo.lock b/example-kernels/Cargo.lock deleted file mode 100644 index 8549198..0000000 --- a/example-kernels/Cargo.lock +++ /dev/null @@ -1,400 +0,0 @@ -# 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.3 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.5.5 (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.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bootloader" -version = "0.5.3" -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.5 (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 = "cast" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cc" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "default-target-bootimage" -version = "0.1.0" -dependencies = [ - "bootloader 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "default-target-cargo" -version = "0.1.0" -dependencies = [ - "bootloader 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.5.5 (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.5" -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.19" -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.60" -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.19 (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.60 (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.7 (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.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.37 (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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "runner" -version = "0.1.0" -dependencies = [ - "bootloader 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "runner-test" -version = "0.1.0" -dependencies = [ - "bootloader 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.5.5 (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.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "testing-qemu-exit-code" -version = "0.1.0" -dependencies = [ - "bootloader 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "testing-serial-result" -version = "0.1.0" -dependencies = [ - "bootloader 0.5.3 (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.5 (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.1.0 (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.7" -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.1.0 (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.1.0 (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.5" -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.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "raw-cpuid 6.1.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.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" -"checksum bootloader 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f7c11b70b5781deec899276f7d67611eaf296a8bd7dcc9b9d37c71a5389c52" -"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" -"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" -"checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0" -"checksum font8x8 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "44226c40489fb1d602344a1d8f1b544570c3435e396dda1eda7b5ef010d8f1be" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "72327b15c228bfe31f1390f93dd5e9279587f0463836393c9df719ce62a3e450" -"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" -"checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" -"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.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"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.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" -"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.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bb8f09c32a991cc758ebcb9b7984f530095d32578a4e7b85db6ee1f0bbe4c9c6" -"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 index ee84626..e20e2aa 100644 --- a/example-kernels/Cargo.toml +++ b/example-kernels/Cargo.toml @@ -1,10 +1,8 @@ [workspace] members = [ "basic", - "default-target-bootimage", - "default-target-cargo", "runner", + "runner-doctest", + "runner-fail-reboot", "runner-test", - "testing-qemu-exit-code", - "testing-serial-result", -] \ No newline at end of file +] diff --git a/example-kernels/basic/Cargo.toml b/example-kernels/basic/Cargo.toml index 76b9db0..5a9a509 100644 --- a/example-kernels/basic/Cargo.toml +++ b/example-kernels/basic/Cargo.toml @@ -5,5 +5,5 @@ authors = ["Philipp Oppermann "] edition = "2018" [dependencies] -bootloader = "0.6.4" -x86_64 = "0.5.3" +bootloader = "0.9.7" +x86_64 = "0.14.1" diff --git a/example-kernels/default-target-bootimage/Cargo.toml b/example-kernels/default-target-bootimage/Cargo.toml deleted file mode 100644 index befa7ff..0000000 --- a/example-kernels/default-target-bootimage/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "default-target-bootimage" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -[dependencies] -bootloader = "0.6.4" -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 deleted file mode 100644 index 44b77a9..0000000 --- a/example-kernels/default-target-bootimage/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![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 deleted file mode 100644 index 79fdf34..0000000 --- a/example-kernels/default-target-cargo/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[build] -target = "../x86_64-bootimage-example-kernels.json" \ No newline at end of file diff --git a/example-kernels/default-target-cargo/Cargo.toml b/example-kernels/default-target-cargo/Cargo.toml deleted file mode 100644 index bb08c52..0000000 --- a/example-kernels/default-target-cargo/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "default-target-cargo" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -[dependencies] -bootloader = "0.6.4" -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 deleted file mode 100644 index 14aead9..0000000 --- a/example-kernels/default-target-cargo/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![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-doctest/.cargo/config b/example-kernels/runner-doctest/.cargo/config new file mode 100644 index 0000000..3b4d89e --- /dev/null +++ b/example-kernels/runner-doctest/.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/default-target-bootimage/.gitignore b/example-kernels/runner-doctest/.gitignore similarity index 100% rename from example-kernels/default-target-bootimage/.gitignore rename to example-kernels/runner-doctest/.gitignore diff --git a/example-kernels/runner-doctest/Cargo.toml b/example-kernels/runner-doctest/Cargo.toml new file mode 100644 index 0000000..ff240ec --- /dev/null +++ b/example-kernels/runner-doctest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "runner-doctest" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.9.7" +x86_64 = "0.14.1" +rlibc = "1.0.0" + +[package.metadata.bootimage] +test-success-exit-code = 33 # (0x10 << 1) | 1 +test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-display", "none"] diff --git a/example-kernels/runner-doctest/src/lib.rs b/example-kernels/runner-doctest/src/lib.rs new file mode 100644 index 0000000..437f374 --- /dev/null +++ b/example-kernels/runner-doctest/src/lib.rs @@ -0,0 +1,92 @@ +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +extern crate rlibc; + +/// add two numbers +/// +/// ``` +/// #![no_std] +/// #![no_main] +/// use runner_doctest::{add, exit_qemu, ExitCode}; +/// #[export_name = "_start"] +/// extern "C" fn start() { +/// assert_eq!(add(1, 2), 3); +/// unsafe { exit_qemu(ExitCode::Success); } +/// } +/// ``` +pub fn add(a: u32, b: u32) -> u32 { + a + b +} + +/// multiply two numbers +/// +/// ``` +/// #![no_std] +/// #![no_main] +/// use runner_doctest::{mul, exit_qemu, ExitCode}; +/// #[export_name = "_start"] +/// extern "C" fn start() { +/// assert_eq!(mul(2, 3), 6); +/// unsafe { exit_qemu(ExitCode::Success); } +/// } +/// ``` +pub fn mul(a: u32, b: u32) -> u32 { + a * b +} + +#[cfg(test)] +fn test_runner(tests: &[&dyn Fn()]) { + for test in tests.iter() { + test(); + } + + unsafe { + exit_qemu(ExitCode::Success); + } +} + +pub enum ExitCode { + Success, + Failed, +} + +impl ExitCode { + fn code(&self) -> u32 { + match self { + ExitCode::Success => 0x10, + ExitCode::Failed => 0x11, + } + } +} + +/// exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) +pub unsafe fn exit_qemu(exit_code: ExitCode) { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(exit_code.code()); +} + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + + unsafe { + exit_qemu(ExitCode::Failed); + } + + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { + exit_qemu(ExitCode::Failed); + } + loop {} +} diff --git a/example-kernels/runner-fail-reboot/.cargo/config b/example-kernels/runner-fail-reboot/.cargo/config new file mode 100644 index 0000000..3b4d89e --- /dev/null +++ b/example-kernels/runner-fail-reboot/.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/default-target-cargo/.gitignore b/example-kernels/runner-fail-reboot/.gitignore similarity index 100% rename from example-kernels/default-target-cargo/.gitignore rename to example-kernels/runner-fail-reboot/.gitignore diff --git a/example-kernels/runner-fail-reboot/Cargo.toml b/example-kernels/runner-fail-reboot/Cargo.toml new file mode 100644 index 0000000..c15f396 --- /dev/null +++ b/example-kernels/runner-fail-reboot/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "runner-fail-reboot" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader = "0.9.7" +x86_64 = "0.14.1" +rlibc = "1.0.0" + +[package.metadata.bootimage] +test-success-exit-code = 0 # this will test for the reboot +test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-display", "none"] diff --git a/example-kernels/runner-fail-reboot/src/lib.rs b/example-kernels/runner-fail-reboot/src/lib.rs new file mode 100644 index 0000000..3472c56 --- /dev/null +++ b/example-kernels/runner-fail-reboot/src/lib.rs @@ -0,0 +1,71 @@ +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +extern crate rlibc; + +pub fn test_runner(tests: &[&dyn Fn()]) { + for test in tests.iter() { + test(); + } + + unsafe { + exit_qemu(ExitCode::Success); + } +} + +#[test_case] +fn should_reboot() { + // this overflows the stack which leads to a triple fault + // the as-if rule might allow this to get optimized away on release builds + #[allow(unconditional_recursion)] + fn stack_overflow() { + stack_overflow() + } + stack_overflow() +} + +pub enum ExitCode { + Success, + Failed, +} + +impl ExitCode { + fn code(&self) -> u32 { + match self { + ExitCode::Success => 0x10, + ExitCode::Failed => 0x11, + } + } +} + +/// exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) +pub unsafe fn exit_qemu(exit_code: ExitCode) { + use x86_64::instructions::port::Port; + + let mut port = Port::::new(0xf4); + port.write(exit_code.code()); +} + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + + unsafe { + exit_qemu(ExitCode::Failed); + } + + loop {} +} + +#[cfg(test)] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { + exit_qemu(ExitCode::Failed); + } + loop {} +} diff --git a/example-kernels/runner-test/Cargo.toml b/example-kernels/runner-test/Cargo.toml index 27989c8..83bcb52 100644 --- a/example-kernels/runner-test/Cargo.toml +++ b/example-kernels/runner-test/Cargo.toml @@ -9,8 +9,9 @@ name = "no-harness" harness = false [dependencies] -bootloader = "0.6.4" -x86_64 = "0.5.3" +bootloader = "0.9.7" +x86_64 = "0.14.1" +rlibc = "1.0.0" [package.metadata.bootimage] test-success-exit-code = 33 # (0x10 << 1) | 1 diff --git a/example-kernels/runner-test/src/lib.rs b/example-kernels/runner-test/src/lib.rs index 64c2518..740e7a9 100644 --- a/example-kernels/runner-test/src/lib.rs +++ b/example-kernels/runner-test/src/lib.rs @@ -5,6 +5,8 @@ #![test_runner(crate::test_runner)] #![reexport_test_harness_main = "test_main"] +extern crate rlibc; + pub fn test_runner(tests: &[&dyn Fn()]) { for test in tests.iter() { test(); diff --git a/example-kernels/runner/Cargo.toml b/example-kernels/runner/Cargo.toml index 7b5de79..17d0be9 100644 --- a/example-kernels/runner/Cargo.toml +++ b/example-kernels/runner/Cargo.toml @@ -5,8 +5,8 @@ authors = ["Philipp Oppermann "] edition = "2018" [dependencies] -bootloader = "0.6.4" -x86_64 = "0.5.3" +bootloader = "0.9.7" +x86_64 = "0.14.1" [package.metadata.bootimage] run-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-display", "none"] diff --git a/example-kernels/testing-qemu-exit-code/.gitignore b/example-kernels/testing-qemu-exit-code/.gitignore deleted file mode 100644 index eccd7b4..0000000 --- a/example-kernels/testing-qemu-exit-code/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target/ -**/*.rs.bk diff --git a/example-kernels/testing-qemu-exit-code/Cargo.toml b/example-kernels/testing-qemu-exit-code/Cargo.toml deleted file mode 100644 index 4abfbe8..0000000 --- a/example-kernels/testing-qemu-exit-code/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "testing-qemu-exit-code" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -[dependencies] -bootloader = "0.6.4" -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 deleted file mode 100644 index 1a506f1..0000000 --- a/example-kernels/testing-qemu-exit-code/src/bin/test-basic-boot.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![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 deleted file mode 100644 index a1c05f8..0000000 --- a/example-kernels/testing-qemu-exit-code/src/bin/test-panic.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![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 deleted file mode 100644 index cdc0ba8..0000000 --- a/example-kernels/testing-qemu-exit-code/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![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 deleted file mode 100644 index eccd7b4..0000000 --- a/example-kernels/testing-serial-result/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target/ -**/*.rs.bk diff --git a/example-kernels/testing-serial-result/Cargo.toml b/example-kernels/testing-serial-result/Cargo.toml deleted file mode 100644 index f280008..0000000 --- a/example-kernels/testing-serial-result/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "testing-serial-result" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -[dependencies] -bootloader = "0.6.4" -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 deleted file mode 100644 index d6982d0..0000000 --- a/example-kernels/testing-serial-result/src/bin/test-basic-boot-serial.rs +++ /dev/null @@ -1,33 +0,0 @@ -#![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 deleted file mode 100644 index 324a28f..0000000 --- a/example-kernels/testing-serial-result/src/bin/test-panic-serial.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![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 deleted file mode 100644 index 6bd2578..0000000 --- a/example-kernels/testing-serial-result/src/lib.rs +++ /dev/null @@ -1,58 +0,0 @@ -#![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 deleted file mode 100644 index f0352e2..0000000 --- a/example-kernels/testing-serial-result/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![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 index 9afe809..6f5613c 100644 --- a/example-kernels/x86_64-bootimage-example-kernels.json +++ b/example-kernels/x86_64-bootimage-example-kernels.json @@ -1,6 +1,6 @@ { "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "arch": "x86_64", "target-endian": "little", "target-pointer-width": "64", @@ -11,5 +11,6 @@ "linker": "rust-lld", "panic-strategy": "abort", "disable-redzone": true, - "features": "-mmx,-sse,+soft-float" + "features": "-mmx,-sse,+soft-float", + "rustc-abi": "x86-softfloat" } diff --git a/src/args.rs b/src/args.rs deleted file mode 100644 index 3c2fb0f..0000000 --- a/src/args.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Parses command line arguments. - -use crate::{config::Config, Command, ErrorMessage}; -use std::path::{Path, PathBuf}; -use std::{env, mem}; - -pub(crate) fn parse_args() -> Result { - let mut args = env::args(); - let is_cargo_bootimage = { - let executable_name = args.next().ok_or("no first argument (executable name)")?; - let file_stem = Path::new(&executable_name) - .file_stem() - .and_then(|s| s.to_str()); - file_stem == Some("cargo-bootimage") - }; - let first = args.next(); - match first.as_ref().map(|s| s.as_str()) { - Some("build") => parse_build_args(args), - Some("bootimage") if is_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") => parse_build_args(args).map(|cmd| match cmd { - Command::Build(args) => { - assert_eq!( - args.bin_name, None, - "No `--bin` argument allowed for `bootimage test`" - ); - Command::Test(args) - } - Command::BuildHelp => Command::TestHelp, - cmd => cmd, - }), - 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) -> Result -where - A: Iterator, -{ - let mut manifest_path: Option = None; - let mut bin_name: Option = None; - let mut target: Option = None; - let mut release: Option = None; - 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) -> Result<(), ErrorMessage> { - let previous = mem::replace(arg, value); - if previous.is_some() { - Err("multiple arguments of same type provided")? - } - Ok(()) - }; - - let mut arg_iter = args.into_iter(); - while let Some(arg) = arg_iter.next() { - if run_args_started { - run_args.push(arg); - continue; - } - match arg.as_ref() { - "--help" | "-h" => { - return Ok(Command::BuildHelp); - } - "--version" => { - return Ok(Command::Version); - } - "--quiet" => { - quiet = true; - } - "--bin" => { - let next = arg_iter.next(); - set(&mut bin_name, next.clone())?; - cargo_args.push(arg); - if let Some(next) = next { - cargo_args.push(next); - } - } - _ if arg.starts_with("--bin=") => { - 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())?; - cargo_args.push(arg); - if let Some(next) = next { - cargo_args.push(next); - } - } - _ if arg.starts_with("--target=") => { - set( - &mut target, - Some(String::from(arg.trim_start_matches("--target="))), - )?; - cargo_args.push(arg); - } - "--manifest-path" => { - let next = arg_iter.next(); - set( - &mut manifest_path, - next.as_ref().map(|p| { - Path::new(&p) - .canonicalize() - .expect("--manifest-path invalid") - }), - )?; - cargo_args.push(arg); - if let Some(next) = next { - cargo_args.push(next); - } - } - _ if arg.starts_with("--manifest-path=") => { - let path = Path::new(arg.trim_start_matches("--manifest-path=")) - .canonicalize() - .expect("--manifest-path invalid"); - set(&mut manifest_path, Some(path))?; - cargo_args.push(arg); - } - "--release" => { - set(&mut release, Some(true))?; - cargo_args.push(arg); - } - "--" => { - run_args_started = true; - } - _ => { - cargo_args.push(arg); - } - }; - } - } - - Ok(Command::Build(Args { - cargo_args, - run_args, - bin_name, - target, - manifest_path, - release: release.unwrap_or(false), - quiet, - })) -} - -#[derive(Debug, Clone)] -pub struct Args { - /// All arguments that are passed to cargo. - 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`). - bin_name: Option, - /// The target triple (also present in `cargo_args`). - target: Option, - /// The release flag (also present in `cargo_args`). - release: bool, -} - -impl Args { - pub fn manifest_path(&self) -> &Option { - &self.manifest_path - } - - pub fn target(&self) -> &Option { - &self.target - } - - pub fn set_target(&mut self, target: String) { - assert!(self.target.is_none()); - self.target = Some(target.clone()); - self.cargo_args.push("--target".into()); - self.cargo_args.push(target); - } - - pub fn bin_name(&self) -> Option<&str> { - self.bin_name.as_ref().map(String::as_str) - } - - pub fn set_bin_name(&mut self, bin_name: String) { - assert!(self.bin_name.is_none()); - self.bin_name = Some(bin_name.clone()); - 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 runner_args = None; - - let mut arg_iter = args.into_iter().fuse(); - - loop { - if executable.is_some() { - let args: Vec<_> = arg_iter.collect(); - if args.len() > 0 { - runner_args = Some(args); - } - break; - } - let next = match arg_iter.next() { - Some(next) => next, - None => break, - }; - match next.as_str() { - "--help" | "-h" => { - return Ok(Command::RunnerHelp); - } - "--version" => { - return Ok(Command::Version); - } - "--quiet" => { - quiet = true; - } - exe => { - executable = Some(PathBuf::from(exe)); - } - } - } - - Ok(Command::Runner(RunnerArgs { - executable: executable.ok_or("excepted path to kernel executable as first argument")?, - quiet, - runner_args, - })) -} - -#[derive(Debug, Clone)] -pub struct RunnerArgs { - pub executable: PathBuf, - /// Suppress any output to stdout. - pub quiet: bool, - pub runner_args: Option>, -} diff --git a/src/args/build.rs b/src/args/build.rs new file mode 100644 index 0000000..c3d18a9 --- /dev/null +++ b/src/args/build.rs @@ -0,0 +1,109 @@ +use anyhow::{anyhow, Context, Result}; +use std::{ + mem, + path::{Path, PathBuf}, +}; + +/// Internal representation of the `cargo bootimage` command. +pub enum BuildCommand { + /// A normal invocation (i.e. no `--help` or `--version`) + Build(BuildArgs), + /// The `--version` command + Version, + /// The `--help` command + Help, +} + +impl BuildCommand { + /// Parse the command line args into a `BuildCommand`. + pub fn parse_args(args: A) -> Result + where + A: Iterator, + { + let mut manifest_path: Option = None; + let mut cargo_args = Vec::new(); + let mut quiet = false; + { + fn set(arg: &mut Option, value: Option) -> Result<()> { + let previous = mem::replace(arg, value); + if previous.is_some() { + return Err(anyhow!("multiple arguments of same type provided")); + } + Ok(()) + } + + let mut arg_iter = args; + while let Some(arg) = arg_iter.next() { + match arg.as_ref() { + "--help" | "-h" => { + return Ok(BuildCommand::Help); + } + "--version" => { + return Ok(BuildCommand::Version); + } + "--quiet" => { + quiet = true; + } + "--manifest-path" => { + let next = arg_iter.next(); + set( + &mut manifest_path, + next.as_ref() + .map(|p| Path::new(&p).canonicalize()) + .transpose() + .context("--manifest-path invalid")?, + )?; + cargo_args.push(arg); + if let Some(next) = next { + cargo_args.push(next); + } + } + _ if arg.starts_with("--manifest-path=") => { + let path = Path::new(arg.trim_start_matches("--manifest-path=")) + .canonicalize() + .context("--manifest-path invalid")?; + set(&mut manifest_path, Some(path))?; + cargo_args.push(arg); + } + _ => { + cargo_args.push(arg); + } + }; + } + } + + Ok(BuildCommand::Build(BuildArgs { + manifest_path, + cargo_args, + quiet, + })) + } +} + +/// Arguments passed to `cargo bootimage`. +#[derive(Debug, Clone)] +pub struct BuildArgs { + /// The manifest path (also present in `cargo_args`). + manifest_path: Option, + /// All arguments that are passed to cargo. + cargo_args: Vec, + /// Suppress any output to stdout. + quiet: bool, +} + +impl BuildArgs { + /// The value of the `--manifest-path` argument, if any. + pub fn manifest_path(&self) -> Option<&Path> { + self.manifest_path.as_deref() + } + + /// Arguments that should be forwarded to `cargo build`. + pub fn cargo_args(&self) -> &[String] { + &self.cargo_args.as_ref() + } + + /// Whether a `--quiet` flag was passed. + pub fn quiet(&self) -> bool { + self.quiet + } +} diff --git a/src/args/mod.rs b/src/args/mod.rs new file mode 100644 index 0000000..e491c92 --- /dev/null +++ b/src/args/mod.rs @@ -0,0 +1,7 @@ +//! Parses command line arguments. + +pub use build::*; +pub use runner::*; + +mod build; +mod runner; diff --git a/src/args/runner.rs b/src/args/runner.rs new file mode 100644 index 0000000..bfaf2ed --- /dev/null +++ b/src/args/runner.rs @@ -0,0 +1,72 @@ +use anyhow::{anyhow, Result}; +use std::path::PathBuf; + +/// Internal representation of the `bootimage runner` command. +pub enum RunnerCommand { + /// A normal invocation of `bootimage runner` (i.e. no `--help` or `--version`) + Runner(RunnerArgs), + /// A command containing `--version` + Version, + /// A command containing `--help` + Help, +} + +impl RunnerCommand { + /// Parse the given argument set into the internal representation. + pub fn parse_args(args: A) -> Result + where + A: Iterator, + { + let mut executable = None; + let mut quiet = false; + let mut runner_args = None; + + let mut arg_iter = args.fuse(); + + loop { + if executable.is_some() { + let args: Vec<_> = arg_iter.collect(); + if !args.is_empty() { + runner_args = Some(args); + } + break; + } + let next = match arg_iter.next() { + Some(next) => next, + None => break, + }; + match next.as_str() { + "--help" | "-h" => { + return Ok(RunnerCommand::Help); + } + "--version" => { + return Ok(RunnerCommand::Version); + } + "--quiet" => { + quiet = true; + } + exe => { + executable = Some(PathBuf::from(exe)); + } + } + } + + Ok(Self::Runner(RunnerArgs { + executable: executable + .ok_or_else(|| anyhow!("excepted path to kernel executable as first argument"))?, + quiet, + runner_args, + })) + } +} + +/// Arguments for the `bootimage runner` command +#[derive(Debug, Clone)] +pub struct RunnerArgs { + /// Path to the executable binary + pub executable: PathBuf, + /// Suppress any output to stdout. + pub quiet: bool, + /// Additional arguments passed to the runner + pub runner_args: Option>, +} diff --git a/src/bin/cargo-bootimage.rs b/src/bin/cargo-bootimage.rs index 5349675..3ea1690 100644 --- a/src/bin/cargo-bootimage.rs +++ b/src/bin/cargo-bootimage.rs @@ -1,3 +1,91 @@ -pub fn main() { - bootimage::lib_main(); +use anyhow::{anyhow, Context, Result}; +use bootimage::{ + args::{BuildArgs, BuildCommand}, + builder::Builder, + config, help, +}; +use std::{ + env, + path::{Path, PathBuf}, +}; + +pub fn main() -> Result<()> { + let mut raw_args = env::args(); + + let executable_name = raw_args + .next() + .ok_or_else(|| anyhow!("no first argument (executable name)"))?; + let file_stem = Path::new(&executable_name) + .file_stem() + .and_then(|s| s.to_str()); + if file_stem != Some("cargo-bootimage") { + return Err(anyhow!( + "Unexpected executable name: expected `cargo-bootimage`, got: `{:?}`", + file_stem + )); + } + if raw_args.next().as_deref() != Some("bootimage") { + return Err(anyhow!("Please invoke this as `cargo bootimage`")); + } + + match BuildCommand::parse_args(raw_args)? { + BuildCommand::Build(args) => build(args), + BuildCommand::Version => { + help::print_version(); + Ok(()) + } + BuildCommand::Help => { + help::print_cargo_bootimage_help(); + Ok(()) + } + } +} + +fn build(args: BuildArgs) -> Result<()> { + let mut builder = Builder::new(args.manifest_path().map(PathBuf::from))?; + let config = config::read_config(builder.manifest_path())?; + let quiet = args.quiet(); + + let executables = builder.build_kernel(&args.cargo_args(), &config, quiet)?; + if executables.is_empty() { + return Err(anyhow!("no executables built")); + } + + for executable in executables { + let out_dir = executable + .parent() + .ok_or_else(|| anyhow!("executable has no parent path"))?; + let bin_name = &executable + .file_stem() + .ok_or_else(|| anyhow!("executable has no file stem"))? + .to_str() + .ok_or_else(|| anyhow!("executable file stem not valid utf8"))?; + + // We don't have access to a CARGO_MANIFEST_DIR environment variable + // here because `cargo bootimage` is started directly by the user. We + // therefore have to find out the path to the Cargo.toml of the + // executables ourselves. For workspace projects, this can be a + // different Cargo.toml than the Cargo.toml in the current directory. + // + // To retrieve the correct Cargo.toml path, we look for the binary name + // in the `cargo metadata` output and then get the manifest path from + // the corresponding package. + let kernel_package = builder + .kernel_package_for_bin(bin_name) + .context("Failed to run cargo metadata to find out kernel manifest path")? + .ok_or_else(|| anyhow!("Failed to find kernel binary in cargo metadata output"))?; + let kernel_manifest_path = &kernel_package.manifest_path.to_owned(); + + let bootimage_path = out_dir.join(format!("bootimage-{}.bin", bin_name)); + builder.create_bootimage(kernel_manifest_path, &executable, &bootimage_path, quiet)?; + if !args.quiet() { + println!( + "Created bootimage for `{}` at `{}`", + bin_name, + bootimage_path.display() + ); + } + } + + Ok(()) } diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index db78c58..0000000 --- a/src/builder.rs +++ /dev/null @@ -1,553 +0,0 @@ -//! 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, binary_feature) = { - let cargo_toml_content = fs::read_to_string(&bootloader_pkg.manifest_path) - .map_err(|err| format!("bootloader has no valid Cargo.toml: {}", err)) - .map_err(CreateBootimageError::BootloaderInvalid)?; - let cargo_toml = cargo_toml_content - .parse::() - .map_err(|e| format!("Failed to parse Cargo.toml of bootloader: {}", e)) - .map_err(CreateBootimageError::BootloaderInvalid)?; - let metadata = cargo_toml.get("package").and_then(|t| t.get("metadata")); - let target = metadata - .and_then(|t| t.get("bootloader")) - .and_then(|t| t.get("target")); - let target_str = target - .and_then(|v| v.as_str()) - .ok_or(CreateBootimageError::BootloaderInvalid( - "No `package.metadata.bootloader.target` key found in Cargo.toml of bootloader\n\n\ - (If you're using the official bootloader crate, you need at least version 0.5.1)" - .into(), - ))?; - - let binary_feature = cargo_toml - .get("features") - .and_then(|f| f.get("binary")) - .is_some(); - - (bootloader_root.join(target_str), binary_feature) - }; - 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), - })?; - let mut features = bootloader_resolve.features.clone(); - if binary_feature { - features.push("binary".into()); - } - features - }; - - // 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("--bin").arg(&bootloader_name); - 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("KERNEL_MANIFEST", &self.kernel_manifest_path); - cmd.env_remove("RUSTFLAGS"); - cmd.env("XBUILD_SYSROOT_PATH", target_dir.join("bootloader-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, - }); - } - - // Pad to nearest block size - { - const BLOCK_SIZE: u64 = 512; - use std::fs::OpenOptions; - let file = OpenOptions::new() - .write(true) - .open(&output_bin_path) - .map_err(|err| CreateBootimageError::Io { - message: "failed to open boot image", - error: err, - })?; - let file_size = file - .metadata() - .map_err(|err| CreateBootimageError::Io { - message: "failed to get size of boot image", - error: err, - })? - .len(); - let remainder = file_size % BLOCK_SIZE; - let padding = if remainder > 0 { - BLOCK_SIZE - remainder - } else { - 0 - }; - file.set_len(file_size + padding) - .map_err(|err| CreateBootimageError::Io { - message: "failed to pad boot image to a multiple of the block size", - error: err, - })?; - } - - 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")?; - if !stderr.is_empty() { - writeln!(f, "\n{}", String::from_utf8_lossy(stderr))?; - } - Ok(()) - } - 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")?; - if !stderr.is_empty() { - writeln!(f, "\n{}", String::from_utf8_lossy(stderr))?; - } - Ok(()) - } - 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/builder/bootloader.rs b/src/builder/bootloader.rs new file mode 100644 index 0000000..5753fc1 --- /dev/null +++ b/src/builder/bootloader.rs @@ -0,0 +1,159 @@ +use super::error::BootloaderError; +use cargo_metadata::{Metadata, Package}; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + +pub struct BuildConfig { + manifest_path: PathBuf, + bootloader_name: String, + target: PathBuf, + features: Vec, + target_dir: PathBuf, + kernel_bin_path: PathBuf, + kernel_manifest_path: PathBuf, + build_std: Option, +} + +impl BuildConfig { + /// Derives the bootloader build config from the project's metadata. + pub fn from_metadata( + project_metadata: &Metadata, + kernel_manifest_path: &Path, + kernel_bin_path: &Path, + ) -> Result { + let kernel_pkg = project_metadata + .packages + .iter() + .find(|p| p.manifest_path == kernel_manifest_path) + .ok_or_else(|| BootloaderError::KernelPackageNotFound { + manifest_path: kernel_manifest_path.to_owned(), + })?; + + let bootloader_pkg = bootloader_package(project_metadata, kernel_pkg)?; + let bootloader_root = bootloader_pkg.manifest_path.parent().ok_or_else(|| { + BootloaderError::BootloaderInvalid("bootloader manifest has no target directory".into()) + })?; + + let cargo_toml_content = fs::read_to_string(&bootloader_pkg.manifest_path) + .map_err(|err| format!("bootloader has no valid Cargo.toml: {}", err)) + .map_err(BootloaderError::BootloaderInvalid)?; + let cargo_toml = cargo_toml_content + .parse::() + .map_err(|e| format!("Failed to parse Cargo.toml of bootloader: {}", e)) + .map_err(BootloaderError::BootloaderInvalid)?; + let metadata = cargo_toml.get("package").and_then(|t| t.get("metadata")); + let target = metadata + .and_then(|t| t.get("bootloader")) + .and_then(|t| t.get("target")); + let target_str = target.and_then(|v| v.as_str()).ok_or_else(|| { + BootloaderError::BootloaderInvalid( + "No `package.metadata.bootloader.target` key found in Cargo.toml of bootloader\n\n\ + (If you're using the official bootloader crate, you need at least version 0.5.1)" + .into(), + ) + })?; + let build_std = { + let key = metadata + .and_then(|t| t.get("bootloader")) + .and_then(|t| t.get("build-std")); + if let Some(key) = key { + let err_msg = "A non-string `package.metadata.bootloader.build-std` key found in \ + Cargo.toml of bootloader"; + let err = || BootloaderError::BootloaderInvalid(err_msg.into()); + Some(key.as_str().ok_or_else(err)?.into()) + } else { + None + } + }; + + let binary_feature = cargo_toml + .get("features") + .and_then(|f| f.get("binary")) + .is_some(); + + let resolve_opt = project_metadata.resolve.as_ref(); + let resolve = resolve_opt.ok_or(BootloaderError::CargoMetadataIncomplete { + key: "resolve".into(), + })?; + let bootloader_resolve = resolve + .nodes + .iter() + .find(|n| n.id == bootloader_pkg.id) + .ok_or(BootloaderError::CargoMetadataIncomplete { + key: format!("resolve[\"{}\"]", bootloader_pkg.name), + })?; + let mut features = bootloader_resolve.features.clone(); + if binary_feature { + features.push("binary".into()); + } + + let bootloader_name = &bootloader_pkg.name; + let target_dir = project_metadata + .target_directory + .join("bootimage") + .join(bootloader_name); + + Ok(BuildConfig { + manifest_path: bootloader_pkg.manifest_path.clone(), + target: bootloader_root.join(target_str), + features, + bootloader_name: bootloader_name.clone(), + target_dir, + kernel_manifest_path: kernel_pkg.manifest_path.clone(), + kernel_bin_path: kernel_bin_path.to_owned(), + build_std, + }) + } + + /// Creates the cargo build command for building the bootloader. + pub fn build_command(&self) -> Command { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let mut cmd = Command::new(&cargo); + if let Some(build_std) = &self.build_std { + cmd.arg("build").arg(&format!("-Zbuild-std={}", build_std)); + } else { + cmd.arg("xbuild"); + } + cmd.arg("--manifest-path"); + cmd.arg(&self.manifest_path); + cmd.arg("--bin").arg(&self.bootloader_name); + cmd.arg("--target-dir").arg(&self.target_dir); + cmd.arg("--features") + .arg(self.features.as_slice().join(" ")); + cmd.arg("--target").arg(&self.target); + cmd.arg("--release"); + cmd.env("KERNEL", &self.kernel_bin_path); + cmd.env("KERNEL_MANIFEST", &self.kernel_manifest_path); + cmd.env("RUSTFLAGS", ""); + cmd.env( + "XBUILD_SYSROOT_PATH", + self.target_dir.join("bootloader-sysroot"), + ); // for cargo-xbuild + cmd + } +} + +/// Returns the package metadata for the bootloader crate +fn bootloader_package<'a>( + project_metadata: &'a Metadata, + kernel_package: &Package, +) -> Result<&'a Package, BootloaderError> { + let bootloader_name = { + let mut dependencies = kernel_package.dependencies.iter(); + let bootloader_package = dependencies + .find(|p| p.rename.as_ref().unwrap_or(&p.name) == "bootloader") + .ok_or(BootloaderError::BootloaderNotFound)?; + bootloader_package.name.clone() + }; + + project_metadata + .packages + .iter() + .find(|p| p.name == bootloader_name) + .ok_or(BootloaderError::CargoMetadataIncomplete { + key: format!("packages[name = `{}`", &bootloader_name), + }) +} diff --git a/src/builder/disk_image.rs b/src/builder/disk_image.rs new file mode 100644 index 0000000..2f3cfab --- /dev/null +++ b/src/builder/disk_image.rs @@ -0,0 +1,62 @@ +use super::error::DiskImageError; +use std::{path::Path, process::Command}; + +pub fn create_disk_image( + bootloader_elf_path: &Path, + output_bin_path: &Path, +) -> Result<(), DiskImageError> { + let llvm_tools = llvm_tools::LlvmTools::new()?; + let objcopy = llvm_tools + .tool(&llvm_tools::exe("llvm-objcopy")) + .ok_or(DiskImageError::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| DiskImageError::Io { + message: "failed to execute llvm-objcopy command", + error: err, + })?; + if !output.status.success() { + return Err(DiskImageError::ObjcopyFailed { + stderr: output.stderr, + }); + } + + pad_to_nearest_block_size(output_bin_path)?; + Ok(()) +} + +fn pad_to_nearest_block_size(output_bin_path: &Path) -> Result<(), DiskImageError> { + const BLOCK_SIZE: u64 = 512; + use std::fs::OpenOptions; + let file = OpenOptions::new() + .write(true) + .open(&output_bin_path) + .map_err(|err| DiskImageError::Io { + message: "failed to open boot image", + error: err, + })?; + let file_size = file + .metadata() + .map_err(|err| DiskImageError::Io { + message: "failed to get size of boot image", + error: err, + })? + .len(); + let remainder = file_size % BLOCK_SIZE; + let padding = if remainder > 0 { + BLOCK_SIZE - remainder + } else { + 0 + }; + file.set_len(file_size + padding) + .map_err(|err| DiskImageError::Io { + message: "failed to pad boot image to a multiple of the block size", + error: err, + }) +} diff --git a/src/builder/error.rs b/src/builder/error.rs new file mode 100644 index 0000000..5d091fb --- /dev/null +++ b/src/builder/error.rs @@ -0,0 +1,160 @@ +use std::{io, path::PathBuf}; +use thiserror::Error; + +/// Represents an error that occurred while creating a new `Builder`. +#[derive(Debug, Error)] +pub enum BuilderError { + /// Failed to locate cargo manifest + #[error("Could not find Cargo.toml file starting from current folder: {0:?}")] + LocateCargoManifest(#[from] locate_cargo_manifest::LocateManifestError), +} + +/// Represents an error that occurred when building the kernel. +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum BuildKernelError { + /// An unexpected I/O error occurred + #[error("I/O error: {message}:\n{error}")] + Io { + /// Desciption of the failed I/O operation + message: &'static str, + /// The I/O error that occurred + error: io::Error, + }, + + /// Could not find the `cargo xbuild` tool. Perhaps it is not installed? + #[error( + "Failed to run `cargo xbuild`. Perhaps it is not installed?\n\ + Run `cargo install cargo-xbuild` to install it." + )] + XbuildNotFound, + + /// Running `cargo build` failed. + #[error("Kernel build failed.\nStderr: {}", String::from_utf8_lossy(.stderr))] + BuildFailed { + /// The standard error output. + stderr: Vec, + }, + + /// The output of `cargo build --message-format=json` was not valid UTF-8 + #[error("Output of kernel build with --message-format=json is not valid UTF-8:\n{0}")] + BuildJsonOutputInvalidUtf8(std::string::FromUtf8Error), + /// The output of `cargo build --message-format=json` was not valid JSON + #[error("Output of kernel build with --message-format=json is not valid JSON:\n{0}")] + BuildJsonOutputInvalidJson(json::Error), +} + +/// Represents an error that occurred when creating a bootimage. +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum CreateBootimageError { + /// Failed to build the bootloader. + #[error("An error occurred while trying to build the bootloader: {0}")] + Bootloader(#[from] BootloaderError), + + /// Error while running `cargo metadata` + #[error("Error while running `cargo metadata` for current project: {0:?}")] + CargoMetadata(#[from] cargo_metadata::Error), + + /// Building the bootloader failed + #[error("Bootloader build failed.\nStderr: {}", String::from_utf8_lossy(.stderr))] + BootloaderBuildFailed { + /// The `cargo build` output to standard error + stderr: Vec, + }, + + /// Disk image creation failed + #[error("An error occurred while trying to create the disk image: {0}")] + DiskImage(#[from] DiskImageError), + + /// An unexpected I/O error occurred + #[error("I/O error: {message}:\n{error}")] + Io { + /// Desciption of the failed I/O operation + message: &'static str, + /// The I/O error that occurred + error: io::Error, + }, + + /// The output of `cargo build --message-format=json` was not valid UTF-8 + #[error("Output of bootloader build with --message-format=json is not valid UTF-8:\n{0}")] + BuildJsonOutputInvalidUtf8(std::string::FromUtf8Error), + /// The output of `cargo build --message-format=json` was not valid JSON + #[error("Output of bootloader build with --message-format=json is not valid JSON:\n{0}")] + BuildJsonOutputInvalidJson(json::Error), +} + +/// There is something wrong with the bootloader dependency. +#[derive(Debug, Error)] +pub enum BootloaderError { + /// Bootloader dependency not found + #[error( + "Bootloader dependency not found\n\n\ + You need to add a dependency on a crate named `bootloader` in your Cargo.toml." + )] + BootloaderNotFound, + + /// Bootloader dependency has not the right format + #[error("The `bootloader` dependency has not the right format: {0}")] + BootloaderInvalid(String), + + /// Could not find kernel package in cargo metadata + #[error( + "Could not find package with manifest path `{manifest_path}` in cargo metadata output" + )] + KernelPackageNotFound { + /// The manifest path of the kernel package + manifest_path: PathBuf, + }, + + /// Could not find some required information in the `cargo metadata` output + #[error("Could not find required key `{key}` in cargo metadata output")] + CargoMetadataIncomplete { + /// The required key that was not found + key: String, + }, +} + +/// Creating the disk image failed. +#[derive(Debug, Error)] +pub enum DiskImageError { + /// The `llvm-tools-preview` rustup component was not found + #[error( + "Could not find the `llvm-tools-preview` rustup component.\n\n\ + You can install by executing `rustup component add llvm-tools-preview`." + )] + LlvmToolsNotFound, + + /// There was another problem locating the `llvm-tools-preview` rustup component + #[error("Failed to locate the `llvm-tools-preview` rustup component: {0:?}")] + LlvmTools(llvm_tools::Error), + + /// The llvm-tools component did not contain the required `llvm-objcopy` executable + #[error("Could not find `llvm-objcopy` in the `llvm-tools-preview` rustup component.")] + LlvmObjcopyNotFound, + + /// The `llvm-objcopy` command failed + #[error("Failed to run `llvm-objcopy`: {}", String::from_utf8_lossy(.stderr))] + ObjcopyFailed { + /// The output of `llvm-objcopy` to standard error + stderr: Vec, + }, + + /// An unexpected I/O error occurred + #[error("I/O error: {message}:\n{error}")] + Io { + /// Desciption of the failed I/O operation + message: &'static str, + /// The I/O error that occurred + error: io::Error, + }, +} + +impl From for DiskImageError { + fn from(err: llvm_tools::Error) -> Self { + match err { + llvm_tools::Error::NotFound => DiskImageError::LlvmToolsNotFound, + other => DiskImageError::LlvmTools(other), + } + } +} diff --git a/src/builder/mod.rs b/src/builder/mod.rs new file mode 100644 index 0000000..3acc5e6 --- /dev/null +++ b/src/builder/mod.rs @@ -0,0 +1,226 @@ +//! Provides functions to build the kernel and the bootloader. + +use crate::config::Config; +use cargo_metadata::Metadata; +use error::{BootloaderError, BuildKernelError, BuilderError, CreateBootimageError}; +use std::{ + path::{Path, PathBuf}, + process, +}; + +/// Provides the build command for the bootloader. +mod bootloader; +/// Provides a function to create the bootable disk image. +mod disk_image; +/// Contains the errors types returned by the `Builder` methods. +pub mod error; + +/// Allows building the kernel and creating a bootable disk image with it. +pub struct Builder { + manifest_path: PathBuf, + project_metadata: Option, +} + +impl Builder { + /// Creates a new builder for the project at the given manifest path + /// + /// If None is passed for `manifest_path`, it is automatically searched. + pub fn new(manifest_path: Option) -> Result { + let manifest_path = match manifest_path.or_else(|| { + std::env::var("CARGO_MANIFEST_DIR") + .ok() + .map(|dir| Path::new(&dir).join("Cargo.toml")) + }) { + Some(path) => path, + None => { + println!("WARNING: `CARGO_MANIFEST_DIR` env variable not set"); + locate_cargo_manifest::locate_manifest()? + } + }; + + Ok(Builder { + manifest_path, + project_metadata: None, + }) + } + + /// Returns the path to the Cargo.toml file of the project. + pub fn manifest_path(&self) -> &Path { + &self.manifest_path + } + + /// Builds the kernel by executing `cargo build` 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( + &mut self, + args: &[String], + config: &Config, + quiet: bool, + ) -> Result, BuildKernelError> { + if !quiet { + println!("Building kernel"); + } + + // try to build kernel + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let mut cmd = process::Command::new(&cargo); + cmd.args(&config.build_command); + cmd.args(args); + if !quiet { + cmd.stdout(process::Stdio::inherit()); + cmd.stderr(process::Stdio::inherit()); + } + let output = cmd.output().map_err(|err| BuildKernelError::Io { + message: "failed to execute kernel build", + error: err, + })?; + if !output.status.success() { + if config.build_command.starts_with(&["xbuild".into()]) { + // try executing `cargo xbuild --help` to check whether cargo-xbuild is installed + let mut help_command = process::Command::new("cargo"); + help_command.arg("xbuild").arg("--help"); + help_command.stdout(process::Stdio::null()); + help_command.stderr(process::Stdio::null()); + if let Ok(help_exit_status) = help_command.status() { + if !help_exit_status.success() { + return Err(BuildKernelError::XbuildNotFound); + } + } + } + return Err(BuildKernelError::BuildFailed { + stderr: output.stderr, + }); + } + + // Retrieve binary paths + let mut cmd = process::Command::new(cargo); + cmd.args(&config.build_command); + cmd.args(args); + cmd.arg("--message-format").arg("json"); + let output = cmd.output().map_err(|err| BuildKernelError::Io { + message: "failed to execute kernel build with json output", + error: err, + })?; + if !output.status.success() { + return Err(BuildKernelError::BuildFailed { + stderr: output.stderr, + }); + } + let mut executables = Vec::new(); + for line in String::from_utf8(output.stdout) + .map_err(BuildKernelError::BuildJsonOutputInvalidUtf8)? + .lines() + { + let mut artifact = + json::parse(line).map_err(BuildKernelError::BuildJsonOutputInvalidJson)?; + if let Some(executable) = artifact["executable"].take_string() { + executables.push(PathBuf::from(executable)); + } + } + + Ok(executables) + } + + /// 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( + &mut self, + kernel_manifest_path: &Path, + bin_path: &Path, + output_bin_path: &Path, + quiet: bool, + ) -> Result<(), CreateBootimageError> { + let bootloader_build_config = bootloader::BuildConfig::from_metadata( + self.project_metadata()?, + kernel_manifest_path, + bin_path, + )?; + + // build bootloader + if !quiet { + println!("Building bootloader"); + } + let mut cmd = bootloader_build_config.build_command(); + if !quiet { + cmd.stdout(process::Stdio::inherit()); + cmd.stderr(process::Stdio::inherit()); + } + let output = cmd.output().map_err(|err| CreateBootimageError::Io { + message: "failed to execute bootloader build command", + error: err, + })?; + if !output.status.success() { + return Err(CreateBootimageError::BootloaderBuildFailed { + stderr: output.stderr, + }); + } + + // Retrieve binary path + let mut cmd = bootloader_build_config.build_command(); + cmd.arg("--message-format").arg("json"); + let output = cmd.output().map_err(|err| CreateBootimageError::Io { + message: "failed to execute bootloader build command with json output", + error: err, + })?; + if !output.status.success() { + return Err(CreateBootimageError::BootloaderBuildFailed { + stderr: output.stderr, + }); + } + let mut bootloader_elf_path = None; + for line in String::from_utf8(output.stdout) + .map_err(CreateBootimageError::BuildJsonOutputInvalidUtf8)? + .lines() + { + let mut artifact = + json::parse(line).map_err(CreateBootimageError::BuildJsonOutputInvalidJson)?; + if let Some(executable) = artifact["executable"].take_string() { + if bootloader_elf_path + .replace(PathBuf::from(executable)) + .is_some() + { + return Err(BootloaderError::BootloaderInvalid( + "bootloader has multiple executables".into(), + ) + .into()); + } + } + } + let bootloader_elf_path = bootloader_elf_path.ok_or_else(|| { + BootloaderError::BootloaderInvalid("bootloader has no executable".into()) + })?; + + disk_image::create_disk_image(&bootloader_elf_path, output_bin_path)?; + + Ok(()) + } + + /// Returns the cargo metadata package that contains the given binary. + pub fn kernel_package_for_bin( + &mut self, + kernel_bin_name: &str, + ) -> Result, cargo_metadata::Error> { + Ok(self.project_metadata()?.packages.iter().find(|p| { + p.targets + .iter() + .any(|t| t.name == kernel_bin_name && t.kind.iter().any(|k| k == "bin")) + })) + } + + fn project_metadata(&mut self) -> Result<&Metadata, cargo_metadata::Error> { + if let Some(ref metadata) = self.project_metadata { + return Ok(metadata); + } + let metadata = cargo_metadata::MetadataCommand::new() + .manifest_path(&self.manifest_path) + .exec()?; + Ok(self.project_metadata.get_or_insert(metadata)) + } +} diff --git a/src/config.rs b/src/config.rs index ce7e964..3bed52a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ //! Parses the `package.metadata.bootimage` configuration table -use crate::ErrorMessage; +use anyhow::{anyhow, Context, Result}; use std::path::Path; use toml::Value; @@ -10,9 +10,12 @@ use toml::Value; /// in the `Cargo.toml` file of the kernel. This struct represents the parsed configuration /// options. #[derive(Debug, Clone)] +#[non_exhaustive] pub struct Config { - /// This target is used if no `--target` argument is passed - pub default_target: Option, + /// The cargo subcommand that is used for building the kernel for `cargo bootimage`. + /// + /// Defaults to `build`. + pub build_command: Vec, /// 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. @@ -30,26 +33,28 @@ pub struct Config { /// An exit code that should be considered as success for test executables (applies to /// `bootimage runner`) pub test_success_exit_code: Option, - non_exhaustive: (), + /// Whether the `-no-reboot` flag should be passed to test executables + /// + /// Defaults to `true` + pub test_no_reboot: bool, } -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) +/// Reads the configuration from a `package.metadata.bootimage` in the given Cargo.toml. +pub fn read_config(manifest_path: &Path) -> Result { + read_config_inner(manifest_path).context("Failed to read bootimage configuration") } -pub(crate) fn read_config_inner(manifest_path: &Path) -> Result { +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) - .map_err(|e| format!("Failed to open Cargo.toml: {}", e))? + .context("Failed to open Cargo.toml")? .read_to_string(&mut content) - .map_err(|e| format!("Failed to read Cargo.toml: {}", e))?; + .context("Failed to read Cargo.toml")?; content .parse::() - .map_err(|e| format!("Failed to parse Cargo.toml: {}", e))? + .context("Failed to parse Cargo.toml")? }; let metadata = cargo_toml @@ -62,16 +67,15 @@ pub(crate) fn read_config_inner(manifest_path: &Path) -> Result metadata .as_table() - .ok_or(format!("Bootimage configuration invalid: {:?}", metadata))?, + .ok_or_else(|| anyhow!("Bootimage configuration invalid: {:?}", metadata))?, }; 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), ("test-timeout", Value::Integer(timeout)) if timeout.is_negative() => { - Err(format!("test-timeout must not be negative"))? + return Err(anyhow!("test-timeout must not be negative")) } ("test-timeout", Value::Integer(timeout)) => { config.test_timeout = Some(timeout as u32); @@ -79,70 +83,72 @@ pub(crate) fn read_config_inner(manifest_path: &Path) -> Result { config.test_success_exit_code = Some(exit_code as i32); } + ("build-command", Value::Array(array)) => { + config.build_command = Some(parse_string_array(array, "build-command")?); + } ("run-command", Value::Array(array)) => { - let mut command = Vec::new(); - for value in array { - match value { - Value::String(s) => command.push(s), - _ => Err(format!("run-command must be a list of strings"))?, - } - } - config.run_command = Some(command); + config.run_command = Some(parse_string_array(array, "run-command")?); } ("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); + config.run_args = Some(parse_string_array(array, "run-args")?); } ("test-args", Value::Array(array)) => { - let mut args = Vec::new(); - for value in array { - match value { - Value::String(s) => args.push(s), - _ => Err(format!("test-args must be a list of strings"))?, - } - } - config.test_args = Some(args); + config.test_args = Some(parse_string_array(array, "test-args")?); } - (key, value) => Err(format!( - "unexpected `package.metadata.bootimage` \ + ("test-no-reboot", Value::Boolean(no_reboot)) => { + config.test_no_reboot = Some(no_reboot); + } + (key, value) => { + return Err(anyhow!( + "unexpected `package.metadata.bootimage` \ key `{}` with value `{}`", - key, value - ))?, + key, + value + )) + } } } Ok(config.into()) } +fn parse_string_array(array: Vec, prop_name: &str) -> Result> { + let mut parsed = Vec::new(); + for value in array { + match value { + Value::String(s) => parsed.push(s), + _ => return Err(anyhow!("{} must be a list of strings", prop_name)), + } + } + Ok(parsed) +} + #[derive(Default)] struct ConfigBuilder { - default_target: Option, + build_command: Option>, run_command: Option>, run_args: Option>, test_args: Option>, test_timeout: Option, test_success_exit_code: Option, + test_no_reboot: Option, } impl Into for ConfigBuilder { fn into(self) -> Config { Config { - default_target: self.default_target, - run_command: self.run_command.unwrap_or(vec![ - "qemu-system-x86_64".into(), - "-drive".into(), - "format=raw,file={}".into(), - ]), + build_command: self.build_command.unwrap_or_else(|| vec!["build".into()]), + run_command: self.run_command.unwrap_or_else(|| { + vec![ + "qemu-system-x86_64".into(), + "-drive".into(), + "format=raw,file={}".into(), + ] + }), run_args: self.run_args, test_args: self.test_args, test_timeout: self.test_timeout.unwrap_or(60 * 5), test_success_exit_code: self.test_success_exit_code, - non_exhaustive: (), + test_no_reboot: self.test_no_reboot.unwrap_or(true), } } } diff --git a/src/help/build_help.txt b/src/help/build_help.txt deleted file mode 100644 index e623053..0000000 --- a/src/help/build_help.txt +++ /dev/null @@ -1,20 +0,0 @@ -Creates a bootable disk image from a Rust kernel - -USAGE: - bootimage build [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 `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 diff --git a/src/help/cargo_bootimage_help.txt b/src/help/cargo_bootimage_help.txt index b257c10..16647ef 100644 --- a/src/help/cargo_bootimage_help.txt +++ b/src/help/cargo_bootimage_help.txt @@ -13,8 +13,11 @@ BUILD_OPTS: 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]` table in the `Cargo.toml`. The + following options are available to configure the build behavior: [package.metadata.bootimage] - default-target = "" This target is used if no `--target` is passed + # The cargo subcommand that will be used for building the kernel. + # + # For building using the `cargo-xbuild` crate, set this to `xbuild`. + build-command = ["build"] diff --git a/src/help/help.txt b/src/help/help.txt index 3d265b9..1325a18 100644 --- a/src/help/help.txt +++ b/src/help/help.txt @@ -1,31 +1,11 @@ Creates a bootable disk image from a Rust kernel USAGE: - bootimage [OPTIONS] Help and version information - 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) + bootimage runner EXECUTABLE [RUN_OPTS] Convert and run an executable -For more information about a subcommand run `bootimage [subcommand] --help`. +For more information about a subcommand run `[subcommand] --help`. -OPTIONS: - -h, --help Prints help information and exit +GENERAL OPTIONS: + -h, --help Prints help information and exit --version Prints version information and exit - -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. - - For configuration options see `bootimage build --help`. - -RUN_OPTS: - Any options are directly passed to the run command. Note that the run - options must be separated from the build options by a "--". - - For configuration options see `bootimage run --help`. diff --git a/src/help/mod.rs b/src/help/mod.rs index 60991db..d29dd9b 100644 --- a/src/help/mod.rs +++ b/src/help/mod.rs @@ -1,38 +1,22 @@ -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() { +/// Prints a general help text. +pub fn print_help() { print!("{}", HELP); } -pub(crate) fn build_help() { - print!("{}", BUILD_HELP); -} - -pub(crate) fn cargo_bootimage_help() { +/// Prints the help for the `cargo bootimage` command. +pub fn print_cargo_bootimage_help() { print!("{}", CARGO_BOOTIMAGE_HELP); } - -pub(crate) fn run_help() { - print!("{}", RUN_HELP); -} - -pub(crate) fn runner_help() { +/// Prints the help for the `bootimage runner` command. +pub fn print_runner_help() { print!("{}", RUNNER_HELP); } -pub(crate) fn test_help() { - print!("{}", TEST_HELP); -} - -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() +/// Prints the version of this crate. +pub fn print_version() { + println!("bootimage {}", env!("CARGO_PKG_VERSION")); } diff --git a/src/help/run_help.txt b/src/help/run_help.txt deleted file mode 100644 index f6c0857..0000000 --- a/src/help/run_help.txt +++ /dev/null @@ -1,23 +0,0 @@ -Creates a bootable disk image from a Rust kernel and launches it in QEMU - -USAGE: - bootimage run [BUILD_OPTS] -- [RUN_OPTS] Build and run a disk image - - (for other forms of usage see `bootimage --help`) - (for BUILD_OPTS see `bootimage build --help`) - -RUN_OPTS: - Any options are directly passed to the run command. Note that the run - options must be separated from the build options by a "--". - -CONFIGURATION: - The behavior of `bootimage run` 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 with the created bootimage (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 run command for non-test executables - run-args = [] \ No newline at end of file diff --git a/src/help/test_help.txt b/src/help/test_help.txt deleted file mode 100644 index a9fa8e1..0000000 --- a/src/help/test_help.txt +++ /dev/null @@ -1,26 +0,0 @@ -Runs integration tests of a Rust kernel - -The following conventions are used: - -- All executables starting with `test-` are treated as unit test. -- Tests must print either ok or failed over the serial port. When printing - failed they can print additional information such as a panic message (in the - next lines). -- Tests are run with a timeout of 1 minute. If the test has not completed in - time, it is reported as "timed out". - - -USAGE: - bootimage test [BUILD_OPTS] Runs integration tests - - (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 index 6d6ce73..d682726 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,99 +4,10 @@ #![warn(missing_docs)] -use args::{Args, RunnerArgs}; -use std::{fmt, process}; - -mod args; +pub 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) - } -} +pub mod run; -impl From for ErrorMessage -where - T: fmt::Display + Send + 'static, -{ - fn from(err: T) -> Self { - ErrorMessage { - message: Box::new(err), - } - } -} +/// Contains help messages for the command line application. +pub mod help; diff --git a/src/main.rs b/src/main.rs index 5349675..3cb34ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,111 @@ -pub fn main() { - bootimage::lib_main(); +/// Executable for `bootimage runner`. +use anyhow::{anyhow, Context, Result}; +use bootimage::{ + args::{RunnerArgs, RunnerCommand}, + builder::Builder, + config, help, run, +}; +use std::process; +use std::{env, path::Path}; + +pub fn main() -> Result<()> { + let mut raw_args = env::args(); + + let executable_name = raw_args + .next() + .ok_or_else(|| anyhow!("no first argument (executable name)"))?; + let file_stem = Path::new(&executable_name) + .file_stem() + .and_then(|s| s.to_str()); + if file_stem != Some("bootimage") { + return Err(anyhow!( + "Unexpected executable name: expected `bootimage`, got: `{:?}`", + file_stem + )); + } + match raw_args.next().as_deref() { + Some("runner") => {}, + Some("--help") | Some("-h") => { + help::print_help(); + return Ok(()) + } + Some("--version") => { + help::print_version(); + return Ok(()) + } + Some(other) => return Err(anyhow!( + "Unsupported subcommand `{:?}`. See `bootimage --help` for an overview of supported subcommands.", other + )), + None => return Err(anyhow!( + "Please invoke bootimage with a subcommand. See `bootimage --help` for more information." + )), + } + + let exit_code = match RunnerCommand::parse_args(raw_args)? { + RunnerCommand::Runner(args) => Some(runner(args)?), + RunnerCommand::Version => { + help::print_version(); + None + } + RunnerCommand::Help => { + help::print_runner_help(); + None + } + }; + + if let Some(code) = exit_code { + process::exit(code); + } + + Ok(()) +} + +pub(crate) fn runner(args: RunnerArgs) -> Result { + let mut builder = Builder::new(None)?; + let config = config::read_config(builder.manifest_path())?; + let exe_parent = args + .executable + .parent() + .ok_or_else(|| anyhow!("kernel executable has no parent"))?; + let is_doctest = exe_parent + .file_name() + .ok_or_else(|| anyhow!("kernel executable's parent has no file name"))? + .to_str() + .ok_or_else(|| anyhow!("kernel executable's parent file name is not valid UTF-8"))? + .starts_with("rustdoctest"); + let is_test = is_doctest || exe_parent.ends_with("deps"); + + let bin_name = args + .executable + .file_stem() + .ok_or_else(|| anyhow!("kernel executable has no file stem"))? + .to_str() + .ok_or_else(|| anyhow!("kernel executable file stem is not valid UTF-8"))?; + + let output_bin_path = exe_parent.join(format!("bootimage-{}.bin", bin_name)); + let executable_canonicalized = args.executable.canonicalize().with_context(|| { + format!( + "failed to canonicalize executable path `{}`", + args.executable.display(), + ) + })?; + + // Cargo sets a CARGO_MANIFEST_DIR environment variable for all runner + // executables. This variable contains the path to the Cargo.toml of the + // crate that the executable belongs to (i.e. not the project root + // manifest for workspace projects) + let manifest_dir = env::var("CARGO_MANIFEST_DIR") + .context("Failed to read CARGO_MANIFEST_DIR environment variable")?; + let kernel_manifest_path = Path::new(&manifest_dir).join("Cargo.toml"); + + builder.create_bootimage( + &kernel_manifest_path, + &executable_canonicalized, + &output_bin_path, + args.quiet, + )?; + + let exit_code = run::run(config, args, &output_bin_path, is_test)?; + + Ok(exit_code) } diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 0000000..e77c0f9 --- /dev/null +++ b/src/run.rs @@ -0,0 +1,147 @@ +//! Provides a function for running a disk image in QEMU. + +use crate::{args::RunnerArgs, config::Config}; +use std::{io, path::Path, process, time::Duration}; +use thiserror::Error; +use wait_timeout::ChildExt; + +/// Run the given disk image in QEMU. +/// +/// Automatically takes into account the runner arguments and the run/test +/// commands defined in the given `Config`. Since test executables are treated +/// differently (run with a timeout and match exit status), the caller needs to +/// specify whether the given disk image is a test or not. +pub fn run( + config: Config, + args: RunnerArgs, + image_path: &Path, + is_test: bool, +) -> Result { + let mut run_command: Vec<_> = config + .run_command + .iter() + .map(|arg| arg.replace("{}", &format!("{}", image_path.display()))) + .collect(); + if is_test { + if config.test_no_reboot { + run_command.push("-no-reboot".to_owned()); + } + if let Some(args) = config.test_args { + run_command.extend(args); + } + } else if let Some(args) = config.run_args { + run_command.extend(args); + } + if let Some(args) = args.runner_args { + run_command.extend(args); + } + + if !args.quiet { + println!("Running: `{}`", run_command.join(" ")); + } + let mut command = process::Command::new(&run_command[0]); + command.args(&run_command[1..]); + + let exit_code = if is_test { + let mut child = command.spawn().map_err(|error| RunError::Io { + context: IoErrorContext::QemuTestCommand { + command: format!("{:?}", command), + }, + error, + })?; + let timeout = Duration::from_secs(config.test_timeout.into()); + match child + .wait_timeout(timeout) + .map_err(context(IoErrorContext::WaitWithTimeout))? + { + None => { + child.kill().map_err(context(IoErrorContext::KillQemu))?; + child.wait().map_err(context(IoErrorContext::WaitForQemu))?; + return Err(RunError::TestTimedOut); + } + Some(exit_status) => { + #[cfg(unix)] + { + if exit_status.code().is_none() { + use std::os::unix::process::ExitStatusExt; + if let Some(signal) = exit_status.signal() { + eprintln!("QEMU process was terminated by signal {}", signal); + } + } + } + let qemu_exit_code = exit_status.code().ok_or(RunError::NoQemuExitCode)?; + match config.test_success_exit_code { + Some(code) if qemu_exit_code == code => 0, + Some(_) if qemu_exit_code == 0 => 1, + _ => qemu_exit_code, + } + } + } + } else { + let status = command.status().map_err(|error| RunError::Io { + context: IoErrorContext::QemuRunCommand { + command: format!("{:?}", command), + }, + error, + })?; + status.code().unwrap_or(1) + }; + + Ok(exit_code) +} + +/// Running the disk image failed. +#[derive(Debug, Error)] +pub enum RunError { + /// Test timed out + #[error("Test timed out")] + TestTimedOut, + + /// Failed to read QEMU exit code + #[error("Failed to read QEMU exit code")] + NoQemuExitCode, + + /// An I/O error occurred + #[error("{context}: An I/O error occurred: {error}")] + Io { + /// The operation that caused the I/O error. + context: IoErrorContext, + /// The I/O error that occurred. + error: io::Error, + }, +} + +/// An I/O error occurred while trying to run the disk image. +#[derive(Debug, Error)] +pub enum IoErrorContext { + /// QEMU command for non-test failed + #[error("Failed to execute QEMU run command `{command}`")] + QemuRunCommand { + /// The QEMU command that was executed + command: String, + }, + + /// QEMU command for test failed + #[error("Failed to execute QEMU test command `{command}`")] + QemuTestCommand { + /// The QEMU command that was executed + command: String, + }, + + /// Waiting for test with timeout failed + #[error("Failed to wait with timeout")] + WaitWithTimeout, + + /// Failed to kill QEMU + #[error("Failed to kill QEMU")] + KillQemu, + + /// Failed to wait for QEMU process + #[error("Failed to wait for QEMU process")] + WaitForQemu, +} + +/// Helper function for IO error construction +fn context(context: IoErrorContext) -> impl FnOnce(io::Error) -> RunError { + |error| RunError::Io { context, error } +} diff --git a/src/subcommand.rs b/src/subcommand.rs deleted file mode 100644 index f848c46..0000000 --- a/src/subcommand.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod build; -pub mod run; -pub mod runner; -pub mod test; diff --git a/src/subcommand/build.rs b/src/subcommand/build.rs deleted file mode 100644 index 4a85a42..0000000 --- a/src/subcommand/build.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::{args::Args, builder::Builder, config, ErrorMessage}; -use std::path::PathBuf; - -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> { - 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) -} diff --git a/src/subcommand/run.rs b/src/subcommand/run.rs deleted file mode 100644 index 3d23ab2..0000000 --- a/src/subcommand/run.rs +++ /dev/null @@ -1,59 +0,0 @@ -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()); - - if args.bin_name().is_none() { - let kernel_package = builder - .kernel_package() - .map_err(|key| format!("Kernel package not found it cargo metadata (`{}`)", key))?; - let bins = kernel_package.targets.iter().filter(|t| t.kind == ["bin"]); - let mut not_test = bins.filter(|t| !t.name.starts_with("test-")); - let bin_name = not_test - .next() - .ok_or("no kernel executable found")? - .name - .to_owned(); - if not_test.count() > 0 { - Err("Multiple kernel executables found. \ - Please specify the binary you want to run as a `--bin` argument")?; - } - args.set_bin_name(bin_name); - } - - 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 deleted file mode 100644 index 90bf4e8..0000000 --- a/src/subcommand/runner.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::{args::RunnerArgs, builder::Builder, config, ErrorMessage}; -use std::{process, time::Duration}; -use wait_timeout::ChildExt; - -pub(crate) fn runner(args: RunnerArgs) -> Result { - let builder = Builder::new(None)?; - let config = config::read_config(builder.kernel_manifest_path())?; - let exe_parent = args - .executable - .parent() - .ok_or("kernel executable has no parent")?; - let is_test = exe_parent.ends_with("deps"); - - let bootimage_bin = { - 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")?; - exe_parent.join(format!("bootimage-{}.bin", file_stem)) - }; - - let executable_canonicalized = args.executable.canonicalize().map_err(|err| { - format!( - "failed to canonicalize executable path `{}`: {}", - args.executable.display(), - err - ) - })?; - builder.create_bootimage(&executable_canonicalized, &bootimage_bin, args.quiet)?; - - let mut run_command: Vec<_> = config - .run_command - .iter() - .map(|arg| arg.replace("{}", &format!("{}", bootimage_bin.display()))) - .collect(); - if is_test { - if let Some(args) = config.test_args { - run_command.extend(args); - } - } else { - if let Some(args) = config.run_args { - run_command.extend(args); - } - } - if let Some(args) = args.runner_args { - run_command.extend(args); - } - - if !args.quiet { - println!("Running: `{}`", run_command.join(" ")); - } - let mut command = process::Command::new(&run_command[0]); - command.args(&run_command[1..]); - - let exit_code = if is_test { - let mut child = command - .spawn() - .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) - .map_err(|e| format!("Failed to wait with timeout: {}", e))? - { - None => { - child - .kill() - .map_err(|e| format!("Failed to kill QEMU: {}", e))?; - child - .wait() - .map_err(|e| format!("Failed to wait for QEMU process: {}", e))?; - return Err(ErrorMessage::from("Timed Out")); - } - Some(exit_status) => match config.test_success_exit_code { - Some(code) if exit_status.code() == Some(code) => 0, - other => other.unwrap_or(1), - }, - } - } else { - let status = command - .status() - .map_err(|e| format!("Failed to execute `{:?}`: {}", command, e))?; - status.code().unwrap_or(1) - }; - - Ok(exit_code) -} diff --git a/src/subcommand/test.rs b/src/subcommand/test.rs deleted file mode 100644 index 7bf06b1..0000000 --- a/src/subcommand/test.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::{args::Args, builder::Builder, config, subcommand::build, ErrorMessage}; -use rayon::prelude::*; -use std::{fs, io, io::Write, process, time::Duration}; -use wait_timeout::ChildExt; - -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 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-")); - - 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)); - 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")?; - } - - test_targets.push((target, test_bin_path)); - } - - let tests = test_targets - .par_iter() - .map(|(target, test_path)| { - println!("RUN: {}", target.name); - - let test_result; - let output_file = format!("{}-output.txt", test_path.display()); - - let mut command = process::Command::new("qemu-system-x86_64"); - command.arg("-drive"); - command.arg(format!("format=raw,file={}", test_path.display())); - command.arg("-device"); - command.arg("isa-debug-exit,iobase=0xf4,iosize=0x04"); - command.arg("-display"); - command.arg("none"); - command.arg("-serial"); - command.arg(format!("file:{}", output_file)); - command.stderr(process::Stdio::null()); - let mut child = command - .spawn() - .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) - .map_err(|e| format!("Failed to wait with timeout: {}", e))? - { - None => { - child - .kill() - .map_err(|e| format!("Failed to kill QEMU: {}", e))?; - child - .wait() - .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).map_err(|e| { - format!("Failed to read test output file {}: {}", output_file, e) - })?; - test_result = handle_exit_status(exit_status, &output, &target.name)?; - } - } - - Ok((target.name.clone(), test_result)) - }) - .collect::, ErrorMessage>>()?; - - println!(""); - if tests.iter().all(|t| t.1 == TestResult::Ok) { - println!("All tests succeeded."); - Ok(()) - } else { - writeln!(io::stderr(), "The following tests failed:")?; - for test in tests.iter().filter(|t| t.1 != TestResult::Ok) { - writeln!(io::stderr(), " {}: {:?}", test.0, test.1)?; - } - Err("Some tests failed".into()) - } -} - -fn handle_exit_status( - exit_status: process::ExitStatus, - output: &str, - target_name: &str, -) -> Result { - match exit_status.code() { - None => { - writeln!(io::stderr(), "FAIL: No Exit Code.")?; - for line in output.lines() { - writeln!(io::stderr(), " {}", line)?; - } - Ok(TestResult::Invalid) - } - Some(code) => { - match code { - // 0 << 1 | 1 - 1 => { - if output.starts_with("ok\n") { - println!("OK: {}", target_name); - Ok(TestResult::Ok) - } else if output.starts_with("failed\n") { - writeln!(io::stderr(), "FAIL:")?; - for line in output[7..].lines() { - writeln!(io::stderr(), " {}", line)?; - } - Ok(TestResult::Failed) - } else { - writeln!(io::stderr(), "FAIL: Invalid Output:")?; - for line in output.lines() { - writeln!(io::stderr(), " {}", line)?; - } - Ok(TestResult::Invalid) - } - } - - // 2 << 1 | 1 - 5 => { - println!("OK: {}", target_name); - Ok(TestResult::Ok) - } - - // 3 << 1 | 1 - 7 => { - let fail_index = output.find("failed\n"); - if fail_index.is_some() { - writeln!(io::stderr(), "FAIL:")?; - let fail_output = output.split_at(fail_index.unwrap()).1; - for line in fail_output[7..].lines() { - writeln!(io::stderr(), " {}", line)?; - } - } else { - writeln!(io::stderr(), "FAIL: {}", target_name)?; - } - Ok(TestResult::Failed) - } - - _ => { - writeln!(io::stderr(), "FAIL: Invalid Exit Code {}:", code)?; - for line in output.lines() { - writeln!(io::stderr(), " {}", line)?; - } - Ok(TestResult::Invalid) - } - } - } - } -} - -#[derive(Debug, PartialEq, Eq)] -enum TestResult { - Ok, - Failed, - TimedOut, - Invalid, -}