├── .gitignore ├── tests └── testsuite │ ├── main.rs │ ├── fixtures │ ├── bin │ │ ├── src │ │ │ └── main.rs │ │ └── Cargo.toml │ ├── error │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── Cargo.toml │ ├── bin_lib │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── Cargo.toml │ ├── example │ │ ├── src │ │ │ └── lib.rs │ │ ├── examples │ │ │ └── example_fixture.rs │ │ └── Cargo.toml │ ├── script │ │ ├── build.rs │ │ ├── src │ │ │ └── lib.rs │ │ └── Cargo.toml │ ├── warn │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── Cargo.toml │ ├── dep │ │ ├── src │ │ │ └── lib.rs │ │ └── Cargo.toml │ ├── lib │ │ ├── src │ │ │ └── lib.rs │ │ └── Cargo.toml │ └── test │ │ ├── tests │ │ └── test.rs │ │ ├── Cargo.toml │ │ └── src │ │ ├── lib.rs │ │ └── main.rs │ ├── run.rs │ └── build.rs ├── release.toml ├── .cargo └── config.toml ├── committed.toml ├── .pre-commit-config.yaml ├── .github ├── workflows │ ├── spelling.yml │ ├── pre-commit.yml │ ├── committed.yml │ ├── audit.yml │ ├── rust-next.yml │ └── ci.yml ├── settings.yml └── renovate.json5 ├── .clippy.toml ├── src ├── bin │ └── bin_fixture.rs ├── lib.rs ├── cargo.rs ├── error.rs ├── msg.rs ├── test.rs ├── format │ ├── diagnostic.rs │ ├── test.rs │ └── mod.rs ├── run.rs └── build.rs ├── examples └── example_fixture.rs ├── LICENSE-MIT ├── README.md ├── CONTRIBUTING.md ├── Cargo.toml ├── CHANGELOG.md ├── Cargo.lock ├── deny.toml └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | tests/**/Cargo.lock 3 | -------------------------------------------------------------------------------- /tests/testsuite/main.rs: -------------------------------------------------------------------------------- 1 | automod::dir!("tests/testsuite"); 2 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | dependent-version = "fix" 2 | allow-branch = ["master"] 3 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [resolver] 2 | incompatible-rust-versions = "fallback" 3 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/bin/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/error/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(a: i32, b: i32) -> i32 { 2 | a + b 3 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/bin_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(a: i32, b: i32) -> i32 { 2 | a + b 3 | } 4 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/example/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(a: i32, b: i32) -> i32 { 2 | a + b 3 | } 4 | -------------------------------------------------------------------------------- /committed.toml: -------------------------------------------------------------------------------- 1 | style="conventional" 2 | ignore_author_re="(dependabot|renovate)" 3 | merge_commit = false 4 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/script/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=build.rs"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/warn/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | 3 | pub fn add(a: i32, b: i32) -> i32 { 4 | a + b 5 | } 6 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/error/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate error; 2 | 3 | fn main() { 4 | println!("Hello, {}", error::add(1, 3)); 5 | } 6 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/warn/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate warn; 2 | 3 | fn main() { 4 | println!("Hello, {}", warn::add(1, 3)); 5 | } 6 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/bin_lib/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate bin_lib; 2 | 3 | fn main() { 4 | println!("Hello, {}", bin_lib::add(1, 3)); 5 | } 6 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/example/examples/example_fixture.rs: -------------------------------------------------------------------------------- 1 | extern crate example; 2 | 3 | fn main() { 4 | assert_eq!(example::add(1, 2), 3); 5 | } 6 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/dep/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/script/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/test/tests/test.rs: -------------------------------------------------------------------------------- 1 | extern crate test_fixture; 2 | 3 | #[test] 4 | fn integration_works() { 5 | assert_eq!(test_fixture::add(2, 2), 4); 6 | } 7 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "example" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "test_fixture" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "bin" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "lib" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "error" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/warn/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "warn" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/bin_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "bin_lib" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/script/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "script" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/dep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "dep" 5 | version = "0.1.0" 6 | authors = ["Ed Page "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | lazy_static = "1.2.0" 11 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/test/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// ```rust 2 | /// assert_eq!(test_fixture::add(1, 2), 3); 3 | /// ``` 4 | pub fn add(a: i32, b: i32) -> i32 { 5 | a + b 6 | } 7 | 8 | #[cfg(test)] 9 | mod tests { 10 | use super::*; 11 | 12 | #[test] 13 | fn unit_works() { 14 | assert_eq!(add(2, 2), 4); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/testsuite/fixtures/test/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate test_fixture; 2 | 3 | fn main() { 4 | println!("answer = {}", test_fixture::add(40, 2)); 5 | println!("answer = {}", add(40, 2)); 6 | } 7 | 8 | /// ```rust 9 | /// assert_eq!(test_fixture::add(1, 2), 3); 10 | /// ``` 11 | pub fn add(a: i32, b: i32) -> i32 { 12 | a + b 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn unit_works() { 21 | assert_eq!(add(2, 2), 4); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_install_hook_types: ["pre-commit", "commit-msg"] 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-yaml 7 | - id: check-json 8 | - id: check-toml 9 | - id: check-merge-conflict 10 | - id: check-case-conflict 11 | - id: detect-private-key 12 | - repo: https://github.com/crate-ci/typos 13 | rev: v1.32.0 14 | hooks: 15 | - id: typos 16 | - repo: https://github.com/crate-ci/committed 17 | rev: v1.1.7 18 | hooks: 19 | - id: committed 20 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [pull_request] 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | CARGO_TERM_COLOR: always 11 | CLICOLOR: 1 12 | 13 | concurrency: 14 | group: "${{ github.workflow }}-${{ github.ref }}" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | spelling: 19 | name: Spell Check with Typos 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout Actions Repository 23 | uses: actions/checkout@v6 24 | - name: Spell Check Repo 25 | uses: crate-ci/typos@master 26 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | permissions: {} # none 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: [master] 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | pre-commit: 21 | permissions: 22 | contents: read 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v6 26 | - uses: actions/setup-python@v6 27 | with: 28 | python-version: '3.x' 29 | - uses: pre-commit/action@v3.0.1 30 | -------------------------------------------------------------------------------- /.github/workflows/committed.yml: -------------------------------------------------------------------------------- 1 | # Not run as part of pre-commit checks because they don't handle sending the correct commit 2 | # range to `committed` 3 | name: Lint Commits 4 | on: [pull_request] 5 | 6 | permissions: 7 | contents: read 8 | 9 | env: 10 | RUST_BACKTRACE: 1 11 | CARGO_TERM_COLOR: always 12 | CLICOLOR: 1 13 | 14 | concurrency: 15 | group: "${{ github.workflow }}-${{ github.ref }}" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | committed: 20 | name: Lint Commits 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout Actions Repository 24 | uses: actions/checkout@v6 25 | with: 26 | fetch-depth: 0 27 | - name: Lint Commits 28 | uses: crate-ci/committed@master 29 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | allow-print-in-tests = true 2 | allow-expect-in-tests = true 3 | allow-unwrap-in-tests = true 4 | allow-dbg-in-tests = true 5 | disallowed-methods = [ 6 | { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 7 | { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 8 | { path = "std::result::Result::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 9 | { path = "std::result::Result::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 10 | { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" }, 11 | { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" }, 12 | ] 13 | -------------------------------------------------------------------------------- /src/bin/bin_fixture.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::error::Error; 3 | use std::io; 4 | use std::io::Write; 5 | use std::process; 6 | 7 | fn run() -> Result<(), Box> { 8 | if let Ok(text) = env::var("stdout") { 9 | println!("{text}"); 10 | } 11 | if let Ok(text) = env::var("stderr") { 12 | eprintln!("{text}"); 13 | } 14 | 15 | let code = env::var("exit") 16 | .ok() 17 | .map(|v| v.parse::()) 18 | .map(|r| r.map(Some)) 19 | .unwrap_or(Ok(None))? 20 | .unwrap_or(0); 21 | process::exit(code); 22 | } 23 | 24 | fn main() { 25 | let code = match run() { 26 | Ok(_) => 0, 27 | Err(ref e) => { 28 | write!(&mut io::stderr(), "{e}").expect("writing to stderr won't fail"); 29 | 1 30 | } 31 | }; 32 | process::exit(code); 33 | } 34 | -------------------------------------------------------------------------------- /examples/example_fixture.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::error::Error; 3 | use std::io; 4 | use std::io::Write; 5 | use std::process; 6 | 7 | fn run() -> Result<(), Box> { 8 | if let Ok(text) = env::var("stdout") { 9 | println!("{text}"); 10 | } 11 | if let Ok(text) = env::var("stderr") { 12 | eprintln!("{text}"); 13 | } 14 | 15 | let code = env::var("exit") 16 | .ok() 17 | .map(|v| v.parse::()) 18 | .map(|r| r.map(Some)) 19 | .unwrap_or(Ok(None))? 20 | .unwrap_or(0); 21 | process::exit(code); 22 | } 23 | 24 | fn main() { 25 | let code = match run() { 26 | Ok(_) => 0, 27 | Err(ref e) => { 28 | write!(&mut io::stderr(), "{e}").expect("writing to stderr won't fail"); 29 | 1 30 | } 31 | }; 32 | process::exit(code); 33 | } 34 | -------------------------------------------------------------------------------- /tests/testsuite/run.rs: -------------------------------------------------------------------------------- 1 | fn test_fixture(name: &str) { 2 | let temp = tempfile::TempDir::new().unwrap(); 3 | 4 | let cmd = escargot::CargoBuild::new() 5 | .manifest_path(format!("tests/testsuite/fixtures/{name}/Cargo.toml")) 6 | .current_release() 7 | .current_target() 8 | .target_dir(temp.path()) 9 | .run() 10 | .unwrap(); 11 | let output = cmd.command().output().unwrap(); 12 | assert!(output.status.success()); 13 | } 14 | 15 | #[test] 16 | fn test_bin() { 17 | test_fixture("bin"); 18 | } 19 | 20 | #[test] 21 | fn test_warn() { 22 | test_fixture("warn"); 23 | } 24 | 25 | #[test] 26 | fn test_error() { 27 | let result = escargot::CargoBuild::new() 28 | .manifest_path("tests/testsuite/fixtures/error/Cargo.toml") 29 | .current_release() 30 | .current_target() 31 | .run(); 32 | assert!(result.is_err()); 33 | println!("```{}```", result.err().unwrap()); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Individual contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | push: 12 | branches: 13 | - master 14 | 15 | env: 16 | RUST_BACKTRACE: 1 17 | CARGO_TERM_COLOR: always 18 | CLICOLOR: 1 19 | 20 | concurrency: 21 | group: "${{ github.workflow }}-${{ github.ref }}" 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | security_audit: 26 | permissions: 27 | issues: write # to create issues (actions-rs/audit-check) 28 | checks: write # to create check (actions-rs/audit-check) 29 | runs-on: ubuntu-latest 30 | # Prevent sudden announcement of a new advisory from failing ci: 31 | continue-on-error: true 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v6 35 | - uses: actions-rs/audit-check@v1 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | cargo_deny: 40 | permissions: 41 | issues: write # to create issues (actions-rs/audit-check) 42 | checks: write # to create check (actions-rs/audit-check) 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | checks: 47 | - bans licenses sources 48 | steps: 49 | - uses: actions/checkout@v6 50 | - uses: EmbarkStudios/cargo-deny-action@v2 51 | with: 52 | command: check ${{ matrix.checks }} 53 | rust-version: stable 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # escargot 2 | 3 | > **Cargo API written in Paris** 4 | 5 | [![codecov](https://codecov.io/gh/crate-ci/escargot/branch/master/graph/badge.svg)](https://codecov.io/gh/crate-ci/escargot) 6 | [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation] 7 | ![License](https://img.shields.io/crates/l/escargot.svg) 8 | [![Crates Status](https://img.shields.io/crates/v/escargot.svg)][Crates.io] 9 | 10 | ## Why escargot 11 | 12 | Compared to depending on `cargo`: 13 | - Faster compile times. 14 | - Simpler API. 15 | - Better interop with projects relying on other cargo versions. 16 | - Probably slower execution, especially on platforms without an optimized `fork` (e.g. Windows). 17 | 18 | ## Relevant crates 19 | 20 | Other related crates: 21 | * [cargo](https://crates.io/crates/cargo) for the real thing 22 | * [cargo-metadata](https://crates.io/crates/cargo_metadata) for a similar project specifically geared to the `metadata` subcommand. 23 | 24 | ## License 25 | 26 | Licensed under either of 27 | 28 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 29 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 30 | 31 | at your option. 32 | 33 | ### Contribution 34 | 35 | Unless you explicitly state otherwise, any contribution intentionally 36 | submitted for inclusion in the work by you, as defined in the Apache-2.0 37 | license, shall be dual-licensed as above, without any additional terms or 38 | conditions. 39 | 40 | [Crates.io]: https://crates.io/crates/escargot 41 | [Documentation]: https://docs.rs/escargot 42 | -------------------------------------------------------------------------------- /tests/testsuite/build.rs: -------------------------------------------------------------------------------- 1 | fn test_fixture(name: &str) { 2 | let temp = tempfile::TempDir::new().unwrap(); 3 | 4 | let msgs = escargot::CargoBuild::new() 5 | .manifest_path(format!("tests/testsuite/fixtures/{name}/Cargo.toml")) 6 | .current_release() 7 | .current_target() 8 | .target_dir(temp.path()) 9 | .exec() 10 | .unwrap(); 11 | for msg in msgs { 12 | let raw_msg = msg.unwrap(); 13 | let msg = raw_msg.decode(); 14 | match msg { 15 | Ok(msg) => println!("{msg:#?}"), 16 | Err(err) => panic!("{err}\nmsg=`{raw_msg:#?}`"), 17 | } 18 | } 19 | } 20 | 21 | #[test] 22 | fn test_bin() { 23 | test_fixture("bin"); 24 | } 25 | 26 | #[test] 27 | fn test_lib() { 28 | test_fixture("lib"); 29 | } 30 | 31 | #[test] 32 | fn test_bin_lib() { 33 | test_fixture("bin_lib"); 34 | } 35 | 36 | #[test] 37 | fn test_warn() { 38 | test_fixture("warn"); 39 | } 40 | 41 | #[test] 42 | fn test_build_script() { 43 | test_fixture("script"); 44 | } 45 | 46 | #[test] 47 | fn test_dependency() { 48 | test_fixture("dep"); 49 | } 50 | 51 | #[test] 52 | fn test_error() { 53 | let msgs: Vec<_> = escargot::CargoBuild::new() 54 | .manifest_path("tests/testsuite/fixtures/error/Cargo.toml") 55 | .current_release() 56 | .current_target() 57 | .exec() 58 | .unwrap() 59 | .collect(); 60 | assert!(1 < msgs.len()); 61 | let error_idx = msgs.len() - 1; 62 | for msg in &msgs[0..error_idx] { 63 | let msg = msg.as_ref().unwrap(); 64 | let msg = msg.decode().unwrap(); 65 | println!("{msg:#?}"); 66 | } 67 | assert!(msgs[error_idx].is_err()); 68 | println!("```{}```", msgs[error_idx].as_ref().err().unwrap()); 69 | } 70 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Escargot: A Cargo API 2 | //! 3 | //! ## Features 4 | //! 5 | //! Features: 6 | //! - `print` for logged output to be printed instead, generally for test writing. 7 | //! 8 | //! ## Why escargot 9 | //! 10 | //! Compared to depending on `cargo`: 11 | //! - Faster compile times. 12 | //! - Simpler API. 13 | //! - Better interop with projects relying on other cargo versions. 14 | //! - Probably slower execution, especially on platforms without an optimized `fork` (e.g. Windows). 15 | //! 16 | //! ## Relevant crates 17 | //! 18 | //! Other related crates: 19 | //! * [cargo](https://crates.io/crates/cargo) for the real thing 20 | //! * [cargo-metadata](https://crates.io/crates/cargo_metadata) for a similar project specifically geared to the `metadata` subcommand. 21 | //! 22 | //! # Example 23 | //! 24 | //! ```rust 25 | //! # let target_dir = tempfile::TempDir::new().unwrap(); 26 | //! escargot::CargoBuild::new() 27 | //! .bin("bin") 28 | //! .current_release() 29 | //! .current_target() 30 | //! .manifest_path("tests/fixtures/bin/Cargo.toml") 31 | //! .target_dir(target_dir.path()) 32 | //! .exec() 33 | //! .unwrap(); 34 | //! ``` 35 | 36 | #![cfg_attr(docsrs, feature(doc_cfg))] 37 | #![allow(clippy::self_named_module_files)] // false positive 38 | #![warn(missing_docs)] 39 | #![warn(clippy::print_stderr)] 40 | #![warn(clippy::print_stdout)] 41 | 42 | #[macro_use] 43 | extern crate serde; 44 | 45 | mod build; 46 | pub use crate::build::*; 47 | mod cargo; 48 | pub use crate::cargo::*; 49 | mod msg; 50 | pub use crate::msg::*; 51 | mod run; 52 | pub use crate::run::*; 53 | #[cfg(feature = "test_unstable")] 54 | mod test; 55 | #[cfg(feature = "test_unstable")] 56 | pub use test::*; 57 | 58 | pub mod error; 59 | pub mod format; 60 | 61 | #[doc = include_str!("../README.md")] 62 | #[cfg(doctest)] 63 | pub struct ReadmeDoctests; 64 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # These settings are synced to GitHub by https://probot.github.io/apps/settings/ 2 | 3 | repository: 4 | description: "Cargo API written in Paris" 5 | homepage: "docs.rs/cargo" 6 | topics: "rust cargo test" 7 | has_issues: true 8 | has_projects: false 9 | has_wiki: false 10 | has_downloads: true 11 | default_branch: master 12 | 13 | # Preference: people do clean commits 14 | allow_merge_commit: true 15 | # Backup in case we need to clean up commits 16 | allow_squash_merge: true 17 | # Not really needed 18 | allow_rebase_merge: false 19 | 20 | allow_auto_merge: true 21 | delete_branch_on_merge: true 22 | 23 | squash_merge_commit_title: "PR_TITLE" 24 | squash_merge_commit_message: "PR_BODY" 25 | merge_commit_message: "PR_BODY" 26 | 27 | labels: 28 | # Type 29 | - name: bug 30 | color: '#b60205' 31 | description: "Not as expected" 32 | - name: enhancement 33 | color: '#1d76db' 34 | description: "Improve the expected" 35 | # Flavor 36 | - name: question 37 | color: "#cc317c" 38 | description: "Uncertainty is involved" 39 | - name: breaking-change 40 | color: "#e99695" 41 | - name: good first issue 42 | color: '#c2e0c6' 43 | description: "Help wanted!" 44 | 45 | # This serves more as documentation. 46 | # Branch protection API was replaced by rulesets but settings isn't updated. 47 | # See https://github.com/repository-settings/app/issues/825 48 | # 49 | # branches: 50 | # - name: master 51 | # protection: 52 | # required_pull_request_reviews: null 53 | # required_conversation_resolution: true 54 | # required_status_checks: 55 | # # Required. Require branches to be up to date before merging. 56 | # strict: false 57 | # contexts: ["CI", "Spell Check with Typos"] 58 | # enforce_admins: false 59 | # restrictions: null 60 | -------------------------------------------------------------------------------- /.github/workflows/rust-next.yml: -------------------------------------------------------------------------------- 1 | name: rust-next 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | schedule: 8 | - cron: '5 5 5 * *' 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test: 21 | name: Test 22 | strategy: 23 | matrix: 24 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 25 | rust: ["stable", "beta"] 26 | include: 27 | - os: ubuntu-latest 28 | rust: "nightly" 29 | continue-on-error: ${{ matrix.rust != 'stable' }} 30 | runs-on: ${{ matrix.os }} 31 | env: 32 | # Reduce amount of data cached 33 | CARGO_PROFILE_DEV_DEBUG: line-tables-only 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v6 37 | - name: Install Rust 38 | uses: dtolnay/rust-toolchain@stable 39 | with: 40 | toolchain: ${{ matrix.rust }} 41 | - uses: Swatinem/rust-cache@v2 42 | - uses: taiki-e/install-action@cargo-hack 43 | - name: Build 44 | run: cargo test --workspace --no-run 45 | - name: Test 46 | run: cargo hack test --each-feature --workspace --exclude-features strict_unstable 47 | latest: 48 | name: "Check latest dependencies" 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout repository 52 | uses: actions/checkout@v6 53 | - name: Install Rust 54 | uses: dtolnay/rust-toolchain@stable 55 | with: 56 | toolchain: stable 57 | - uses: Swatinem/rust-cache@v2 58 | - uses: taiki-e/install-action@cargo-hack 59 | - name: Update dependencies 60 | run: cargo update 61 | - name: Build 62 | run: cargo test --workspace --no-run 63 | - name: Test 64 | run: cargo hack test --each-feature --workspace --exclude-features strict_unstable 65 | -------------------------------------------------------------------------------- /src/cargo.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi; 3 | use std::process; 4 | use std::str; 5 | 6 | use crate::build::CargoBuild; 7 | 8 | /// The current process' target triplet. 9 | pub const CURRENT_TARGET: &str = include_str!(concat!(env!("OUT_DIR"), "/current_target.txt")); 10 | 11 | fn cargo_bin() -> &'static ffi::OsStr { 12 | static CARGO_BIN: std::sync::OnceLock = std::sync::OnceLock::new(); 13 | CARGO_BIN.get_or_init(|| env::var_os("CARGO").unwrap_or_else(|| "cargo".into())) 14 | } 15 | 16 | /// Top-level command. 17 | #[derive(Debug)] 18 | pub struct Cargo { 19 | cmd: process::Command, 20 | } 21 | 22 | impl Cargo { 23 | /// Create a top-level command. 24 | pub fn new() -> Self { 25 | Self { 26 | cmd: process::Command::new(cargo_bin()), 27 | } 28 | } 29 | 30 | /// Manually pass an argument that is unsupported. 31 | /// 32 | /// Caution: Passing in a sub-command or `--` can throw off the API. 33 | pub fn arg>(mut self, arg: S) -> Self { 34 | self.cmd.arg(arg); 35 | self 36 | } 37 | 38 | /// Manually pass arguments that are unsupported. 39 | /// 40 | /// Caution: Passing in a sub-command or `--` can throw off the API. 41 | pub fn args, S: AsRef>(mut self, args: I) -> Self { 42 | self.cmd.args(args); 43 | self 44 | } 45 | 46 | /// Run the `build` subcommand. 47 | pub fn build(self) -> CargoBuild { 48 | self.build_with("build") 49 | } 50 | 51 | /// Run a custom `build` subcommand. 52 | pub fn build_with>(mut self, name: S) -> CargoBuild { 53 | self.cmd.arg(name).arg("--message-format=json"); 54 | CargoBuild::with_command(self.cmd) 55 | } 56 | 57 | /// Return the underlying [`process::Command`] 58 | pub fn into_command(self) -> process::Command { 59 | self.cmd 60 | } 61 | } 62 | 63 | impl Default for Cargo { 64 | fn default() -> Self { 65 | Self::new() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error reporting API. 2 | 3 | use std::error::Error; 4 | use std::fmt; 5 | 6 | /// Result of a cargo command. 7 | pub type CargoResult = Result; 8 | 9 | /// For programmatically processing failures. 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 11 | pub enum ErrorKind { 12 | /// Spawning the cargo subommand failed. 13 | InvalidCommand, 14 | /// The cargo subcommand returned an error. 15 | CommandFailed, 16 | /// Parsing the cargo subcommand's output failed. 17 | InvalidOutput, 18 | } 19 | 20 | impl fmt::Display for ErrorKind { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | match *self { 23 | ErrorKind::InvalidOutput => write!(f, "Spawning the cargo subommand failed."), 24 | ErrorKind::CommandFailed => write!(f, "The cargo subcommand returned an error."), 25 | ErrorKind::InvalidCommand => write!(f, "Parsing the cargo subcommand's output failed."), 26 | } 27 | } 28 | } 29 | 30 | /// Cargo command failure information. 31 | #[derive(Debug)] 32 | pub struct CargoError { 33 | kind: ErrorKind, 34 | context: Option, 35 | cause: Option>, 36 | } 37 | 38 | impl CargoError { 39 | pub(crate) fn new(kind: ErrorKind) -> Self { 40 | Self { 41 | kind, 42 | context: None, 43 | cause: None, 44 | } 45 | } 46 | 47 | pub(crate) fn set_context(mut self, context: S) -> Self 48 | where 49 | S: Into, 50 | { 51 | let context = context.into(); 52 | self.context = Some(context); 53 | self 54 | } 55 | 56 | pub(crate) fn set_cause(mut self, cause: E) -> Self 57 | where 58 | E: Error + Send + Sync + 'static, 59 | { 60 | let cause = Box::new(cause); 61 | self.cause = Some(cause); 62 | self 63 | } 64 | 65 | /// For programmatically processing failures. 66 | pub fn kind(&self) -> ErrorKind { 67 | self.kind 68 | } 69 | } 70 | 71 | impl Error for CargoError { 72 | fn cause(&self) -> Option<&dyn Error> { 73 | self.cause.as_ref().map(|c| { 74 | let c: &dyn Error = c.as_ref(); 75 | c 76 | }) 77 | } 78 | } 79 | 80 | impl fmt::Display for CargoError { 81 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 82 | writeln!(f, "Cargo command failed: {}", self.kind)?; 83 | if let Some(ref context) = self.context { 84 | writeln!(f, "{context}")?; 85 | } 86 | if let Some(ref cause) = self.cause { 87 | writeln!(f, "Cause: {cause}")?; 88 | } 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | schedule: [ 3 | 'before 5am on the first day of the month', 4 | ], 5 | semanticCommits: 'enabled', 6 | commitMessageLowerCase: 'never', 7 | configMigration: true, 8 | dependencyDashboard: true, 9 | customManagers: [ 10 | { 11 | customType: 'regex', 12 | managerFilePatterns: [ 13 | '/^rust-toolchain\\.toml$/', 14 | '/Cargo.toml$/', 15 | '/clippy.toml$/', 16 | '/\\.clippy.toml$/', 17 | '/^\\.github/workflows/ci.yml$/', 18 | '/^\\.github/workflows/rust-next.yml$/', 19 | ], 20 | matchStrings: [ 21 | 'STABLE.*?(?\\d+\\.\\d+(\\.\\d+)?)', 22 | '(?\\d+\\.\\d+(\\.\\d+)?).*?STABLE', 23 | ], 24 | depNameTemplate: 'STABLE', 25 | packageNameTemplate: 'rust-lang/rust', 26 | datasourceTemplate: 'github-releases', 27 | }, 28 | ], 29 | packageRules: [ 30 | { 31 | commitMessageTopic: 'Rust Stable', 32 | matchManagers: [ 33 | 'custom.regex', 34 | ], 35 | matchDepNames: [ 36 | 'STABLE', 37 | ], 38 | extractVersion: '^(?\\d+\\.\\d+)', // Drop the patch version 39 | schedule: [ 40 | '* * * * *', 41 | ], 42 | automerge: true, 43 | }, 44 | // Goals: 45 | // - Keep version reqs low, ignoring compatible normal/build dependencies 46 | // - Take advantage of latest dev-dependencies 47 | // - Rollup safe upgrades to reduce CI runner load 48 | // - Help keep number of versions down by always using latest breaking change 49 | // - Have lockfile and manifest in-sync 50 | { 51 | matchManagers: [ 52 | 'cargo', 53 | ], 54 | matchDepTypes: [ 55 | 'build-dependencies', 56 | 'dependencies', 57 | ], 58 | matchCurrentVersion: '>=0.1.0', 59 | matchUpdateTypes: [ 60 | 'patch', 61 | ], 62 | enabled: false, 63 | }, 64 | { 65 | matchManagers: [ 66 | 'cargo', 67 | ], 68 | matchDepTypes: [ 69 | 'build-dependencies', 70 | 'dependencies', 71 | ], 72 | matchCurrentVersion: '>=1.0.0', 73 | matchUpdateTypes: [ 74 | 'minor', 75 | 'patch', 76 | ], 77 | enabled: false, 78 | }, 79 | { 80 | matchManagers: [ 81 | 'cargo', 82 | ], 83 | matchDepTypes: [ 84 | 'dev-dependencies', 85 | ], 86 | matchCurrentVersion: '>=0.1.0', 87 | matchUpdateTypes: [ 88 | 'patch', 89 | ], 90 | automerge: true, 91 | groupName: 'compatible (dev)', 92 | }, 93 | { 94 | matchManagers: [ 95 | 'cargo', 96 | ], 97 | matchDepTypes: [ 98 | 'dev-dependencies', 99 | ], 100 | matchCurrentVersion: '>=1.0.0', 101 | matchUpdateTypes: [ 102 | 'minor', 103 | 'patch', 104 | ], 105 | automerge: true, 106 | groupName: 'compatible (dev)', 107 | }, 108 | ], 109 | } 110 | -------------------------------------------------------------------------------- /src/msg.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::BufRead; 3 | use std::io::Read; 4 | use std::process; 5 | 6 | use crate::error::{CargoError, CargoResult, ErrorKind}; 7 | use crate::format; 8 | 9 | /// Messages returned from a cargo sub-command. 10 | #[derive(Debug)] 11 | pub struct CommandMessages(InnerCommandMessages); 12 | 13 | #[derive(Debug)] 14 | struct InnerCommandMessages { 15 | done: bool, 16 | child: process::Child, 17 | stdout: io::BufReader, 18 | stderr: io::BufReader, 19 | } 20 | 21 | impl CommandMessages { 22 | /// Run the command, allowing iteration over ndjson messages. 23 | pub fn with_command(mut cmd: process::Command) -> CargoResult { 24 | let mut child = cmd 25 | .stdout(process::Stdio::piped()) 26 | .stderr(process::Stdio::piped()) 27 | .spawn() 28 | .map_err(|e| CargoError::new(ErrorKind::InvalidCommand).set_cause(e))?; 29 | let stdout = child.stdout.take().expect("piped above"); 30 | let stdout = io::BufReader::new(stdout); 31 | let stderr = child.stderr.take().expect("piped above"); 32 | let stderr = io::BufReader::new(stderr); 33 | let msgs = InnerCommandMessages { 34 | done: false, 35 | child, 36 | stdout, 37 | stderr, 38 | }; 39 | Ok(CommandMessages(msgs)) 40 | } 41 | 42 | #[inline] 43 | fn next_msg(&mut self) -> CargoResult> { 44 | #![allow(clippy::branches_sharing_code)] 45 | 46 | let mut content = String::new(); 47 | let len = self 48 | .0 49 | .stdout 50 | .read_line(&mut content) 51 | .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; 52 | if 0 < len { 53 | Ok(Some(Message(content))) 54 | } else { 55 | let status = self 56 | .0 57 | .child 58 | .wait() 59 | .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; 60 | if !status.success() && !self.0.done { 61 | self.0.done = true; 62 | 63 | let mut data = vec![]; 64 | self.0 65 | .stderr 66 | .read_to_end(&mut data) 67 | .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; 68 | let err = CargoError::new(ErrorKind::CommandFailed) 69 | .set_context(String::from_utf8_lossy(&data)); 70 | Err(err) 71 | } else { 72 | self.0.done = true; 73 | Ok(None) 74 | } 75 | } 76 | } 77 | } 78 | 79 | impl Drop for CommandMessages { 80 | fn drop(&mut self) { 81 | if !self.0.done { 82 | let _ = self.0.child.wait(); 83 | } 84 | } 85 | } 86 | 87 | impl Iterator for CommandMessages { 88 | type Item = CargoResult; 89 | 90 | #[inline] 91 | fn next(&mut self) -> Option> { 92 | match self.next_msg() { 93 | Ok(Some(x)) => Some(Ok(x)), 94 | Ok(None) => None, 95 | Err(e) => Some(Err(e)), 96 | } 97 | } 98 | } 99 | 100 | /// An individual message from a cargo sub-command. 101 | #[derive(Debug, Clone, PartialEq, Eq)] 102 | pub struct Message(String); 103 | 104 | impl Message { 105 | /// Deserialize the message. 106 | pub fn decode(&self) -> CargoResult> { 107 | self.decode_custom() 108 | } 109 | 110 | /// Deserialize the message. 111 | pub fn decode_custom<'a, T>(&'a self) -> CargoResult 112 | where 113 | T: serde::Deserialize<'a>, 114 | { 115 | let data = serde_json::from_str(self.0.as_str()) 116 | .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; 117 | Ok(data) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to escargot 2 | 3 | Thanks for wanting to contribute! There are many ways to contribute and we 4 | appreciate any level you're willing to do. 5 | 6 | ## Feature Requests 7 | 8 | Need some new functionality to help? You can let us know by opening an 9 | [issue][new issue]. It's helpful to look through [all issues][all issues] in 10 | case it's already being talked about. 11 | 12 | ## Bug Reports 13 | 14 | Please let us know about what problems you run into, whether in behavior or 15 | ergonomics of API. You can do this by opening an [issue][new issue]. It's 16 | helpful to look through [all issues][all issues] in case it's already being 17 | talked about. 18 | 19 | ## Pull Requests 20 | 21 | Looking for an idea? Check our [issues][issues]. If the issue looks open ended, 22 | it is probably best to post on the issue how you are thinking of resolving the 23 | issue so you can get feedback early in the process. We want you to be 24 | successful and it can be discouraging to find out a lot of re-work is needed. 25 | 26 | Already have an idea? It might be good to first [create an issue][new issue] 27 | to propose it so we can make sure we are aligned and lower the risk of having 28 | to re-work some of it and the discouragement that goes along with that. 29 | 30 | ### Process 31 | 32 | As a heads up, we'll be running your PR through the following gauntlet: 33 | - warnings turned to compile errors 34 | - `cargo test` 35 | - `rustfmt` 36 | - `clippy` 37 | - `rustdoc` 38 | - [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit style 39 | - [`typos`](https://github.com/crate-ci/typos) to check spelling 40 | 41 | Not everything can be checked automatically though. 42 | 43 | We request that the commit history gets cleaned up. 44 | 45 | We ask that commits are atomic, meaning they are complete and have a single responsibility. 46 | A complete commit should build, pass tests, update documentation and tests, and not have dead code. 47 | 48 | PRs should tell a cohesive story, with refactor and test commits that keep the 49 | fix or feature commits simple and clear. 50 | 51 | Specifically, we would encourage 52 | - File renames be isolated into their own commit 53 | - Add tests in a commit before their feature or fix, showing the current behavior (i.e. they should pass). 54 | The diff for the feature/fix commit will then show how the behavior changed, 55 | making the commit's intent clearer to reviewers and the community, and showing people that the 56 | test is verifying the expected state. 57 | - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520) 58 | 59 | Note that we are talking about ideals. 60 | We understand having a clean history requires more advanced git skills; 61 | feel free to ask us for help! 62 | We might even suggest where it would work to be lax. 63 | We also understand that editing some early commits may cause a lot of churn 64 | with merge conflicts which can make it not worth editing all of the history. 65 | 66 | For code organization, we recommend 67 | - Grouping `impl` blocks next to their type (or trait) 68 | - Grouping private items after the `pub` item that uses them. 69 | - The intent is to help people quickly find the "relevant" details, allowing them to "dig deeper" as needed. Or put another way, the `pub` items serve as a table-of-contents. 70 | - The exact order is fuzzy; do what makes sense 71 | 72 | ## Releasing 73 | 74 | Pre-requisites 75 | - Running `cargo login` 76 | - Push permission to the repo 77 | - [`cargo-release`](https://github.com/crate-ci/cargo-release/) 78 | 79 | When we're ready to release, a project owner should do the following 80 | 1. Update the changelog (see `cargo release changes` for ideas) 81 | 2. Determine what the next version is, according to semver 82 | 3. Run [`cargo release -x `](https://github.com/crate-ci/cargo-release) 83 | 84 | [issues]: https://github.com/crate-ci/escargot/issues 85 | [new issue]: https://github.com/crate-ci/escargot/issues/new 86 | [all issues]: https://github.com/crate-ci/escargot/issues?utf8=%E2%9C%93&q=is%3Aissue 87 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | [workspace.package] 5 | repository = "https://github.com/crate-ci/escargot.git" 6 | license = "MIT OR Apache-2.0" 7 | edition = "2021" 8 | rust-version = "1.70" # MSRV 9 | include = [ 10 | "build.rs", 11 | "src/**/*", 12 | "Cargo.toml", 13 | "Cargo.lock", 14 | "LICENSE*", 15 | "README.md", 16 | "examples/**/*" 17 | ] 18 | 19 | [workspace.lints.rust] 20 | rust_2018_idioms = { level = "warn", priority = -1 } 21 | unnameable_types = "warn" 22 | unreachable_pub = "warn" 23 | unsafe_op_in_unsafe_fn = "warn" 24 | unused_lifetimes = "warn" 25 | unused_macro_rules = "warn" 26 | unused_qualifications = "warn" 27 | 28 | [workspace.lints.clippy] 29 | bool_assert_comparison = "allow" 30 | branches_sharing_code = "allow" 31 | checked_conversions = "warn" 32 | collapsible_else_if = "allow" 33 | create_dir = "warn" 34 | dbg_macro = "warn" 35 | debug_assert_with_mut_call = "warn" 36 | doc_markdown = "warn" 37 | empty_enum = "warn" 38 | enum_glob_use = "warn" 39 | expl_impl_clone_on_copy = "warn" 40 | explicit_deref_methods = "warn" 41 | explicit_into_iter_loop = "warn" 42 | fallible_impl_from = "warn" 43 | filter_map_next = "warn" 44 | flat_map_option = "warn" 45 | float_cmp_const = "warn" 46 | fn_params_excessive_bools = "warn" 47 | from_iter_instead_of_collect = "warn" 48 | if_same_then_else = "allow" 49 | implicit_clone = "warn" 50 | imprecise_flops = "warn" 51 | inconsistent_struct_constructor = "warn" 52 | inefficient_to_string = "warn" 53 | infinite_loop = "warn" 54 | invalid_upcast_comparisons = "warn" 55 | large_digit_groups = "warn" 56 | large_stack_arrays = "warn" 57 | large_types_passed_by_value = "warn" 58 | let_and_return = "allow" # sometimes good to name what you are returning 59 | linkedlist = "warn" 60 | lossy_float_literal = "warn" 61 | macro_use_imports = "warn" 62 | mem_forget = "warn" 63 | mutex_integer = "warn" 64 | needless_continue = "allow" 65 | needless_for_each = "warn" 66 | negative_feature_names = "warn" 67 | path_buf_push_overwrite = "warn" 68 | ptr_as_ptr = "warn" 69 | rc_mutex = "warn" 70 | redundant_feature_names = "warn" 71 | ref_option_ref = "warn" 72 | rest_pat_in_fully_bound_structs = "warn" 73 | result_large_err = "allow" 74 | same_functions_in_if_condition = "warn" 75 | self_named_module_files = "warn" 76 | semicolon_if_nothing_returned = "warn" 77 | str_to_string = "warn" 78 | string_add = "warn" 79 | string_add_assign = "warn" 80 | string_lit_as_bytes = "warn" 81 | string_to_string = "warn" 82 | todo = "warn" 83 | trait_duplication_in_bounds = "warn" 84 | uninlined_format_args = "warn" 85 | verbose_file_reads = "warn" 86 | wildcard_imports = "warn" 87 | zero_sized_map_values = "warn" 88 | 89 | [profile.dev] 90 | panic = "abort" 91 | 92 | [profile.release] 93 | panic = "abort" 94 | codegen-units = 1 95 | lto = true 96 | # debug = "line-tables-only" # requires Cargo 1.71 97 | 98 | [package] 99 | name = "escargot" 100 | version = "0.5.15" 101 | description = "Cargo API written in Paris" 102 | homepage = "https://github.com/crate-ci/escargot" 103 | categories = ["development-tools::build-utils"] 104 | keywords = ["cargo", "packaging"] 105 | repository.workspace = true 106 | license.workspace = true 107 | edition.workspace = true 108 | rust-version.workspace = true 109 | include.workspace = true 110 | 111 | [package.metadata.docs.rs] 112 | all-features = true 113 | rustdoc-args = ["--generate-link-to-definition"] 114 | 115 | [package.metadata.release] 116 | pre-release-replacements = [ 117 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, 118 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 119 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, 120 | {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, 121 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/crate-ci/escargot/compare/{{tag_name}}...HEAD", exactly=1}, 122 | ] 123 | 124 | [features] 125 | # Upcoming features in cargo. 126 | cargo_unstable = [] 127 | # Programmatic use of `cargo test` (relies on an unstable CLI API). 128 | test_unstable = [] 129 | # This mostly exists for testing, to catch new fields and enum variants being 130 | # added (for the cases we actually cover). 131 | strict_unstable = [] 132 | # This is for when using `escargot` in tests and you want logged output to 133 | # instead be printed because no logger is configured. 134 | print = [] 135 | 136 | [dependencies] 137 | serde = { version = "1.0.113", features = ["derive"] } 138 | serde_json = "1.0" 139 | log = "0.4.4" 140 | 141 | [dev-dependencies] 142 | tempfile = "3.3.0" 143 | automod = "1.0.14" 144 | 145 | [lints] 146 | workspace = true 147 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - master 11 | 12 | env: 13 | RUST_BACKTRACE: 1 14 | CARGO_TERM_COLOR: always 15 | CLICOLOR: 1 16 | 17 | concurrency: 18 | group: "${{ github.workflow }}-${{ github.ref }}" 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | ci: 23 | permissions: 24 | contents: none 25 | name: CI 26 | needs: [test, msrv, lockfile, docs, rustfmt, clippy, minimal-versions] 27 | runs-on: ubuntu-latest 28 | if: "always()" 29 | steps: 30 | - name: Failed 31 | run: exit 1 32 | if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')" 33 | test: 34 | name: Test 35 | strategy: 36 | matrix: 37 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 38 | rust: ["stable"] 39 | continue-on-error: ${{ matrix.rust != 'stable' }} 40 | runs-on: ${{ matrix.os }} 41 | env: 42 | # Reduce amount of data cached 43 | CARGO_PROFILE_DEV_DEBUG: line-tables-only 44 | steps: 45 | - name: Checkout repository 46 | uses: actions/checkout@v6 47 | - name: Install Rust 48 | uses: dtolnay/rust-toolchain@stable 49 | with: 50 | toolchain: ${{ matrix.rust }} 51 | - uses: Swatinem/rust-cache@v2 52 | - uses: taiki-e/install-action@cargo-hack 53 | - name: Build 54 | run: cargo test --workspace --no-run 55 | - name: Test 56 | run: cargo hack test --each-feature --workspace --exclude-features strict_unstable 57 | msrv: 58 | name: "Check MSRV" 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Checkout repository 62 | uses: actions/checkout@v6 63 | - name: Install Rust 64 | uses: dtolnay/rust-toolchain@stable 65 | with: 66 | toolchain: stable 67 | - uses: Swatinem/rust-cache@v2 68 | - uses: taiki-e/install-action@cargo-hack 69 | - name: Default features 70 | run: cargo hack check --each-feature --locked --rust-version --ignore-private --workspace --all-targets --keep-going --exclude-features strict_unstable 71 | minimal-versions: 72 | name: Minimal versions 73 | runs-on: ubuntu-latest 74 | steps: 75 | - name: Checkout repository 76 | uses: actions/checkout@v6 77 | - name: Install stable Rust 78 | uses: dtolnay/rust-toolchain@stable 79 | with: 80 | toolchain: stable 81 | - name: Install nightly Rust 82 | uses: dtolnay/rust-toolchain@stable 83 | with: 84 | toolchain: nightly 85 | - name: Downgrade dependencies to minimal versions 86 | run: cargo +nightly generate-lockfile -Z minimal-versions 87 | - name: Compile with minimal versions 88 | run: cargo +stable check --workspace --all-features --locked --keep-going 89 | lockfile: 90 | runs-on: ubuntu-latest 91 | steps: 92 | - name: Checkout repository 93 | uses: actions/checkout@v6 94 | - name: Install Rust 95 | uses: dtolnay/rust-toolchain@stable 96 | with: 97 | toolchain: stable 98 | - uses: Swatinem/rust-cache@v2 99 | - name: "Is lockfile updated?" 100 | run: cargo update --workspace --locked 101 | docs: 102 | name: Docs 103 | runs-on: ubuntu-latest 104 | steps: 105 | - name: Checkout repository 106 | uses: actions/checkout@v6 107 | - name: Install Rust 108 | uses: dtolnay/rust-toolchain@stable 109 | with: 110 | toolchain: "1.92" # STABLE 111 | - uses: Swatinem/rust-cache@v2 112 | - name: Check documentation 113 | env: 114 | RUSTDOCFLAGS: -D warnings 115 | run: cargo doc --workspace --all-features --no-deps --document-private-items --keep-going 116 | rustfmt: 117 | name: rustfmt 118 | runs-on: ubuntu-latest 119 | steps: 120 | - name: Checkout repository 121 | uses: actions/checkout@v6 122 | - name: Install Rust 123 | uses: dtolnay/rust-toolchain@stable 124 | with: 125 | toolchain: "1.92" # STABLE 126 | components: rustfmt 127 | - uses: Swatinem/rust-cache@v2 128 | - name: Check formatting 129 | run: cargo fmt --all -- --check 130 | clippy: 131 | name: clippy 132 | runs-on: ubuntu-latest 133 | permissions: 134 | security-events: write # to upload sarif results 135 | steps: 136 | - name: Checkout repository 137 | uses: actions/checkout@v6 138 | - name: Install Rust 139 | uses: dtolnay/rust-toolchain@stable 140 | with: 141 | toolchain: "1.92" # STABLE 142 | components: clippy 143 | - uses: Swatinem/rust-cache@v2 144 | - name: Install SARIF tools 145 | run: cargo install clippy-sarif --locked 146 | - name: Install SARIF tools 147 | run: cargo install sarif-fmt --locked 148 | - name: Check 149 | run: > 150 | cargo clippy --workspace --all-features --all-targets --message-format=json 151 | | clippy-sarif 152 | | tee clippy-results.sarif 153 | | sarif-fmt 154 | continue-on-error: true 155 | - name: Upload 156 | uses: github/codeql-action/upload-sarif@v4 157 | with: 158 | sarif_file: clippy-results.sarif 159 | wait-for-processing: true 160 | - name: Report status 161 | run: cargo clippy --workspace --all-features --all-targets --keep-going -- -D warnings --allow deprecated 162 | coverage: 163 | name: Coverage 164 | runs-on: ubuntu-latest 165 | steps: 166 | - name: Checkout repository 167 | uses: actions/checkout@v6 168 | - name: Install Rust 169 | uses: dtolnay/rust-toolchain@stable 170 | with: 171 | toolchain: stable 172 | - uses: Swatinem/rust-cache@v2 173 | - name: Install cargo-tarpaulin 174 | run: cargo install cargo-tarpaulin 175 | - name: Gather coverage 176 | run: cargo tarpaulin --output-dir coverage --out lcov 177 | - name: Publish to Coveralls 178 | uses: coverallsapp/github-action@master 179 | with: 180 | github-token: ${{ secrets.GITHUB_TOKEN }} 181 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use std::path; 2 | use std::process; 3 | 4 | use crate::error::{CargoError, CargoResult}; 5 | use crate::format; 6 | use crate::msg::CommandMessages; 7 | 8 | /// The `test` subcommand (emulated). 9 | /// 10 | /// Created via [`CargoBuild::run_tests`]. 11 | /// 12 | /// Benefits over spawning `cargo test`: 13 | /// - Able to cache binary path, avoiding cargo overhead. 14 | /// - Independent of CWD. 15 | /// - stdout/stderr are clean of `cargo test` output. 16 | /// 17 | /// Required feature: `test_unstable` since the format parsed is unstable. 18 | /// 19 | /// Relevant features 20 | /// - `print` for logged output to be printed instead, generally for test writing. 21 | /// 22 | /// # Example 23 | /// 24 | /// ```rust 25 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 26 | /// let run = escargot::CargoBuild::new() 27 | /// .test("test") 28 | /// .manifest_path("tests/testsuite/fixtures/test/Cargo.toml") 29 | /// .target_dir(target_dir.path()) 30 | /// .run_tests().unwrap() 31 | /// .next().unwrap().unwrap(); 32 | /// println!("artifact={}", run.path().display()); 33 | /// ``` 34 | /// 35 | /// [`CargoBuild::run_tests`]: crate::CargoBuild::run_tests() 36 | #[derive(Debug)] 37 | pub struct CargoTest { 38 | bin_path: path::PathBuf, 39 | kind: String, 40 | name: String, 41 | } 42 | 43 | impl CargoTest { 44 | pub(crate) fn with_messages( 45 | msgs: CommandMessages, 46 | ) -> impl Iterator> { 47 | extract_binary_paths(msgs) 48 | } 49 | 50 | /// The `name` of test 51 | /// 52 | /// Used to offer filtering or displays. 53 | /// 54 | /// # Example 55 | /// 56 | /// ```rust 57 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 58 | /// let run: Result, _> = escargot::CargoBuild::new() 59 | /// .tests() 60 | /// .current_release() 61 | /// .current_target() 62 | /// .manifest_path("tests/testsuite/fixtures/test/Cargo.toml") 63 | /// .target_dir(target_dir.path()) 64 | /// .run_tests() 65 | /// .unwrap() 66 | /// .collect(); 67 | /// let run = run.unwrap(); 68 | /// let mut names: Vec<_> = run.iter().map(|r| r.name()).collect(); 69 | /// names.sort_unstable(); 70 | /// assert_eq!(names, ["test", "test_fixture", "test_fixture"]); 71 | /// ``` 72 | pub fn name(&self) -> &str { 73 | self.name.as_str() 74 | } 75 | 76 | /// The `kind` of test 77 | /// 78 | /// Used to distinguish between integration tests (`test`) and unit tests (`bin`, `lib`). 79 | /// 80 | /// # Example 81 | /// 82 | /// ```rust 83 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 84 | /// let run: Result, _> = escargot::CargoBuild::new() 85 | /// .tests() 86 | /// .current_release() 87 | /// .current_target() 88 | /// .manifest_path("tests/testsuite/fixtures/test/Cargo.toml") 89 | /// .target_dir(target_dir.path()) 90 | /// .run_tests() 91 | /// .unwrap() 92 | /// .collect(); 93 | /// let run = run.unwrap(); 94 | /// let mut kinds: Vec<_> = run.iter().map(|r| r.kind()).collect(); 95 | /// kinds.sort_unstable(); 96 | /// assert_eq!(kinds, ["bin", "lib", "test"]); 97 | /// ``` 98 | pub fn kind(&self) -> &str { 99 | self.kind.as_str() 100 | } 101 | 102 | /// Path to the specified binary. 103 | /// 104 | /// This is to support alternative ways of launching the binary besides [`Command`]. 105 | /// 106 | /// # Example 107 | /// 108 | /// ```rust 109 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 110 | /// let run: Vec<_> = escargot::CargoBuild::new() 111 | /// .tests() 112 | /// .current_release() 113 | /// .current_target() 114 | /// .manifest_path("tests/testsuite/fixtures/test/Cargo.toml") 115 | /// .target_dir(target_dir.path()) 116 | /// .run_tests() 117 | /// .unwrap() 118 | /// .collect(); 119 | /// assert_eq!(run.len(), 3); 120 | /// ``` 121 | /// 122 | /// [`Command`]: std::process::Command 123 | pub fn path(&self) -> &path::Path { 124 | &self.bin_path 125 | } 126 | 127 | /// Run the build artifact. 128 | pub fn command(&self) -> process::Command { 129 | let mut cmd = process::Command::new(self.path()); 130 | cmd.arg("-Z").arg("unstable-options").arg("--format=json"); 131 | cmd 132 | } 133 | 134 | /// Run the configured test, returning test events. 135 | pub fn exec(&self) -> CargoResult { 136 | CommandMessages::with_command(self.command()) 137 | } 138 | } 139 | 140 | fn extract_bin(msg: &format::Message<'_>) -> Option { 141 | match msg { 142 | format::Message::CompilerArtifact(art) => { 143 | if art.profile.test { 144 | let bin_path = art 145 | .filenames 146 | .first() 147 | .expect("files must exist") 148 | .to_path_buf(); 149 | let kind = art 150 | .target 151 | .kind 152 | .first() 153 | .expect("kind must exist") 154 | .as_ref() 155 | .to_owned(); 156 | let name = art.target.name.as_ref().to_owned(); 157 | Some(CargoTest { 158 | bin_path, 159 | kind, 160 | name, 161 | }) 162 | } else { 163 | None 164 | } 165 | } 166 | _ => None, 167 | } 168 | } 169 | 170 | fn transpose(r: Result, E>) -> Option> { 171 | match r { 172 | Ok(Some(x)) => Some(Ok(x)), 173 | Ok(None) => None, 174 | Err(e) => Some(Err(e)), 175 | } 176 | } 177 | 178 | fn extract_binary_paths( 179 | msgs: CommandMessages, 180 | ) -> impl Iterator> { 181 | msgs.filter_map(move |m| { 182 | let m = m.and_then(|m| { 183 | let m = m.decode()?; 184 | format::log_message(&m); 185 | let p = extract_bin(&m); 186 | Ok(p) 187 | }); 188 | transpose(m) 189 | }) 190 | } 191 | -------------------------------------------------------------------------------- /src/format/diagnostic.rs: -------------------------------------------------------------------------------- 1 | //! This module contains `Diagnostic` and the types/functions it uses for deserialization. 2 | 3 | use std::borrow; 4 | use std::path; 5 | 6 | type CowPath<'a> = borrow::Cow<'a, path::Path>; 7 | type CowStr<'a> = borrow::Cow<'a, str>; 8 | 9 | /// The error code associated to this diagnostic. 10 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 11 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 12 | #[non_exhaustive] 13 | pub struct DiagnosticCode<'a> { 14 | /// The code itself. 15 | #[serde(borrow)] 16 | pub code: CowStr<'a>, 17 | /// An explanation for the code 18 | #[serde(borrow)] 19 | pub explanation: Option>, 20 | } 21 | 22 | /// A line of code associated with the Diagnostic 23 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 24 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 25 | #[non_exhaustive] 26 | pub struct DiagnosticSpanLine<'a> { 27 | /// The line of code associated with the error 28 | #[serde(borrow)] 29 | pub text: CowStr<'a>, 30 | /// Start of the section of the line to highlight. 1-based, character offset in self.text 31 | pub highlight_start: usize, 32 | /// End of the section of the line to highlight. 1-based, character offset in self.text 33 | pub highlight_end: usize, 34 | } 35 | 36 | /// Macro expansion information associated with a diagnostic. 37 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 38 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 39 | #[non_exhaustive] 40 | pub struct DiagnosticSpanMacroExpansion<'a> { 41 | /// span where macro was applied to generate this code; note that 42 | /// this may itself derive from a macro (if 43 | /// `span.expansion.is_some()`) 44 | #[serde(borrow)] 45 | pub span: DiagnosticSpan<'a>, 46 | 47 | /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") 48 | #[serde(borrow)] 49 | pub macro_decl_name: CowStr<'a>, 50 | 51 | /// span where macro was defined (if known) 52 | #[serde(borrow)] 53 | pub def_site_span: Option>, 54 | } 55 | 56 | /// A section of the source code associated with a Diagnostic 57 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 58 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 59 | #[non_exhaustive] 60 | pub struct DiagnosticSpan<'a> { 61 | /// The file name this diagnostic comes from. 62 | #[serde(borrow)] 63 | pub file_name: CowPath<'a>, 64 | /// The byte offset in the file where this diagnostic starts from. 65 | pub byte_start: u32, 66 | /// The byte offset in the file where this diagnostic ends. 67 | pub byte_end: u32, 68 | /// 1-based. The line in the file. 69 | pub line_start: usize, 70 | /// 1-based. The line in the file. 71 | pub line_end: usize, 72 | /// 1-based, character offset. 73 | pub column_start: usize, 74 | /// 1-based, character offset. 75 | pub column_end: usize, 76 | /// Is this a "primary" span -- meaning the point, or one of the points, 77 | /// where the error occurred? 78 | pub is_primary: bool, 79 | /// Source text from the start of `line_start` to the end of `line_end`. 80 | #[serde(borrow)] 81 | pub text: Vec>, 82 | /// Label that should be placed at this location (if any) 83 | #[serde(borrow)] 84 | pub label: Option>, 85 | /// If we are suggesting a replacement, this will contain text 86 | /// that should be sliced in atop this span. 87 | #[serde(borrow)] 88 | pub suggested_replacement: Option>, 89 | /// If the suggestion is approximate 90 | pub suggestion_applicability: Option, 91 | /// Macro invocations that created the code at this span, if any. 92 | #[serde(borrow)] 93 | pub expansion: Option>>, 94 | } 95 | 96 | /// Whether a suggestion can be safely applied. 97 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 98 | pub enum Applicability { 99 | /// The suggested replacement can be applied automatically safely 100 | MachineApplicable, 101 | /// The suggested replacement has placeholders that will need to be manually 102 | /// replaced. 103 | HasPlaceholders, 104 | /// The suggested replacement may be incorrect in some circumstances. Needs 105 | /// human review. 106 | MaybeIncorrect, 107 | /// The suggested replacement will probably not work. 108 | Unspecified, 109 | #[cfg(not(feature = "strict_unstable"))] 110 | #[doc(hidden)] 111 | #[serde(other)] 112 | Unknown, 113 | } 114 | 115 | /// A diagnostic message generated by rustc 116 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 117 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 118 | #[non_exhaustive] 119 | pub struct Diagnostic<'a> { 120 | /// Can be used to distinguish the different formats. 121 | /// 122 | /// When parsing, care should be taken to be forwards-compatible with future changes to the format. 123 | #[serde(borrow, rename = "$message_type")] 124 | pub message_type: Option>, 125 | /// The error message of this diagnostic. 126 | #[serde(borrow)] 127 | pub message: CowStr<'a>, 128 | /// The associated error code for this diagnostic 129 | #[serde(borrow)] 130 | pub code: Option>, 131 | /// The severity of the diagnostic. 132 | pub level: DiagnosticLevel, 133 | /// A list of source code spans this diagnostic is associated with. 134 | #[serde(borrow)] 135 | pub spans: Vec>, 136 | /// Associated diagnostic messages. 137 | #[serde(borrow)] 138 | pub children: Vec>, 139 | /// The message as rustc would render it 140 | #[serde(borrow)] 141 | pub rendered: Option>, 142 | } 143 | 144 | /// The diagnostic level 145 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 146 | #[serde(rename_all = "lowercase")] 147 | pub enum DiagnosticLevel { 148 | /// Internal compiler error 149 | #[serde(rename = "error: internal compiler error")] 150 | Ice, 151 | /// Error 152 | Error, 153 | /// Warning 154 | Warning, 155 | /// Note 156 | Note, 157 | /// Help 158 | Help, 159 | #[cfg(not(feature = "strict_unstable"))] 160 | #[doc(hidden)] 161 | #[serde(other)] 162 | Unknown, 163 | } 164 | -------------------------------------------------------------------------------- /src/run.rs: -------------------------------------------------------------------------------- 1 | use std::path; 2 | use std::process; 3 | 4 | use crate::error::{CargoError, CargoResult, ErrorKind}; 5 | use crate::format; 6 | use crate::msg::CommandMessages; 7 | 8 | /// The `run` subcommand (emulated). 9 | /// 10 | /// Created via [`CargoBuild::run`][crate::CargoBuild::run]. 11 | /// 12 | /// Benefits over spawning `cargo run`: 13 | /// - Able to cache binary path, avoiding cargo overhead. 14 | /// - Independent of CWD. 15 | /// - stdout/stderr are clean of `cargo run` output. 16 | /// 17 | /// Relevant features 18 | /// - `print` for logged output to be printed instead, generally for test writing. 19 | /// 20 | /// # Example 21 | /// 22 | /// To create a [`CargoRun`]: 23 | /// ```rust 24 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 25 | /// let run = escargot::CargoBuild::new() 26 | /// .bin("bin") 27 | /// .current_release() 28 | /// .current_target() 29 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 30 | /// .target_dir(target_dir.path()) 31 | /// .run() 32 | /// .unwrap(); 33 | /// println!("artifact={}", run.path().display()); 34 | /// ``` 35 | /// See [`CargoRun::path`] for how to then run the newly compiled 36 | /// program. 37 | #[derive(Debug)] 38 | pub struct CargoRun { 39 | bin_path: path::PathBuf, 40 | } 41 | 42 | impl CargoRun { 43 | pub(crate) fn from_message( 44 | msgs: CommandMessages, 45 | is_bin: bool, 46 | is_example: bool, 47 | ) -> CargoResult { 48 | let kind = match (is_bin, is_example) { 49 | (true, true) => { 50 | return Err(CargoError::new(ErrorKind::CommandFailed) 51 | .set_context("Ambiguous which binary is intended, multiple selected")); 52 | } 53 | (false, true) => "example", 54 | _ => "bin", 55 | }; 56 | let bin_path = extract_binary_path(msgs, kind)?; 57 | Ok(Self { bin_path }) 58 | } 59 | 60 | /// Path to the specified binary. 61 | /// 62 | /// This is to support alternative ways of launching the binary besides [`Command`]. 63 | /// 64 | /// # Example 65 | /// 66 | /// ```rust 67 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 68 | /// let run = escargot::CargoBuild::new() 69 | /// .bin("bin") 70 | /// .current_release() 71 | /// .current_target() 72 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 73 | /// .target_dir(target_dir.path()) 74 | /// .run() 75 | /// .unwrap(); 76 | /// println!("artifact={}", run.path().display()); 77 | /// ``` 78 | /// or 79 | /// ```rust,no_run 80 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 81 | /// let run = escargot::CargoBuild::new() 82 | /// .example("example_fixture") 83 | /// .current_release() 84 | /// .current_target() 85 | /// .manifest_path("tests/testsuite/fixtures/example/Cargo.toml") 86 | /// .target_dir(target_dir.path()) 87 | /// .run() 88 | /// .unwrap(); 89 | /// println!("artifact={}", run.path().display()); 90 | /// ``` 91 | /// 92 | /// [`Command`]: std::process::Command 93 | pub fn path(&self) -> &path::Path { 94 | &self.bin_path 95 | } 96 | 97 | /// Run the build artifact. 98 | /// 99 | /// # Example 100 | /// 101 | /// ```rust,no_run 102 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 103 | /// let run = escargot::CargoBuild::new() 104 | /// .bin("bin") 105 | /// .current_release() 106 | /// .current_target() 107 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 108 | /// .target_dir(target_dir.path()) 109 | /// .run() 110 | /// .unwrap() 111 | /// .command() 112 | /// .arg("--help") 113 | /// .status() 114 | /// .unwrap(); 115 | /// ``` 116 | /// or 117 | /// ```rust 118 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 119 | /// let run = escargot::CargoBuild::new() 120 | /// .example("example_fixture") 121 | /// .current_release() 122 | /// .current_target() 123 | /// .manifest_path("tests/testsuite/fixtures/example/Cargo.toml") 124 | /// .target_dir(target_dir.path()) 125 | /// .run() 126 | /// .unwrap() 127 | /// .command() 128 | /// .arg("--help") 129 | /// .status() 130 | /// .unwrap(); 131 | /// ``` 132 | pub fn command(&self) -> process::Command { 133 | process::Command::new(self.path()) 134 | } 135 | } 136 | 137 | fn extract_bin<'a>(msg: &'a format::Message<'_>, desired_kind: &str) -> Option<&'a path::Path> { 138 | match msg { 139 | format::Message::CompilerArtifact(art) => { 140 | if !art.profile.test 141 | && art.target.crate_types == ["bin"] 142 | && art.target.kind == [desired_kind] 143 | { 144 | Some(art.filenames.first().expect("files must exist")) 145 | } else { 146 | None 147 | } 148 | } 149 | _ => None, 150 | } 151 | } 152 | 153 | fn transpose(r: Result, E>) -> Option> { 154 | match r { 155 | Ok(Some(x)) => Some(Ok(x)), 156 | Ok(None) => None, 157 | Err(e) => Some(Err(e)), 158 | } 159 | } 160 | 161 | fn extract_binary_paths( 162 | msgs: CommandMessages, 163 | kind: &'static str, 164 | ) -> impl Iterator> { 165 | msgs.filter_map(move |m| { 166 | let m = m.and_then(|m| { 167 | let m = m.decode()?; 168 | format::log_message(&m); 169 | let p = extract_bin(&m, kind).map(|p| p.to_path_buf()); 170 | Ok(p) 171 | }); 172 | transpose(m) 173 | }) 174 | } 175 | 176 | fn extract_binary_path( 177 | msgs: CommandMessages, 178 | kind: &'static str, 179 | ) -> Result { 180 | let bins: Result, CargoError> = extract_binary_paths(msgs, kind).collect(); 181 | let bins = bins?; 182 | if bins.is_empty() { 183 | return Err(CargoError::new(ErrorKind::CommandFailed).set_context("No binaries in crate")); 184 | } else if bins.len() != 1 { 185 | return Err(CargoError::new(ErrorKind::CommandFailed) 186 | .set_context(std::format!("Ambiguous which binary is intended: {bins:?}"))); 187 | } 188 | Ok(bins.into_iter().next().expect("already validated")) 189 | } 190 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](https://semver.org/). 6 | 7 | 8 | ## [Unreleased] - ReleaseDate 9 | 10 | ## [0.5.15] - 2025-08-11 11 | 12 | ### Features 13 | 14 | - format: expose build success flag 15 | 16 | ## [0.5.14] - 2025-04-15 17 | 18 | ### Features 19 | 20 | - Add `into_command()` functions 21 | - Add `Debug` impls 22 | 23 | ## [0.5.13] - 2024-10-29 24 | 25 | ### Fixes 26 | 27 | - Add support for new `debuginfo` values 28 | 29 | ## [0.5.12] - 2024-07-25 30 | 31 | ## [0.5.11] - 2024-06-06 32 | 33 | ### Compatibility 34 | 35 | - Dropped MSRV to 1.65 36 | 37 | ## [0.5.10] - 2024-02-25 38 | 39 | ### Features 40 | 41 | - Add `CargoBuild::bins` 42 | 43 | ## [0.5.9] - 2024-02-09 44 | 45 | ### Compatibility 46 | 47 | - Bumped MSRV to 1.72 48 | - Breaking change with *(test_unstable)* feature 49 | 50 | ### Features 51 | 52 | - Add support for `Diagnostic::message_type` 53 | 54 | ### Fixes 55 | 56 | - *(test_unstable)* Fixed spelling of `Failure` 57 | 58 | ## [0.5.8] - 2023-08-02 59 | 60 | ### Features 61 | 62 | - `CargoBuild::env_remove` support 63 | 64 | ## [0.5.7] - 2021-12-15 65 | 66 | #### Fixes 67 | 68 | - Update version requirements to minimum required 69 | 70 | ## [0.5.6] - 2021-11-08 71 | 72 | #### Fixes 73 | 74 | - Get `print` feature compiling 75 | - Keep stdout clean with `print` by printing to stderr 76 | 77 | ## [0.5.5] - 2021-11-08 78 | 79 | #### Features 80 | 81 | - Add some missing traits 82 | 83 | ## [0.5.4] - 2021-11-08 84 | 85 | #### Features 86 | 87 | - Add `CargoBuild::args` 88 | 89 | ## [0.5.3] - 2021-11-08 90 | 91 | #### Features 92 | 93 | - Add `CargoBuild::examples` 94 | - Add extra message fields 95 | 96 | ## [0.5.2] - 2021-03-27 97 | 98 | #### Features 99 | 100 | * New `env` method for passing environment variables to the `cargo` calls. 101 | 102 | ## [0.5.1] - 2021-02-01 103 | 104 | #### Features 105 | 106 | * Support `build-finished` message 107 | 108 | ## [0.5.0] - 2019-04-08 109 | 110 | #### Features 111 | 112 | * Expose creating CommandMessages ([49d8767e](https://github.com/crate-ci/escargot/commit/49d8767e0122edebd0078e1ea1781a2eaf727ee5)) 113 | * **tests:** Unstable support for running tests ([31293d79](https://github.com/crate-ci/escargot/commit/31293d796e2587cbc31bfff87af0fa4b22575de0)) 114 | 115 | #### Breaking Changes 116 | 117 | * Rename MessageIter -> CommandMessages ([f4742d8e](https://github.com/crate-ci/escargot/commit/f4742d8e1eb6b2bc242f24a5f0ceb0f9fb517070), breaks [#](https://github.com/crate-ci/escargot/issues/)) 118 | 119 | #### Bug Fixes 120 | 121 | * Gracefully handle upcoming cargo features ([a00f2408](https://github.com/crate-ci/escargot/commit/a00f240831ddc71b1846005df4917111e3690a82)) 122 | 123 | ## [0.4.0] - 2018-12-31 124 | 125 | #### Features 126 | 127 | * Serialization formats ([45f6e17a](https://github.com/crate-ci/escargot/commit/45f6e17a857baae7239c1a85ef6f7ccfa4baf35b)) 128 | * Stream messages ([343027d4](https://github.com/crate-ci/escargot/commit/343027d40cdeb94b820ecb0a8fbb145fcf3f19c7), breaks [#](https://github.com/crate-ci/escargot/issues/)) 129 | * Support CARGO env variable ([89021bec](https://github.com/crate-ci/escargot/commit/89021bec77cbef36a18e84917515b6ca3ebcc889), closes [#12](https://github.com/crate-ci/escargot/issues/12)) 130 | * **build:** 131 | * Xargo support ([82c9c845](https://github.com/crate-ci/escargot/commit/82c9c845fe30e07bf29e1da6e5d2e884b3c5cc2b), closes [#16](https://github.com/crate-ci/escargot/issues/16)) 132 | * Support features ([e3575d37](https://github.com/crate-ci/escargot/commit/e3575d37399708080344de41a1344e52e97a9368), closes [#10](https://github.com/crate-ci/escargot/issues/10)) 133 | * target-dir support ([d7885f40](https://github.com/crate-ci/escargot/commit/d7885f40e498bf7653a89be51460978496161f76), closes [#17](https://github.com/crate-ci/escargot/issues/17)) 134 | 135 | #### Bug Fixes 136 | 137 | * Remove cargo-test support ([7aecd540](https://github.com/crate-ci/escargot/commit/7aecd5403f8c614aff685ed27b3305b5648c4dd6)) 138 | 139 | #### Breaking Changes 140 | 141 | * Nest lesser details ([e0133dbb](https://github.com/crate-ci/escargot/commit/e0133dbb1c01c5ac983b9376ae1c8e71dacaa42e), breaks [#](https://github.com/crate-ci/escargot/issues/)) 142 | * Stream messages ([343027d4](https://github.com/crate-ci/escargot/commit/343027d40cdeb94b820ecb0a8fbb145fcf3f19c7), breaks [#](https://github.com/crate-ci/escargot/issues/)) 143 | * MessageItr -> MessageIter ([07c4b257](https://github.com/crate-ci/escargot/commit/07c4b25740898b75af7b5d291be04ac737c5cd6c), closes [#9](https://github.com/crate-ci/escargot/issues/9), breaks [#](https://github.com/crate-ci/escargot/issues/)) 144 | 145 | ## [0.3.1] - 2018-08-07 146 | 147 | #### Bug Fixes 148 | 149 | * **run:** Example support ([99029550](https://github.com/crate-ci/escargot/commit/990295504ebd195f330e7b3e19b01e86a7b401f7), closes [#7](https://github.com/crate-ci/escargot/issues/7)) 150 | 151 | ## [0.3.0] - 2018-08-05 152 | 153 | #### Features 154 | 155 | * Emulate run subcommand ([df4607a8](https://github.com/crate-ci/escargot/commit/df4607a8170a27d746e7c259e05c478a02d570e5)) 156 | 157 | #### Breaking Changes 158 | 159 | * `current_target` spelling is corrected ([df4607a8](https://github.com/crate-ci/escargot/commit/df4607a8170a27d746e7c259e05c478a02d570e5)) 160 | * Removed parts of `CargoError` ([df4607a8](https://github.com/crate-ci/escargot/commit/df4607a8170a27d746e7c259e05c478a02d570e5)) 161 | 162 | ## [0.2.0] - 2018-06-27 163 | 164 | #### Breaking Changes 165 | 166 | * Define concrete CargoError ([445cb391](https://github.com/crate-ci/escargot/commit/445cb39156b63ce1894d40b31805273d995e185c), breaks [#](https://github.com/crate-ci/escargot/issues/)) 167 | 168 | 169 | [Unreleased]: https://github.com/crate-ci/escargot/compare/v0.5.15...HEAD 170 | [0.5.15]: https://github.com/crate-ci/escargot/compare/v0.5.14...v0.5.15 171 | [0.5.14]: https://github.com/crate-ci/escargot/compare/v0.5.13...v0.5.14 172 | [0.5.13]: https://github.com/crate-ci/escargot/compare/v0.5.12...v0.5.13 173 | [0.5.12]: https://github.com/crate-ci/escargot/compare/v0.5.11...v0.5.12 174 | [0.5.11]: https://github.com/crate-ci/escargot/compare/v0.5.10...v0.5.11 175 | [0.5.10]: https://github.com/crate-ci/escargot/compare/v0.5.9...v0.5.10 176 | [0.5.9]: https://github.com/crate-ci/escargot/compare/v0.5.8...v0.5.9 177 | [0.5.8]: https://github.com/crate-ci/escargot/compare/v0.5.7...v0.5.8 178 | [0.5.7]: https://github.com/crate-ci/escargot/compare/v0.5.6...v0.5.7 179 | [0.5.6]: https://github.com/crate-ci/escargot/compare/v0.5.5...v0.5.6 180 | [0.5.5]: https://github.com/crate-ci/escargot/compare/v0.5.4...v0.5.5 181 | [0.5.4]: https://github.com/crate-ci/escargot/compare/v0.5.3...v0.5.4 182 | [0.5.3]: https://github.com/crate-ci/escargot/compare/v0.5.2...v0.5.3 183 | [0.5.2]: https://github.com/crate-ci/escargot/compare/v0.5.1...v0.5.2 184 | [0.5.1]: https://github.com/crate-ci/escargot/compare/v0.5.0...v0.5.1 185 | [0.5.0]: https://github.com/crate-ci/escargot/compare/v0.4.0...v0.5.0 186 | [0.4.0]: https://github.com/crate-ci/escargot/compare/v0.3.1...v0.4.0 187 | [0.3.1]: https://github.com/crate-ci/escargot/compare/v0.3.0...v0.3.1 188 | [0.3.0]: https://github.com/crate-ci/escargot/compare/v0.2.0...v0.3.0 189 | -------------------------------------------------------------------------------- /src/format/test.rs: -------------------------------------------------------------------------------- 1 | //! Test runner emitted events. 2 | //! 3 | //! Required feature: `test_unstable` since the format parsed is unstable. 4 | 5 | use serde::Deserialize; 6 | 7 | // See https://github.com/rust-lang/rust/tree/master/src/libtest/formatters/json.rs 8 | 9 | /// Test-runner event. 10 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 11 | #[serde(rename_all = "snake_case")] 12 | #[serde(tag = "type")] 13 | pub enum Event { 14 | /// Suite event. 15 | Suite(Suite), 16 | /// Test case event. 17 | Test(Test), 18 | /// Benchmark event. 19 | Bench(Bench), 20 | #[cfg(not(feature = "strict_unstable"))] 21 | #[doc(hidden)] 22 | #[serde(other)] 23 | Unknown, 24 | } 25 | 26 | /// Suite event. 27 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 28 | #[serde(rename_all = "snake_case")] 29 | #[serde(tag = "event")] 30 | pub enum Suite { 31 | /// Suite-started event. 32 | Started(SuiteStarted), 33 | /// Suite-finished successfully event. 34 | Ok(SuiteOk), 35 | /// Suite-finished with failure event. 36 | Failed(SuiteFailed), 37 | #[cfg(not(feature = "strict_unstable"))] 38 | #[doc(hidden)] 39 | #[serde(other)] 40 | Unknown, 41 | } 42 | 43 | /// Suite-started event. 44 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 45 | #[non_exhaustive] 46 | pub struct SuiteStarted { 47 | /// Number of test cases in the suite. 48 | pub test_count: usize, 49 | } 50 | 51 | /// Suite-finished successfully event. 52 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 53 | #[non_exhaustive] 54 | pub struct SuiteOk { 55 | /// Cases that passed. 56 | pub passed: usize, 57 | /// Cases that failed. 58 | pub failed: usize, 59 | /// Cases that were allowed to fail. 60 | pub allowed_fail: usize, 61 | /// Ignored cases. 62 | pub ignored: usize, 63 | /// Benchmarks 64 | pub measured: usize, 65 | /// Cases filtered out by caller. 66 | pub filtered_out: usize, 67 | } 68 | 69 | /// Suite-finished with failure event. 70 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 71 | #[non_exhaustive] 72 | pub struct SuiteFailed { 73 | /// Cases that passed. 74 | pub passed: usize, 75 | /// Cases that failed. 76 | pub failed: usize, 77 | /// Cases that were allowed to fail. 78 | pub allowed_fail: usize, 79 | /// Ignored cases. 80 | pub ignored: usize, 81 | /// Benchmarks 82 | pub measured: usize, 83 | /// Cases filtered out by caller. 84 | pub filtered_out: usize, 85 | } 86 | 87 | /// Test case event. 88 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 89 | #[serde(rename_all = "snake_case")] 90 | #[serde(tag = "event")] 91 | pub enum Test { 92 | /// Case-started event. 93 | Started(TestStarted), 94 | /// Case-finished successfully event. 95 | Ok(TestOk), 96 | /// Case-finished with failure event. 97 | Failed(TestFailed), 98 | /// Case-ignored event. 99 | Ignored(TestIgnored), 100 | /// Case-allowed-failure event. 101 | AllowedFailure(TestAllowedFailure), 102 | /// Case-timeout event. 103 | Timeout(TestTimeout), 104 | #[cfg(not(feature = "strict_unstable"))] 105 | #[doc(hidden)] 106 | #[serde(other)] 107 | Unknown, 108 | } 109 | 110 | /// Case-started event. 111 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 112 | #[non_exhaustive] 113 | pub struct TestStarted { 114 | /// Test case name. 115 | pub name: String, 116 | } 117 | 118 | /// Case-finished successfully event. 119 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 120 | #[non_exhaustive] 121 | pub struct TestOk { 122 | /// Test case name. 123 | pub name: String, 124 | } 125 | 126 | /// Case-finished with failure event. 127 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 128 | #[non_exhaustive] 129 | pub struct TestFailed { 130 | /// Test case name. 131 | pub name: String, 132 | /// Test's stdout 133 | pub stdout: Option, 134 | /// Test failure mssage 135 | pub message: Option, 136 | } 137 | 138 | /// Case-ignored event. 139 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 140 | #[non_exhaustive] 141 | pub struct TestIgnored { 142 | /// Test case name. 143 | pub name: String, 144 | } 145 | 146 | /// Case-allowed-failure event. 147 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 148 | #[non_exhaustive] 149 | pub struct TestAllowedFailure { 150 | /// Test case name. 151 | pub name: String, 152 | } 153 | 154 | /// Case-timeout event. 155 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 156 | #[non_exhaustive] 157 | pub struct TestTimeout { 158 | /// Test case name. 159 | pub name: String, 160 | } 161 | 162 | /// Benchmark event. 163 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 164 | #[non_exhaustive] 165 | pub struct Bench { 166 | /// Benchmark name. 167 | pub name: String, 168 | /// Median performance. 169 | pub median: usize, 170 | /// Deviation from median. 171 | pub deviation: usize, 172 | /// Mb/s 173 | pub mib_per_second: Option, 174 | } 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | use super::*; 179 | 180 | #[test] 181 | fn suite_started() { 182 | let input = r#"{ "type": "suite", "event": "started", "test_count": 10 }"#; 183 | let _data: Event = serde_json::from_str(input).unwrap(); 184 | } 185 | 186 | #[test] 187 | fn suite_ok() { 188 | let input = "{ \"type\": \"suite\", \ 189 | \"event\": \"ok\", \ 190 | \"passed\": 6, \ 191 | \"failed\": 5, \ 192 | \"allowed_fail\": 4, \ 193 | \"ignored\": 3, \ 194 | \"measured\": 2, \ 195 | \"filtered_out\": 1 }"; 196 | let _data: Event = serde_json::from_str(input).unwrap(); 197 | } 198 | 199 | #[test] 200 | fn suite_failed() { 201 | let input = "{ \"type\": \"suite\", \ 202 | \"event\": \"failed\", \ 203 | \"passed\": 6, \ 204 | \"failed\": 5, \ 205 | \"allowed_fail\": 4, \ 206 | \"ignored\": 3, \ 207 | \"measured\": 2, \ 208 | \"filtered_out\": 1 }"; 209 | let _data: Event = serde_json::from_str(input).unwrap(); 210 | } 211 | 212 | #[test] 213 | fn test_started() { 214 | let input = r#"{ "type": "test", "event": "started", "name": "foo" }"#; 215 | let _data: Event = serde_json::from_str(input).unwrap(); 216 | } 217 | 218 | #[test] 219 | fn test_timeout() { 220 | let input = r#"{ "type": "test", "event": "timeout", "name": "foo" }"#; 221 | let _data: Event = serde_json::from_str(input).unwrap(); 222 | } 223 | 224 | #[test] 225 | fn bench() { 226 | let input = "{ \"type\": \"bench\", \ 227 | \"name\": \"foo\", \ 228 | \"median\": 10, \ 229 | \"deviation\": 2 }"; 230 | let _data: Event = serde_json::from_str(input).unwrap(); 231 | } 232 | 233 | #[test] 234 | fn bench_full() { 235 | let input = "{ \"type\": \"bench\", \ 236 | \"name\": \"foo\", \ 237 | \"median\": 10, \ 238 | \"deviation\": 2, \ 239 | \"mib_per_second\": 1 }"; 240 | let _data: Event = serde_json::from_str(input).unwrap(); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "automod" 7 | version = "1.0.15" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ebb4bd301db2e2ca1f5be131c24eb8ebf2d9559bc3744419e93baf8ddea7e670" 10 | dependencies = [ 11 | "proc-macro2", 12 | "quote", 13 | "syn 2.0.60", 14 | ] 15 | 16 | [[package]] 17 | name = "bitflags" 18 | version = "2.6.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 21 | 22 | [[package]] 23 | name = "cfg-if" 24 | version = "1.0.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 27 | 28 | [[package]] 29 | name = "errno" 30 | version = "0.3.12" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 33 | dependencies = [ 34 | "libc", 35 | "windows-sys", 36 | ] 37 | 38 | [[package]] 39 | name = "escargot" 40 | version = "0.5.15" 41 | dependencies = [ 42 | "automod", 43 | "log", 44 | "serde", 45 | "serde_json", 46 | "tempfile", 47 | ] 48 | 49 | [[package]] 50 | name = "fastrand" 51 | version = "2.3.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 54 | 55 | [[package]] 56 | name = "getrandom" 57 | version = "0.3.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 60 | dependencies = [ 61 | "cfg-if", 62 | "libc", 63 | "r-efi", 64 | "wasi", 65 | ] 66 | 67 | [[package]] 68 | name = "itoa" 69 | version = "1.0.4" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 72 | 73 | [[package]] 74 | name = "libc" 75 | version = "0.2.172" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 78 | 79 | [[package]] 80 | name = "linux-raw-sys" 81 | version = "0.9.4" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 84 | 85 | [[package]] 86 | name = "log" 87 | version = "0.4.17" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 90 | dependencies = [ 91 | "cfg-if", 92 | ] 93 | 94 | [[package]] 95 | name = "once_cell" 96 | version = "1.21.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 99 | 100 | [[package]] 101 | name = "proc-macro2" 102 | version = "1.0.81" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 105 | dependencies = [ 106 | "unicode-ident", 107 | ] 108 | 109 | [[package]] 110 | name = "quote" 111 | version = "1.0.36" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 114 | dependencies = [ 115 | "proc-macro2", 116 | ] 117 | 118 | [[package]] 119 | name = "r-efi" 120 | version = "5.2.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 123 | 124 | [[package]] 125 | name = "rustix" 126 | version = "1.0.7" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 129 | dependencies = [ 130 | "bitflags", 131 | "errno", 132 | "libc", 133 | "linux-raw-sys", 134 | "windows-sys", 135 | ] 136 | 137 | [[package]] 138 | name = "ryu" 139 | version = "1.0.11" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 142 | 143 | [[package]] 144 | name = "serde" 145 | version = "1.0.148" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" 148 | dependencies = [ 149 | "serde_derive", 150 | ] 151 | 152 | [[package]] 153 | name = "serde_derive" 154 | version = "1.0.148" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" 157 | dependencies = [ 158 | "proc-macro2", 159 | "quote", 160 | "syn 1.0.105", 161 | ] 162 | 163 | [[package]] 164 | name = "serde_json" 165 | version = "1.0.89" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" 168 | dependencies = [ 169 | "itoa", 170 | "ryu", 171 | "serde", 172 | ] 173 | 174 | [[package]] 175 | name = "syn" 176 | version = "1.0.105" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" 179 | dependencies = [ 180 | "proc-macro2", 181 | "quote", 182 | "unicode-ident", 183 | ] 184 | 185 | [[package]] 186 | name = "syn" 187 | version = "2.0.60" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 190 | dependencies = [ 191 | "proc-macro2", 192 | "quote", 193 | "unicode-ident", 194 | ] 195 | 196 | [[package]] 197 | name = "tempfile" 198 | version = "3.23.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 201 | dependencies = [ 202 | "fastrand", 203 | "getrandom", 204 | "once_cell", 205 | "rustix", 206 | "windows-sys", 207 | ] 208 | 209 | [[package]] 210 | name = "unicode-ident" 211 | version = "1.0.5" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 214 | 215 | [[package]] 216 | name = "wasi" 217 | version = "0.14.2+wasi-0.2.4" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 220 | dependencies = [ 221 | "wit-bindgen-rt", 222 | ] 223 | 224 | [[package]] 225 | name = "windows-sys" 226 | version = "0.52.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 229 | dependencies = [ 230 | "windows-targets", 231 | ] 232 | 233 | [[package]] 234 | name = "windows-targets" 235 | version = "0.52.5" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 238 | dependencies = [ 239 | "windows_aarch64_gnullvm", 240 | "windows_aarch64_msvc", 241 | "windows_i686_gnu", 242 | "windows_i686_gnullvm", 243 | "windows_i686_msvc", 244 | "windows_x86_64_gnu", 245 | "windows_x86_64_gnullvm", 246 | "windows_x86_64_msvc", 247 | ] 248 | 249 | [[package]] 250 | name = "windows_aarch64_gnullvm" 251 | version = "0.52.5" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 254 | 255 | [[package]] 256 | name = "windows_aarch64_msvc" 257 | version = "0.52.5" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 260 | 261 | [[package]] 262 | name = "windows_i686_gnu" 263 | version = "0.52.5" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 266 | 267 | [[package]] 268 | name = "windows_i686_gnullvm" 269 | version = "0.52.5" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 272 | 273 | [[package]] 274 | name = "windows_i686_msvc" 275 | version = "0.52.5" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 278 | 279 | [[package]] 280 | name = "windows_x86_64_gnu" 281 | version = "0.52.5" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 284 | 285 | [[package]] 286 | name = "windows_x86_64_gnullvm" 287 | version = "0.52.5" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 290 | 291 | [[package]] 292 | name = "windows_x86_64_msvc" 293 | version = "0.52.5" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 296 | 297 | [[package]] 298 | name = "wit-bindgen-rt" 299 | version = "0.39.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 302 | dependencies = [ 303 | "bitflags", 304 | ] 305 | -------------------------------------------------------------------------------- /src/build.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::process; 3 | 4 | use crate::cargo::Cargo; 5 | use crate::cargo::CURRENT_TARGET; 6 | use crate::error::CargoResult; 7 | use crate::msg::CommandMessages; 8 | use crate::run::CargoRun; 9 | #[cfg(feature = "test_unstable")] 10 | use crate::test::CargoTest; 11 | 12 | /// The `build` subcommand. 13 | /// 14 | /// # Example 15 | /// 16 | /// ```rust 17 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 18 | /// escargot::CargoBuild::new() 19 | /// .bin("bin") 20 | /// .current_release() 21 | /// .current_target() 22 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 23 | /// .target_dir(target_dir.path()) 24 | /// .exec() 25 | /// .unwrap(); 26 | /// ``` 27 | #[derive(Debug)] 28 | pub struct CargoBuild { 29 | cmd: process::Command, 30 | bin: bool, 31 | example: bool, 32 | } 33 | 34 | impl CargoBuild { 35 | /// Shortcut to create a `build` subcommand. 36 | /// 37 | /// See also [`Cargo`]. 38 | /// 39 | /// # Example 40 | /// 41 | /// ```rust 42 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 43 | /// escargot::CargoBuild::new() 44 | /// .bin("bin") 45 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 46 | /// .target_dir(target_dir.path()) 47 | /// .exec() 48 | /// .unwrap(); 49 | /// ``` 50 | /// 51 | pub fn new() -> Self { 52 | Cargo::new().build() 53 | } 54 | 55 | pub(crate) fn with_command(cmd: process::Command) -> Self { 56 | Self { 57 | cmd, 58 | bin: false, 59 | example: false, 60 | } 61 | } 62 | 63 | /// Return the underlying [`process::Command`] 64 | pub fn into_command(self) -> process::Command { 65 | self.cmd 66 | } 67 | 68 | /// Build from `name` package in workspaces. 69 | /// 70 | /// # Example 71 | /// 72 | /// ```rust 73 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 74 | /// escargot::CargoBuild::new() 75 | /// .package("bin") 76 | /// .bin("bin") 77 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 78 | /// .target_dir(target_dir.path()) 79 | /// .exec() 80 | /// .unwrap(); 81 | /// ``` 82 | pub fn package>(self, name: S) -> Self { 83 | self.arg("--package").arg(name) 84 | } 85 | 86 | /// Build all binaries. 87 | /// 88 | /// # Example 89 | /// 90 | /// ```rust 91 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 92 | /// escargot::CargoBuild::new() 93 | /// .bins() 94 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 95 | /// .target_dir(target_dir.path()) 96 | /// .exec() 97 | /// .unwrap(); 98 | /// ``` 99 | pub fn bins(mut self) -> Self { 100 | self.bin = true; 101 | self.arg("--bins") 102 | } 103 | 104 | /// Build only `name` binary. 105 | /// 106 | /// # Example 107 | /// 108 | /// ```rust 109 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 110 | /// escargot::CargoBuild::new() 111 | /// .bin("bin") 112 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 113 | /// .target_dir(target_dir.path()) 114 | /// .exec() 115 | /// .unwrap(); 116 | /// ``` 117 | pub fn bin>(mut self, name: S) -> Self { 118 | self.bin = true; 119 | self.arg("--bin").arg(name) 120 | } 121 | 122 | /// Build all examples 123 | /// 124 | /// # Example 125 | /// 126 | /// ```rust 127 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 128 | /// escargot::CargoBuild::new() 129 | /// .examples() 130 | /// .manifest_path("tests/testsuite/fixtures/example/Cargo.toml") 131 | /// .target_dir(target_dir.path()) 132 | /// .exec() 133 | /// .unwrap(); 134 | /// ``` 135 | pub fn examples(mut self) -> Self { 136 | self.example = true; 137 | self.arg("--examples") 138 | } 139 | 140 | /// Build only `name` example. 141 | /// 142 | /// # Example 143 | /// 144 | /// ```rust 145 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 146 | /// escargot::CargoBuild::new() 147 | /// .example("example_fixture") 148 | /// .manifest_path("tests/testsuite/fixtures/example/Cargo.toml") 149 | /// .target_dir(target_dir.path()) 150 | /// .exec() 151 | /// .unwrap(); 152 | /// ``` 153 | pub fn example>(mut self, name: S) -> Self { 154 | self.example = true; 155 | self.arg("--example").arg(name) 156 | } 157 | 158 | /// Build all tests 159 | /// 160 | /// # Example 161 | /// 162 | /// ```rust 163 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 164 | /// escargot::CargoBuild::new() 165 | /// .tests() 166 | /// .manifest_path("tests/testsuite/fixtures/test/Cargo.toml") 167 | /// .target_dir(target_dir.path()) 168 | /// .exec() 169 | /// .unwrap(); 170 | /// ``` 171 | pub fn tests(self) -> Self { 172 | self.arg("--tests") 173 | } 174 | 175 | /// Build only `name` test. 176 | /// 177 | /// # Example 178 | /// 179 | /// ```rust 180 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 181 | /// escargot::CargoBuild::new() 182 | /// .test("test") 183 | /// .manifest_path("tests/testsuite/fixtures/test/Cargo.toml") 184 | /// .target_dir(target_dir.path()) 185 | /// .exec() 186 | /// .unwrap(); 187 | /// ``` 188 | pub fn test>(self, name: S) -> Self { 189 | self.arg("--test").arg(name) 190 | } 191 | 192 | /// Path to Cargo.toml 193 | pub fn manifest_path>(self, path: S) -> Self { 194 | self.arg("--manifest-path").arg(path) 195 | } 196 | 197 | /// Build artifacts in release mode, with optimizations. 198 | pub fn release(self) -> Self { 199 | self.arg("--release") 200 | } 201 | 202 | /// Inserts or updates an environment variable mapping. 203 | pub fn env(mut self, key: K, val: V) -> Self 204 | where 205 | K: AsRef, 206 | V: AsRef, 207 | { 208 | self.cmd.env(key, val); 209 | 210 | self 211 | } 212 | 213 | /// Removes an environment variable 214 | pub fn env_remove(mut self, key: K) -> Self 215 | where 216 | K: AsRef, 217 | { 218 | self.cmd.env_remove(key); 219 | self 220 | } 221 | 222 | /// Infer [`Self::release`] from how the current process was built 223 | pub fn current_release(self) -> Self { 224 | #[cfg(debug_assertions)] 225 | { 226 | self 227 | } 228 | #[cfg(not(debug_assertions))] 229 | { 230 | self.release() 231 | } 232 | } 233 | 234 | /// Build for the target triplet. 235 | pub fn target>(self, triplet: S) -> Self { 236 | self.arg("--target").arg(triplet) 237 | } 238 | 239 | /// Infer [`Self::target`] from how the current process was built 240 | pub fn current_target(self) -> Self { 241 | self.target(CURRENT_TARGET) 242 | } 243 | 244 | /// Directory for all generated artifacts 245 | pub fn target_dir>(self, dir: S) -> Self { 246 | self.arg("--target-dir").arg(dir) 247 | } 248 | 249 | /// Activate all available features 250 | pub fn all_features(self) -> Self { 251 | self.arg("--all-features") 252 | } 253 | 254 | /// Do not activate the `default` feature 255 | pub fn no_default_features(self) -> Self { 256 | self.arg("--no-default-features") 257 | } 258 | 259 | /// Space-separated list of features to activate 260 | pub fn features>(self, features: S) -> Self { 261 | self.arg("--features").arg(features) 262 | } 263 | 264 | /// Manually pass an argument that is unsupported. 265 | /// 266 | /// Caution: Passing in `--` can throw off the API. 267 | pub fn arg>(mut self, arg: S) -> Self { 268 | self.cmd.arg(arg); 269 | self 270 | } 271 | 272 | /// Manually pass arguments that are unsupported. 273 | /// 274 | /// Caution: Passing in `--` can throw off the API. 275 | pub fn args, S: AsRef>(mut self, args: I) -> Self { 276 | self.cmd.args(args); 277 | self 278 | } 279 | 280 | /// Build the configured target, returning compiler messages. 281 | pub fn exec(self) -> CargoResult { 282 | CommandMessages::with_command(self.cmd) 283 | } 284 | 285 | /// Provide a proxy for running the built target. 286 | /// 287 | /// # Example 288 | /// 289 | /// ```rust 290 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 291 | /// let run = escargot::CargoBuild::new() 292 | /// .bin("bin") 293 | /// .current_release() 294 | /// .current_target() 295 | /// .manifest_path("tests/testsuite/fixtures/bin/Cargo.toml") 296 | /// .target_dir(target_dir.path()) 297 | /// .run() 298 | /// .unwrap(); 299 | /// println!("artifact={}", run.path().display()); 300 | /// ``` 301 | pub fn run(self) -> CargoResult { 302 | let msgs = CommandMessages::with_command(self.cmd)?; 303 | CargoRun::from_message(msgs, self.bin, self.example) 304 | } 305 | 306 | /// Provide a proxy for running the built target. 307 | /// 308 | /// Required feature: `test_unstable` since the format parsed is unstable. 309 | /// 310 | /// # Example 311 | /// 312 | /// ```rust 313 | /// # let target_dir = tempfile::TempDir::new().unwrap(); 314 | /// let run = escargot::CargoBuild::new() 315 | /// .test("test") 316 | /// .current_release() 317 | /// .current_target() 318 | /// .manifest_path("tests/testsuite/fixtures/test/Cargo.toml") 319 | /// .target_dir(target_dir.path()) 320 | /// .run_tests().unwrap() 321 | /// .next().unwrap().unwrap(); 322 | /// println!("artifact={}", run.path().display()); 323 | /// ``` 324 | #[cfg(feature = "test_unstable")] 325 | pub fn run_tests(self) -> CargoResult>> { 326 | let msgs = CommandMessages::with_command(self.cmd)?; 327 | Ok(CargoTest::with_messages(msgs)) 328 | } 329 | } 330 | 331 | impl Default for CargoBuild { 332 | fn default() -> Self { 333 | Self::new() 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/format/mod.rs: -------------------------------------------------------------------------------- 1 | //! Serialization formats for cargo messages. 2 | 3 | use std::borrow; 4 | use std::path; 5 | 6 | pub mod diagnostic; 7 | 8 | #[cfg(feature = "test_unstable")] 9 | pub mod test; 10 | 11 | type CowPath<'a> = borrow::Cow<'a, path::Path>; 12 | type CowStr<'a> = borrow::Cow<'a, str>; 13 | 14 | /// A cargo message 15 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 16 | #[serde(tag = "reason", rename_all = "kebab-case")] 17 | #[allow(clippy::large_enum_variant)] 18 | pub enum Message<'a> { 19 | /// Build completed, all further output should not be parsed 20 | BuildFinished(BuildFinished), 21 | /// The compiler generated an artifact 22 | #[serde(borrow)] 23 | CompilerArtifact(Artifact<'a>), 24 | /// The compiler wants to display a message 25 | #[serde(borrow)] 26 | CompilerMessage(FromCompiler<'a>), 27 | /// A build script successfully executed. 28 | #[serde(borrow)] 29 | BuildScriptExecuted(BuildScript<'a>), 30 | #[cfg(not(feature = "strict_unstable"))] 31 | #[doc(hidden)] 32 | #[serde(other)] 33 | Unknown, 34 | } 35 | 36 | /// Build completed, all further output should not be parsed. 37 | /// 38 | /// See 39 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 40 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 41 | #[non_exhaustive] 42 | pub struct BuildFinished { 43 | /// Whether or not the build finished successfully. 44 | pub success: bool, 45 | } 46 | 47 | /// A compiler-generated file. 48 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 49 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 50 | #[non_exhaustive] 51 | pub struct Artifact<'a> { 52 | /// The workspace member this artifact belongs to 53 | #[serde(borrow)] 54 | pub package_id: WorkspaceMember<'a>, 55 | /// The full path to the artifact's manifest 56 | #[serde(borrow)] 57 | pub manifest_path: Option>, 58 | /// The cargo target (lib, bin, example, etc.) that generated the artifacts. 59 | #[serde(borrow)] 60 | pub target: Target<'a>, 61 | /// The profile indicates which compiler settings were used. 62 | #[serde(borrow)] 63 | pub profile: ArtifactProfile<'a>, 64 | /// The enabled features for this artifact 65 | #[serde(borrow)] 66 | pub features: Vec>, 67 | /// The full paths to the generated artifacts 68 | #[serde(borrow)] 69 | pub filenames: Vec>, 70 | /// The path to the executable that was created 71 | /// 72 | /// `None` if this step did not generate an executable. 73 | #[serde(borrow)] 74 | #[serde(default)] 75 | pub executable: Option>, 76 | /// Whether or not this step was actually executed. 77 | /// 78 | /// When `true`, this means that the pre-existing artifacts were 79 | /// up-to-date, and `rustc` was not executed. When `false`, this means that 80 | /// `rustc` was run to generate the artifacts. 81 | pub fresh: bool, 82 | } 83 | 84 | /// A single target (lib, bin, example, ...) provided by a crate 85 | #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] 86 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 87 | #[non_exhaustive] 88 | pub struct Target<'a> { 89 | /// Name as given in the `Cargo.toml` or generated from the file name 90 | #[serde(borrow)] 91 | pub name: CowStr<'a>, 92 | /// Kind of target ("bin", "example", "test", "bench", "lib") 93 | #[serde(borrow)] 94 | pub kind: Vec>, 95 | /// Almost the same as `kind`, except when an example is a library instead of an executable. 96 | /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example` 97 | #[serde(default)] 98 | #[serde(borrow)] 99 | pub crate_types: Vec>, 100 | /// Whether this is a doctest or not 101 | #[serde(default)] 102 | pub doctest: Option, 103 | /// Whether this is documentation or not 104 | #[serde(default)] 105 | pub doc: Option, 106 | /// Whether this is a test file 107 | #[serde(default)] 108 | pub test: bool, 109 | 110 | #[serde(default)] 111 | #[serde(rename = "required-features")] 112 | /// This target is built only if these features are enabled. 113 | /// It doesn't apply to `lib` targets. 114 | #[serde(borrow)] 115 | pub required_features: Vec>, 116 | /// Path to the main source file of the target 117 | #[serde(borrow)] 118 | pub src_path: CowPath<'a>, 119 | /// Rust edition for this target 120 | #[serde(default = "edition_default")] 121 | #[serde(borrow)] 122 | pub edition: CowStr<'a>, 123 | } 124 | 125 | fn edition_default() -> CowStr<'static> { 126 | "2015".into() 127 | } 128 | 129 | /// A workspace member. This is basically identical to `cargo::core::package_id::PackageId`, except 130 | /// that this does not use `Arc` internally. 131 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 132 | #[serde(transparent)] 133 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 134 | pub struct WorkspaceMember<'a> { 135 | /// The raw package id as given by cargo 136 | #[serde(borrow)] 137 | raw: CowStr<'a>, 138 | } 139 | 140 | /// Profile settings used to determine which compiler flags to use for a 141 | /// target. 142 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 143 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 144 | #[non_exhaustive] 145 | pub struct ArtifactProfile<'a> { 146 | /// Optimization level. Possible values are 0-3, s or z. 147 | #[serde(borrow)] 148 | pub opt_level: CowStr<'a>, 149 | /// The amount of debug info. 150 | pub debuginfo: Option>, 151 | /// State of the `cfg(debug_assertions)` directive, enabling macros like 152 | /// `debug_assert!` 153 | pub debug_assertions: bool, 154 | /// State of the overflow checks. 155 | pub overflow_checks: bool, 156 | /// Whether this profile is a test 157 | pub test: bool, 158 | } 159 | 160 | /// The amount of debug info. 0 for none, 1 for limited, 2 for full 161 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 162 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 163 | #[serde(untagged)] 164 | #[non_exhaustive] 165 | pub enum DebugInfo<'a> { 166 | /// 0 for none, 1 for limited, 2 for full 167 | Level(u32), 168 | /// none, limited, full, etc 169 | #[serde(borrow)] 170 | Name(CowStr<'a>), 171 | } 172 | 173 | /// Message left by the compiler 174 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 175 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 176 | #[non_exhaustive] 177 | pub struct FromCompiler<'a> { 178 | /// The workspace member this message belongs to 179 | #[serde(borrow)] 180 | pub package_id: WorkspaceMember<'a>, 181 | /// The full path to the artifact's manifest 182 | #[serde(borrow)] 183 | pub manifest_path: Option>, 184 | /// The target this message is aimed at 185 | #[serde(borrow)] 186 | pub target: Target<'a>, 187 | /// The message the compiler sent. 188 | #[serde(borrow)] 189 | pub message: diagnostic::Diagnostic<'a>, 190 | } 191 | 192 | /// Output of a Build Script execution. 193 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 194 | #[cfg_attr(feature = "strict_unstable", serde(deny_unknown_fields))] 195 | #[non_exhaustive] 196 | pub struct BuildScript<'a> { 197 | /// The workspace member this build script execution belongs to 198 | #[serde(borrow)] 199 | pub package_id: WorkspaceMember<'a>, 200 | /// The outdir used. 201 | #[serde(borrow)] 202 | #[serde(default)] 203 | pub out_dir: Option>, 204 | /// The libs to link 205 | #[serde(borrow)] 206 | pub linked_libs: Vec>, 207 | /// The paths to search when resolving libs 208 | #[serde(borrow)] 209 | pub linked_paths: Vec>, 210 | /// The paths to search when resolving libs 211 | #[serde(borrow)] 212 | pub cfgs: Vec>, 213 | /// The environment variables to add to the compilation 214 | #[serde(borrow)] 215 | pub env: Vec<(CowStr<'a>, CowStr<'a>)>, 216 | } 217 | 218 | #[cfg(not(feature = "print"))] 219 | pub(crate) fn log_message(msg: &Message<'_>) { 220 | match msg { 221 | Message::BuildFinished(ref finished) => { 222 | log::trace!("Build Finished: {:?}", finished.success); 223 | } 224 | Message::CompilerArtifact(ref art) => { 225 | log::trace!("Building {:#?}", art.package_id,); 226 | } 227 | Message::CompilerMessage(ref comp) => { 228 | let content = comp 229 | .message 230 | .rendered 231 | .as_ref() 232 | .map(|s| s.as_ref()) 233 | .unwrap_or_else(|| comp.message.message.as_ref()); 234 | match comp.message.level { 235 | diagnostic::DiagnosticLevel::Ice => log::error!("{}", content), 236 | diagnostic::DiagnosticLevel::Error => log::error!("{}", content), 237 | diagnostic::DiagnosticLevel::Warning => log::warn!("{}", content), 238 | diagnostic::DiagnosticLevel::Note => log::info!("{}", content), 239 | diagnostic::DiagnosticLevel::Help => log::info!("{}", content), 240 | #[cfg(not(feature = "strict_unstable"))] 241 | _ => log::warn!("Unknown message: {:#?}", msg), 242 | } 243 | } 244 | Message::BuildScriptExecuted(ref script) => { 245 | log::trace!("Ran script from {:#?}", script.package_id); 246 | } 247 | #[cfg(not(feature = "strict_unstable"))] 248 | _ => { 249 | log::warn!("Unknown message: {:#?}", msg); 250 | } 251 | } 252 | } 253 | 254 | #[cfg(feature = "print")] 255 | #[allow(clippy::print_stderr)] 256 | pub(crate) fn log_message(msg: &Message<'_>) { 257 | match msg { 258 | Message::BuildFinished(ref finished) => { 259 | eprintln!("Build Finished: {:?}", finished.success); 260 | } 261 | Message::CompilerArtifact(ref art) => { 262 | eprintln!("Building {:#?}", art.package_id,); 263 | } 264 | Message::CompilerMessage(ref comp) => { 265 | let content = comp 266 | .message 267 | .rendered 268 | .as_ref() 269 | .map(|s| s.as_ref()) 270 | .unwrap_or_else(|| comp.message.message.as_ref()); 271 | match comp.message.level { 272 | diagnostic::DiagnosticLevel::Ice => eprintln!("{content}"), 273 | diagnostic::DiagnosticLevel::Error => eprintln!("{content}"), 274 | diagnostic::DiagnosticLevel::Warning => eprintln!("{content}"), 275 | diagnostic::DiagnosticLevel::Note => eprintln!("{content}"), 276 | diagnostic::DiagnosticLevel::Help => eprintln!("{content}"), 277 | #[cfg(not(feature = "strict_unstable"))] 278 | _ => eprintln!("Unknown message: {:#?}", msg), 279 | } 280 | } 281 | Message::BuildScriptExecuted(ref script) => { 282 | eprintln!("Ran script from {:#?}", script.package_id); 283 | } 284 | #[cfg(not(feature = "strict_unstable"))] 285 | _ => { 286 | eprintln!("Unknown message: {:#?}", msg); 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Note that all fields that take a lint level have these possible values: 2 | # * deny - An error will be produced and the check will fail 3 | # * warn - A warning will be produced, but the check will not fail 4 | # * allow - No warning or error will be produced, though in some cases a note 5 | # will be 6 | 7 | # Root options 8 | 9 | # The graph table configures how the dependency graph is constructed and thus 10 | # which crates the checks are performed against 11 | [graph] 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #"x86_64-unknown-linux-musl", 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | # When creating the dependency graph used as the source of truth when checks are 30 | # executed, this field can be used to prune crates from the graph, removing them 31 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 32 | # is pruned from the graph, all of its dependencies will also be pruned unless 33 | # they are connected to another crate in the graph that hasn't been pruned, 34 | # so it should be used with care. The identifiers are [Package ID Specifications] 35 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 36 | #exclude = [] 37 | # If true, metadata will be collected with `--all-features`. Note that this can't 38 | # be toggled off if true, if you want to conditionally enable `--all-features` it 39 | # is recommended to pass `--all-features` on the cmd line instead 40 | all-features = false 41 | # If true, metadata will be collected with `--no-default-features`. The same 42 | # caveat with `all-features` applies 43 | no-default-features = false 44 | # If set, these feature will be enabled when collecting metadata. If `--features` 45 | # is specified on the cmd line they will take precedence over this option. 46 | #features = [] 47 | 48 | # The output table provides options for how/if diagnostics are outputted 49 | [output] 50 | # When outputting inclusion graphs in diagnostics that include features, this 51 | # option can be used to specify the depth at which feature edges will be added. 52 | # This option is included since the graphs can be quite large and the addition 53 | # of features from the crate(s) to all of the graph roots can be far too verbose. 54 | # This option can be overridden via `--feature-depth` on the cmd line 55 | feature-depth = 1 56 | 57 | # This section is considered when running `cargo deny check advisories` 58 | # More documentation for the advisories section can be found here: 59 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 60 | [advisories] 61 | # The path where the advisory databases are cloned/fetched into 62 | #db-path = "$CARGO_HOME/advisory-dbs" 63 | # The url(s) of the advisory databases to use 64 | #db-urls = ["https://github.com/rustsec/advisory-db"] 65 | # A list of advisory IDs to ignore. Note that ignored advisories will still 66 | # output a note when they are encountered. 67 | ignore = [ 68 | #"RUSTSEC-0000-0000", 69 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 70 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 71 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 72 | ] 73 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 74 | # If this is false, then it uses a built-in git library. 75 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 76 | # See Git Authentication for more information about setting up git authentication. 77 | #git-fetch-with-cli = true 78 | 79 | # This section is considered when running `cargo deny check licenses` 80 | # More documentation for the licenses section can be found here: 81 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 82 | [licenses] 83 | # List of explicitly allowed licenses 84 | # See https://spdx.org/licenses/ for list of possible licenses 85 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 86 | allow = [ 87 | "MIT", 88 | "MIT-0", 89 | "Apache-2.0", 90 | "BSD-2-Clause", 91 | "BSD-3-Clause", 92 | "MPL-2.0", 93 | "Unicode-DFS-2016", 94 | "Unicode-3.0", 95 | "CC0-1.0", 96 | "ISC", 97 | "OpenSSL", 98 | "Zlib", 99 | "NCSA", 100 | ] 101 | # The confidence threshold for detecting a license from license text. 102 | # The higher the value, the more closely the license text must be to the 103 | # canonical license text of a valid SPDX license file. 104 | # [possible values: any between 0.0 and 1.0]. 105 | confidence-threshold = 0.8 106 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 107 | # aren't accepted for every possible crate as with the normal allow list 108 | exceptions = [ 109 | # Each entry is the crate and version constraint, and its specific allow 110 | # list 111 | #{ allow = ["Zlib"], crate = "adler32" }, 112 | ] 113 | 114 | # Some crates don't have (easily) machine readable licensing information, 115 | # adding a clarification entry for it allows you to manually specify the 116 | # licensing information 117 | [[licenses.clarify]] 118 | # The package spec the clarification applies to 119 | crate = "ring" 120 | # The SPDX expression for the license requirements of the crate 121 | expression = "MIT AND ISC AND OpenSSL" 122 | # One or more files in the crate's source used as the "source of truth" for 123 | # the license expression. If the contents match, the clarification will be used 124 | # when running the license check, otherwise the clarification will be ignored 125 | # and the crate will be checked normally, which may produce warnings or errors 126 | # depending on the rest of your configuration 127 | license-files = [ 128 | # Each entry is a crate relative path, and the (opaque) hash of its contents 129 | { path = "LICENSE", hash = 0xbd0eed23 } 130 | ] 131 | 132 | [licenses.private] 133 | # If true, ignores workspace crates that aren't published, or are only 134 | # published to private registries. 135 | # To see how to mark a crate as unpublished (to the official registry), 136 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 137 | ignore = true 138 | # One or more private registries that you might publish crates to, if a crate 139 | # is only published to private registries, and ignore is true, the crate will 140 | # not have its license(s) checked 141 | registries = [ 142 | #"https://sekretz.com/registry 143 | ] 144 | 145 | # This section is considered when running `cargo deny check bans`. 146 | # More documentation about the 'bans' section can be found here: 147 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 148 | [bans] 149 | # Lint level for when multiple versions of the same crate are detected 150 | multiple-versions = "warn" 151 | # Lint level for when a crate version requirement is `*` 152 | wildcards = "allow" 153 | # The graph highlighting used when creating dotgraphs for crates 154 | # with multiple versions 155 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 156 | # * simplest-path - The path to the version with the fewest edges is highlighted 157 | # * all - Both lowest-version and simplest-path are used 158 | highlight = "all" 159 | # The default lint level for `default` features for crates that are members of 160 | # the workspace that is being checked. This can be overridden by allowing/denying 161 | # `default` on a crate-by-crate basis if desired. 162 | workspace-default-features = "allow" 163 | # The default lint level for `default` features for external crates that are not 164 | # members of the workspace. This can be overridden by allowing/denying `default` 165 | # on a crate-by-crate basis if desired. 166 | external-default-features = "allow" 167 | # List of crates that are allowed. Use with care! 168 | allow = [ 169 | #"ansi_term@0.11.0", 170 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 171 | ] 172 | # List of crates to deny 173 | deny = [ 174 | #"ansi_term@0.11.0", 175 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 176 | # Wrapper crates can optionally be specified to allow the crate when it 177 | # is a direct dependency of the otherwise banned crate 178 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 179 | ] 180 | 181 | # List of features to allow/deny 182 | # Each entry the name of a crate and a version range. If version is 183 | # not specified, all versions will be matched. 184 | #[[bans.features]] 185 | #crate = "reqwest" 186 | # Features to not allow 187 | #deny = ["json"] 188 | # Features to allow 189 | #allow = [ 190 | # "rustls", 191 | # "__rustls", 192 | # "__tls", 193 | # "hyper-rustls", 194 | # "rustls", 195 | # "rustls-pemfile", 196 | # "rustls-tls-webpki-roots", 197 | # "tokio-rustls", 198 | # "webpki-roots", 199 | #] 200 | # If true, the allowed features must exactly match the enabled feature set. If 201 | # this is set there is no point setting `deny` 202 | #exact = true 203 | 204 | # Certain crates/versions that will be skipped when doing duplicate detection. 205 | skip = [ 206 | #"ansi_term@0.11.0", 207 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 208 | ] 209 | # Similarly to `skip` allows you to skip certain crates during duplicate 210 | # detection. Unlike skip, it also includes the entire tree of transitive 211 | # dependencies starting at the specified crate, up to a certain depth, which is 212 | # by default infinite. 213 | skip-tree = [ 214 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 215 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 216 | ] 217 | 218 | # This section is considered when running `cargo deny check sources`. 219 | # More documentation about the 'sources' section can be found here: 220 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 221 | [sources] 222 | # Lint level for what to happen when a crate from a crate registry that is not 223 | # in the allow list is encountered 224 | unknown-registry = "deny" 225 | # Lint level for what to happen when a crate from a git repository that is not 226 | # in the allow list is encountered 227 | unknown-git = "deny" 228 | # List of URLs for allowed crate registries. Defaults to the crates.io index 229 | # if not specified. If it is specified but empty, no registries are allowed. 230 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 231 | # List of URLs for allowed Git repositories 232 | allow-git = [] 233 | 234 | [sources.allow-org] 235 | # 1 or more github.com organizations to allow git sources for 236 | github = [] 237 | # 1 or more gitlab.com organizations to allow git sources for 238 | gitlab = [] 239 | # 1 or more bitbucket.org organizations to allow git sources for 240 | bitbucket = [] 241 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------