├── .devcontainer └── devcontainer.json ├── .github └── workflows │ └── check.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── example_project ├── .gitignore ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── semver_check ├── .gitignore ├── Cargo.toml ├── build.rs └── src │ └── lib.rs ├── src ├── dependencies.rs ├── environment.rs ├── git.rs ├── krono.rs ├── lib.rs └── util.rs └── tests └── testbox_tests.rs /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "features": { 4 | "ghcr.io/devcontainers/features/rust:1": {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Clippy, Format & Test 2 | 3 | on: [pull_request, push, workflow_dispatch] 4 | 5 | jobs: 6 | fmt: 7 | name: rustfmt 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: dtolnay/rust-toolchain@stable 12 | with: 13 | components: rustfmt 14 | - run: cargo fmt --all -- --check 15 | - run: cargo fmt --manifest-path=example_project/Cargo.toml --all -- --check 16 | 17 | check: 18 | name: cargo check 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | toolchain: [stable, "1.81"] 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: dtolnay/rust-toolchain@stable 26 | with: 27 | toolchain: ${{ matrix.toolchain }} 28 | - run: cargo check --no-default-features 29 | - run: cargo check --no-default-features --features cargo-lock 30 | - run: cargo check --no-default-features --features dependency-tree 31 | - run: cargo check --no-default-features --features git2 32 | - run: cargo check --no-default-features --features semver 33 | - run: cargo check --no-default-features --features chrono 34 | - run: cargo check --all-features 35 | - run: cargo check --manifest-path=example_project/Cargo.toml 36 | 37 | semver_crate: 38 | name: cargo check of internal crate 39 | runs-on: ubuntu-latest 40 | strategy: 41 | matrix: 42 | toolchain: [stable, "1.83"] 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: dtolnay/rust-toolchain@stable 46 | with: 47 | toolchain: ${{ matrix.toolchain }} 48 | - run: cargo check --manifest-path=semver_check/Cargo.toml 49 | 50 | clippy: 51 | name: cargo clippy 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v4 55 | - uses: dtolnay/rust-toolchain@stable 56 | with: 57 | components: clippy 58 | - run: cargo clippy --all --tests -- -D warnings 59 | - run: cargo clippy --all-features --all --tests -- -D warnings 60 | - run: cargo clippy --manifest-path=example_project/Cargo.toml --all --tests -- --warn clippy::pedantic -D warnings 61 | 62 | test: 63 | name: cargo test 64 | strategy: 65 | matrix: 66 | os: [ubuntu-latest, windows-latest, macos-latest] 67 | toolchain: [stable, nightly] 68 | runs-on: ${{ matrix.os }} 69 | steps: 70 | - uses: actions/checkout@v4 71 | - uses: dtolnay/rust-toolchain@master 72 | with: 73 | targets: x86_64-unknown-none 74 | toolchain: ${{ matrix.toolchain }} 75 | - if: matrix.toolchain == 'nightly' 76 | run: echo "RUSTFLAGS=-Zfmt-debug=none" >> $GITHUB_ENV 77 | - run: cargo test --no-default-features 78 | - run: cargo test --all-features 79 | - run: cargo test -- --ignored nostd_testbox 80 | - run: cargo run --manifest-path=example_project/Cargo.toml 81 | 82 | semver: 83 | name: Semver check 84 | runs-on: ubuntu-latest 85 | steps: 86 | - uses: actions/checkout@v4 87 | - run: sudo apt update && sudo apt install -y cmake 88 | - uses: obi1kenobi/cargo-semver-checks-action@v2 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.8.0] 8 | - Add override-variables 9 | - Bump MSRV to 1.81 (due to dependencies) 10 | 11 | ## [0.7.7] 12 | - Fix `NUM_JOB` to 1 if `SOURCE_DATE_EPOCH` is set, in order to better support reproducible builds 13 | - Set MSRV to 1.74 (due to dependencies) 14 | 15 | ## [0.7.6] 16 | - Do not depend on `fmt::Debug`-output (`fmt-debug=none`) 17 | - Bump `git2` to 0.20 18 | - Use `static`- instead of `const`-items throughout 19 | 20 | ## [0.7.5] - 2024-10-17 21 | ### Changed 22 | - Bump `cargo-lock` to 10.0 23 | 24 | ## [0.7.4] - 2024-07-07 25 | ### Added 26 | - Honor `SOURCE_DATE_EPOCH` in `BUILT_TIME_UTC` 27 | 28 | ### Changed 29 | - Bump `git2` to 0.19 30 | 31 | ## [0.7.3] - 2024-05-21 32 | ### Added 33 | - Search for lockfile in manifest's parent directory (crates in workspaces) 34 | 35 | ## [0.7.2] - 2024-04-09 36 | ### Changed 37 | - Fixed hard error in case `rustdoc` is missing 38 | 39 | ## [0.7.1] - 2023-10-14 40 | ### Changed 41 | - Fixed `no_std` builds 42 | 43 | ## [0.7.0] - 2023-08-09 44 | ### Changed 45 | - The `Options`-type has been removed in favor of controlling `built`'s behavior by means of feature-flags. 46 | - `cargo-lock` is now an optional dependency 47 | - Bump `git2` to 0.18 48 | 49 | ## [0.6.1] - 2023-06-19 50 | ### Changed 51 | - Bump `git2` to 0.17 52 | - Bump `cargo-lock` to 9.0 53 | 54 | ### Added 55 | - `FEATURES_LOWERCASE` and `FEATURES_LOWERCASE_STR` 56 | 57 | ## [0.6.0] - 2023-02-09 58 | ### Changed 59 | - Identical re-release after yanking 0.5.3, due to semver failure 60 | 61 | ## [0.5.3] - 2023-02-08 62 | ### Changed 63 | - Bump `git2` to 0.16, mitigating GHSA-8643-3wh5-rmjq 64 | 65 | ### Added 66 | - Add `GIT_COMMIT_HASH_SHORT` 67 | 68 | ## [0.5.2] - 2022-12-03 69 | ### Changed 70 | - Removed unused transitive dependency on `time` 71 | - Bump `cargo-lock` to 8.0 72 | - Bump `git2` to 0.15 73 | - Fix unescaped quotes in literals 74 | 75 | ### Added 76 | - Added GitHub Actions to the list of detected CI platforms 77 | 78 | ## [0.5.1] - 2021-05-27 79 | ### Changed 80 | - Bump `cargo-lock` to 7.0 81 | - Bump `semver` to 1.0 82 | 83 | ## [0.5.0] - 2021-05-01 84 | ### Fixed 85 | - Fix possibly wrong `CFG_` values in cross-compilation scenarios 86 | - Fix handling of backspaces in doc-attributes 87 | 88 | ### Changed 89 | - Switch deprecated `tempdir` to `tempfile` 90 | - Add `#allow(dead_code)` to generated code 91 | - Bump `cargo-lock` to 6.0 92 | - Bump `semver` to 0.11 93 | - Publicly re-export dependencies 94 | 95 | ## [0.4.4] - 2020-12-01 96 | ### Added 97 | - Added `PKG_LICENSE` and `PKG_REPOSITORY` 98 | 99 | ## [0.4.3] - 2020-08-19 100 | ### Fixed 101 | - Fix handling of unescaped special characters 102 | 103 | ## [0.4.2] - 2020-05-27 104 | ### Added 105 | - Add `GIT_DIRTY` 106 | - Add `GIT_COMMIT_HASH` and `GIT_HEAD_REF` 107 | 108 | ### Changed 109 | - Bump `semver` to 0.10 110 | 111 | [unreleased]: https://github.com/lukaslueg/built/compare/0.8.0...master 112 | [0.8.0]: https://github.com/lukaslueg/built/compare/0.7.7...0.8.0 113 | [0.7.7]: https://github.com/lukaslueg/built/compare/0.7.6...0.7.7 114 | [0.7.6]: https://github.com/lukaslueg/built/compare/0.7.5...0.7.6 115 | [0.7.5]: https://github.com/lukaslueg/built/compare/0.7.4...0.7.5 116 | [0.7.4]: https://github.com/lukaslueg/built/compare/0.7.3...0.7.4 117 | [0.7.3]: https://github.com/lukaslueg/built/compare/0.7.2...0.7.3 118 | [0.7.2]: https://github.com/lukaslueg/built/compare/0.7.1...0.7.2 119 | [0.7.1]: https://github.com/lukaslueg/built/compare/0.7.0...0.7.1 120 | [0.7.0]: https://github.com/lukaslueg/built/compare/0.6.1...0.7.0 121 | [0.6.1]: https://github.com/lukaslueg/built/compare/0.6.0...0.6.1 122 | [0.6.0]: https://github.com/lukaslueg/built/compare/0.5.3...0.6.0 123 | [0.5.3]: https://github.com/lukaslueg/built/compare/0.5.2...0.5.3 124 | [0.5.2]: https://github.com/lukaslueg/built/compare/0.5.1...0.5.2 125 | [0.5.1]: https://github.com/lukaslueg/built/compare/0.5.0...0.5.1 126 | [0.5.0]: https://github.com/lukaslueg/built/compare/0.4.4...0.5.0 127 | [0.4.4]: https://github.com/lukaslueg/built/compare/0.4.3...0.4.4 128 | [0.4.3]: https://github.com/lukaslueg/built/compare/0.4.2...0.4.3 129 | [0.4.2]: https://github.com/lukaslueg/built/compare/0.4.1...0.4.2 130 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "built" 3 | version = "0.8.0" 4 | description = "Provides a crate with information from the time it was built." 5 | repository = "https://github.com/lukaslueg/built" 6 | documentation = "https://docs.rs/built" 7 | authors = ["Lukas Lueg "] 8 | license = "MIT" 9 | readme = "README.md" 10 | keywords = ["cargo", "build"] 11 | edition = "2021" 12 | rust-version = "1.81" 13 | 14 | [dependencies] 15 | cargo-lock = { version = "10.0", optional = true, default-features = false } 16 | semver = { version = "1.0", optional = true } 17 | chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] } 18 | git2 = { version = "0.20", optional = true, default-features = false, features = [] } 19 | 20 | [dev-dependencies] 21 | tempfile = "3" 22 | 23 | [features] 24 | dependency-tree = [ "cargo-lock/dependency-tree" ] 25 | 26 | [package.metadata.docs.rs] 27 | features = [ "cargo-lock", "chrono", "dependency-tree", "git2", "semver" ] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2023 Lukas Lueg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```built``` provides a crate with information from the time it was built. 2 | 3 | [![Crates.io Version](https://img.shields.io/crates/v/built.svg)](https://crates.io/crates/built) 4 | [![Docs](https://docs.rs/built/badge.svg)](https://docs.rs/built) 5 | [![Clippy, Format & Test](https://github.com/lukaslueg/built/actions/workflows/check.yml/badge.svg)](https://github.com/lukaslueg/built/actions/workflows/check.yml) 6 | [![Downloads](https://img.shields.io/crates/d/built)](https://crates.io/crates/built) 7 | 8 | `built` is used as a build-time dependency to collect various information 9 | about the build-environment, serialize this information into Rust-code and 10 | provide that to the crate. The information collected by `built` include: 11 | 12 | * Various metadata like version, authors, homepage etc. as set by `Cargo.toml` 13 | * The tag or commit id if the crate was being compiled from within a Git repository. 14 | * The values of `CARGO_CFG_*` build script environment variables, like `CARGO_CFG_TARGET_OS` and `CARGO_CFG_TARGET_ARCH`. 15 | * The features the crate was compiled with. 16 | * The various dependencies, dependencies of dependencies and their versions Cargo ultimately chose to compile. 17 | * The presence of a CI-platform like `Github Actions`, `Travis CI` and `AppVeyor`. 18 | * The compiler and it's version; the documentation-generator and it's version. 19 | 20 | See [the example](https://github.com/lukaslueg/built/tree/master/example_project) or the [docs](https://docs.rs/built) for more information. 21 | 22 | ### MSRV 23 | 24 | `built-0.8` itself currently requires Rust 1.81 with all features enabled. 25 | 26 | --- 27 | 28 | ```rust,ignore 29 | // In build.rs 30 | 31 | fn main() { 32 | built::write_built_file().expect("Failed to acquire build-time information") 33 | } 34 | ``` 35 | 36 | ```rust,ignore 37 | // In lib.rs or main.rs 38 | 39 | // Include the generated-file as a separate module 40 | pub mod built_info { 41 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 42 | } 43 | 44 | println!( 45 | "This is version {}, built for {} by {}.", 46 | built_info::PKG_VERSION, 47 | built_info::TARGET, 48 | built_info::RUSTC_VERSION 49 | ); 50 | 51 | match built_info::CI_PLATFORM { 52 | None => print!("It seems I've not been built on a continuous integration platform,"), 53 | Some(ci) => print!("I've been built on CI-platform {},", ci), 54 | } 55 | 56 | if built::util::detect_ci().is_some() { 57 | println!(" but I'm currently executing on one!"); 58 | } else { 59 | println!(" and I'm currently not executing on one!"); 60 | } 61 | 62 | //.... 63 | ``` 64 | 65 | > This is version 0.1.0, built for x86_64-unknown-linux-gnu by rustc 1.77.2 (25ef9e3d8 2024-04-09). 66 | > 67 | > I was built from git `0.7.2-3-g4014a2e`, commit 4014a2eb4e8575bec9a1f17ae7b92d8956ecd1fd, short_commit 4014a2e; the working directory was "clean". The branch was `refs/heads/master`. 68 | > 69 | > I was built for a x86_64-CPU, which is a little-endian architecture. I was compiled to run on linux (a unix-breed) and my runtime should be gnu. 70 | > 71 | > It seems I've not been built on a continuous integration platform, and I'm currently not executing on one! 72 | > 73 | > I was built with profile "debug", features "" on 2024-04-14 14:28:31 +00:00 (38 seconds ago) using `built 0.7.2` (with help from `android-tzdata 0.1.1, android_system_properties 0.1.5, autocfg 1.2.0, bitflags 2.5.0, bumpalo 3.16.0, cargo-lock 9.0.0, cc 1.0.94, cfg-if 1.0.0, chrono 0.4.37, core-foundation-sys 0.8.6, equivalent 1.0.1, fixedbitset 0.4.2, form_urlencoded 1.2.1, git2 0.18.3, hashbrown 0.14.3, iana-time-zone 0.1.60, iana-time-zone-haiku 0.1.2, idna 0.5.0, indexmap 2.2.6, jobserver 0.1.30, js-sys 0.3.69, libc 0.2.153, libgit2-sys 0.16.2+1.7.2, libz-sys 1.1.16, log 0.4.21, memchr 2.7.2, num-traits 0.2.18, once_cell 1.19.0, percent-encoding 2.3.1, petgraph 0.6.4, pkg-config 0.3.30, proc-macro2 1.0.79, quote 1.0.36, semver 1.0.22, serde 1.0.197, serde_derive 1.0.197, serde_spanned 0.6.5, syn 2.0.58, tinyvec 1.6.0, tinyvec_macros 0.1.1, toml 0.7.8, toml_datetime 0.6.5, toml_edit 0.19.15, unicode-bidi 0.3.15, unicode-ident 1.0.12, unicode-normalization 0.1.23, url 2.5.0, vcpkg 0.2.15, wasm-bindgen 0.2.92, wasm-bindgen-backend 0.2.92, wasm-bindgen-macro 0.2.92, wasm-bindgen-macro-support 0.2.92, wasm-bindgen-shared 0.2.92, windows-core 0.52.0, windows-targets 0.52.5, windows_aarch64_gnullvm 0.52.5, windows_aarch64_msvc 0.52.5, windows_i686_gnu 0.52.5, windows_i686_gnullvm 0.52.5, windows_i686_msvc 0.52.5, windows_x86_64_gnu 0.52.5, windows_x86_64_gnullvm 0.52.5, windows_x86_64_msvc 0.52.5, winnow 0.5.40`). 74 | -------------------------------------------------------------------------------- /example_project/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /example_project/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example_project" 3 | version = "0.1.0" 4 | authors = ["Lukas Lueg "] 5 | build = "build.rs" 6 | edition = "2021" 7 | publish = false 8 | 9 | [dependencies] 10 | built = { version = "0.8", path="../", features = ["chrono", "semver"] } 11 | 12 | [build-dependencies] 13 | built = { version = "0.8", path="../", features = ["cargo-lock", "dependency-tree", "git2", "chrono", "semver"] } 14 | -------------------------------------------------------------------------------- /example_project/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | built::write_built_file().expect("Failed to acquire build-time information"); 3 | } 4 | -------------------------------------------------------------------------------- /example_project/src/main.rs: -------------------------------------------------------------------------------- 1 | // The file `built.rs` was placed there by cargo and `build.rs` 2 | mod built_info { 3 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 4 | } 5 | 6 | fn main() { 7 | // Print various information produced by `built`. See the docs for a full list. 8 | 9 | println!( 10 | "This is version {}, built for {} by {}.\n", 11 | built_info::PKG_VERSION, 12 | built_info::TARGET, 13 | built_info::RUSTC_VERSION 14 | ); 15 | 16 | if let (Some(v), Some(dirty), Some(hash), Some(short_hash)) = ( 17 | built_info::GIT_VERSION, 18 | built_info::GIT_DIRTY, 19 | built_info::GIT_COMMIT_HASH, 20 | built_info::GIT_COMMIT_HASH_SHORT, 21 | ) { 22 | print!( 23 | "I was built from git `{}`, commit {}, short_commit {}; the working directory was \"{}\".", 24 | v, 25 | hash, 26 | short_hash, 27 | if dirty { "dirty" } else { "clean" } 28 | ); 29 | } 30 | 31 | match built_info::GIT_HEAD_REF { 32 | Some(r) => println!(" The branch was `{r}`.\n"), 33 | None => println!("\n"), 34 | } 35 | 36 | print!( 37 | "I was built for a {}-CPU, which is a {}-endian architecture. ", 38 | built_info::CFG_TARGET_ARCH, 39 | built_info::CFG_ENDIAN 40 | ); 41 | 42 | println!( 43 | "I was compiled to run on {} (a {}-breed) and my runtime should be {}.\n", 44 | built_info::CFG_OS, 45 | built_info::CFG_FAMILY, 46 | built_info::CFG_ENV 47 | ); 48 | 49 | match built_info::CI_PLATFORM { 50 | None => print!("It seems I've not been built on a continuous integration platform,"), 51 | Some(ci) => print!("I've been built on CI-platform {ci},"), 52 | } 53 | if built::util::detect_ci().is_some() { 54 | println!(" but I'm currently executing on one!\n"); 55 | } else { 56 | println!(" and I'm currently not executing on one!\n"); 57 | } 58 | 59 | let built_time = built::util::strptime(built_info::BUILT_TIME_UTC); 60 | println!( 61 | "I was built with profile \"{}\", features \"{}\" on {} ({} seconds ago) using `{}` (with help from `{}`).", 62 | built_info::PROFILE, 63 | built_info::FEATURES_STR, 64 | built_time.with_timezone(&built::chrono::offset::Local), 65 | (built::chrono::offset::Utc::now() - built_time).num_seconds(), 66 | built_info::DIRECT_DEPENDENCIES_STR, 67 | built_info::INDIRECT_DEPENDENCIES_STR 68 | ); 69 | 70 | let bad_dep = 71 | built::util::parse_versions(built_info::DEPENDENCIES.iter()).any(|(name, ver)| { 72 | name == "DeleteAllMyFiles" && ver < built::semver::Version::parse("1.1.4").unwrap() 73 | }); 74 | if bad_dep { 75 | println!( 76 | "I was built with DeleteAllMyFiles < 1.1.4, which is known to sometimes not really delete all your files. Beware!" 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /semver_check/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /semver_check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "semver_check" 3 | version = "0.1.0" 4 | authors = ["Lukas Lueg "] 5 | build = "build.rs" 6 | edition = "2021" 7 | publish = false 8 | 9 | [build-dependencies] 10 | built = { version = "0.8", path="../", features = ["cargo-lock", "dependency-tree", "git2", "chrono", "semver"] } 11 | -------------------------------------------------------------------------------- /semver_check/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | built::write_built_file().expect("Failed to acquire build-time information"); 3 | } 4 | -------------------------------------------------------------------------------- /semver_check/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Internal crate to provide semver-checks on generated code. 2 | //! 3 | //! This ensures that the code *generated* by `built` does not semver-break 4 | //! downstream crates. 5 | //! All we do here is to assign/use items generated by `built` in a way that 6 | //! was documented on the last semver-compatible version. 7 | //! If `built` breaks this crate, it always breaks downstream crates. If updating 8 | //! this crate is required, and this crate semver-breaks, `built` breaks downstream 9 | //! crates as well. Both cases require a semver-bump of `built`. 10 | 11 | mod built_info { 12 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 13 | } 14 | 15 | pub const CI_PLATFORM: Option<&str> = built_info::CI_PLATFORM; 16 | pub const PKG_VERSION: &str = built_info::PKG_VERSION; 17 | pub const PKG_VERSION_MAJOR: &str = built_info::PKG_VERSION_MAJOR; 18 | pub const PKG_VERSION_MINOR: &str = built_info::PKG_VERSION_MINOR; 19 | pub const PKG_VERSION_PATCH: &str = built_info::PKG_VERSION_PATCH; 20 | pub const PKG_VERSION_PRE: &str = built_info::PKG_VERSION_PRE; 21 | pub const PKG_AUTHORS: &str = built_info::PKG_AUTHORS; 22 | pub const PKG_NAME: &str = built_info::PKG_NAME; 23 | pub const PKG_DESCRIPTION: &str = built_info::PKG_DESCRIPTION; 24 | pub const PKG_HOMEPAGE: &str = built_info::PKG_HOMEPAGE; 25 | pub const PKG_LICENSE: &str = built_info::PKG_LICENSE; 26 | pub const PKG_REPOSITORY: &str = built_info::PKG_REPOSITORY; 27 | pub const TARGET: &str = built_info::TARGET; 28 | pub const HOST: &str = built_info::HOST; 29 | pub const PROFILE: &str = built_info::PROFILE; 30 | pub const RUSTC: &str = built_info::RUSTC; 31 | pub const RUSTDOC: &str = built_info::RUSTDOC; 32 | pub const RUSTC_VERSION: &str = built_info::RUSTC_VERSION; 33 | pub const RUSTDOC_VERSION: &str = built_info::RUSTDOC_VERSION; 34 | pub const OPT_LEVEL: &str = built_info::OPT_LEVEL; 35 | pub const NUM_JOBS: u32 = built_info::NUM_JOBS; 36 | pub const DEBUG: bool = built_info::DEBUG; 37 | pub const FEATURES: &[&str] = &built_info::FEATURES; 38 | pub const FEATURES_STR: &str = built_info::FEATURES_STR; 39 | pub const FEATURES_LOWERCASE: &[&str] = &built_info::FEATURES_LOWERCASE; 40 | pub const FEATURES_LOWERCASE_STR: &str = built_info::FEATURES_LOWERCASE_STR; 41 | pub const CFG_TARGET_ARCH: &str = built_info::CFG_TARGET_ARCH; 42 | pub const CFG_ENDIAN: &str = built_info::CFG_ENDIAN; 43 | pub const CFG_ENV: &str = built_info::CFG_ENV; 44 | pub const CFG_FAMILY: &str = built_info::CFG_FAMILY; 45 | pub const CFG_OS: &str = built_info::CFG_OS; 46 | pub const CFG_POINTER_WIDTH: &str = built_info::CFG_POINTER_WIDTH; 47 | 48 | // cargo-lock 49 | pub const DEPENDENCIES: &[(&str, &str)] = &built_info::DEPENDENCIES; 50 | pub const DEPENDENCIES_STR: &str = built_info::DEPENDENCIES_STR; 51 | 52 | // dependency-tree 53 | pub const DIRECT_DEPENDENCIES: &[(&str, &str)] = &built_info::DIRECT_DEPENDENCIES; 54 | pub const DIRECT_DEPENDENCIES_STR: &str = built_info::DIRECT_DEPENDENCIES_STR; 55 | pub const INDIRECT_DEPENDENCIES: &[(&str, &str)] = &built_info::INDIRECT_DEPENDENCIES; 56 | pub const INDIRECT_DEPENDENCIES_STR: &str = built_info::INDIRECT_DEPENDENCIES_STR; 57 | 58 | // git2 59 | pub const GIT_VERSION: Option<&str> = built_info::GIT_VERSION; 60 | pub const GIT_DIRTY: Option = built_info::GIT_DIRTY; 61 | pub const GIT_HEAD_REF: Option<&str> = built_info::GIT_HEAD_REF; 62 | pub const GIT_COMMIT_HASH: Option<&str> = built_info::GIT_COMMIT_HASH; 63 | pub const GIT_COMMIT_HASH_SHORT: Option<&str> = built_info::GIT_COMMIT_HASH_SHORT; 64 | 65 | // chrono 66 | pub const BUILT_TIME_UTC: &str = built_info::BUILT_TIME_UTC; 67 | 68 | pub static OVERRIDE_VARIABLES_USED: [&str; 0] = []; 69 | -------------------------------------------------------------------------------- /src/dependencies.rs: -------------------------------------------------------------------------------- 1 | use crate::util::TupleArrayDisplay; 2 | use crate::{write_str_variable, write_variable}; 3 | use std::{collections, fs, io, path}; 4 | 5 | fn package_names<'a, I>(packages: I) -> Vec<(String, String)> 6 | where 7 | I: IntoIterator, 8 | { 9 | let mut res = packages 10 | .into_iter() 11 | .map(|package| (package.name.to_string(), package.version.to_string())) 12 | .collect::>() 13 | .into_iter() 14 | .collect::>(); 15 | res.sort_unstable(); 16 | res 17 | } 18 | 19 | fn find_lockfile(base: &path::Path) -> io::Result { 20 | base.ancestors() 21 | .find_map(|p| { 22 | let lockfile = p.join("Cargo.lock"); 23 | lockfile.exists().then(|| lockfile.to_owned()) 24 | }) 25 | .ok_or(io::Error::other("Cargo.lock not found")) 26 | } 27 | 28 | #[cfg(feature = "dependency-tree")] 29 | struct Dependencies { 30 | deps: Vec<(String, String)>, 31 | direct_deps: Vec<(String, String)>, 32 | indirect_deps: Vec<(String, String)>, 33 | } 34 | 35 | #[cfg(feature = "dependency-tree")] 36 | impl Dependencies { 37 | fn new(lockfile: &cargo_lock::Lockfile) -> Self { 38 | use cargo_lock::dependency::graph::EdgeDirection; 39 | 40 | let tree = lockfile 41 | .dependency_tree() 42 | .expect("properly formed lockfile"); 43 | let graph = tree.graph(); 44 | 45 | let root_pkg_idx = graph 46 | .externals(EdgeDirection::Incoming) 47 | .collect::>(); 48 | let deps = package_names(graph.node_indices().filter_map(|idx| { 49 | if root_pkg_idx.contains(&idx) { 50 | None 51 | } else { 52 | Some(&graph[idx]) 53 | } 54 | })); 55 | let direct_deps_idx = root_pkg_idx 56 | .iter() 57 | .flat_map(|idx| graph.neighbors_directed(*idx, EdgeDirection::Outgoing)) 58 | .collect::>(); 59 | let direct_deps = package_names(direct_deps_idx.iter().map(|dep_idx| &graph[*dep_idx])); 60 | let indirect_deps = package_names(graph.node_indices().filter_map(|idx| { 61 | if root_pkg_idx.contains(&idx) | direct_deps_idx.contains(&idx) { 62 | None 63 | } else { 64 | Some(&graph[idx]) 65 | } 66 | })); 67 | 68 | Self { 69 | deps, 70 | direct_deps, 71 | indirect_deps, 72 | } 73 | } 74 | } 75 | 76 | #[cfg(feature = "dependency-tree")] 77 | pub fn write_dependencies(manifest_location: &path::Path, mut w: &fs::File) -> io::Result<()> { 78 | use io::{Read, Write}; 79 | 80 | let mut lock_buf = String::new(); 81 | fs::File::open(find_lockfile(manifest_location)?)?.read_to_string(&mut lock_buf)?; 82 | let lockfile = lock_buf.parse().expect("Failed to parse lockfile"); 83 | 84 | let dependencies = Dependencies::new(&lockfile); 85 | 86 | write_variable!( 87 | w, 88 | "DEPENDENCIES", 89 | format_args!("[(&str, &str); {}]", dependencies.deps.len()), 90 | TupleArrayDisplay(&dependencies.deps), 91 | "An array of effective dependencies as documented by `Cargo.lock`." 92 | ); 93 | write_str_variable!( 94 | w, 95 | "DEPENDENCIES_STR", 96 | dependencies 97 | .deps 98 | .iter() 99 | .map(|(n, v)| format!("{n} {v}")) 100 | .collect::>() 101 | .join(", "), 102 | "The effective dependencies as a comma-separated string." 103 | ); 104 | 105 | write_variable!( 106 | w, 107 | "DIRECT_DEPENDENCIES", 108 | format_args!("[(&str, &str); {}]", dependencies.direct_deps.len()), 109 | TupleArrayDisplay(&dependencies.direct_deps), 110 | "An array of direct dependencies as documented by `Cargo.lock`." 111 | ); 112 | write_str_variable!( 113 | w, 114 | "DIRECT_DEPENDENCIES_STR", 115 | dependencies 116 | .direct_deps 117 | .iter() 118 | .map(|(n, v)| format!("{n} {v}")) 119 | .collect::>() 120 | .join(", "), 121 | "The direct dependencies as a comma-separated string." 122 | ); 123 | 124 | write_variable!( 125 | w, 126 | "INDIRECT_DEPENDENCIES", 127 | format_args!("[(&str, &str); {}]", dependencies.indirect_deps.len()), 128 | TupleArrayDisplay(&dependencies.indirect_deps), 129 | "An array of indirect dependencies as documented by `Cargo.lock`." 130 | ); 131 | write_str_variable!( 132 | w, 133 | "INDIRECT_DEPENDENCIES_STR", 134 | dependencies 135 | .indirect_deps 136 | .iter() 137 | .map(|(n, v)| format!("{n} {v}")) 138 | .collect::>() 139 | .join(", "), 140 | "The indirect dependencies as a comma-separated string." 141 | ); 142 | 143 | Ok(()) 144 | } 145 | 146 | #[cfg(not(feature = "dependency-tree"))] 147 | pub fn write_dependencies(manifest_location: &path::Path, mut w: &fs::File) -> io::Result<()> { 148 | use io::{Read, Write}; 149 | 150 | let mut lock_buf = String::new(); 151 | fs::File::open(find_lockfile(manifest_location)?)?.read_to_string(&mut lock_buf)?; 152 | let lockfile: cargo_lock::Lockfile = lock_buf.parse().expect("Failed to parse lockfile"); 153 | 154 | let deps = package_names(&lockfile.packages); 155 | 156 | write_variable!( 157 | w, 158 | "DEPENDENCIES", 159 | format_args!("[(&str, &str); {}]", deps.len()), 160 | TupleArrayDisplay(&deps), 161 | "An array of effective dependencies as documented by `Cargo.lock`." 162 | ); 163 | write_str_variable!( 164 | w, 165 | "DEPENDENCIES_STR", 166 | deps.iter() 167 | .map(|(n, v)| format!("{n} {v}")) 168 | .collect::>() 169 | .join(", "), 170 | "The effective dependencies as a comma-separated string." 171 | ); 172 | 173 | Ok(()) 174 | } 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | static LOCK_TOML_BUFFER: &str = r#" 179 | # This file is automatically @generated by Cargo. 180 | # It is not intended for manual editing. 181 | version = 3 182 | 183 | [[package]] 184 | name = "foo" 185 | version = "0.0.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "f7dbb6acfeff1d490fba693a402456f76b344fea77a5e7cae43b5970c3332b8f" 188 | 189 | [[package]] 190 | name = "foobar" 191 | version = "0.0.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "d9c0d152c1d2a9673211b9f3c02a4786715ce730dbd5f94f2f895fc0bb9eed63" 194 | 195 | [[package]] 196 | name = "memchr" 197 | version = "2.6.3" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 200 | 201 | [[package]] 202 | name = "minimal-lexical" 203 | version = "0.2.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 206 | 207 | [[package]] 208 | name = "nom" 209 | version = "7.1.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 212 | dependencies = [ 213 | "memchr", 214 | "minimal-lexical", 215 | ] 216 | 217 | [[package]] 218 | name = "dummy" 219 | version = "0.1.0" 220 | dependencies = [ 221 | "foo", 222 | "foobar", 223 | "nom", 224 | ] 225 | "#; 226 | 227 | #[test] 228 | fn parse_deps() { 229 | let lockfile: cargo_lock::Lockfile = 230 | LOCK_TOML_BUFFER.parse().expect("Failed to parse lockfile"); 231 | let deps = super::package_names(&lockfile.packages); 232 | assert_eq!( 233 | deps, 234 | [ 235 | ("dummy".to_owned(), "0.1.0".to_owned()), 236 | ("foo".to_owned(), "0.0.0".to_owned()), 237 | ("foobar".to_owned(), "0.0.0".to_owned()), 238 | ("memchr".to_owned(), "2.6.3".to_owned()), 239 | ("minimal-lexical".to_owned(), "0.2.1".to_owned()), 240 | ("nom".to_owned(), "7.1.3".to_owned()), 241 | ] 242 | ); 243 | } 244 | 245 | #[test] 246 | #[cfg(feature = "dependency-tree")] 247 | fn direct_deps() { 248 | let lockfile = LOCK_TOML_BUFFER.parse().expect("Failed to parse lockfile"); 249 | let dependencies = super::Dependencies::new(&lockfile); 250 | assert_eq!( 251 | dependencies.deps, 252 | [ 253 | ("foo".to_owned(), "0.0.0".to_owned()), 254 | ("foobar".to_owned(), "0.0.0".to_owned()), 255 | ("memchr".to_owned(), "2.6.3".to_owned()), 256 | ("minimal-lexical".to_owned(), "0.2.1".to_owned()), 257 | ("nom".to_owned(), "7.1.3".to_owned()), 258 | ] 259 | ); 260 | assert_eq!( 261 | dependencies.direct_deps, 262 | [ 263 | ("foo".to_owned(), "0.0.0".to_owned()), 264 | ("foobar".to_owned(), "0.0.0".to_owned()), 265 | ("nom".to_owned(), "7.1.3".to_owned()), 266 | ] 267 | ); 268 | assert_eq!( 269 | dependencies.indirect_deps, 270 | [ 271 | ("memchr".to_owned(), "2.6.3".to_owned()), 272 | ("minimal-lexical".to_owned(), "0.2.1".to_owned()), 273 | ] 274 | ); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/environment.rs: -------------------------------------------------------------------------------- 1 | use crate::util::{self, ArrayDisplay}; 2 | use crate::{fmt_option_str, write_str_variable, write_variable}; 3 | use std::{cell, collections, env, ffi, fmt, fs, io, process}; 4 | 5 | const BUILT_OVERRIDE_PREFIX: &str = "BUILT_OVERRIDE_"; 6 | 7 | #[derive(Debug, Default)] 8 | enum EnvironmentValue { 9 | #[default] 10 | Unused, 11 | Queried, 12 | Used, 13 | } 14 | 15 | impl EnvironmentValue { 16 | fn upgrade_to_queried(&mut self) { 17 | if matches!(self, EnvironmentValue::Unused) { 18 | *self = EnvironmentValue::Queried; 19 | } 20 | } 21 | 22 | fn upgrade_to_used(&mut self) { 23 | *self = EnvironmentValue::Used; 24 | } 25 | 26 | pub fn is_unused(&self) -> bool { 27 | matches!(self, EnvironmentValue::Unused) 28 | } 29 | 30 | pub fn is_used(&self) -> bool { 31 | matches!(self, EnvironmentValue::Used) 32 | } 33 | } 34 | 35 | pub struct EnvironmentMap { 36 | map: collections::HashMap)>, 37 | override_prefix: String, 38 | } 39 | 40 | fn get_version_from_cmd(executable: &ffi::OsStr) -> io::Result { 41 | let output = process::Command::new(executable).arg("-V").output()?; 42 | let mut v = String::from_utf8(output.stdout).unwrap(); 43 | v.pop(); // remove newline 44 | Ok(v) 45 | } 46 | 47 | impl EnvironmentMap { 48 | pub fn new() -> Self { 49 | let map = env::vars_os() 50 | .filter_map(|(k, v)| match (k.into_string(), v.into_string()) { 51 | (Ok(k), Ok(v)) => Some((k, (v, cell::RefCell::default()))), 52 | _ => None, 53 | }) 54 | .collect::>(); 55 | let override_prefix = format!("{}{}_", BUILT_OVERRIDE_PREFIX, map["CARGO_PKG_NAME"].0); 56 | Self { 57 | map, 58 | override_prefix, 59 | } 60 | } 61 | 62 | fn override_key(&self, key: &str) -> String { 63 | let mut prefixed_key = self.override_prefix.clone(); 64 | prefixed_key.push_str(key); 65 | prefixed_key 66 | } 67 | 68 | pub fn get(&self, key: &str) -> Option<&str> { 69 | self.map.get(key).map(|v| { 70 | v.1.borrow_mut().upgrade_to_queried(); 71 | v.0.as_str() 72 | }) 73 | } 74 | 75 | pub fn contains_key(&self, key: &str) -> bool { 76 | self.get(key).is_some() 77 | } 78 | 79 | pub fn filter_map_keys(&self, mut f: F) -> impl Iterator 80 | where 81 | F: FnMut(&str) -> Option<&str>, 82 | { 83 | self.map.iter().filter_map(move |v| match f(v.0.as_str()) { 84 | Some(w) => { 85 | v.1 .1.borrow_mut().upgrade_to_queried(); 86 | Some(w) 87 | } 88 | None => None, 89 | }) 90 | } 91 | 92 | pub fn get_override_var<'a, T>(&'a self, key: &str) -> Option 93 | where 94 | T: util::ParseFromEnv<'a>, 95 | { 96 | self.map.get(&self.override_key(key)).map(|v| { 97 | v.1.borrow_mut().upgrade_to_queried(); 98 | match T::parse_from_env(v.0.as_str()) { 99 | Ok(t) => { 100 | v.1.borrow_mut().upgrade_to_used(); 101 | t 102 | } 103 | Err(e) => { 104 | panic!("Failed to parse `{key}`=`{0}`: {e:?}", v.0); 105 | } 106 | } 107 | }) 108 | } 109 | 110 | pub fn unused_override_vars(&self) -> impl Iterator { 111 | self.map.iter().filter_map(|(k, v)| { 112 | if k.starts_with(&self.override_prefix) && v.1.borrow().is_unused() { 113 | Some(k.as_str()) 114 | } else { 115 | None 116 | } 117 | }) 118 | } 119 | 120 | pub fn used_override_vars(&self) -> impl Iterator { 121 | self.map.iter().filter_map(|(k, v)| { 122 | if v.1.borrow().is_used() { 123 | Some(k.strip_prefix(&self.override_prefix).unwrap()) 124 | } else { 125 | None 126 | } 127 | }) 128 | } 129 | 130 | pub fn write_ci(&self, mut w: &fs::File) -> io::Result<()> { 131 | use io::Write; 132 | 133 | let ci = match self.get_override_var("CI_PLATFORM") { 134 | Some(v) => v, 135 | None => self.detect_ci().map(|ci| ci.to_string()), 136 | }; 137 | write_variable!( 138 | w, 139 | "CI_PLATFORM", 140 | "Option<&str>", 141 | fmt_option_str(ci), 142 | "The Continuous Integration platform detected during compilation." 143 | ); 144 | Ok(()) 145 | } 146 | 147 | pub fn write_env(&self, mut w: &fs::File) -> io::Result<()> { 148 | use io::Write; 149 | macro_rules! write_env_str { 150 | ($(($name:ident, $env_name:expr, $doc:expr)),*) => {$( 151 | let v = match self.get_override_var(stringify!($name)) { 152 | Some(v) => v, 153 | None => self.get($env_name).expect(stringify!(Missing expected environment variable $env_name)), 154 | }; 155 | write_str_variable!( 156 | w, 157 | stringify!($name), 158 | v, 159 | $doc 160 | ); 161 | )*} 162 | } 163 | 164 | write_env_str!( 165 | (PKG_VERSION, "CARGO_PKG_VERSION", "The full version."), 166 | ( 167 | PKG_VERSION_MAJOR, 168 | "CARGO_PKG_VERSION_MAJOR", 169 | "The major version." 170 | ), 171 | ( 172 | PKG_VERSION_MINOR, 173 | "CARGO_PKG_VERSION_MINOR", 174 | "The minor version." 175 | ), 176 | ( 177 | PKG_VERSION_PATCH, 178 | "CARGO_PKG_VERSION_PATCH", 179 | "The patch version." 180 | ), 181 | ( 182 | PKG_VERSION_PRE, 183 | "CARGO_PKG_VERSION_PRE", 184 | "The pre-release version." 185 | ), 186 | ( 187 | PKG_AUTHORS, 188 | "CARGO_PKG_AUTHORS", 189 | "A colon-separated list of authors." 190 | ), 191 | (PKG_NAME, "CARGO_PKG_NAME", "The name of the package."), 192 | (PKG_DESCRIPTION, "CARGO_PKG_DESCRIPTION", "The description."), 193 | (PKG_HOMEPAGE, "CARGO_PKG_HOMEPAGE", "The homepage."), 194 | (PKG_LICENSE, "CARGO_PKG_LICENSE", "The license."), 195 | ( 196 | PKG_REPOSITORY, 197 | "CARGO_PKG_REPOSITORY", 198 | "The source repository as advertised in Cargo.toml." 199 | ), 200 | ( 201 | TARGET, 202 | "TARGET", 203 | "The target triple that was being compiled for." 204 | ), 205 | (HOST, "HOST", "The host triple of the rust compiler."), 206 | ( 207 | PROFILE, 208 | "PROFILE", 209 | "`release` for release builds, `debug` for other builds." 210 | ), 211 | (RUSTC, "RUSTC", "The compiler that cargo resolved to use."), 212 | ( 213 | RUSTDOC, 214 | "RUSTDOC", 215 | "The documentation generator that cargo resolved to use." 216 | ) 217 | ); 218 | 219 | write_str_variable!( 220 | w, 221 | "OPT_LEVEL", 222 | self.get_override_var("OPT_LEVEL") 223 | .unwrap_or_else(|| env::var("OPT_LEVEL").unwrap()), 224 | "Value of OPT_LEVEL for the profile used during compilation." 225 | ); 226 | 227 | write_variable!( 228 | w, 229 | "NUM_JOBS", 230 | "u32", 231 | self.get_override_var("NUM_JOBS").unwrap_or_else(|| { 232 | if env::var(crate::SOURCE_DATE_EPOCH).is_ok() { 233 | 1u32 234 | } else { 235 | env::var("NUM_JOBS").unwrap().parse().unwrap() 236 | } 237 | }), 238 | "The parallelism that was specified during compilation." 239 | ); 240 | 241 | write_variable!( 242 | w, 243 | "DEBUG", 244 | "bool", 245 | self.get_override_var("DEBUG") 246 | .unwrap_or_else(|| env::var("DEBUG").unwrap() == "true"), 247 | "Value of DEBUG for the profile used during compilation." 248 | ); 249 | Ok(()) 250 | } 251 | 252 | pub fn write_features(&self, mut w: &fs::File) -> io::Result<()> { 253 | use io::Write; 254 | 255 | let mut features = self.get_override_var("FEATURES").unwrap_or_else(|| { 256 | self.filter_map_keys(|k| k.strip_prefix("CARGO_FEATURE_")) 257 | .map(|f| f.to_owned()) 258 | .collect::>() 259 | }); 260 | features.sort_unstable(); 261 | 262 | write_variable!( 263 | w, 264 | "FEATURES", 265 | format_args!("[&str; {}]", features.len()), 266 | ArrayDisplay(&features, |t, f| write!(f, "\"{}\"", t.escape_default())), 267 | "The features that were enabled during compilation." 268 | ); 269 | let features_str = features.join(", "); 270 | write_str_variable!( 271 | w, 272 | "FEATURES_STR", 273 | features_str, 274 | "The features as a comma-separated string." 275 | ); 276 | 277 | let mut lowercase_features = features 278 | .iter() 279 | .map(|name| name.to_lowercase()) 280 | .collect::>(); 281 | lowercase_features.sort_unstable(); 282 | 283 | write_variable!( 284 | w, 285 | "FEATURES_LOWERCASE", 286 | format_args!("[&str; {}]", lowercase_features.len()), 287 | ArrayDisplay(&lowercase_features, |val, fmt| write!( 288 | fmt, 289 | "\"{}\"", 290 | val.escape_default() 291 | )), 292 | "The features as above, as lowercase strings." 293 | ); 294 | let lowercase_features_str = lowercase_features.join(", "); 295 | write_str_variable!( 296 | w, 297 | "FEATURES_LOWERCASE_STR", 298 | lowercase_features_str, 299 | "The feature-string as above, from lowercase strings." 300 | ); 301 | 302 | Ok(()) 303 | } 304 | 305 | pub fn write_cfg(&self, mut w: &fs::File) -> io::Result<()> { 306 | use io::Write; 307 | 308 | write_str_variable!( 309 | w, 310 | "CFG_TARGET_ARCH", 311 | self.get_override_var("CFG_TARGET_ARCH") 312 | .unwrap_or_else(|| self.get("CARGO_CFG_TARGET_ARCH").unwrap()), 313 | "The target architecture, given by `CARGO_CFG_TARGET_ARCH`." 314 | ); 315 | 316 | write_str_variable!( 317 | w, 318 | "CFG_ENDIAN", 319 | self.get_override_var("CFG_ENDIAN") 320 | .unwrap_or_else(|| self.get("CARGO_CFG_TARGET_ENDIAN").unwrap()), 321 | "The endianness, given by `CARGO_CFG_TARGET_ENDIAN`." 322 | ); 323 | 324 | write_str_variable!( 325 | w, 326 | "CFG_ENV", 327 | self.get_override_var("CFG_ENV") 328 | .unwrap_or_else(|| self.get("CARGO_CFG_TARGET_ENV").unwrap()), 329 | "The toolchain-environment, given by `CARGO_CFG_TARGET_ENV`." 330 | ); 331 | 332 | write_str_variable!( 333 | w, 334 | "CFG_FAMILY", 335 | self.get_override_var("CFG_FAMILY") 336 | .unwrap_or_else(|| self.get("CARGO_CFG_TARGET_FAMILY").unwrap_or_default()), 337 | "The OS-family, given by `CARGO_CFG_TARGET_FAMILY`." 338 | ); 339 | 340 | write_str_variable!( 341 | w, 342 | "CFG_OS", 343 | self.get_override_var("CFG_OS") 344 | .unwrap_or_else(|| self.get("CARGO_CFG_TARGET_OS").unwrap()), 345 | "The operating system, given by `CARGO_CFG_TARGET_OS`." 346 | ); 347 | 348 | write_str_variable!( 349 | w, 350 | "CFG_POINTER_WIDTH", 351 | self.get_override_var("CFG_POINTER_WIDTH") 352 | .unwrap_or_else(|| self.get("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap()), 353 | "The pointer width, given by `CARGO_CFG_TARGET_POINTER_WIDTH`." 354 | ); 355 | 356 | Ok(()) 357 | } 358 | 359 | pub fn write_compiler_version(&self, mut w: &fs::File) -> io::Result<()> { 360 | use std::io::Write; 361 | 362 | let rustc; 363 | let rustc_version; 364 | match self.get_override_var("RUSTC") { 365 | Some(v) => { 366 | rustc = v; 367 | rustc_version = self 368 | .get_override_var("RUSTC_VERSION") 369 | .expect("RUSTC_VERSION must be overridden if RUSTC is") 370 | } 371 | None => { 372 | rustc = self.get("RUSTC").unwrap(); 373 | rustc_version = get_version_from_cmd(rustc.as_ref())?; 374 | } 375 | } 376 | 377 | let rustdoc; 378 | let rustdoc_version; 379 | match self.get_override_var("RUSTDOC") { 380 | Some(v) => { 381 | rustdoc = v; 382 | rustdoc_version = self.get_override_var("RUSTDOC_VERSION").unwrap_or_default(); 383 | } 384 | None => { 385 | rustdoc = self.get("RUSTDOC").unwrap(); 386 | rustdoc_version = get_version_from_cmd(rustdoc.as_ref()).unwrap_or_default(); 387 | } 388 | } 389 | 390 | write_str_variable!( 391 | w, 392 | "RUSTC_VERSION", 393 | rustc_version, 394 | format_args!("The output of `{rustc} -V`") 395 | ); 396 | 397 | write_str_variable!( 398 | w, 399 | "RUSTDOC_VERSION", 400 | rustdoc_version, 401 | format_args!( 402 | "The output of `{rustdoc} -V`; empty string if `{rustdoc} -V` failed to execute" 403 | ) 404 | ); 405 | Ok(()) 406 | } 407 | 408 | pub fn detect_ci(&self) -> Option { 409 | macro_rules! detect { 410 | ($(($k:expr, $v:expr, $i:ident)),*) => {$( 411 | if self.get($k).map_or(false, |v| v == $v) { 412 | return Some(CIPlatform::$i); 413 | } 414 | )*}; 415 | ($(($k:expr, $i:ident)),*) => {$( 416 | if self.contains_key($k) { 417 | return Some(CIPlatform::$i); 418 | } 419 | )*}; 420 | ($($k:expr),*) => {$( 421 | if self.contains_key($k) { 422 | return Some(CIPlatform::Generic); 423 | } 424 | )*}; 425 | } 426 | // Variable names collected by watson/ci-info 427 | detect!( 428 | ("TRAVIS", Travis), 429 | ("CIRCLECI", Circle), 430 | ("GITLAB_CI", GitLab), 431 | ("APPVEYOR", AppVeyor), 432 | ("DRONE", Drone), 433 | ("MAGNUM", Magnum), 434 | ("SEMAPHORE", Semaphore), 435 | ("JENKINS_URL", Jenkins), 436 | ("bamboo_planKey", Bamboo), 437 | ("TF_BUILD", TFS), 438 | ("TEAMCITY_VERSION", TeamCity), 439 | ("BUILDKITE", Buildkite), 440 | ("HUDSON_URL", Hudson), 441 | ("GO_PIPELINE_LABEL", GoCD), 442 | ("BITBUCKET_COMMIT", BitBucket), 443 | ("GITHUB_ACTIONS", GitHubActions) 444 | ); 445 | 446 | if self.contains_key("TASK_ID") && self.contains_key("RUN_ID") { 447 | return Some(CIPlatform::TaskCluster); 448 | } 449 | 450 | detect!(("CI_NAME", "codeship", Codeship)); 451 | 452 | detect!( 453 | "CI", // Could be Travis, Circle, GitLab, AppVeyor or CodeShip 454 | "CONTINUOUS_INTEGRATION", // Probably Travis 455 | "BUILD_NUMBER" // Jenkins, TeamCity 456 | ); 457 | None 458 | } 459 | } 460 | 461 | /// Various Continuous Integration platforms whose presence can be detected. 462 | pub enum CIPlatform { 463 | /// 464 | Travis, 465 | /// 466 | Circle, 467 | /// 468 | GitLab, 469 | /// 470 | AppVeyor, 471 | /// 472 | Codeship, 473 | /// 474 | Drone, 475 | /// 476 | Magnum, 477 | /// 478 | Semaphore, 479 | /// 480 | Jenkins, 481 | /// 482 | Bamboo, 483 | /// 484 | TFS, 485 | /// 486 | TeamCity, 487 | /// 488 | Buildkite, 489 | /// 490 | Hudson, 491 | /// 492 | TaskCluster, 493 | /// 494 | GoCD, 495 | /// 496 | BitBucket, 497 | /// 498 | GitHubActions, 499 | /// Unspecific 500 | Generic, 501 | } 502 | 503 | impl fmt::Display for CIPlatform { 504 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 505 | f.write_str(match *self { 506 | CIPlatform::Travis => "Travis CI", 507 | CIPlatform::Circle => "CircleCI", 508 | CIPlatform::GitLab => "GitLab", 509 | CIPlatform::AppVeyor => "AppVeyor", 510 | CIPlatform::Codeship => "CodeShip", 511 | CIPlatform::Drone => "Drone", 512 | CIPlatform::Magnum => "Magnum", 513 | CIPlatform::Semaphore => "Semaphore", 514 | CIPlatform::Jenkins => "Jenkins", 515 | CIPlatform::Bamboo => "Bamboo", 516 | CIPlatform::TFS => "Team Foundation Server", 517 | CIPlatform::TeamCity => "TeamCity", 518 | CIPlatform::Buildkite => "Buildkite", 519 | CIPlatform::Hudson => "Hudson", 520 | CIPlatform::TaskCluster => "TaskCluster", 521 | CIPlatform::GoCD => "GoCD", 522 | CIPlatform::BitBucket => "BitBucket", 523 | CIPlatform::GitHubActions => "GitHub Actions", 524 | CIPlatform::Generic => "Generic CI", 525 | }) 526 | } 527 | } 528 | -------------------------------------------------------------------------------- /src/git.rs: -------------------------------------------------------------------------------- 1 | use crate::{environment, fmt_option_str, write_variable}; 2 | use std::{fs, io, path}; 3 | 4 | pub fn write_git_version( 5 | manifest_location: &path::Path, 6 | envmap: &environment::EnvironmentMap, 7 | mut w: &fs::File, 8 | ) -> io::Result<()> { 9 | use io::Write; 10 | 11 | // CIs will do shallow clones of repositories, causing libgit2 to error 12 | // out. We try to detect if we are running on a CI and ignore the 13 | // error. 14 | let (mut tag, mut dirty) = ( 15 | envmap.get_override_var("GIT_VERSION"), 16 | envmap.get_override_var("GIT_DIRTY"), 17 | ); 18 | if tag.is_none() || dirty.is_none() { 19 | if let Some((git_tag, git_dirty)) = get_repo_description(manifest_location).ok().flatten() { 20 | if tag.is_none() { 21 | tag = Some(git_tag); 22 | } 23 | if dirty.is_none() { 24 | dirty = Some(git_dirty); 25 | } 26 | }; 27 | } 28 | write_variable!( 29 | w, 30 | "GIT_VERSION", 31 | "Option<&str>", 32 | fmt_option_str(tag), 33 | "If the crate was compiled from within a git-repository, \ 34 | `GIT_VERSION` contains HEAD's tag. The short commit id is used if HEAD is not tagged." 35 | ); 36 | write_variable!( 37 | w, 38 | "GIT_DIRTY", 39 | "Option", 40 | match dirty { 41 | Some(true) => "Some(true)", 42 | Some(false) => "Some(false)", 43 | None => "None", 44 | }, 45 | "If the repository had dirty/staged files." 46 | ); 47 | 48 | let (mut branch, mut commit, mut commit_short) = ( 49 | envmap.get_override_var("GIT_HEAD_REF"), 50 | envmap.get_override_var::("GIT_COMMIT_HASH"), 51 | envmap.get_override_var("GIT_COMMIT_HASH_SHORT"), 52 | ); 53 | if branch.is_none() || commit.is_none() || commit_short.is_none() { 54 | if let Some((git_branch, git_commit, git_commit_short)) = 55 | get_repo_head(manifest_location).ok().flatten() 56 | { 57 | if branch.is_none() { 58 | branch = git_branch; 59 | } 60 | if commit.is_none() { 61 | commit = Some(git_commit); 62 | } 63 | if commit_short.is_none() { 64 | commit_short = Some(git_commit_short); 65 | } 66 | } 67 | } 68 | if let (Some(h), None) = (&commit, &commit_short) { 69 | commit_short = Some(h.chars().take(8).collect()) 70 | } 71 | 72 | let doc = "If the crate was compiled from within a git-repository, `GIT_HEAD_REF` \ 73 | contains full name to the reference pointed to by HEAD \ 74 | (e.g.: `refs/heads/master`). If HEAD is detached or the branch name is not \ 75 | valid UTF-8 `None` will be stored.\n"; 76 | write_variable!( 77 | w, 78 | "GIT_HEAD_REF", 79 | "Option<&str>", 80 | fmt_option_str(branch), 81 | doc 82 | ); 83 | 84 | write_variable!( 85 | w, 86 | "GIT_COMMIT_HASH", 87 | "Option<&str>", 88 | fmt_option_str(commit), 89 | "If the crate was compiled from within a git-repository, `GIT_COMMIT_HASH` \ 90 | contains HEAD's full commit SHA-1 hash." 91 | ); 92 | 93 | write_variable!( 94 | w, 95 | "GIT_COMMIT_HASH_SHORT", 96 | "Option<&str>", 97 | fmt_option_str(commit_short), 98 | "If the crate was compiled from within a git-repository, `GIT_COMMIT_HASH_SHORT` \ 99 | contains HEAD's short commit SHA-1 hash." 100 | ); 101 | 102 | Ok(()) 103 | } 104 | 105 | /// Retrieves the git-tag or hash describing the exact version and a boolean 106 | /// that indicates if the repository currently has dirty/staged files. 107 | /// 108 | /// If a valid git-repo can't be discovered at or above the given path, 109 | /// `Ok(None)` is returned instead of an `Err`-value. 110 | /// 111 | /// # Errors 112 | /// Errors from `git2` are returned if the repository does exists at all. 113 | #[cfg(feature = "git2")] 114 | pub fn get_repo_description(root: &std::path::Path) -> Result, git2::Error> { 115 | match git2::Repository::discover(root) { 116 | Ok(repo) => { 117 | let mut desc_opt = git2::DescribeOptions::new(); 118 | desc_opt.describe_tags().show_commit_oid_as_fallback(true); 119 | let tag = repo 120 | .describe(&desc_opt) 121 | .and_then(|desc| desc.format(None))?; 122 | let mut st_opt = git2::StatusOptions::new(); 123 | st_opt.include_ignored(false); 124 | st_opt.include_untracked(false); 125 | let dirty = repo 126 | .statuses(Some(&mut st_opt))? 127 | .iter() 128 | .any(|status| !matches!(status.status(), git2::Status::CURRENT)); 129 | Ok(Some((tag, dirty))) 130 | } 131 | Err(ref e) 132 | if e.class() == git2::ErrorClass::Repository 133 | && e.code() == git2::ErrorCode::NotFound => 134 | { 135 | Ok(None) 136 | } 137 | Err(e) => Err(e), 138 | } 139 | } 140 | 141 | /// Retrieves the branch name and hash of HEAD. 142 | /// 143 | /// The returned value is a tuple of head's reference-name, long-hash and short-hash. The 144 | /// branch name will be `None` if the head is detached, or it's not valid UTF-8. 145 | /// 146 | /// If a valid git-repo can't be discovered at or above the given path, 147 | /// `Ok(None)` is returned instead of an `Err`-value. 148 | /// 149 | /// # Errors 150 | /// Errors from `git2` are returned if the repository does exists at all. 151 | #[cfg(feature = "git2")] 152 | pub fn get_repo_head( 153 | root: &std::path::Path, 154 | ) -> Result, String, String)>, git2::Error> { 155 | match git2::Repository::discover(root) { 156 | Ok(repo) => { 157 | // Supposed to be the reference pointed to by HEAD, but it's HEAD 158 | // itself, if detached 159 | let head_ref = repo.head()?; 160 | let branch = { 161 | // Check whether `head` is really the pointed to reference and 162 | // not HEAD itself. 163 | if repo.head_detached()? { 164 | None 165 | } else { 166 | head_ref.name() 167 | } 168 | }; 169 | let head = head_ref.peel_to_commit()?; 170 | let commit = head.id(); 171 | let commit_short = head.into_object().short_id()?; 172 | Ok(Some(( 173 | branch.map(ToString::to_string), 174 | format!("{commit}"), 175 | commit_short.as_str().unwrap_or_default().to_string(), 176 | ))) 177 | } 178 | Err(ref e) 179 | if e.class() == git2::ErrorClass::Repository 180 | && e.code() == git2::ErrorCode::NotFound => 181 | { 182 | Ok(None) 183 | } 184 | Err(e) => Err(e), 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod tests { 190 | #[test] 191 | fn parse_git_repo() { 192 | use std::fs; 193 | use std::path; 194 | 195 | let repo_root = tempfile::tempdir().unwrap(); 196 | assert_eq!(super::get_repo_description(repo_root.as_ref()), Ok(None)); 197 | 198 | let repo = git2::Repository::init_opts( 199 | &repo_root, 200 | git2::RepositoryInitOptions::new() 201 | .external_template(false) 202 | .mkdir(false) 203 | .no_reinit(true) 204 | .mkpath(false), 205 | ) 206 | .unwrap(); 207 | 208 | let cruft_file = repo_root.path().join("cruftfile"); 209 | std::fs::write(&cruft_file, "Who? Me?").unwrap(); 210 | 211 | let project_root = repo_root.path().join("project_root"); 212 | fs::create_dir(&project_root).unwrap(); 213 | 214 | let sig = git2::Signature::now("foo", "bar").unwrap(); 215 | let mut idx = repo.index().unwrap(); 216 | idx.add_path(path::Path::new("cruftfile")).unwrap(); 217 | idx.write().unwrap(); 218 | let commit_oid = repo 219 | .commit( 220 | Some("HEAD"), 221 | &sig, 222 | &sig, 223 | "Testing testing 1 2 3", 224 | &repo.find_tree(idx.write_tree().unwrap()).unwrap(), 225 | &[], 226 | ) 227 | .unwrap(); 228 | 229 | let binding = repo 230 | .find_commit(commit_oid) 231 | .unwrap() 232 | .into_object() 233 | .short_id() 234 | .unwrap(); 235 | 236 | let commit_oid_short = binding.as_str().unwrap(); 237 | 238 | let commit_hash = commit_oid.to_string(); 239 | let commit_hash_short = commit_oid_short.to_string(); 240 | 241 | assert!(commit_hash.starts_with(&commit_hash_short)); 242 | 243 | // The commit, the commit-id is something and the repo is not dirty 244 | let (tag, dirty) = super::get_repo_description(&project_root).unwrap().unwrap(); 245 | assert!(!tag.is_empty()); 246 | assert!(!dirty); 247 | 248 | // Tag the commit, it should be retrieved 249 | repo.tag( 250 | "foobar", 251 | &repo 252 | .find_object(commit_oid, Some(git2::ObjectType::Commit)) 253 | .unwrap(), 254 | &sig, 255 | "Tagged foobar", 256 | false, 257 | ) 258 | .unwrap(); 259 | 260 | let (tag, dirty) = super::get_repo_description(&project_root).unwrap().unwrap(); 261 | assert_eq!(tag, "foobar"); 262 | assert!(!dirty); 263 | 264 | // Make some dirt 265 | std::fs::write(cruft_file, "now dirty").unwrap(); 266 | let (tag, dirty) = super::get_repo_description(&project_root).unwrap().unwrap(); 267 | assert_eq!(tag, "foobar"); 268 | assert!(dirty); 269 | 270 | let branch_short_name = "baz"; 271 | let branch_name = "refs/heads/baz"; 272 | let commit = repo.find_commit(commit_oid).unwrap(); 273 | repo.branch(branch_short_name, &commit, true).unwrap(); 274 | repo.set_head(branch_name).unwrap(); 275 | 276 | assert_eq!( 277 | super::get_repo_head(&project_root), 278 | Ok(Some(( 279 | Some(branch_name.to_owned()), 280 | commit_hash, 281 | commit_hash_short 282 | ))) 283 | ); 284 | } 285 | 286 | #[test] 287 | fn detached_head_repo() { 288 | let repo_root = tempfile::tempdir().unwrap(); 289 | let repo = git2::Repository::init_opts( 290 | &repo_root, 291 | git2::RepositoryInitOptions::new() 292 | .external_template(false) 293 | .mkdir(false) 294 | .no_reinit(true) 295 | .mkpath(false), 296 | ) 297 | .unwrap(); 298 | let sig = git2::Signature::now("foo", "bar").unwrap(); 299 | let commit_oid = repo 300 | .commit( 301 | Some("HEAD"), 302 | &sig, 303 | &sig, 304 | "Testing", 305 | &repo 306 | .find_tree(repo.index().unwrap().write_tree().unwrap()) 307 | .unwrap(), 308 | &[], 309 | ) 310 | .unwrap(); 311 | 312 | let binding = repo 313 | .find_commit(commit_oid) 314 | .unwrap() 315 | .into_object() 316 | .short_id() 317 | .unwrap(); 318 | 319 | let commit_oid_short = binding.as_str().unwrap(); 320 | 321 | let commit_hash = commit_oid.to_string(); 322 | let commit_hash_short = commit_oid_short.to_string(); 323 | 324 | assert!(commit_hash.starts_with(&commit_hash_short)); 325 | 326 | repo.set_head_detached(commit_oid).unwrap(); 327 | assert_eq!( 328 | super::get_repo_head(repo_root.as_ref()), 329 | Ok(Some((None, commit_hash, commit_hash_short))) 330 | ); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/krono.rs: -------------------------------------------------------------------------------- 1 | use crate::{environment, util, write_str_variable, write_variable}; 2 | use std::{fs, io}; 3 | 4 | impl<'a> util::ParseFromEnv<'a> for chrono::DateTime { 5 | type Err = chrono::ParseError; 6 | 7 | fn parse_from_env(s: &'a str) -> Result { 8 | Ok(chrono::DateTime::parse_from_rfc2822(s)?.with_timezone(&chrono::offset::Utc)) 9 | } 10 | } 11 | 12 | /// Parse a time-string as formatted by `built`. 13 | /// 14 | /// ``` 15 | /// use chrono::Datelike; 16 | /// 17 | /// pub mod build_info { 18 | /// pub static BUILT_TIME_UTC: &'static str = "Tue, 14 Feb 2017 05:21:41 GMT"; 19 | /// } 20 | /// 21 | /// assert_eq!(built::util::strptime(&build_info::BUILT_TIME_UTC).year(), 2017); 22 | /// ``` 23 | /// 24 | /// # Panics 25 | /// If the string can't be parsed. This should never happen with input provided 26 | /// by `built`. 27 | #[must_use] 28 | pub fn strptime(s: &str) -> chrono::DateTime { 29 | chrono::DateTime::parse_from_rfc2822(s) 30 | .unwrap() 31 | .with_timezone(&chrono::offset::Utc) 32 | } 33 | 34 | fn get_source_date_epoch_from_env() -> Option> { 35 | match std::env::var(crate::SOURCE_DATE_EPOCH) { 36 | Ok(val) => { 37 | let ts = match val.parse::() { 38 | Ok(ts) => ts, 39 | Err(_) => { 40 | eprintln!("SOURCE_DATE_EPOCH defined, but not a i64"); 41 | return None; 42 | } 43 | }; 44 | match chrono::DateTime::from_timestamp(ts, 0) { 45 | Some(now) => Some(now), 46 | None => { 47 | eprintln!("SOURCE_DATE_EPOCH can't be represented as a UTC-time"); 48 | None 49 | } 50 | } 51 | } 52 | Err(_) => None, 53 | } 54 | } 55 | 56 | pub fn write_time(mut w: &fs::File, envmap: &environment::EnvironmentMap) -> io::Result<()> { 57 | use io::Write; 58 | 59 | let now = match envmap.get_override_var("BUILT_TIME_UTC") { 60 | Some(v) => v, 61 | None => get_source_date_epoch_from_env().unwrap_or_else(chrono::offset::Utc::now), 62 | }; 63 | write_str_variable!( 64 | w, 65 | "BUILT_TIME_UTC", 66 | now.to_rfc2822(), 67 | "The build time in RFC2822, UTC." 68 | ); 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) Lukas Lueg 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | // 23 | 24 | #![allow(clippy::needless_doctest_main)] 25 | 26 | //! Provides a crate with information from the time it was built. 27 | //! 28 | //! `built` is used as a build-time dependency to collect various information 29 | //! about the build-environment, serialize this information into Rust-code and 30 | //! provide that to the crate. The information collected by `built` include: 31 | //! 32 | //! * Various metadata like version, authors, homepage etc. as set by `Cargo.toml` 33 | //! * The tag or commit id if the crate was being compiled from within a Git repository. 34 | //! * The values of `CARGO_CFG_*` build script environment variables, like `CARGO_CFG_TARGET_OS` and `CARGO_CFG_TARGET_ARCH`. 35 | //! * The features the crate was compiled with. 36 | //! * The various dependencies, dependencies of dependencies and their versions Cargo ultimately chose to compile. 37 | //! * The presence of a CI-platform like `Github Actions`, `Travis CI` and `AppVeyor`. 38 | //! * The compiler and it's version; the documentation-generator and it's version. 39 | //! 40 | //! `built` does not add any further runtime-dependencies to a crate; all information 41 | //! is serialized as types from `stdlib`. One can include `built` as a 42 | //! runtime-dependency and use it's convenience functions. 43 | //! 44 | //! To add `built` to a crate, add it as a build-time dependency, use a build-script 45 | //! to collect and serialize the build-time information, and `include!` the generated code. 46 | //! 47 | //! Add this to `Cargo.toml`: 48 | //! 49 | //! ```toml 50 | //! [package] 51 | //! build = "build.rs" 52 | //! 53 | //! [build-dependencies] 54 | //! built = "0.8" 55 | //! ``` 56 | //! 57 | //! Add or modify a build-script. In `build.rs`: 58 | //! 59 | //! ```rust,no_run 60 | //! fn main() { 61 | //! built::write_built_file().expect("Failed to acquire build-time information"); 62 | //! } 63 | //! ``` 64 | //! 65 | //! The build-script will by default write a file named `built.rs` into Cargo's output 66 | //! directory. It can be picked up in `main.rs` (or anywhere else) like this: 67 | //! 68 | //! ```rust,ignore 69 | //! // Use of a mod or pub mod is not actually necessary. 70 | //! pub mod built_info { 71 | //! // The file has been placed there by the build script. 72 | //! include!(concat!(env!("OUT_DIR"), "/built.rs")); 73 | //! } 74 | //! ``` 75 | //! 76 | //! ...and then used somewhere in the crate's code: 77 | //! 78 | //! ```rust 79 | //! # mod built_info { 80 | //! # pub static PKG_VERSION_PRE: &str = ""; 81 | //! # pub static CI_PLATFORM: Option<&str> = None; 82 | //! # pub static GIT_VERSION: Option<&str> = None; 83 | //! # pub static DEPENDENCIES: [(&str, &str); 0] = []; 84 | //! # pub static BUILT_TIME_UTC: &str = "Tue, 14 Feb 2017 05:21:41 GMT"; 85 | //! # } 86 | //! # 87 | //! # enum LogLevel { TRACE, ERROR }; 88 | //! /// Determine if current version is a pre-release or was built from a git-repo 89 | //! fn release_is_unstable() -> bool { 90 | //! return !built_info::PKG_VERSION_PRE.is_empty() || built_info::GIT_VERSION.is_some() 91 | //! } 92 | //! 93 | //! /// Default log-level, enhanced on CI 94 | //! fn default_log_level() -> LogLevel { 95 | //! if built_info::CI_PLATFORM.is_some() { 96 | //! LogLevel::TRACE 97 | //! } else { 98 | //! LogLevel::ERROR 99 | //! } 100 | //! } 101 | //! 102 | //! /// The time this crate was built 103 | //! #[cfg(feature = "chrono")] 104 | //! fn built_time() -> built::chrono::DateTime { 105 | //! built::util::strptime(built_info::BUILT_TIME_UTC) 106 | //! .with_timezone(&built::chrono::offset::Local) 107 | //! } 108 | //! 109 | //! /// If another crate pulls in a dependency we don't like, print a warning 110 | //! #[cfg(feature = "semver")] 111 | //! fn check_sane_dependencies() { 112 | //! if built::util::parse_versions(&built_info::DEPENDENCIES) 113 | //! .any(|(name, ver)| name == "DeleteAllMyFiles" 114 | //! && ver < built::semver::Version::parse("1.1.4").unwrap()) { 115 | //! eprintln!("DeleteAllMyFiles < 1.1.4 may not delete all your files. Beware!"); 116 | //! } 117 | //! } 118 | //! ``` 119 | //! 120 | //! 121 | //! ## Overrides 122 | //! 123 | //! Most values otherwise detected by `built` can be manually set using environment variables. The 124 | //! primary use-case for this is to allow automated build-systems and package-managers to enforce 125 | //! certain values, e.g. to hide the presence of a CI-platform and/or allow for reproducible 126 | //! builds. 127 | //! 128 | //! The values set via the environment take precedence over what `built` would otherwise detect. 129 | //! There is no mechanism to ensure that values derived from override-variables are sensible, 130 | //! besides enforcing the correct type. 131 | //! 132 | //! Override-variables are prefixed by `BUILT_OVERRIDE_{PKG_NAME}_`, where `{PKG_NAME}` is the name 133 | //! of the package as reported by `cargo`. For example, if the package is named "mypkg", and the value 134 | //! to be overridden is named "CI_PLATFORM", then the override variable is named 135 | //! "BUILT_OVERRIDE_mypkg_CI_PLATFORM". Remember that more than one package in a dependency-graph 136 | //! might use `built` internally, so you might have to override multiple instances of the same 137 | //! value. 138 | //! Unused override variables result in a warning at compile time, albeit cargo only reports those 139 | //! for path-specific (e.g. local) packages. 140 | //! 141 | //! An override-variable's text-presentation must parse to the value's respective type: 142 | //! * Strings are used as-is. 143 | //! * Integers and `bool` must parse via their `std::str::FromStr`-implementation. 144 | //! * `std::option::Option` parses the string `BUILT_OVERRIDE_NONE` as `Option::None`; every other value 145 | //! must parse as `T`. For example, `BUILT_OVERRIDE_mypkg_CI_PLATFORM=BUILT_OVERRIDE_NONE` forces 146 | //! `CI_PLATFORM.is_none()`. 147 | //! * List-like values are parsed as comma-separated values. 148 | //! 149 | //! If an override-variable can't be parsed, the build-process will abort with a `panic!()`. 150 | //! 151 | //! Notice that values that were overridden are recorded in `OVERRIDE_VARIABLES_USED`. 152 | //! 153 | //! Please refer to the respective item's documentation for more information on overrides. 154 | //! 155 | //! ## Feature flags 156 | //! The information that `built` collects and makes available in `built.rs` depends 157 | //! on the features that were enabled on the build-time dependency. 158 | //! 159 | //! ### _Always available_ 160 | //! The following information is available regardless of feature-flags. 161 | //! 162 | //! ``` 163 | //! /// The Continuous Integration platform detected during compilation. 164 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_CI_PLATFORM`. 165 | //! pub static CI_PLATFORM: Option<&str> = None; 166 | //! 167 | //! /// The full version. 168 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_VERSION`. 169 | //! pub static PKG_VERSION: &str = "0.1.0"; 170 | //! /// The major version. 171 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_MAJOR`. 172 | //! pub static PKG_VERSION_MAJOR: &str = "0"; 173 | //! /// The minor version. 174 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_MINOR`. 175 | //! pub static PKG_VERSION_MINOR: &str = "1"; 176 | //! /// "The patch version. 177 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_PATCH`. 178 | //! pub static PKG_VERSION_PATCH: &str = "0"; 179 | //! /// "The pre-release version. 180 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_PRE`. 181 | //! pub static PKG_VERSION_PRE: &str = ""; 182 | //! 183 | //! /// "A colon-separated list of authors. 184 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_AUTHORS`. 185 | //! pub static PKG_AUTHORS: &str = "Lukas Lueg "; 186 | //! 187 | //! /// The name of the package. 188 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_NAME`. 189 | //! pub static PKG_NAME: &str = "example_project"; 190 | //! /// "The description. 191 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_DESCRIPTION`. 192 | //! pub static PKG_DESCRIPTION: &str = ""; 193 | //! /// "The homepage. 194 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_HOMEPAGE`. 195 | //! pub static PKG_HOMEPAGE: &str = ""; 196 | //! /// "The license. 197 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_LICENSE`. 198 | //! pub static PKG_LICENSE: &str = "MIT"; 199 | //! /// The source repository as advertised in Cargo.toml. 200 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PKG_REPOSITORY`. 201 | //! pub static PKG_REPOSITORY: &str = ""; 202 | //! 203 | //! /// The target triple that was being compiled for. 204 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_TARGET`. 205 | //! pub static TARGET: &str = "x86_64-unknown-linux-gnu"; 206 | //! /// The host triple of the rust compiler. 207 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_HOST`. 208 | //! pub static HOST: &str = "x86_64-unknown-linux-gnu"; 209 | //! /// `release` for release builds, `debug` for other builds. 210 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_PROFILE`. 211 | //! pub static PROFILE: &str = "debug"; 212 | //! 213 | //! /// The compiler that cargo resolved to use. 214 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_RUSTC`; notice that 215 | //! /// if `RUSTC` is overridden, `RUSTC_VERSION` *must* also be overridden. 216 | //! pub static RUSTC: &str = "rustc"; 217 | //! /// The documentation-generator that cargo resolved to use. 218 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_RUSTDOC`; notice that if `RUSTDOC` 219 | //! /// is overridden, `RUSTDOC_VERSION` *must* also be overridden. 220 | //! pub static RUSTDOC: &str = "rustdoc"; 221 | //! /// The output of `rustc -V` 222 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_RUSTC_VERSION`. 223 | //! pub static RUSTC_VERSION: &str = "rustc 1.43.1 (8d69840ab 2020-05-04)"; 224 | //! /// The output of `rustdoc -V` 225 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_RUSTDOC_VERSION`. 226 | //! pub static RUSTDOC_VERSION: &str = "rustdoc 1.43.1 (8d69840ab 2020-05-04)"; 227 | //! 228 | //! /// Value of OPT_LEVEL for the profile used during compilation. 229 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_OPT_LEVEL`. 230 | //! pub static OPT_LEVEL: &str = "0"; 231 | //! /// The parallelism that was specified during compilation. 232 | //! /// If `SOURCE_DATE_EPOCH` is set, `NUM_JOBS` is forced to `1`. 233 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_NUM_JOBS`. 234 | //! pub static NUM_JOBS: u32 = 8; 235 | //! /// "Value of DEBUG for the profile used during compilation. 236 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_NUM_DEBUG`. 237 | //! pub static DEBUG: bool = true; 238 | //! 239 | //! /// The features that were enabled during compilation. 240 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_FEATURES`. 241 | //! pub static FEATURES: [&str; 0] = []; 242 | //! /// The features as a comma-separated string. 243 | //! pub static FEATURES_STR: &str = ""; 244 | //! /// The features as above, as lowercase strings. 245 | //! pub static FEATURES_LOWERCASE: [&str; 0] = []; 246 | //! /// The feature-string as above, from lowercase strings. 247 | //! pub static FEATURES_LOWERCASE_STR: &str = ""; 248 | //! 249 | //! /// The target architecture, given by `CARGO_CFG_TARGET_ARCH`. 250 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_CFG_TARGET_ARCH`. 251 | //! pub static CFG_TARGET_ARCH: &str = "x86_64"; 252 | //! /// The endianness, given by `CARGO_CFG_TARGET_ENDIAN`. 253 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_CFG_ENDIAN`. 254 | //! pub static CFG_ENDIAN: &str = "little"; 255 | //! /// The toolchain-environment, given by `CARGO_CFG_TARGET_ENV`. 256 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_CFG_ENV`. 257 | //! pub static CFG_ENV: &str = "gnu"; 258 | //! /// The OS-family, given by `CARGO_CFG_TARGET_FAMILY`. 259 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_CFG_FAMILY`. 260 | //! pub static CFG_FAMILY: &str = "unix"; 261 | //! /// The operating system, given by `CARGO_CFG_TARGET_OS`. 262 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_CFG_OS`. 263 | //! pub static CFG_OS: &str = "linux"; 264 | //! /// The pointer width, given by `CARGO_CFG_TARGET_POINTER_WIDTH`. 265 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_CFG_POINTER_WIDTH`. 266 | //! pub static CFG_POINTER_WIDTH: &str = "64"; 267 | //! 268 | //! /// The override-variables that were used during compilation. 269 | //! pub static OVERRIDE_VARIABLES_USED: [&str; 0] = []; 270 | //! ``` 271 | //! 272 | //! ### `cargo-lock` 273 | //! Parses `Cargo.lock`and generates representations of dependencies and their versions. 274 | //! 275 | //! For this to work, `Cargo.lock` needs to actually be there; this is (usually) 276 | //! only true for executables and not for libraries. Cargo will only create a 277 | //! `Cargo.lock` for the top-level crate in a dependency-tree. In case 278 | //! of a library, the top-level crate will decide which crate/version 279 | //! combination to compile and there will be no `Cargo.lock` while the library 280 | //! gets compiled as a dependency. 281 | //! 282 | //! Parsing `Cargo.lock` instead of `Cargo.toml` allows to serialize the 283 | //! precise versions Cargo chose to compile. One can't, however, distinguish 284 | //! `build-dependencies`, `dev-dependencies` and `dependencies`. Furthermore, 285 | //! some dependencies never show up if Cargo had not been forced to 286 | //! actually use them (e.g. `dev-dependencies` with `cargo test` never 287 | //! having been executed). 288 | //! 289 | //! Note that if the `dependency-tree`-feature is not active, the list of dependencies 290 | //! contains the root-package(s) as well. 291 | //! 292 | //! ``` 293 | //! /// An array of effective dependencies as documented by `Cargo.lock`. 294 | //! pub static DEPENDENCIES: [(&str, &str); 37] = [("autocfg", "1.0.0"), ("bitflags", "1.2.1"), ("built", "0.4.1"), ("cargo-lock", "4.0.1"), ("cc", "1.0.54"), ("cfg-if", "0.1.10"), ("chrono", "0.4.11"), ("example_project", "0.1.0"), ("git2", "0.13.6"), ("idna", "0.2.0"), ("jobserver", "0.1.21"), ("libc", "0.2.71"), ("libgit2-sys", "0.12.6+1.0.0"), ("libz-sys", "1.0.25"), ("log", "0.4.8"), ("matches", "0.1.8"), ("num-integer", "0.1.42"), ("num-traits", "0.2.11"), ("percent-encoding", "2.1.0"), ("pkg-config", "0.3.17"), ("proc-macro2", "1.0.17"), ("quote", "1.0.6"), ("semver", "1.0.0"), ("serde", "1.0.110"), ("serde_derive", "1.0.110"), ("smallvec", "1.4.0"), ("syn", "1.0.25"), ("time", "0.1.43"), ("toml", "0.5.6"), ("unicode-bidi", "0.3.4"), ("unicode-normalization", "0.1.12"), ("unicode-xid", "0.2.0"), ("url", "2.1.1"), ("vcpkg", "0.2.8"), ("winapi", "0.3.8"), ("winapi-i686-pc-windows-gnu", "0.4.0"), ("winapi-x86_64-pc-windows-gnu", "0.4.0")]; 295 | //! /// The effective dependencies as a comma-separated string. 296 | //! pub static DEPENDENCIES_STR: &str = "autocfg 1.0.0, bitflags 1.2.1, built 0.4.1, cargo-lock 4.0.1, cc 1.0.54, cfg-if 0.1.10, chrono 0.4.11, example_project 0.1.0, git2 0.13.6, idna 0.2.0, jobserver 0.1.21, libc 0.2.71, libgit2-sys 0.12.6+1.0.0, libz-sys 1.0.25, log 0.4.8, matches 0.1.8, num-integer 0.1.42, num-traits 0.2.11, percent-encoding 2.1.0, pkg-config 0.3.17, proc-macro2 1.0.17, quote 1.0.6, semver 1.0.0, serde 1.0.110, serde_derive 1.0.110, smallvec 1.4.0, syn 1.0.25, time 0.1.43, toml 0.5.6, unicode-bidi 0.3.4, unicode-normalization 0.1.12, unicode-xid 0.2.0, url 2.1.1, vcpkg 0.2.8, winapi 0.3.8, winapi-i686-pc-windows-gnu 0.4.0, winapi-x86_64-pc-windows-gnu 0.4.0"; 297 | //! ``` 298 | //! 299 | //! ### `dependency-tree` (implies `cargo-lock`) 300 | //! Solve the dependency-graph in `Cargo.lock` to discern direct and indirect 301 | //! dependencies. 302 | //! 303 | //! "Direct" dependencies are those which the root-package(s) depends on. 304 | //! "Indirect" dependencies are those which are not direct dependencies. 305 | //! 306 | //! ``` 307 | //! /// An array of direct dependencies as documented by `Cargo.lock`. 308 | //! pub static DIRECT_DEPENDENCIES: [(&str, &str); 1] = [("built", "0.6.1")]; 309 | //! /// The direct dependencies as a comma-separated string. 310 | //! pub static DIRECT_DEPENDENCIES_STR: &str = r"built 0.6.1"; 311 | //! 312 | //! /// An array of indirect dependencies as documented by `Cargo.lock`. 313 | //! pub static INDIRECT_DEPENDENCIES: [(&str, &str); 64] = [("android-tzdata", "0.1.1"), ("android_system_properties", "0.1.5"), ("autocfg", "1.1.0"), ("bitflags", "2.4.0"), ("bumpalo", "3.13.0"), ("cargo-lock", "9.0.0"), ("cc", "1.0.83"), ("cfg-if", "1.0.0"), ("chrono", "0.4.29"), ("core-foundation-sys", "0.8.4"), ("equivalent", "1.0.1"), ("example_project", "0.1.0"), ("fixedbitset", "0.4.2"), ("form_urlencoded", "1.2.0"), ("git2", "0.18.0"), ("hashbrown", "0.14.0"), ("iana-time-zone", "0.1.57"), ("iana-time-zone-haiku", "0.1.2"), ("idna", "0.4.0"), ("indexmap", "2.0.0"), ("jobserver", "0.1.26"), ("js-sys", "0.3.64"), ("libc", "0.2.147"), ("libgit2-sys", "0.16.1+1.7.1"), ("libz-sys", "1.1.12"), ("log", "0.4.20"), ("memchr", "2.6.3"), ("num-traits", "0.2.16"), ("once_cell", "1.18.0"), ("percent-encoding", "2.3.0"), ("petgraph", "0.6.4"), ("pkg-config", "0.3.27"), ("proc-macro2", "1.0.66"), ("quote", "1.0.33"), ("semver", "1.0.18"), ("serde", "1.0.188"), ("serde_derive", "1.0.188"), ("serde_spanned", "0.6.3"), ("syn", "2.0.31"), ("tinyvec", "1.6.0"), ("tinyvec_macros", "0.1.1"), ("toml", "0.7.6"), ("toml_datetime", "0.6.3"), ("toml_edit", "0.19.14"), ("unicode-bidi", "0.3.13"), ("unicode-ident", "1.0.11"), ("unicode-normalization", "0.1.22"), ("url", "2.4.1"), ("vcpkg", "0.2.15"), ("wasm-bindgen", "0.2.87"), ("wasm-bindgen-backend", "0.2.87"), ("wasm-bindgen-macro", "0.2.87"), ("wasm-bindgen-macro-support", "0.2.87"), ("wasm-bindgen-shared", "0.2.87"), ("windows", "0.48.0"), ("windows-targets", "0.48.5"), ("windows_aarch64_gnullvm", "0.48.5"), ("windows_aarch64_msvc", "0.48.5"), ("windows_i686_gnu", "0.48.5"), ("windows_i686_msvc", "0.48.5"), ("windows_x86_64_gnu", "0.48.5"), ("windows_x86_64_gnullvm", "0.48.5"), ("windows_x86_64_msvc", "0.48.5"), ("winnow", "0.5.15")]; 314 | //! /// The indirect dependencies as a comma-separated string. 315 | //! pub static INDIRECT_DEPENDENCIES_STR: &str = r"android-tzdata 0.1.1, android_system_properties 0.1.5, autocfg 1.1.0, bitflags 2.4.0, bumpalo 3.13.0, cargo-lock 9.0.0, cc 1.0.83, cfg-if 1.0.0, chrono 0.4.29, core-foundation-sys 0.8.4, equivalent 1.0.1, example_project 0.1.0, fixedbitset 0.4.2, form_urlencoded 1.2.0, git2 0.18.0, hashbrown 0.14.0, iana-time-zone 0.1.57, iana-time-zone-haiku 0.1.2, idna 0.4.0, indexmap 2.0.0, jobserver 0.1.26, js-sys 0.3.64, libc 0.2.147, libgit2-sys 0.16.1+1.7.1, libz-sys 1.1.12, log 0.4.20, memchr 2.6.3, num-traits 0.2.16, once_cell 1.18.0, percent-encoding 2.3.0, petgraph 0.6.4, pkg-config 0.3.27, proc-macro2 1.0.66, quote 1.0.33, semver 1.0.18, serde 1.0.188, serde_derive 1.0.188, serde_spanned 0.6.3, syn 2.0.31, tinyvec 1.6.0, tinyvec_macros 0.1.1, toml 0.7.6, toml_datetime 0.6.3, toml_edit 0.19.14, unicode-bidi 0.3.13, unicode-ident 1.0.11, unicode-normalization 0.1.22, url 2.4.1, vcpkg 0.2.15, wasm-bindgen 0.2.87, wasm-bindgen-backend 0.2.87, wasm-bindgen-macro 0.2.87, wasm-bindgen-macro-support 0.2.87, wasm-bindgen-shared 0.2.87, windows 0.48.0, windows-targets 0.48.5, windows_aarch64_gnullvm 0.48.5, windows_aarch64_msvc 0.48.5, windows_i686_gnu 0.48.5, windows_i686_msvc 0.48.5, windows_x86_64_gnu 0.48.5, windows_x86_64_gnullvm 0.48.5, windows_x86_64_msvc 0.48.5, winnow 0.5.15"; 316 | //! ``` 317 | //! 318 | //! ### `git2` 319 | //! Try to open the git-repository at `manifest_location` and retrieve `HEAD` 320 | //! tag or commit id. 321 | //! 322 | //! Notice that `GIT_HEAD_REF` is `None` if `HEAD` is detached or not valid UTF-8. 323 | //! 324 | //! Continuous Integration platforms like `Travis` and `AppVeyor` will 325 | //! do shallow clones, causing `libgit2` to be unable to get a meaningful 326 | //! result. `GIT_VERSION` and `GIT_DIRTY` will therefore always be `None` if 327 | //! a CI-platform is detected. 328 | //! ``` 329 | //! /// If the crate was compiled from within a git-repository, 330 | //! /// `GIT_VERSION` contains HEAD's tag. The short commit id is used 331 | //! /// if HEAD is not tagged. 332 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_GIT_VERSION`. 333 | //! pub static GIT_VERSION: Option<&str> = Some("0.4.1-10-gca2af4f"); 334 | //! 335 | //! /// If the repository had dirty/staged files. 336 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_GIT_DIRTY`. 337 | //! pub static GIT_DIRTY: Option = Some(true); 338 | //! 339 | //! /// If the crate was compiled from within a git-repository, 340 | //! /// `GIT_HEAD_REF` contains full name to the reference pointed to by 341 | //! /// HEAD (e.g.: `refs/heads/master`). If HEAD is detached or the branch 342 | //! /// name is not valid UTF-8 `None` will be stored. 343 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_GIT_HEAD_REF`. 344 | //! pub static GIT_HEAD_REF: Option<&str> = Some("refs/heads/master"); 345 | //! 346 | //! /// If the crate was compiled from within a git-repository, 347 | //! /// `GIT_COMMIT_HASH` contains HEAD's full commit SHA-1 hash. 348 | //! /// Can be overridden with `BUILT_OVERRIDE_{pkg_name}_GIT_GIT_COMMIT_HASH`. 349 | //! pub static GIT_COMMIT_HASH: Option<&str> = Some("ca2af4f11bb8f4f6421c4cccf428bf4862573daf"); 350 | //! 351 | //! /// If the crate was compiled from within a git-repository, 352 | //! /// `GIT_COMMIT_HASH_SHORT` contains HEAD's short commit SHA-1 hash. 353 | //! /// Can be overriden using `BUILT_OVERRIDE_{pkg_name}_GIT_COMMIT_HASH_SHORT`. 354 | //! pub static GIT_COMMIT_HASH_SHORT: Option<&str> = Some("ca2af4f"); 355 | //! ``` 356 | //! 357 | //! ### `chrono` 358 | //! 359 | //! The build-time is recorded as `BUILT_TIME_UTC`. If `built` is included as a runtime-dependency, 360 | //! it can parse the string-representation into a `time:Tm` with the help of 361 | //! `built::util::strptime()`. 362 | //! 363 | //! `built` honors the environment variable `SOURCE_DATE_EPOCH`. If the variable is defined and 364 | //! parses to a valid UTC timestamp, that build-time is used instead of the current local time. 365 | //! The variable is silently ignored if defined but but does not parse to a valid UTC timestamp. 366 | //! 367 | //! ``` 368 | //! /// The built-time in RFC2822, UTC 369 | //! /// Can be overridden with`BUILT_OVERRIDE_BUILT_TIME_UTC`; the override takes precedence 370 | //! /// over `SOURCE_DATE_EPOCH`; it *must* parse via `chrono::DateTime::parse_from_rfc2822()`. 371 | //! pub static BUILT_TIME_UTC: &str = "Wed, 27 May 2020 18:12:39 +0000"; 372 | //! ``` 373 | 374 | #[cfg(feature = "cargo-lock")] 375 | mod dependencies; 376 | mod environment; 377 | #[cfg(feature = "git2")] 378 | mod git; 379 | #[cfg(feature = "chrono")] 380 | mod krono; 381 | pub mod util; 382 | 383 | use std::{env, fmt, fs, io, io::Write, path}; 384 | 385 | #[cfg(feature = "semver")] 386 | pub use semver; 387 | 388 | #[cfg(feature = "chrono")] 389 | pub use chrono; 390 | 391 | pub use environment::CIPlatform; 392 | 393 | #[doc = include_str!("../README.md")] 394 | #[allow(dead_code)] 395 | type _READMETEST = (); 396 | 397 | /// If `SOURCE_DATE_EPOCH` is defined, it's value is used instead of 398 | /// `chrono::..::now()` as `BUILT_TIME_UTC`. 399 | /// The presence of `SOURCE_DATE_EPOCH` also soft-indicates that a 400 | /// reproducible build is desired, which we may or may not be able 401 | /// to honor. 402 | const SOURCE_DATE_EPOCH: &str = "SOURCE_DATE_EPOCH"; 403 | 404 | macro_rules! write_variable { 405 | ($writer:expr, $name:expr, $datatype:expr, $value:expr, $doc:expr) => { 406 | writeln!( 407 | $writer, 408 | "#[allow(clippy::needless_raw_string_hashes)]\n#[doc=r#\"{}\"#]\n#[allow(dead_code)]\npub static {}: {} = {};", 409 | $doc, $name, $datatype, $value 410 | )?; 411 | }; 412 | } 413 | pub(crate) use write_variable; 414 | 415 | macro_rules! write_str_variable { 416 | ($writer:expr, $name:expr, $value:expr, $doc:expr) => { 417 | write_variable!( 418 | $writer, 419 | $name, 420 | "&str", 421 | format_args!("\"{}\"", $value.escape_default()), 422 | $doc 423 | ); 424 | }; 425 | } 426 | pub(crate) use write_str_variable; 427 | 428 | pub(crate) fn fmt_option_str(o: Option) -> String { 429 | match o { 430 | Some(s) => format!("Some(\"{s}\")"), 431 | None => "None".to_owned(), 432 | } 433 | } 434 | 435 | /// Writes rust-code describing the crate at `manifest_location` to a new file named `dst`. 436 | /// 437 | /// # Errors 438 | /// The function returns an error if the file at `dst` already exists or can't 439 | /// be written to. This should not be a concern if the filename points to 440 | /// `OUR_DIR`. 441 | pub fn write_built_file_with_opts( 442 | #[cfg(any(feature = "cargo-lock", feature = "git2"))] manifest_location: Option<&path::Path>, 443 | dst: &path::Path, 444 | ) -> io::Result<()> { 445 | let mut built_file = fs::File::create(dst)?; 446 | built_file.write_all( 447 | r#"// 448 | // EVERYTHING BELOW THIS POINT WAS AUTO-GENERATED DURING COMPILATION. DO NOT MODIFY. 449 | // 450 | "# 451 | .as_ref(), 452 | )?; 453 | 454 | let envmap = environment::EnvironmentMap::new(); 455 | envmap.write_ci(&built_file)?; 456 | envmap.write_env(&built_file)?; 457 | envmap.write_features(&built_file)?; 458 | envmap.write_compiler_version(&built_file)?; 459 | envmap.write_cfg(&built_file)?; 460 | 461 | #[cfg(feature = "git2")] 462 | { 463 | if let Some(manifest_location) = manifest_location { 464 | git::write_git_version(manifest_location, &envmap, &built_file)?; 465 | } 466 | } 467 | 468 | #[cfg(feature = "cargo-lock")] 469 | if let Some(manifest_location) = manifest_location { 470 | dependencies::write_dependencies(manifest_location, &built_file)?; 471 | } 472 | 473 | #[cfg(feature = "chrono")] 474 | krono::write_time(&built_file, &envmap)?; 475 | 476 | let mut used_override_vars = envmap.used_override_vars().collect::>(); 477 | used_override_vars.sort_unstable(); 478 | write_variable!( 479 | built_file, 480 | "OVERRIDE_VARIABLES_USED", 481 | format_args!("[&str; {}]", used_override_vars.len()), 482 | util::ArrayDisplay(&used_override_vars, |t, f| write!( 483 | f, 484 | "\"{}\"", 485 | t.escape_default() 486 | )), 487 | "The override-variables that were used during compilation." 488 | ); 489 | 490 | built_file.write_all( 491 | r#"// 492 | // EVERYTHING ABOVE THIS POINT WAS AUTO-GENERATED DURING COMPILATION. DO NOT MODIFY. 493 | // 494 | "# 495 | .as_ref(), 496 | )?; 497 | 498 | let unused_override_vars = envmap.unused_override_vars().collect::>().join(", "); 499 | if !unused_override_vars.is_empty() { 500 | println!("cargo::warning=At least one environment variable looks like an override-variable but was ignored by built: `{unused_override_vars}`. Typo?"); 501 | } 502 | 503 | Ok(()) 504 | } 505 | 506 | /// A shorthand for calling `write_built_file_with_opts()` with `CARGO_MANIFEST_DIR` and 507 | /// `[OUT_DIR]/built.rs`. 508 | /// 509 | /// # Errors 510 | /// Same as `write_built_file_with_opts()`. 511 | /// 512 | /// # Panics 513 | /// If `CARGO_MANIFEST_DIR` or `OUT_DIR` are not set. 514 | pub fn write_built_file() -> io::Result<()> { 515 | let dst = path::Path::new(&env::var("OUT_DIR").expect("OUT_DIR not set")).join("built.rs"); 516 | write_built_file_with_opts( 517 | #[cfg(any(feature = "cargo-lock", feature = "git2"))] 518 | Some( 519 | env::var("CARGO_MANIFEST_DIR") 520 | .expect("CARGO_MANIFEST_DIR") 521 | .as_ref(), 522 | ), 523 | &dst, 524 | )?; 525 | Ok(()) 526 | } 527 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Various convenience functions for `built` at runtime. 2 | 3 | use std::fmt; 4 | use std::fmt::Write; 5 | 6 | #[cfg(feature = "git2")] 7 | pub use crate::git::{get_repo_description, get_repo_head}; 8 | 9 | #[cfg(feature = "chrono")] 10 | pub use crate::krono::strptime; 11 | 12 | /// Parses version-strings with `semver::Version::parse()`. 13 | /// 14 | /// This function is only available if `built` was compiled with the 15 | /// `semver` feature. 16 | /// 17 | /// The function takes a reference to an array of names and version numbers as 18 | /// serialized by `built` and returns an iterator over the unchanged names 19 | /// and parsed version numbers. 20 | /// 21 | /// ``` 22 | /// pub mod build_info { 23 | /// pub static DEPENDENCIES: [(&'static str, &'static str); 1] = [("built", "0.1.0")]; 24 | /// } 25 | /// 26 | /// let deps = build_info::DEPENDENCIES; 27 | /// assert!(built::util::parse_versions(&deps) 28 | /// .any(|(name, ver)| name == "built" && 29 | /// ver >= semver::Version::parse("0.1.0").unwrap())); 30 | /// ``` 31 | /// 32 | /// # Panics 33 | /// If a version can't be parsed by `semver::Version::parse()`. This should never 34 | /// happen with version strings provided by Cargo and `built`. 35 | #[cfg(feature = "semver")] 36 | pub fn parse_versions<'a, T>( 37 | name_and_versions: T, 38 | ) -> impl Iterator 39 | where 40 | T: IntoIterator, 41 | { 42 | fn parse_version<'a>(t: &'a (&'a str, &'a str)) -> (&'a str, semver::Version) { 43 | (t.0, t.1.parse().unwrap()) 44 | } 45 | name_and_versions.into_iter().map(parse_version) 46 | } 47 | 48 | /// Detect execution on various Continuous Integration platforms. 49 | /// 50 | /// CI-platforms are detected by the presence of known environment variables. 51 | /// This allows to detect specific CI-platform (like `GitLab`); various 52 | /// generic environment variables are also checked, which may result in 53 | /// `CIPlatform::Generic`. 54 | /// 55 | /// Since some platforms have fairly generic environment variables to begin with 56 | /// (e.g. `TASK_ID`), this function may have false positives. 57 | #[must_use] 58 | pub fn detect_ci() -> Option { 59 | crate::environment::EnvironmentMap::new().detect_ci() 60 | } 61 | 62 | pub(crate) trait ParseFromEnv<'a> 63 | where 64 | Self: Sized, 65 | { 66 | type Err: std::fmt::Debug; 67 | 68 | fn parse_from_env(s: &'a str) -> Result; 69 | } 70 | 71 | impl<'a> ParseFromEnv<'a> for &'a str { 72 | type Err = std::convert::Infallible; 73 | 74 | fn parse_from_env(s: &'a str) -> Result { 75 | Ok(s) 76 | } 77 | } 78 | 79 | macro_rules! parsefromenv_impl { 80 | ($tie:ty) => { 81 | impl ParseFromEnv<'_> for $tie { 82 | type Err = ::Err; 83 | 84 | fn parse_from_env(s: &str) -> Result { 85 | s.parse() 86 | } 87 | } 88 | }; 89 | ($($tie:ty),+) => { 90 | $( 91 | parsefromenv_impl!($tie); 92 | )+ 93 | }; 94 | } 95 | parsefromenv_impl!(i64, i32, i16, i8, u64, u32, u16, u8, bool, String); 96 | 97 | impl<'a, T> ParseFromEnv<'a> for Vec 98 | where 99 | T: ParseFromEnv<'a>, 100 | { 101 | type Err = >::Err; 102 | 103 | fn parse_from_env(s: &'a str) -> Result { 104 | s.split(',') 105 | .map(|chunk| T::parse_from_env(chunk.trim())) 106 | .collect() 107 | } 108 | } 109 | 110 | impl<'a, T> ParseFromEnv<'a> for Option 111 | where 112 | T: ParseFromEnv<'a>, 113 | { 114 | type Err = >::Err; 115 | 116 | fn parse_from_env(s: &'a str) -> Result { 117 | if s == "BUILT_OVERRIDE_NONE" { 118 | Ok(None) 119 | } else { 120 | Ok(Some(T::parse_from_env(s)?)) 121 | } 122 | } 123 | } 124 | 125 | pub(crate) struct ArrayDisplay<'a, T, F>(pub &'a [T], pub F) 126 | where 127 | F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result; 128 | 129 | impl fmt::Display for ArrayDisplay<'_, T, F> 130 | where 131 | F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result, 132 | { 133 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 134 | f.write_char('[')?; 135 | for (i, v) in self.0.iter().enumerate() { 136 | if i != 0 { 137 | f.write_str(", ")?; 138 | } 139 | (self.1)(v, f)?; 140 | } 141 | f.write_char(']') 142 | } 143 | } 144 | 145 | #[cfg(feature = "cargo-lock")] 146 | pub(crate) struct TupleArrayDisplay<'a, T>(pub &'a [(T, T)]); 147 | 148 | #[cfg(feature = "cargo-lock")] 149 | impl fmt::Display for TupleArrayDisplay<'_, T> 150 | where 151 | T: AsRef, 152 | { 153 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 154 | write!( 155 | f, 156 | "{}", 157 | ArrayDisplay(self.0, |(a, b), fmt| write!( 158 | fmt, 159 | r#"("{}", "{}")"#, 160 | a.as_ref().escape_default(), 161 | b.as_ref().escape_default() 162 | )) 163 | ) 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | use super::*; 170 | 171 | #[test] 172 | fn test_parse_env() { 173 | assert_eq!(String::parse_from_env("foo"), Ok("foo".to_owned())); 174 | assert_eq!(<&str>::parse_from_env("foo"), Ok("foo")); 175 | 176 | assert_eq!(i64::parse_from_env("123"), Ok(123)); 177 | assert_eq!(i32::parse_from_env("123"), Ok(123)); 178 | assert_eq!(i16::parse_from_env("123"), Ok(123)); 179 | assert_eq!(i8::parse_from_env("123"), Ok(123)); 180 | assert_eq!(u64::parse_from_env("123"), Ok(123)); 181 | assert_eq!(u32::parse_from_env("123"), Ok(123)); 182 | assert_eq!(u16::parse_from_env("123"), Ok(123)); 183 | assert_eq!(u8::parse_from_env("123"), Ok(123)); 184 | 185 | assert_eq!(i8::parse_from_env("-123"), Ok(-123)); 186 | assert!(u8::parse_from_env("-123").is_err()); 187 | assert!(i32::parse_from_env("foo").is_err()); 188 | assert!(i32::parse_from_env("").is_err()); 189 | 190 | assert_eq!(bool::parse_from_env("true"), Ok(true)); 191 | assert_eq!(bool::parse_from_env("false"), Ok(false)); 192 | assert!(bool::parse_from_env("").is_err()); 193 | assert!(bool::parse_from_env("foo").is_err()); 194 | 195 | assert!(Option::::parse_from_env("").is_err()); 196 | assert_eq!(Option::<&str>::parse_from_env(""), Ok(Some(""))); 197 | assert_eq!( 198 | Option::parse_from_env("BUILT_OVERRIDE_NONE"), 199 | Ok(Option::::None) 200 | ); 201 | assert_eq!(Option::parse_from_env("123"), Ok(Some(123u8))); 202 | assert_eq!(Option::::parse_from_env("true"), Ok(Some(true))); 203 | assert!(Option::::parse_from_env("123").is_err()); 204 | 205 | assert_eq!( 206 | Vec::<&str>::parse_from_env("foo, b a r , foo"), 207 | Ok(vec!["foo", "b a r", "foo"]) 208 | ); 209 | assert_eq!( 210 | Vec::::parse_from_env("123,456,789"), 211 | Ok(vec![123, 456, 789]) 212 | ); 213 | 214 | assert_eq!( 215 | Option::parse_from_env("123,456"), 216 | Ok(Some(vec![123u32, 456u32])) 217 | ); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /tests/testbox_tests.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections, env, ffi, fs, 3 | io::{self, Write}, 4 | path, process, 5 | }; 6 | 7 | struct Project { 8 | root: tempfile::TempDir, 9 | files: Vec<(path::PathBuf, Vec)>, 10 | env: collections::HashMap, 11 | } 12 | 13 | impl Project { 14 | fn new() -> Project { 15 | Project { 16 | root: tempfile::tempdir().unwrap(), 17 | files: Default::default(), 18 | env: Default::default(), 19 | } 20 | } 21 | 22 | fn add_file, C: Into>>( 23 | &mut self, 24 | name: N, 25 | content: C, 26 | ) -> &mut Self { 27 | self.files.push((name.into(), content.into())); 28 | self 29 | } 30 | 31 | fn set_env(&mut self, k: K, v: V) -> &mut Self 32 | where 33 | K: Into, 34 | V: Into, 35 | { 36 | self.env.insert(k.into(), v.into()); 37 | self 38 | } 39 | 40 | fn bootstrap(&mut self) -> &mut Self { 41 | let built_root = get_built_root(); 42 | let features = if cfg!(feature = "git2") { 43 | r#"["git2"]"# 44 | } else { 45 | "[]" 46 | }; 47 | 48 | self.add_file( 49 | "Cargo.toml", 50 | format!( 51 | r#" 52 | [package] 53 | name = "testbox" 54 | version = "0.0.1" 55 | build = "build.rs" 56 | 57 | [build-dependencies] 58 | built = {{ path = "{}", features = {} }}"#, 59 | built_root.display().to_string().escape_default(), 60 | &features, 61 | ), 62 | ) 63 | .add_file( 64 | "build.rs", 65 | r#" 66 | fn main() { 67 | built::write_built_file().expect("writing failed"); 68 | }"#, 69 | ); 70 | self 71 | } 72 | 73 | /// Hold on to the tempdir, it will be removed when dropped! 74 | fn create(self) -> io::Result { 75 | fs::DirBuilder::new() 76 | .create(self.root.path().join("src")) 77 | .unwrap(); 78 | for (name, content) in self.files { 79 | let fname = self.root.path().join(name); 80 | assert!(fname.is_absolute()); 81 | fs::create_dir_all(fname.parent().unwrap()).unwrap(); 82 | let mut file = fs::File::create(fname)?; 83 | file.write_all(&content)?; 84 | } 85 | Ok(self.root) 86 | } 87 | 88 | fn create_and_run(mut self, extra_args: &[&str]) -> (String, String) { 89 | let env = std::mem::take(&mut self.env); 90 | let root = self.create().expect("Creating the project failed"); 91 | Self::run(root.as_ref(), extra_args, env) 92 | } 93 | 94 | fn create_and_build(self, extra_args: &[&str]) { 95 | let root = self.create().expect("Creating the project failed"); 96 | Self::build(root.as_ref(), extra_args); 97 | } 98 | 99 | fn run(root: &std::path::Path, extra_args: &[&str], env: I) -> (String, String) 100 | where 101 | I: IntoIterator, 102 | K: AsRef, 103 | V: AsRef, 104 | { 105 | let cargo_result = process::Command::new("cargo") 106 | .current_dir(root) 107 | .arg("run") 108 | .args(extra_args) 109 | .envs(env) 110 | .output() 111 | .expect("cargo failed"); 112 | let stderr = String::from_utf8_lossy(&cargo_result.stderr); 113 | let stdout = String::from_utf8_lossy(&cargo_result.stdout); 114 | assert!( 115 | cargo_result.status.success(), 116 | "cargo failed with {}", 117 | stderr 118 | ); 119 | assert!(stdout.contains("builttestsuccess")); 120 | (stderr.into_owned(), stdout.into_owned()) 121 | } 122 | 123 | fn build(root: &std::path::Path, extra_args: &[&str]) { 124 | let cargo_result = process::Command::new("cargo") 125 | .current_dir(root) 126 | .arg("build") 127 | .args(extra_args) 128 | .output() 129 | .expect("cargo failed"); 130 | assert!( 131 | cargo_result.status.success(), 132 | "cargo failed with {}", 133 | String::from_utf8_lossy(&cargo_result.stderr) 134 | ); 135 | } 136 | 137 | #[cfg(feature = "git2")] 138 | fn init_git(&self) -> git2::Repository { 139 | git2::Repository::init_opts( 140 | &self.root, 141 | git2::RepositoryInitOptions::new() 142 | .external_template(false) 143 | .mkdir(false) 144 | .no_reinit(true) 145 | .mkpath(false), 146 | ) 147 | .expect("git-init failed") 148 | } 149 | } 150 | 151 | /// Tries to find built's Cargo.toml, panics if it ends up in / 152 | fn get_built_root() -> path::PathBuf { 153 | env::current_exe() 154 | .map(|path| { 155 | let mut path = path; 156 | loop { 157 | if path.join("Cargo.toml").exists() { 158 | break; 159 | } 160 | path = path::PathBuf::from(path.parent().unwrap()); 161 | } 162 | path 163 | }) 164 | .unwrap() 165 | } 166 | 167 | #[test] 168 | #[ignore = "requires target x86_64-unknown-none"] 169 | fn nostd_testbox() { 170 | let mut p = Project::new(); 171 | 172 | let built_root = get_built_root(); 173 | 174 | p.add_file( 175 | "Cargo.toml", 176 | format!( 177 | r#" 178 | [package] 179 | name = "nostd_testbox" 180 | version = "1.2.3-rc1" 181 | authors = ["Joe", "Bob"] 182 | build = "build.rs" 183 | description = "xobtset" 184 | homepage = "localhost" 185 | repository = "https://dev.example.com/sources/testbox/" 186 | license = "MIT" 187 | 188 | [build-dependencies] 189 | built = {{ path = "{}", default_features=false }} 190 | 191 | [profile.dev] 192 | panic = "abort" 193 | 194 | [profile.release] 195 | panic = "abort" 196 | 197 | [features] 198 | default = ["SuperAwesome", "MegaAwesome"] 199 | SuperAwesome = [] 200 | MegaAwesome = []"#, 201 | built_root.display().to_string().escape_default(), 202 | ), 203 | ) 204 | .add_file( 205 | "build.rs", 206 | r#" 207 | fn main() { 208 | built::write_built_file().unwrap(); 209 | }"#, 210 | ) 211 | .add_file( 212 | "src/main.rs", 213 | r#" 214 | #![no_main] 215 | #![no_std] 216 | 217 | use core::panic::PanicInfo; 218 | 219 | mod built_info { 220 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 221 | } 222 | 223 | #[panic_handler] 224 | fn panic(_panic: &PanicInfo<'_>) -> ! { 225 | loop {} 226 | } 227 | 228 | #[no_mangle] 229 | pub unsafe extern "C" fn main() -> ! { 230 | loop {} 231 | } 232 | "#, 233 | ); 234 | 235 | p.create_and_build(&["--target", "x86_64-unknown-none"]); 236 | } 237 | 238 | #[test] 239 | fn unused_override() { 240 | let mut p = Project::new(); 241 | p.bootstrap() 242 | .add_file( 243 | "src/main.rs", 244 | r#" 245 | mod built_info { 246 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 247 | } 248 | 249 | fn main() { 250 | println!("builttestsuccess"); 251 | } 252 | "#, 253 | ) 254 | .set_env("BUILT_OVERRIDE_testbox_FOO", "BAR") 255 | .set_env("BUILT_OVERRIDE_testbox1_FOO", "BAR"); 256 | let (stderr, _) = p.create_and_run(&[]); 257 | assert!(stderr.contains("but was ignored by built: `BUILT_OVERRIDE_testbox_FOO`.")); 258 | assert!(!stderr.contains("but was ignored by built: `BUILT_OVERRIDE_testbox1_FOO`.")); 259 | } 260 | 261 | #[test] 262 | fn minimal_testbox() { 263 | let mut p = Project::new(); 264 | 265 | let built_root = get_built_root(); 266 | 267 | p.add_file( 268 | "Cargo.toml", 269 | format!( 270 | r#" 271 | [package] 272 | name = "minimal_testbox" 273 | version = "1.2.3-rc1" 274 | authors = ["Joe", "Bob"] 275 | build = "build.rs" 276 | description = "xobtset" 277 | homepage = "localhost" 278 | repository = "https://dev.example.com/sources/testbox/" 279 | license = "MIT" 280 | 281 | [dependencies] 282 | built = {{ path = "{built_root}", default_features=false }} 283 | 284 | [build-dependencies] 285 | built = {{ path = "{built_root}", default_features=false }} 286 | 287 | [features] 288 | default = ["SuperAwesome", "MegaAwesome"] 289 | SuperAwesome = [] 290 | MegaAwesome = []"#, 291 | built_root = built_root.display().to_string().escape_default() 292 | ), 293 | ); 294 | 295 | p.add_file( 296 | "build.rs", 297 | r#" 298 | 299 | fn main() { 300 | built::write_built_file().unwrap(); 301 | }"#, 302 | ); 303 | 304 | p.add_file( 305 | "src/main.rs", 306 | r#" 307 | //! The minimal testbox. 308 | 309 | mod built_info { 310 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 311 | } 312 | 313 | fn main() { 314 | assert_eq!(built_info::PKG_VERSION, "1.2.3-rc1"); 315 | assert_eq!(built_info::PKG_VERSION_MAJOR, "1"); 316 | assert_eq!(built_info::PKG_VERSION_MINOR, "2"); 317 | assert_eq!(built_info::PKG_VERSION_PATCH, "3"); 318 | assert_eq!(built_info::PKG_VERSION_PRE, "rc1"); 319 | assert_eq!(built_info::PKG_AUTHORS, "Joe:Bob"); 320 | assert_eq!(built_info::PKG_NAME, "minimal_testbox"); 321 | assert_eq!(built_info::PKG_DESCRIPTION, "xobtset"); 322 | assert_eq!(built_info::PKG_HOMEPAGE, "localhost"); 323 | assert_eq!(built_info::PKG_LICENSE, "MIT"); 324 | assert_eq!(built_info::PKG_REPOSITORY, "https://dev.example.com/sources/testbox/"); 325 | assert!(built_info::NUM_JOBS > 0); 326 | assert!(built_info::OPT_LEVEL == "0"); 327 | assert!(built_info::DEBUG); 328 | assert_eq!(built_info::PROFILE, "debug"); 329 | assert_eq!(built_info::FEATURES, 330 | ["DEFAULT", "MEGAAWESOME", "SUPERAWESOME"]); 331 | assert_eq!(built_info::FEATURES_STR, 332 | "DEFAULT, MEGAAWESOME, SUPERAWESOME"); 333 | assert_eq!(built_info::FEATURES_LOWERCASE, 334 | ["default", "megaawesome", "superawesome"]); 335 | assert_eq!(built_info::FEATURES_LOWERCASE_STR, 336 | "default, megaawesome, superawesome"); 337 | assert_ne!(built_info::RUSTC_VERSION, ""); 338 | assert_ne!(built_info::RUSTDOC_VERSION, ""); 339 | assert_ne!(built_info::HOST, ""); 340 | assert_ne!(built_info::TARGET, ""); 341 | assert_ne!(built_info::RUSTC, ""); 342 | assert_ne!(built_info::RUSTDOC, ""); 343 | assert_ne!(built_info::CFG_TARGET_ARCH, ""); 344 | assert_ne!(built_info::CFG_ENDIAN, ""); 345 | assert_ne!(built_info::CFG_FAMILY, ""); 346 | assert_ne!(built_info::CFG_OS, ""); 347 | assert_ne!(built_info::CFG_POINTER_WIDTH, ""); 348 | // For CFG_ENV, empty string is a possible value. 349 | let _: &'static str = built_info::CFG_ENV; 350 | println!("builttestsuccess"); 351 | }"#, 352 | ); 353 | 354 | p.create_and_run(&[]); 355 | } 356 | 357 | #[test] 358 | fn simple_workspace() { 359 | let mut p = Project::new(); 360 | let built_root = get_built_root(); 361 | 362 | p.add_file("Cargo.toml", "[workspace]\nmembers = ['foobar']"); 363 | p.add_file( 364 | "foobar/Cargo.toml", 365 | format!( 366 | r#"[package] 367 | name = "foobar" 368 | version = "5.6.7" 369 | build = "build.rs" 370 | 371 | [build-dependencies] 372 | built = {{ path = "{}" }}"#, 373 | built_root.display().to_string().escape_default() 374 | ), 375 | ); 376 | p.add_file( 377 | "foobar/build.rs", 378 | "fn main() { built::write_built_file().unwrap(); }", 379 | ); 380 | p.add_file( 381 | "foobar/src/main.rs", 382 | r#" 383 | mod built_info { 384 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 385 | } 386 | fn main() { 387 | assert_eq!(built_info::PKG_VERSION, "5.6.7"); 388 | println!("builttestsuccess"); 389 | } 390 | "#, 391 | ); 392 | p.create_and_run(&[]); 393 | } 394 | 395 | #[test] 396 | #[cfg(all( 397 | feature = "cargo-lock", 398 | feature = "dependency-tree", 399 | feature = "git2", 400 | feature = "chrono", 401 | feature = "semver" 402 | ))] 403 | fn full_testbox() { 404 | let mut p = Project::new(); 405 | 406 | let built_root = get_built_root(); 407 | 408 | p.add_file( 409 | "Cargo.toml", 410 | format!( 411 | r#" 412 | [package] 413 | name = "testbox" 414 | version = "1.2.3-rc1" 415 | authors = ["Joe", "Bob", "Harry:Potter"] 416 | build = "build.rs" 417 | description = "xobtset" 418 | homepage = "localhost" 419 | repository = "https://dev.example.com/sources/testbox/" 420 | license = "MIT" 421 | 422 | [dependencies] 423 | built = {{ path = "{built_root}", features=["cargo-lock", "dependency-tree", "git2", "chrono", "semver"] }} 424 | 425 | [build-dependencies] 426 | built = {{ path = "{built_root}", features=["cargo-lock", "dependency-tree", "git2", "chrono", "semver"] }} 427 | 428 | [features] 429 | default = ["SuperAwesome", "MegaAwesome"] 430 | SuperAwesome = [] 431 | MegaAwesome = []"#, 432 | built_root = built_root.display().to_string().escape_default() 433 | ), 434 | ) 435 | .add_file( 436 | "build.rs", 437 | r#" 438 | fn main() { 439 | built::write_built_file().unwrap(); 440 | }"#, 441 | ) 442 | .set_env("CONTINUOUS_INTEGRATION", "1") 443 | .add_file( 444 | "src/main.rs", 445 | r#" 446 | //! The testbox. 447 | 448 | mod built_info { 449 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 450 | } 451 | 452 | fn main() { 453 | assert_eq!(built_info::GIT_VERSION, None); 454 | assert_eq!(built_info::GIT_DIRTY, None); 455 | assert_eq!(built_info::GIT_COMMIT_HASH, None); 456 | assert_eq!(built_info::GIT_COMMIT_HASH_SHORT, None); 457 | assert_eq!(built_info::GIT_HEAD_REF, None); 458 | assert!(built_info::CI_PLATFORM.is_some()); 459 | assert_eq!(built_info::PKG_VERSION, "1.2.3-rc1"); 460 | assert_eq!(built_info::PKG_VERSION_MAJOR, "1"); 461 | assert_eq!(built_info::PKG_VERSION_MINOR, "2"); 462 | assert_eq!(built_info::PKG_VERSION_PATCH, "3"); 463 | assert_eq!(built_info::PKG_VERSION_PRE, "rc1"); 464 | assert_eq!(built_info::PKG_AUTHORS, "Joe:Bob:Harry:Potter"); 465 | assert_eq!(built_info::PKG_NAME, "testbox"); 466 | assert_eq!(built_info::PKG_DESCRIPTION, "xobtset"); 467 | assert_eq!(built_info::PKG_HOMEPAGE, "localhost"); 468 | assert_eq!(built_info::PKG_LICENSE, "MIT"); 469 | assert_eq!(built_info::PKG_REPOSITORY, "https://dev.example.com/sources/testbox/"); 470 | assert!(built_info::NUM_JOBS > 0); 471 | assert!(built_info::OPT_LEVEL == "0"); 472 | assert!(built_info::DEBUG); 473 | assert_eq!(built_info::PROFILE, "debug"); 474 | assert_eq!(built_info::FEATURES, 475 | ["DEFAULT", "MEGAAWESOME", "SUPERAWESOME"]); 476 | assert_eq!(built_info::FEATURES_STR, 477 | "DEFAULT, MEGAAWESOME, SUPERAWESOME"); 478 | assert_eq!(built_info::FEATURES_LOWERCASE, 479 | ["default", "megaawesome", "superawesome"]); 480 | assert_eq!(built_info::FEATURES_LOWERCASE_STR, 481 | "default, megaawesome, superawesome"); 482 | assert_ne!(built_info::RUSTC_VERSION, ""); 483 | assert_ne!(built_info::RUSTDOC_VERSION, ""); 484 | assert_ne!(built_info::DEPENDENCIES_STR, ""); 485 | assert_ne!(built_info::DIRECT_DEPENDENCIES_STR, ""); 486 | assert_ne!(built_info::INDIRECT_DEPENDENCIES_STR, ""); 487 | assert_ne!(built_info::HOST, ""); 488 | assert_ne!(built_info::TARGET, ""); 489 | assert_ne!(built_info::RUSTC, ""); 490 | assert_ne!(built_info::RUSTDOC, ""); 491 | assert_ne!(built_info::CFG_TARGET_ARCH, ""); 492 | assert_ne!(built_info::CFG_ENDIAN, ""); 493 | assert_ne!(built_info::CFG_FAMILY, ""); 494 | assert_ne!(built_info::CFG_OS, ""); 495 | assert_ne!(built_info::CFG_POINTER_WIDTH, ""); 496 | // For CFG_ENV, empty string is a possible value. 497 | let _: &'static str = built_info::CFG_ENV; 498 | 499 | assert!(built::util::parse_versions(built_info::DEPENDENCIES.iter()) 500 | .any(|(name, ver)| name == "toml" && ver >= built::semver::Version::parse("0.1.0").unwrap())); 501 | 502 | assert_eq!(built_info::DIRECT_DEPENDENCIES.len(), 1); 503 | assert_eq!(built_info::DIRECT_DEPENDENCIES[0].0, "built"); 504 | 505 | assert!((built::chrono::offset::Utc::now() - built::util::strptime(built_info::BUILT_TIME_UTC)).num_days() <= 1); 506 | 507 | assert!(built_info::OVERRIDE_VARIABLES_USED.is_empty()); 508 | 509 | println!("builttestsuccess"); 510 | }"#, 511 | ); 512 | p.create_and_run(&[]); 513 | } 514 | 515 | #[test] 516 | #[cfg(all(feature = "git2", feature = "chrono", feature = "semver"))] 517 | fn overridden_testbox() { 518 | let mut p = Project::new(); 519 | 520 | let built_root = get_built_root(); 521 | 522 | p.add_file( 523 | "Cargo.toml", 524 | format!( 525 | r#" 526 | [package] 527 | name = "testbox" 528 | version = "1.2.3-rc1" 529 | authors = ["Joe", "Bob", "Harry:Potter"] 530 | build = "build.rs" 531 | description = "xobtset" 532 | homepage = "localhost" 533 | repository = "https://dev.example.com/sources/testbox/" 534 | license = "MIT" 535 | 536 | [dependencies] 537 | built = {{ path = "{built_root}", features=["git2", "chrono", "semver"] }} 538 | 539 | [build-dependencies] 540 | built = {{ path = "{built_root}", features=["git2", "chrono", "semver"] }} 541 | 542 | [features] 543 | default = ["SuperAwesome", "MegaAwesome"] 544 | SuperAwesome = [] 545 | MegaAwesome = []"#, 546 | built_root = built_root.display().to_string().escape_default() 547 | ), 548 | ) 549 | .add_file( 550 | "build.rs", 551 | r#" 552 | fn main() { 553 | built::write_built_file().unwrap(); 554 | }"#, 555 | ) 556 | .set_env("BUILT_OVERRIDE_testbox_GIT_VERSION", "GIT_VERSION1") 557 | .set_env("BUILT_OVERRIDE_testbox_GIT_DIRTY", "false") 558 | .set_env("BUILT_OVERRIDE_testbox_GIT_COMMIT_HASH", "1234567890") 559 | .set_env("BUILT_OVERRIDE_testbox_GIT_HEAD_REF", "GIT_REF") 560 | .set_env("BUILT_OVERRIDE_testbox_CI_PLATFORM", "TESTBOXCI") 561 | .set_env("BUILT_OVERRIDE_testbox_PKG_VERSION", "1.2.3.4") 562 | .set_env("BUILT_OVERRIDE_testbox_PKG_VERSION_MAJOR", "abc") 563 | .set_env("BUILT_OVERRIDE_testbox_PKG_VERSION_MINOR", "def") 564 | .set_env("BUILT_OVERRIDE_testbox_PKG_VERSION_PATCH", "ghi") 565 | .set_env("BUILT_OVERRIDE_testbox_PKG_VERSION_PRE", "jkl") 566 | .set_env("BUILT_OVERRIDE_testbox_PKG_AUTHORS", "The council") 567 | .set_env("BUILT_OVERRIDE_testbox_PKG_NAME", "OVERRIDEBOX") 568 | .set_env("BUILT_OVERRIDE_testbox_PKG_DESCRIPTION", "TEST") 569 | .set_env("BUILT_OVERRIDE_testbox_PKG_HOMEPAGE", "foreignhost") 570 | .set_env("BUILT_OVERRIDE_testbox_PKG_LICENSE", "MITv2") 571 | .set_env("BUILT_OVERRIDE_testbox_PKG_REPOSITORY", "8.8.8.8") 572 | .set_env("BUILT_OVERRIDE_testbox_NUM_JOBS", "999") 573 | .set_env( 574 | "BUILT_OVERRIDE_testbox_OPT_LEVEL", 575 | "not_too_fast_not_too_slow", 576 | ) 577 | .set_env("BUILT_OVERRIDE_testbox_DEBUG", "false") 578 | .set_env("BUILT_OVERRIDE_testbox_PROFILE", "MEDIUM") 579 | .set_env( 580 | "BUILT_OVERRIDE_testbox_FEATURES", 581 | "cup_holder,Stereo Sound, dynamic range", 582 | ) 583 | .set_env("BUILT_OVERRIDE_testbox_RUSTC", "overridec") 584 | .set_env("BUILT_OVERRIDE_testbox_RUSTC_VERSION", "overridec v1") 585 | .set_env("BUILT_OVERRIDE_testbox_RUSTDOC", "overridedoc") 586 | .set_env("BUILT_OVERRIDE_testbox_RUSTDOC_VERSION", "overridedoc v1") 587 | .set_env("BUILT_OVERRIDE_testbox_HOST", "overridehost") 588 | .set_env("BUILT_OVERRIDE_testbox_TARGET", "potato") 589 | .set_env("BUILT_OVERRIDE_testbox_CFG_TARGET_ARCH", "potatoes") 590 | .set_env("BUILT_OVERRIDE_testbox_CFG_ENDIAN", "random") 591 | .set_env("BUILT_OVERRIDE_testbox_CFG_FAMILY", "v0") 592 | .set_env("BUILT_OVERRIDE_testbox_CFG_OS", "none") 593 | .set_env("BUILT_OVERRIDE_testbox_CFG_POINTER_WIDTH", "63.9998") 594 | .set_env("BUILT_OVERRIDE_testbox_CFG_ENV", "calm") 595 | .set_env( 596 | "BUILT_OVERRIDE_testbox_BUILT_TIME_UTC", 597 | "Sat, 25 May 2024 12:15:59 +0000", 598 | ) 599 | .add_file( 600 | "src/main.rs", 601 | r#" 602 | //! The testbox. 603 | 604 | mod built_info { 605 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 606 | } 607 | 608 | fn main() { 609 | assert_eq!(built_info::GIT_VERSION, Some("GIT_VERSION1")); 610 | assert_eq!(built_info::GIT_DIRTY, Some(false)); 611 | assert_eq!(built_info::GIT_COMMIT_HASH, Some("1234567890")); 612 | assert_eq!(built_info::GIT_COMMIT_HASH_SHORT, Some("12345678")); 613 | assert_eq!(built_info::GIT_HEAD_REF, Some("GIT_REF")); 614 | assert_eq!(built_info::CI_PLATFORM, Some("TESTBOXCI")); 615 | assert_eq!(built_info::PKG_VERSION, "1.2.3.4"); 616 | assert_eq!(built_info::PKG_VERSION_MAJOR, "abc"); 617 | assert_eq!(built_info::PKG_VERSION_MINOR, "def"); 618 | assert_eq!(built_info::PKG_VERSION_PATCH, "ghi"); 619 | assert_eq!(built_info::PKG_VERSION_PRE, "jkl"); 620 | assert_eq!(built_info::PKG_AUTHORS, "The council"); 621 | assert_eq!(built_info::PKG_NAME, "OVERRIDEBOX"); 622 | assert_eq!(built_info::PKG_DESCRIPTION, "TEST"); 623 | assert_eq!(built_info::PKG_HOMEPAGE, "foreignhost"); 624 | assert_eq!(built_info::PKG_LICENSE, "MITv2"); 625 | assert_eq!(built_info::PKG_REPOSITORY, "8.8.8.8"); 626 | assert_eq!(built_info::NUM_JOBS, 999); 627 | assert_eq!(built_info::OPT_LEVEL, "not_too_fast_not_too_slow"); 628 | assert!(!built_info::DEBUG); 629 | assert_eq!(built_info::PROFILE, "MEDIUM"); 630 | assert_eq!(built_info::FEATURES, 631 | ["Stereo Sound", "cup_holder", "dynamic range"]); 632 | assert_eq!(built_info::FEATURES_STR, 633 | "Stereo Sound, cup_holder, dynamic range"); 634 | assert_eq!(built_info::FEATURES_LOWERCASE, 635 | ["cup_holder", "dynamic range", "stereo sound"]); 636 | assert_eq!(built_info::FEATURES_LOWERCASE_STR, 637 | "cup_holder, dynamic range, stereo sound"); 638 | assert_eq!(built_info::RUSTC, "overridec"); 639 | assert_eq!(built_info::RUSTC_VERSION, "overridec v1"); 640 | assert_eq!(built_info::RUSTDOC, "overridedoc"); 641 | assert_eq!(built_info::RUSTDOC_VERSION, "overridedoc v1"); 642 | assert_eq!(built_info::HOST, "overridehost"); 643 | assert_eq!(built_info::TARGET, "potato"); 644 | assert_eq!(built_info::CFG_TARGET_ARCH, "potatoes"); 645 | assert_eq!(built_info::CFG_ENDIAN, "random"); 646 | assert_eq!(built_info::CFG_FAMILY, "v0"); 647 | assert_eq!(built_info::CFG_OS, "none"); 648 | assert_eq!(built_info::CFG_POINTER_WIDTH, "63.9998"); 649 | assert_eq!(built_info::CFG_ENV, "calm"); 650 | 651 | assert_eq!(built::util::strptime(built_info::BUILT_TIME_UTC).to_rfc2822(), 652 | "Sat, 25 May 2024 12:15:59 +0000"); 653 | assert_eq!(built_info::OVERRIDE_VARIABLES_USED, [ 654 | "BUILT_TIME_UTC", 655 | "CFG_ENDIAN", 656 | "CFG_ENV", 657 | "CFG_FAMILY", 658 | "CFG_OS", 659 | "CFG_POINTER_WIDTH", 660 | "CFG_TARGET_ARCH", 661 | "CI_PLATFORM", 662 | "DEBUG", 663 | "FEATURES", 664 | "GIT_COMMIT_HASH", 665 | "GIT_DIRTY", 666 | "GIT_HEAD_REF", 667 | "GIT_VERSION", 668 | "HOST", 669 | "NUM_JOBS", 670 | "OPT_LEVEL", 671 | "PKG_AUTHORS", 672 | "PKG_DESCRIPTION", 673 | "PKG_HOMEPAGE", 674 | "PKG_LICENSE", 675 | "PKG_NAME", 676 | "PKG_REPOSITORY", 677 | "PKG_VERSION", 678 | "PKG_VERSION_MAJOR", 679 | "PKG_VERSION_MINOR", 680 | "PKG_VERSION_PATCH", 681 | "PKG_VERSION_PRE", 682 | "PROFILE", 683 | "RUSTC", 684 | "RUSTC_VERSION", 685 | "RUSTDOC", 686 | "RUSTDOC_VERSION", 687 | "TARGET", 688 | ]); 689 | println!("builttestsuccess"); 690 | }"#, 691 | ); 692 | p.create_and_run(&[]); 693 | } 694 | 695 | #[test] 696 | #[cfg(feature = "chrono")] 697 | fn source_date_epoch() { 698 | let mut p = Project::new(); 699 | let built_root = get_built_root(); 700 | 701 | p.add_file( 702 | "Cargo.toml", 703 | format!( 704 | r#" 705 | [package] 706 | name = "testbox" 707 | version = "1.2.3-rc1" 708 | authors = ["Joe", "Bob", "Harry:Potter"] 709 | build = "build.rs" 710 | description = "xobtset" 711 | 712 | [dependencies] 713 | built = {{ path = "{built_root}", features=["chrono"] }} 714 | 715 | [build-dependencies] 716 | built = {{ path = "{built_root}", features=["chrono"] }}"#, 717 | built_root = built_root.display().to_string().escape_default() 718 | ), 719 | ) 720 | .add_file( 721 | "build.rs", 722 | r#" 723 | fn main() { 724 | built::write_built_file().unwrap(); 725 | }"#, 726 | ) 727 | .add_file( 728 | "src/main.rs", 729 | r#" 730 | mod built_info { 731 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 732 | } 733 | 734 | fn main() { 735 | assert_eq!(built::util::strptime(built_info::BUILT_TIME_UTC).to_rfc2822(), 736 | "Sat, 25 May 2024 12:15:59 +0000"); 737 | assert_eq!(built_info::NUM_JOBS, 1); 738 | println!("builttestsuccess"); 739 | }"#, 740 | ) 741 | .set_env("SOURCE_DATE_EPOCH", "1716639359"); 742 | p.create_and_run(&[]); 743 | } 744 | 745 | #[test] 746 | #[cfg(feature = "git2")] 747 | fn git_no_git() { 748 | // `root` isn't even a git-repo 749 | let mut p = Project::new(); 750 | p.bootstrap().add_file( 751 | "src/main.rs", 752 | r#" 753 | mod built_info { 754 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 755 | } 756 | 757 | fn main() { 758 | assert_eq!(built_info::GIT_DIRTY, None); 759 | println!("builttestsuccess"); 760 | } 761 | "#, 762 | ); 763 | 764 | p.create_and_run(&[]); 765 | } 766 | 767 | #[test] 768 | #[cfg(feature = "git2")] 769 | fn clean_then_dirty_git() { 770 | let mut p = Project::new(); 771 | p.bootstrap().add_file( 772 | "src/main.rs", 773 | r#" 774 | mod built_info { 775 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 776 | } 777 | 778 | fn main() { 779 | assert_eq!(built_info::GIT_DIRTY, Some(false)); 780 | println!("builttestsuccess"); 781 | } 782 | "#, 783 | ); 784 | let repo = p.init_git(); 785 | let root = p.create().expect("Creating the project failed"); 786 | 787 | let sig = git2::Signature::now("foo", "bar").unwrap(); 788 | 789 | let mut idx = repo.index().unwrap(); 790 | for p in &["src/main.rs", "build.rs"] { 791 | idx.add_path(path::Path::new(p)).unwrap(); 792 | } 793 | idx.write().unwrap(); 794 | repo.commit( 795 | Some("HEAD"), 796 | &sig, 797 | &sig, 798 | "Testing testing 1 2 3", 799 | &repo.find_tree(idx.write_tree().unwrap()).unwrap(), 800 | &[], 801 | ) 802 | .unwrap(); 803 | Project::run( 804 | root.as_ref(), 805 | &[], 806 | std::iter::empty::<(ffi::OsString, ffi::OsString)>(), 807 | ); 808 | 809 | let mut f = std::fs::OpenOptions::new() 810 | .write(true) 811 | .truncate(true) 812 | .open(root.path().join("src/main.rs")) 813 | .unwrap(); 814 | f.write_all( 815 | r#" 816 | mod built_info { 817 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 818 | } 819 | 820 | fn main() { 821 | assert_eq!(built_info::GIT_DIRTY, Some(true)); 822 | assert!(built_info::GIT_COMMIT_HASH.is_some()); 823 | assert!(built_info::GIT_COMMIT_HASH_SHORT.is_some()); 824 | assert!(built_info::GIT_COMMIT_HASH.unwrap().starts_with(built_info::GIT_COMMIT_HASH_SHORT.unwrap())); 825 | println!("builttestsuccess"); 826 | } 827 | "# 828 | .as_bytes(), 829 | ) 830 | .unwrap(); 831 | 832 | Project::run( 833 | root.as_ref(), 834 | &[], 835 | std::iter::empty::<(ffi::OsString, ffi::OsString)>(), 836 | ); 837 | } 838 | 839 | #[test] 840 | #[cfg(feature = "git2")] 841 | fn empty_git() { 842 | // Issue #7, git can be there and still fail 843 | let mut p = Project::new(); 844 | p.bootstrap().add_file( 845 | "src/main.rs", 846 | r#" 847 | mod built_info { 848 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 849 | } 850 | 851 | fn main() { 852 | println!("builttestsuccess"); 853 | } 854 | "#, 855 | ); 856 | p.init_git(); 857 | p.create_and_run(&[]); 858 | } 859 | 860 | #[cfg(target_os = "windows")] 861 | #[test] 862 | fn absolute_paths() { 863 | // Issue #35. Usually binaries we refer to are simply executables names but sometimes they are 864 | // absolute paths, containing backslashes, and everything gets sad on this devilish platform. 865 | 866 | let mut p = Project::new(); 867 | p.bootstrap().add_file( 868 | "src/main.rs", 869 | r#" 870 | mod built_info { 871 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 872 | } 873 | 874 | fn main() { 875 | println!("builttestsuccess"); 876 | } 877 | "#, 878 | ); 879 | 880 | let rustc_exe_buf = String::from_utf8( 881 | process::Command::new("where") 882 | .arg("rustc") 883 | .output() 884 | .expect("Unable to locate absolute path to rustc using `where`") 885 | .stdout, 886 | ) 887 | .unwrap(); 888 | let rustc_exe = rustc_exe_buf.split("\r\n").next().unwrap(); 889 | 890 | // There should at least be `C:\` 891 | assert!(rustc_exe.contains('\\')); 892 | 893 | let root = p.create().expect("Creating the project failed"); 894 | let cargo_result = process::Command::new("cargo") 895 | .current_dir(&root) 896 | .arg("run") 897 | .env("RUSTC", &rustc_exe) 898 | .output() 899 | .expect("cargo failed"); 900 | if !cargo_result.status.success() { 901 | panic!( 902 | "cargo failed with {}", 903 | String::from_utf8_lossy(&cargo_result.stderr) 904 | ); 905 | } 906 | } 907 | --------------------------------------------------------------------------------