├── .gitignore ├── tests ├── crate_itself_fixed_no_std │ ├── .gitignore │ ├── src │ │ └── main.rs │ ├── Cargo.lock │ └── Cargo.toml ├── dependency_default_std │ ├── .gitignore │ ├── src │ │ └── lib.rs │ ├── Cargo.toml │ └── Cargo.lock ├── detect_explicit_use_std │ ├── .gitignore │ ├── src │ │ └── main.rs │ ├── Cargo.toml │ └── Cargo.lock ├── detect_explicit_use_std_all_files │ ├── .gitignore │ ├── src │ │ ├── some_module.rs │ │ └── main.rs │ ├── Cargo.toml │ └── Cargo.lock ├── crate_itself_not_test_no_std │ ├── src │ │ └── main.rs │ ├── Cargo.toml │ └── Cargo.lock ├── detect_explict_use_std.rs ├── crate_itself.rs ├── crate_itself_not_test_no_std.rs ├── dependency_default_std.rs └── detect_explict_use_std_all_files.rs ├── ci ├── before_deploy.ps1 ├── script.sh ├── before_deploy.sh └── install.sh ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── .travis.yml ├── src ├── check.rs ├── util.rs ├── main.rs ├── check_source.rs └── ext.rs ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /tests/crate_itself_fixed_no_std/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /tests/dependency_default_std/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std_all_files/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /tests/crate_itself_fixed_no_std/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | fn main() { 4 | println!("Hello, world!"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std_all_files/src/some_module.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | use std::string::String; 3 | 4 | fn some_fn() {} 5 | -------------------------------------------------------------------------------- /tests/crate_itself_not_test_no_std/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | 3 | fn main() { 4 | println!("Hello, world!"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std_all_files/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | mod some_module; 4 | 5 | fn main() { 6 | println!("Hello, world!"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use std::ops::Add; 4 | use std::string::String; 5 | 6 | fn main() { 7 | println!("Hello, world!"); 8 | } 9 | -------------------------------------------------------------------------------- /tests/dependency_default_std/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | #[test] 6 | fn it_works() { 7 | assert_eq!(2 + 2, 4); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/crate_itself_not_test_no_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate_itself_not_test_no_std" 3 | version = "0.1.0" 4 | authors = ["Joey Yandle "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "detect_explicit_use_std" 3 | version = "0.1.0" 4 | authors = ["Maximilian Goisser "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tests/crate_itself_fixed_no_std/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "crate_itself_fixed_no_std" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /tests/crate_itself_fixed_no_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate_itself_fixed_no_std" 3 | version = "0.1.0" 4 | authors = ["Maximilian Goisser "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "detect_explicit_use_std" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /tests/crate_itself_not_test_no_std/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "crate_itself_not_test_no_std" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std_all_files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "detect_explicit_use_std_all_files" 3 | version = "0.1.0" 4 | authors = ["Maximilian Goisser "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tests/detect_explicit_use_std_all_files/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "detect_explicit_use_std_all_files" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /tests/dependency_default_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dependency_default_std" 3 | version = "0.1.0" 4 | authors = ["Maximilian Goisser "] 5 | 6 | [dependencies] 7 | serde = { version = "1.0.80", default-features = false } 8 | 9 | [features] 10 | default = ["serde/std"] 11 | -------------------------------------------------------------------------------- /tests/dependency_default_std/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "dependency_default_std" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", 8 | ] 9 | 10 | [[package]] 11 | name = "serde" 12 | version = "1.0.80" 13 | source = "registry+https://github.com/rust-lang/crates.io-index" 14 | 15 | [metadata] 16 | "checksum serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "15c141fc7027dd265a47c090bf864cf62b42c4d228bbcf4e51a0c9e2b0d3f7ef" 17 | -------------------------------------------------------------------------------- /ci/before_deploy.ps1: -------------------------------------------------------------------------------- 1 | # This script takes care of packaging the build artifacts that will go in the 2 | # release zipfile 3 | 4 | $SRC_DIR = $PWD.Path 5 | $STAGE = [System.Guid]::NewGuid().ToString() 6 | 7 | Set-Location $ENV:Temp 8 | New-Item -Type Directory -Name $STAGE 9 | Set-Location $STAGE 10 | 11 | $ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip" 12 | 13 | # TODO Update this to package the right artifacts 14 | Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\cargo-nono.exe" '.\' 15 | 16 | 7z a "$ZIP" * 17 | 18 | Push-AppveyorArtifact "$ZIP" 19 | 20 | Remove-Item *.* -Force 21 | Set-Location .. 22 | Remove-Item $STAGE 23 | Set-Location $SRC_DIR 24 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | # This script takes care of testing your crate 2 | 3 | set -ex 4 | 5 | # TODO This is the "test phase", tweak it as you see fit 6 | main() { 7 | cross build --target $TARGET 8 | cross build --target $TARGET --release 9 | 10 | if [ ! -z $DISABLE_TESTS ]; then 11 | return 12 | fi 13 | # ISSUE #37 - Tests don't work quite right on linux CI 14 | if [ $TRAVIS_OS_NAME = linux ]; then 15 | return 16 | fi 17 | 18 | cross test --target $TARGET 19 | cross test --target $TARGET --release 20 | 21 | cross run --target $TARGET 22 | cross run --target $TARGET --release 23 | } 24 | 25 | # we don't run the "test phase" when doing deploys 26 | if [ -z $TRAVIS_TAG ]; then 27 | main 28 | fi 29 | -------------------------------------------------------------------------------- /tests/detect_explict_use_std.rs: -------------------------------------------------------------------------------- 1 | extern crate assert_cmd; 2 | 3 | use assert_cmd::prelude::*; 4 | use std::process::Command; 5 | 6 | #[test] 7 | fn it_fails_with_exit_code_1() { 8 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 9 | .unwrap() 10 | .arg("check") 11 | .current_dir("./tests/detect_explicit_use_std") 12 | .assert() 13 | .code(1); 14 | } 15 | 16 | #[test] 17 | fn it_prints_cause() { 18 | let output = Command::cargo_bin(env!("CARGO_PKG_NAME")) 19 | .unwrap() 20 | .arg("check") 21 | .current_dir("./tests/detect_explicit_use_std") 22 | .output() 23 | .unwrap() 24 | .stdout; 25 | let output = String::from_utf8(output).unwrap(); 26 | 27 | let expected_cause = "Source code contains an explicit `use std::` statement"; 28 | assert!(output.contains(expected_cause)); 29 | } 30 | -------------------------------------------------------------------------------- /ci/before_deploy.sh: -------------------------------------------------------------------------------- 1 | # This script takes care of building your crate and packaging it for release 2 | 3 | set -ex 4 | 5 | main() { 6 | local src=$(pwd) \ 7 | stage= 8 | 9 | case $TRAVIS_OS_NAME in 10 | linux) 11 | stage=$(mktemp -d) 12 | ;; 13 | osx) 14 | stage=$(mktemp -d -t tmp) 15 | ;; 16 | esac 17 | 18 | test -f Cargo.lock || cargo generate-lockfile 19 | 20 | # TODO Update this to build the artifacts that matter to you 21 | RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo build --release 22 | 23 | # TODO Update this to package the right artifacts 24 | # cp target/$TARGET/release/cargo-nono $stage/ 25 | cp target/release/cargo-nono $stage/ 26 | 27 | cd $stage 28 | tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz * 29 | cd $src 30 | 31 | rm -rf $stage 32 | } 33 | 34 | main 35 | -------------------------------------------------------------------------------- /tests/crate_itself.rs: -------------------------------------------------------------------------------- 1 | extern crate assert_cmd; 2 | 3 | use assert_cmd::prelude::*; 4 | use std::process::Command; 5 | 6 | mod crate_itself_fixed_no_std { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_succeeds() { 11 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 12 | .unwrap() 13 | .arg("check") 14 | .current_dir("./tests/crate_itself_fixed_no_std") 15 | .assert() 16 | .success(); 17 | } 18 | } 19 | 20 | #[test] 21 | fn it_prints_checkmark() { 22 | let output = Command::cargo_bin(env!("CARGO_PKG_NAME")) 23 | .unwrap() 24 | .arg("check") 25 | .current_dir("./tests/crate_itself_fixed_no_std") 26 | .output() 27 | .unwrap() 28 | .stdout; 29 | let output = String::from_utf8(output).unwrap(); 30 | 31 | let expected_cause = "crate_itself_fixed_no_std: ✅"; 32 | assert!(output.contains(expected_cause)); 33 | } 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-nono" 3 | version = "0.1.9" 4 | authors = ["Maximilian Goisser "] 5 | description = "Detect (possible) no_std compatibility of your crate and dependencies" 6 | keywords = ["cargo", "no_std", "nono", "subcommand"] 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/hobofan/cargo-nono" 9 | documentation = "https://github.com/hobofan/cargo-nono" 10 | edition = "2018" 11 | 12 | [dependencies] 13 | cargo_metadata = "0.8.2" 14 | serde = "1.0.99" 15 | serde_json = "1.0.40" 16 | console = "0.7.7" 17 | clap = "2.33.0" 18 | glob = "0.3.0" 19 | syn = { version = "1.0.2", default-features = false, features = ["full", "extra-traits", "parsing"] } 20 | quote = { version = "1.0.1", default-features = false } 21 | proc-macro2 = { version = "1.0.1", default-features = false } 22 | 23 | [dev-dependencies] 24 | assert_cmd = "0.11.1" 25 | 26 | [features] 27 | default = [] 28 | proc_macro_spans = [] 29 | -------------------------------------------------------------------------------- /tests/crate_itself_not_test_no_std.rs: -------------------------------------------------------------------------------- 1 | extern crate assert_cmd; 2 | 3 | use assert_cmd::prelude::*; 4 | use std::process::Command; 5 | 6 | mod crate_itself_not_test_no_std { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_succeeds() { 11 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 12 | .unwrap() 13 | .arg("check") 14 | .current_dir("./tests/crate_itself_not_test_no_std") 15 | .assert() 16 | .success(); 17 | } 18 | } 19 | 20 | #[test] 21 | fn it_prints_checkmark() { 22 | let output = Command::cargo_bin(env!("CARGO_PKG_NAME")) 23 | .unwrap() 24 | .arg("check") 25 | .current_dir("./tests/crate_itself_not_test_no_std") 26 | .output() 27 | .unwrap() 28 | .stdout; 29 | let output = String::from_utf8(output).unwrap(); 30 | 31 | let expected_cause = "crate_itself_not_test_no_std: ✅"; 32 | assert!(output.contains(expected_cause)); 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 The cargo-nono contributors. 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 | -------------------------------------------------------------------------------- /tests/dependency_default_std.rs: -------------------------------------------------------------------------------- 1 | extern crate assert_cmd; 2 | 3 | use assert_cmd::prelude::*; 4 | use std::process::Command; 5 | 6 | #[test] 7 | fn it_fails_with_exit_code_1() { 8 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 9 | .unwrap() 10 | .arg("check") 11 | .current_dir("./tests/dependency_default_std") 12 | .assert() 13 | .code(1); 14 | } 15 | 16 | #[test] 17 | fn it_succeeds_with_no_default_features() { 18 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 19 | .unwrap() 20 | .arg("check") 21 | .arg("--no-default-features") 22 | .current_dir("./tests/dependency_default_std") 23 | .assert() 24 | .success(); 25 | } 26 | 27 | #[test] 28 | fn it_prints_cause() { 29 | let output = Command::cargo_bin(env!("CARGO_PKG_NAME")) 30 | .unwrap() 31 | .arg("check") 32 | .current_dir("./tests/dependency_default_std") 33 | .output() 34 | .unwrap() 35 | .stdout; 36 | let output = String::from_utf8(output).unwrap(); 37 | 38 | let expected_cause = 39 | "Caused by implicitly enabled default feature from \"dependency_default_std:0.1.0\""; 40 | assert!(output.contains(expected_cause)); 41 | } 42 | -------------------------------------------------------------------------------- /tests/detect_explict_use_std_all_files.rs: -------------------------------------------------------------------------------- 1 | extern crate assert_cmd; 2 | 3 | use assert_cmd::prelude::*; 4 | use std::process::Command; 5 | 6 | #[test] 7 | fn it_fails_with_exit_code_1() { 8 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 9 | .unwrap() 10 | .arg("check") 11 | .current_dir("./tests/detect_explicit_use_std_all_files") 12 | .assert() 13 | .code(1); 14 | } 15 | 16 | #[test] 17 | fn it_prints_cause() { 18 | let output = Command::cargo_bin(env!("CARGO_PKG_NAME")) 19 | .unwrap() 20 | .arg("check") 21 | .current_dir("./tests/detect_explicit_use_std_all_files") 22 | .output() 23 | .unwrap() 24 | .stdout; 25 | let output = String::from_utf8(output).unwrap(); 26 | 27 | let expected_cause = "Source code contains an explicit `use std::` statement"; 28 | assert!(output.contains(expected_cause)); 29 | } 30 | 31 | #[test] 32 | fn it_does_not_warn_about_no_std_statement() { 33 | let output = Command::cargo_bin(env!("CARGO_PKG_NAME")) 34 | .unwrap() 35 | .arg("check") 36 | .current_dir("./tests/detect_explicit_use_std_all_files") 37 | .output() 38 | .unwrap() 39 | .stdout; 40 | let output = String::from_utf8(output).unwrap(); 41 | 42 | let expected_cause = "Did not find a #![no_std] attribute"; 43 | assert!(!output.contains(expected_cause)); 44 | } 45 | -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local target= 5 | if [ $TRAVIS_OS_NAME = linux ]; then 6 | target=x86_64-unknown-linux-musl 7 | sort=sort 8 | else 9 | target=x86_64-apple-darwin 10 | sort=gsort # for `sort --sort-version`, from brew's coreutils. 11 | fi 12 | 13 | # Builds for iOS are done on OSX, but require the specific target to be 14 | # installed. 15 | case $TARGET in 16 | aarch64-apple-ios) 17 | rustup target install aarch64-apple-ios 18 | ;; 19 | armv7-apple-ios) 20 | rustup target install armv7-apple-ios 21 | ;; 22 | armv7s-apple-ios) 23 | rustup target install armv7s-apple-ios 24 | ;; 25 | i386-apple-ios) 26 | rustup target install i386-apple-ios 27 | ;; 28 | x86_64-apple-ios) 29 | rustup target install x86_64-apple-ios 30 | ;; 31 | esac 32 | 33 | # This fetches latest stable release 34 | local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ 35 | | cut -d/ -f3 \ 36 | | grep -E '^v[0.1.0-9.]+$' \ 37 | | $sort --version-sort \ 38 | | tail -n1) 39 | curl -LSfs https://japaric.github.io/trust/install.sh | \ 40 | sh -s -- \ 41 | --force \ 42 | --git japaric/cross \ 43 | --tag $tag \ 44 | --target $target 45 | } 46 | 47 | main 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## cargo nono - Detect (possible) no_std compatibility of your crate and dependencies 2 | 3 | ## Motivation 4 | 5 | From embedded programming, over smart contracts in Rust, to general cross-platform portable crates, `#![no_std]` crates are becoming more and more widespread. 6 | However it is currently a very cumbersome process to find out if and why (not) a crate is compatible with `no_std` usage, and often requires a lengthy trial and error process, and digging through the source of all your dependencies. 7 | 8 | **cargo nono** tries to aid you in navigating the current minefield that is `no_std` usage, and it's biggest "no no"s. 9 | 10 | ## Installation 11 | 12 | ### Prebuilt binaries 13 | 14 | `cargo-nono` also comes as prebuilt binaries (useful for CI): 15 | 16 | ```bash 17 | curl -LSfs https://japaric.github.io/trust/install.sh | \ 18 | sh -s -- --git hobofan/cargo-nono 19 | ``` 20 | 21 | ### From crates.io 22 | 23 | ```bash 24 | cargo install cargo-nono 25 | # For warnings with more informative messages install like this 26 | RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-nono 27 | ``` 28 | 29 | ## Demo 30 | 31 | [![asciicast](https://asciinema.org/a/212278.svg)](https://asciinema.org/a/212278) 32 | 33 | ## Usage 34 | 35 | Run in the crate directory you want to check: 36 | 37 | ``` 38 | cargo nono check 39 | ``` 40 | 41 | The `cargo nono check` subcommand also understands the `--no-default-features` and `--features ` flags to help in conditional `no_std` setups. 42 | 43 | ## Features 44 | 45 | - Tries to infer `no_std` compatibility in dependencies by looking for a `#![no_std]` attribute or the often used conditional `#![cfg_attr(not(feature = "std"), no_std)]` 46 | - Helps in pinpointing which dependencies and feature flags activate `std` feature flags 47 | - Warn of `use std::` statements in code 48 | 49 | ### Planned features 50 | 51 | - Warn of `[build-dependencies]` features bleeding over: [cargo#5730](https://github.com/rust-lang/cargo/issues/5730) 52 | 53 | ## License 54 | 55 | Licensed under either of 56 | 57 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 58 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 59 | 60 | at your option. 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Based on the "trust" template v0.1.2 2 | # https://github.com/japaric/trust/tree/v0.1.2 3 | 4 | dist: trusty 5 | language: rust 6 | services: docker 7 | sudo: required 8 | 9 | # TODO Rust builds on stable by default, this can be 10 | # overridden on a case by case basis down below. 11 | 12 | env: 13 | global: 14 | - CRATE_NAME=cargo-nono 15 | 16 | matrix: 17 | allow_failures: 18 | - rust: nightly 19 | fast_finish: true 20 | # TODO These are all the build jobs. Adjust as necessary. Comment out what you 21 | # don't need 22 | include: 23 | # Linux 24 | # - env: TARGET=aarch64-unknown-linux-gnu 25 | # - env: TARGET=arm-unknown-linux-gnueabi 26 | # - env: TARGET=armv7-unknown-linux-gnueabihf 27 | # - env: TARGET=i686-unknown-linux-gnu 28 | # - env: TARGET=i686-unknown-linux-musl 29 | # - env: TARGET=mips-unknown-linux-gnu 30 | # - env: TARGET=mips64-unknown-linux-gnuabi64 31 | # - env: TARGET=mips64el-unknown-linux-gnuabi64 32 | # - env: TARGET=mipsel-unknown-linux-gnu 33 | # - env: TARGET=powerpc-unknown-linux-gnu 34 | # - env: TARGET=powerpc64-unknown-linux-gnu 35 | # - env: TARGET=powerpc64le-unknown-linux-gnu 36 | # - env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1 37 | - env: TARGET=x86_64-unknown-linux-gnu 38 | # - env: TARGET=x86_64-unknown-linux-musl 39 | 40 | # OSX 41 | # - env: TARGET=i686-apple-darwin 42 | # os: osx 43 | - env: TARGET=x86_64-apple-darwin 44 | os: osx 45 | 46 | # *BSD 47 | # - env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1 48 | # - env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1 49 | # - env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1 50 | 51 | # Windows 52 | # - env: TARGET=x86_64-pc-windows-gnu 53 | 54 | # Bare metal 55 | # These targets don't support std and as such are likely not suitable for 56 | # most crates. 57 | # - env: TARGET=thumbv6m-none-eabi 58 | # - env: TARGET=thumbv7em-none-eabi 59 | # - env: TARGET=thumbv7em-none-eabihf 60 | # - env: TARGET=thumbv7m-none-eabi 61 | 62 | # Testing other channels 63 | - env: TARGET=x86_64-unknown-linux-gnu 64 | rust: nightly 65 | - env: TARGET=x86_64-apple-darwin 66 | os: osx 67 | rust: nightly 68 | 69 | before_install: 70 | - set -e 71 | - rustup self update 72 | 73 | install: 74 | - sh ci/install.sh 75 | - source ~/.cargo/env || true 76 | 77 | script: 78 | - bash ci/script.sh 79 | 80 | after_script: set +e 81 | 82 | before_deploy: 83 | - sh ci/before_deploy.sh 84 | 85 | deploy: 86 | # TODO update `api_key.secure` 87 | # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new 88 | # - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789 89 | # - Paste the output down here 90 | api_key: 91 | secure: utHees/+n+Lpqz/6b7kIZhP7xxPiHcG9W+uID+izTaU8A+T1m7/z5f5Hxv/qIYRKvXe4+NTg+gaSGw01Sw2UtteWJqlP09AkulaD2GXxQDagyfB/TvJMrAzkQajBM6N14gReoqRSnY9r6xnMQevQwOckvIpEGS9SmXkzw8rz+8+LEnVP6cSkqAjfLmYfYoFV8shHcE5nH6AA65+BSgYPve+Zfzncxcg8cy82e3H0FiXcQv8HmIR5ive73gMzwv91lA4EHwiPoBsQRvRSBbYEJ1x9k7qdCZHne/HoNeTjPC7XN2U30LkKjcq1d+uCfA3lq0/GTo3E1jjFqRfMwDhaF99EyFVdPbi/4/vecsmuISUAVDop+s9nB8b4gattqeJ3lUoo0cTUA+M13cDI2KqjZOgWbgaL1AaeMWgmtBxPSUOTONmWYTb925m/oVmhPSaxqgrD0lFuAfJSVH2Zf78zsxoARbO6giiyKcA7o91hX38wEYBEnLfZSVsaAy+3AVcdoaC9KFqg3Mn1iQgL7PEniLAzQN1MuSHsfhjO91WoSRQNYXs4rZ+bmbarlAWyzriDFF9v8y8SmRxB7UWll4Ons+w3JfQqLmx2ZOln7h7J6xgOOtvdo6ZvkdV8HI8f3zzTjflU3rTJnr/JADkypA4jmOQsUiTIdKIr0yQUBOzbupM= 92 | file_glob: true 93 | file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.* 94 | on: 95 | # TODO Here you can pick which targets will generate binary releases 96 | # In this example, there are some targets that are tested using the stable 97 | # and nightly channels. This condition makes sure there is only one release 98 | # for such targets and that's generated using the stable channel 99 | condition: $TRAVIS_RUST_VERSION = stable 100 | tags: true 101 | provider: releases 102 | skip_cleanup: true 103 | 104 | cache: cargo 105 | before_cache: 106 | # Travis can't cache files that are not readable by "others" 107 | - chmod -R a+r $HOME/.cargo 108 | 109 | branches: 110 | only: 111 | # release tags 112 | - /^\d+\.\d+\.\d+.*$/ 113 | - master 114 | 115 | notifications: 116 | email: 117 | on_success: never 118 | -------------------------------------------------------------------------------- /src/check.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2; 2 | use proc_macro2::TokenTree; 3 | 4 | use crate::check_source::*; 5 | use crate::ext::*; 6 | 7 | #[derive(Debug, PartialEq, Eq)] 8 | pub enum CrateSupport { 9 | OnlyWithoutFeature(String), 10 | /// proc macros are not actually linked, so they don't hinder no_std support 11 | ProcMacro, 12 | SourceOffenses(Vec), 13 | NoOffenseDetected, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct ConditionalAttribute { 18 | condition: proc_macro2::TokenStream, 19 | pub attribute: syn::Ident, 20 | } 21 | 22 | impl ConditionalAttribute { 23 | pub fn from_attribute(attr: &syn::Attribute) -> Option { 24 | let cfg_attr_path: syn::Path = syn::parse_quote!(cfg_attr); 25 | if attr.path == cfg_attr_path { 26 | if let Some(ref first_group_ts) = attr.clone().tokens.into_iter().next() { 27 | // Group of the surrounding parenthesis 28 | if let TokenTree::Group(group) = first_group_ts { 29 | let mut inner_group_stream = group.stream().into_iter(); 30 | let condition_part_1 = inner_group_stream.next(); 31 | let condition_part_2 = inner_group_stream.next(); 32 | inner_group_stream.next(); 33 | let gated_attr = inner_group_stream.next(); 34 | 35 | if let Some(TokenTree::Ident(ref gated_attr_ident)) = gated_attr { 36 | let mut condition = proc_macro2::TokenStream::new(); 37 | condition.extend(condition_part_1); 38 | condition.extend(condition_part_2); 39 | 40 | return Some(ConditionalAttribute { 41 | condition, 42 | attribute: gated_attr_ident.clone(), 43 | }); 44 | } 45 | } 46 | } 47 | } 48 | return None; 49 | } 50 | 51 | pub fn required_feature(&self) -> Option { 52 | let not_ident: syn::Ident = syn::parse_quote!(not); 53 | let feature_ident: syn::Ident = syn::parse_quote!(feature); 54 | let equal_punct: proc_macro2::Punct = syn::parse_quote!(=); 55 | 56 | let mut ts = self.condition.clone().into_iter(); 57 | if let Some(TokenTree::Ident(not_ident_parsed)) = ts.next() { 58 | if not_ident == not_ident_parsed { 59 | if let Some(TokenTree::Group(group_parsed)) = ts.next() { 60 | let mut group_stream = group_parsed.stream().into_iter(); 61 | let feat_ident = group_stream.next(); 62 | let eq_punct = group_stream.next(); 63 | let required_literal = group_stream.next(); 64 | 65 | if let ( 66 | Some(TokenTree::Ident(feat_ident_parsed)), 67 | Some(TokenTree::Punct(equal_punct_parsed)), 68 | Some(TokenTree::Literal(req_literal)), 69 | ) = (feat_ident, eq_punct, required_literal) 70 | { 71 | if feature_ident == feat_ident_parsed 72 | && equal_punct.as_char() == equal_punct_parsed.as_char() 73 | { 74 | return Some(req_literal); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | return None; 81 | } 82 | } 83 | 84 | pub struct CheckResult { 85 | pub package_name: String, 86 | pub support: CrateSupport, 87 | pub active_features: Vec, 88 | } 89 | 90 | impl CheckResult { 91 | pub fn no_std_itself(&self) -> bool { 92 | match self.support { 93 | CrateSupport::ProcMacro => true, 94 | CrateSupport::OnlyWithoutFeature(ref feature) => !self.is_feature_active(feature), 95 | CrateSupport::NoOffenseDetected => true, 96 | CrateSupport::SourceOffenses(_) => false, 97 | } 98 | } 99 | 100 | pub fn is_feature_active(&self, feature: &str) -> bool { 101 | self.find_active_feature_by_name(feature).is_some() 102 | } 103 | 104 | pub fn find_active_feature_by_name(&self, feature: &str) -> Option<&Feature> { 105 | self.active_features.iter().find(|n| &n.name == feature) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::Command; 3 | use std::str::from_utf8; 4 | use cargo_metadata::{Dependency, Metadata, Package, PackageId}; 5 | 6 | use crate::ext::{Feature, FeatureCause}; 7 | 8 | pub fn metadata_run(additional_args: Option) -> Result { 9 | let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo")); 10 | let mut cmd = Command::new(cargo); 11 | cmd.arg("metadata"); 12 | cmd.args(&["--format-version", "1"]); 13 | if let Some(additional_args) = additional_args { 14 | cmd.arg(&additional_args); 15 | } 16 | 17 | let output = cmd.output().unwrap(); 18 | let stdout = from_utf8(&output.stdout).unwrap(); 19 | let meta = serde_json::from_str(stdout) 20 | .expect("Fetching metadata failed. Please call cargo-nono from within a cargo project."); 21 | Ok(meta) 22 | } 23 | 24 | pub fn features_from_args( 25 | package_id: String, 26 | no_default: bool, 27 | features_args: Vec, 28 | ) -> Vec { 29 | let mut features = Vec::new(); 30 | if !no_default { 31 | let mut feature = Feature::new(package_id.clone(), "default".to_owned()); 32 | feature 33 | .causes 34 | .push(FeatureCause::Default(package_id.clone())); 35 | features.push(feature); 36 | } 37 | for features_args_str in features_args { 38 | let feats = features_args_str.split(","); 39 | for feat in feats { 40 | let mut feature = Feature::new(package_id.clone(), feat.to_owned()); 41 | feature.causes.push(FeatureCause::CliFlag(feat.to_owned())); 42 | features.push(feature); 43 | } 44 | } 45 | 46 | features 47 | } 48 | 49 | pub fn main_ws_member_from_args<'a>( 50 | metadata: &'a Metadata, 51 | package_arg: Option<&str>, 52 | ) -> &'a PackageId { 53 | let target_workspace_member; 54 | if metadata.workspace_members.len() == 1 { 55 | target_workspace_member = metadata.workspace_members.get(0).unwrap(); 56 | } else { 57 | let workspace_members = &metadata.workspace_members[..]; 58 | let workspace_packages: Vec<_> = metadata 59 | .packages 60 | .iter() 61 | .filter(|p| workspace_members.contains(&p.id)) 62 | .collect(); 63 | let package_names: Vec<_> = workspace_packages 64 | .iter() 65 | .map(|n| n.name.clone()) 66 | .collect(); 67 | 68 | target_workspace_member = match package_arg { 69 | Some(package_name) => { 70 | let member = workspace_packages 71 | .iter() 72 | .find(|p| p.name == package_name); 73 | if member.is_none() { 74 | println!( 75 | "⚠️ Unknown package \"{}\". Please provide one of {:?} via --package flag.", 76 | package_name, package_names 77 | ); 78 | std::process::exit(1); 79 | } 80 | &member.unwrap().id 81 | } 82 | None => { 83 | let current_dir = env::current_dir().unwrap(); 84 | let member = workspace_packages 85 | .iter() 86 | .find(|p| { 87 | if let Some(package_dir) = p.manifest_path.parent() { 88 | package_dir == current_dir.as_path() 89 | } else { 90 | false 91 | } 92 | }); 93 | if member.is_none() { 94 | println!( 95 | "⚠️ Multiple packages present in workspace. Please provide one of {:?} via --package flag.", 96 | package_names 97 | ); 98 | std::process::exit(1); 99 | } 100 | &member.unwrap().id 101 | } 102 | }; 103 | } 104 | target_workspace_member 105 | } 106 | 107 | pub fn dependencies_to_packages( 108 | package: &Package, 109 | metadata: &Metadata, 110 | dependencies: &[Dependency], 111 | ) -> Vec { 112 | let resolve_node = metadata 113 | .resolve 114 | .clone() 115 | .unwrap() 116 | .nodes 117 | .into_iter() 118 | .find(|n| n.id == package.id) 119 | .unwrap(); 120 | // All dependency packages of the package 121 | let dependency_packages: Vec = metadata 122 | .packages 123 | .iter() 124 | .filter(|n| resolve_node.dependencies.contains(&n.id)) 125 | .map(|n| n.clone()) 126 | .collect(); 127 | 128 | // limit packages to only the activated dependencies 129 | dependency_packages 130 | .into_iter() 131 | .filter(|package| { 132 | for dependency in dependencies.iter() { 133 | if package.name == dependency.name { 134 | return true; 135 | } 136 | } 137 | return false; 138 | }) 139 | .collect() 140 | } 141 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod check; 2 | mod check_source; 3 | mod ext; 4 | mod util; 5 | 6 | use clap::{App, Arg, SubCommand}; 7 | use console::Emoji; 8 | use std::path::PathBuf; 9 | 10 | use crate::check::*; 11 | use crate::check_source::*; 12 | use crate::ext::*; 13 | use crate::util::*; 14 | 15 | use cargo_metadata::{Metadata, Package}; 16 | 17 | pub static SUCCESS: Emoji = Emoji("✅ ", "SUCCESS"); 18 | pub static FAILURE: Emoji = Emoji("❌ ", "FAILURE"); 19 | pub static MAYBE: Emoji = Emoji("❓ ", "MAYBE"); 20 | 21 | fn check_and_print_package( 22 | package: &Package, 23 | resolved_dependency_features: &[Feature], 24 | metadata: &Metadata, 25 | metadata_full: &Metadata, 26 | is_main_pkg: bool, 27 | ) -> bool { 28 | let mut package_did_fail = false; 29 | 30 | let package_features: Vec = resolved_dependency_features 31 | .iter() 32 | .filter(|n| n.package_id == package.id.repr) 33 | .map(|n| n.to_owned()) 34 | .collect(); 35 | let active_features = package.active_features_for_features(&package_features); 36 | let active_dependencies = package.active_dependencies(&active_features); 37 | let _active_packages = dependencies_to_packages(&package, &metadata_full, &active_dependencies); 38 | let _resolved_dependency_features = 39 | package.all_dependency_features(&metadata_full, &active_features); 40 | 41 | let mut support = CrateSupport::NoOffenseDetected; 42 | if package.is_proc_macro() { 43 | support = CrateSupport::ProcMacro; 44 | } 45 | if support == CrateSupport::NoOffenseDetected { 46 | match is_main_pkg { 47 | false => { 48 | let srcs: Vec<_> = package 49 | .lib_target_sources() 50 | .into_iter() 51 | .map(PathBuf::from) 52 | .collect(); 53 | // TODO: check more than one 54 | support = srcs 55 | .into_iter() 56 | .map(|src_path| get_crate_support_from_source(&src_path)) 57 | .next() 58 | .unwrap_or(CrateSupport::NoOffenseDetected); 59 | } 60 | true => { 61 | let srcs: Vec<_> = package 62 | .bin_target_sources() 63 | .into_iter() 64 | .chain(package.lib_target_sources()) 65 | .map(PathBuf::from) 66 | .collect(); 67 | support = srcs 68 | .into_iter() 69 | .map(|src_path| get_crate_support_from_source(&src_path)) 70 | .next() 71 | .unwrap_or(CrateSupport::NoOffenseDetected); 72 | } 73 | } 74 | } 75 | 76 | let check = CheckResult { 77 | package_name: package.name.clone(), 78 | support, 79 | active_features: active_features, 80 | }; 81 | 82 | // set flag that at least one crate check failed 83 | if !check.no_std_itself() { 84 | package_did_fail = true; 85 | } 86 | let overall_res = match check.no_std_itself() { 87 | true => SUCCESS, 88 | false => FAILURE, 89 | }; 90 | println!("{}: {}", check.package_name, overall_res); 91 | if check.no_std_itself() { 92 | return package_did_fail; 93 | } 94 | if let CrateSupport::OnlyWithoutFeature(feature) = &check.support { 95 | println!( 96 | " - Crate supports no_std if \"{}\" feature is deactivated.", 97 | feature 98 | ); 99 | let feat = check.find_active_feature_by_name(&feature).unwrap(); 100 | feat.print(&metadata, 2); 101 | } 102 | if let CrateSupport::SourceOffenses(ref offenses) = check.support { 103 | for offense in offenses { 104 | match offense { 105 | SourceOffense::MissingNoStdAttribute => { 106 | println!(" - Did not find a #![no_std] attribute or a simple conditional attribute like #![cfg_attr(not(feature = \"std\"), no_std)] in the crate source. Crate most likely doesn't support no_std without changes."); 107 | } 108 | SourceOffense::UseStdStatement(stmt) => { 109 | println!(" - Source code contains an explicit `use std::` statement."); 110 | println!("{}", stmt); 111 | } 112 | } 113 | } 114 | } 115 | 116 | package_did_fail 117 | } 118 | 119 | fn main() { 120 | let mut app = App::new("cargo nono") 121 | .arg(Arg::with_name("dummy").hidden(true).possible_value("nono")) 122 | .subcommand( 123 | SubCommand::with_name("check") 124 | .arg(Arg::with_name("no-default-features").long("no-default-features")) 125 | .arg( 126 | Arg::with_name("features") 127 | .long("features") 128 | .multiple(true) 129 | .takes_value(true), 130 | ) 131 | .arg(Arg::with_name("package").long("package").takes_value(true)), 132 | ); 133 | 134 | let matches = app.clone().get_matches(); 135 | if let Some(matches) = matches.subcommand_matches("check") { 136 | let metadata_full = metadata_run(Some("--all-features".to_owned())).unwrap(); 137 | let metadata = metadata_run(None).unwrap(); 138 | 139 | let target_workspace_member = 140 | main_ws_member_from_args(&metadata, matches.value_of("package")); 141 | 142 | let target_package = metadata 143 | .find_package(&target_workspace_member.repr) 144 | .unwrap(); 145 | let features = features_from_args( 146 | target_package.id.repr.clone(), 147 | matches.is_present("no-default-features"), 148 | matches 149 | .values_of("features") 150 | .map(|n| n.into_iter().map(|m| m.to_owned()).collect()) 151 | .unwrap_or(Vec::new()) 152 | .to_owned(), 153 | ); 154 | 155 | let active_features = target_package.active_features_for_features(&features); 156 | let active_dependencies = target_package.active_dependencies(&active_features); 157 | let active_packages = 158 | dependencies_to_packages(&target_package, &metadata_full, &active_dependencies); 159 | 160 | let mut package_did_fail = false; 161 | let resolved_dependency_features = 162 | target_package.all_dependency_features(&metadata_full, &active_features); 163 | 164 | let main_package = metadata 165 | .packages 166 | .iter() 167 | .find(|n| &n.id == target_workspace_member) 168 | .expect("Unable to find main package."); 169 | if check_and_print_package( 170 | main_package, 171 | &resolved_dependency_features, 172 | &metadata, 173 | &metadata_full, 174 | true, 175 | ) { 176 | package_did_fail = true; 177 | } 178 | 179 | for package in active_packages.iter() { 180 | if check_and_print_package( 181 | package, 182 | &resolved_dependency_features, 183 | &metadata, 184 | &metadata_full, 185 | false, 186 | ) { 187 | package_did_fail = true; 188 | } 189 | } 190 | match package_did_fail { 191 | true => std::process::exit(1), 192 | false => std::process::exit(0), 193 | } 194 | } 195 | app.print_help().unwrap(); 196 | println!(""); // print newline since print_help doesn't do that 197 | } 198 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/check_source.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::fs::File; 3 | use std::io::Read; 4 | use std::path::PathBuf; 5 | 6 | #[cfg(feature = "proc_macro_spans")] 7 | use std::io::BufRead; 8 | #[cfg(feature = "proc_macro_spans")] 9 | use syn::spanned::Spanned; 10 | 11 | use crate::check::*; 12 | 13 | #[derive(Debug, PartialEq, Eq)] 14 | pub enum SourceOffense { 15 | /// Source code is missing a `#![no_std]` attribute. 16 | /// Only valid for entry point file (main.rs / lib.rs). 17 | MissingNoStdAttribute, 18 | /// Source code contains an explicit `use std::` statement. 19 | UseStdStatement(UseStdStmt), 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct UseStdStmt { 24 | src_path: PathBuf, 25 | item_tree: syn::UseTree, 26 | } 27 | 28 | impl UseStdStmt { 29 | #[cfg(feature = "proc_macro_spans")] 30 | // TODO: can be made available without proc_macro_spans by parsing from UseTree 31 | fn statement_str(&self) -> String { 32 | let file = File::open(&self.src_path).unwrap(); 33 | let file = std::io::BufReader::new(file); 34 | let line = file 35 | .lines() 36 | .skip(self.item_tree.span().start().line - 1) 37 | .next() 38 | .unwrap() 39 | .unwrap(); 40 | 41 | let raw_part: String = line 42 | .chars() 43 | .skip(self.item_tree.span().start().column) 44 | .take(self.item_tree.span().end().column - self.item_tree.span().start().column) 45 | .collect(); 46 | 47 | raw_part 48 | } 49 | 50 | #[cfg(feature = "proc_macro_spans")] 51 | /// `std::path::PathBuf` -> `["std", "path", "PathBuf"]` 52 | fn path_parts(&self) -> Vec { 53 | let raw_part = self.statement_str(); 54 | raw_part.split("::").map(|n| n.to_owned()).collect() 55 | } 56 | } 57 | 58 | impl PartialEq for UseStdStmt { 59 | fn eq(&self, other: &UseStdStmt) -> bool { 60 | self.src_path == other.src_path 61 | } 62 | } 63 | impl Eq for UseStdStmt {} 64 | 65 | #[cfg(feature = "proc_macro_spans")] 66 | impl fmt::Display for UseStdStmt { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | let file = File::open(&self.src_path).unwrap(); 69 | let file = std::io::BufReader::new(file); 70 | let line = file 71 | .lines() 72 | .skip(self.item_tree.span().start().line - 1) 73 | .next() 74 | .unwrap() 75 | .unwrap(); 76 | 77 | let statement_str = self.statement_str(); 78 | 79 | let replacement_suggestion = find_use_std_statement_replacement(&self.path_parts()); 80 | let replacement_suggestion = replacement_suggestion.map(|n| n.join("::")); 81 | 82 | writeln!( 83 | f, 84 | " --> {src}:{line}:{column}", 85 | src = self 86 | .src_path 87 | .strip_prefix(std::env::current_dir().unwrap()) 88 | .unwrap() 89 | .display(), 90 | line = self.item_tree.span().start().line, 91 | column = self.item_tree.span().start().column 92 | )?; 93 | writeln!(f, " |")?; 94 | 95 | writeln!( 96 | f, 97 | "{line_num:<4}|{line}", 98 | line_num = self.item_tree.span().start().line, 99 | line = line 100 | )?; 101 | 102 | let mut underline: Vec = vec![]; 103 | for _ in 0..self.item_tree.span().start().column { 104 | underline.push(' '); 105 | } 106 | for _ in self.item_tree.span().start().column..self.item_tree.span().end().column { 107 | underline.push('^'); 108 | } 109 | let underline: String = underline.into_iter().collect(); 110 | 111 | writeln!(f, " |{line}", line = underline)?; 112 | if let Some(replacement_suggestion) = replacement_suggestion { 113 | writeln!( 114 | f, 115 | "help: Try replacing `{original}` with `{replacement}`.", 116 | original = statement_str, 117 | replacement = replacement_suggestion 118 | )?; 119 | } 120 | Ok(()) 121 | } 122 | } 123 | 124 | #[cfg(not(feature = "proc_macro_spans"))] 125 | impl fmt::Display for UseStdStmt { 126 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 127 | writeln!( 128 | f, 129 | " --> {src}", 130 | src = self 131 | .src_path 132 | .strip_prefix(std::env::current_dir().unwrap()) 133 | .unwrap_or(&self.src_path) 134 | .display(), 135 | ) 136 | } 137 | } 138 | 139 | pub fn get_crate_support_from_source(main_src_path: &PathBuf) -> CrateSupport { 140 | let main_file_support = check_source(main_src_path, true); 141 | 142 | let mut offenses = vec![]; 143 | match main_file_support { 144 | CrateSupport::OnlyWithoutFeature(_) => return main_file_support, 145 | CrateSupport::ProcMacro => return main_file_support, 146 | CrateSupport::SourceOffenses(mut off) => offenses.append(&mut off), 147 | CrateSupport::NoOffenseDetected => {} 148 | }; 149 | 150 | let other_source_files_pattern = format!( 151 | "{}/**/*.rs", 152 | main_src_path.parent().unwrap().to_str().unwrap(), 153 | ); 154 | let other_source_files = glob::glob(&other_source_files_pattern).unwrap(); 155 | 156 | for other_source_file in other_source_files { 157 | let file_support = check_source(&other_source_file.unwrap(), false); 158 | match file_support { 159 | CrateSupport::SourceOffenses(mut off) => offenses.append(&mut off), 160 | _ => {} 161 | } 162 | } 163 | 164 | match offenses.is_empty() { 165 | true => CrateSupport::NoOffenseDetected, 166 | false => CrateSupport::SourceOffenses(offenses), 167 | } 168 | } 169 | 170 | fn check_source(source_path: &PathBuf, is_main_file: bool) -> CrateSupport { 171 | let mut file = File::open(&source_path).expect("Unable to open file"); 172 | 173 | let mut src = String::new(); 174 | file.read_to_string(&mut src).expect("Unable to read file"); 175 | 176 | let syntax = syn::parse_file(&src).expect("Unable to parse file"); 177 | 178 | for attr in &syntax.attrs { 179 | if let Some(conditional_attr) = ConditionalAttribute::from_attribute(&attr) { 180 | let no_std_ident: syn::Ident = syn::parse_quote!(no_std); 181 | if conditional_attr.attribute == no_std_ident { 182 | if let Some(required_feature) = conditional_attr.required_feature() { 183 | let mut feature_name = required_feature.to_string(); 184 | feature_name = feature_name[1..feature_name.len() - 1].to_owned(); 185 | return CrateSupport::OnlyWithoutFeature(feature_name); 186 | } 187 | } 188 | } 189 | } 190 | 191 | let mut offenses = vec![]; 192 | 193 | let use_statements: Vec<_> = syntax 194 | .items 195 | .iter() 196 | .filter_map(|item| match item { 197 | syn::Item::Use(item) => Some(item), 198 | _ => None, 199 | }) 200 | .collect(); 201 | 202 | let std_ident: syn::Ident = syn::parse_quote!(std); 203 | for use_statement in &use_statements { 204 | match use_statement.tree { 205 | syn::UseTree::Path(ref first_path) => { 206 | let first_ident = &first_path.ident; 207 | if first_ident == &std_ident { 208 | let stmt = UseStdStmt { 209 | src_path: source_path.clone(), 210 | item_tree: use_statement.tree.clone(), 211 | }; 212 | offenses.push(SourceOffense::UseStdStatement(stmt)); 213 | } 214 | } 215 | _ => { 216 | // FIXME: #19 - ignore non-trivial use statements for now 217 | } 218 | } 219 | } 220 | 221 | if is_main_file { 222 | let always_no_std: syn::Attribute = syn::parse_quote!(#![no_std]); 223 | let contains_always_no_std = syntax.attrs.contains(&always_no_std); 224 | if !contains_always_no_std { 225 | let not_test_no_std: syn::Attribute = syn::parse_quote!(#![cfg_attr(not(test), no_std)]); 226 | let contains_not_test_no_std = syntax.attrs.contains(¬_test_no_std); 227 | if !contains_not_test_no_std { 228 | offenses.push(SourceOffense::MissingNoStdAttribute); 229 | } 230 | } 231 | } 232 | 233 | match offenses.is_empty() { 234 | true => CrateSupport::NoOffenseDetected, 235 | false => CrateSupport::SourceOffenses(offenses), 236 | } 237 | } 238 | 239 | /// Really hacky way of trying to find a replacment for a `use std::` statment. 240 | /// 241 | /// Right now checks if `std` can be replaced by `core` in the following way: 242 | /// - Find out directory of `rustdoc` via `rustup which rustdoc` 243 | /// - Infer rust doc directory for that 244 | /// - Try to find a file in `core` docs that would serve as replacment for `std` item 245 | #[allow(dead_code)] 246 | fn find_use_std_statement_replacement(path_parts: &[String]) -> Option> { 247 | let rustup_output = std::process::Command::new("rustup") 248 | .args(&vec!["which", "rustdoc"]) 249 | .output() 250 | .expect("failed to execute rustup"); 251 | let rustdoc_dir = String::from_utf8(rustup_output.stdout).unwrap(); 252 | let mut doc_dir = PathBuf::from(&rustdoc_dir); 253 | doc_dir.pop(); 254 | doc_dir = doc_dir.join("../share/doc/rust/html"); 255 | 256 | let mut core_dir = doc_dir.join("core"); 257 | for path_part in path_parts.iter().skip(1).take(path_parts.len() - 2) { 258 | core_dir = core_dir.join(path_part); 259 | } 260 | 261 | let glob_pattern = format!( 262 | "{}/*.{}.html", 263 | core_dir.to_str().unwrap(), 264 | path_parts.last().unwrap() 265 | ); 266 | let mut glob_files = glob::glob(&glob_pattern).unwrap(); 267 | match glob_files.next().is_some() { 268 | true => { 269 | let replacement_path = vec!["core".to_owned()] 270 | .into_iter() 271 | .chain(path_parts.into_iter().skip(1).map(|n| n.clone())) 272 | .collect(); 273 | return Some(replacement_path); 274 | } 275 | false => {} 276 | }; 277 | 278 | // check for module index files, so that module use statments like `use std::ops` 279 | // are checked correctly 280 | let glob_pattern_index = format!( 281 | "{}/{}/index.html", 282 | core_dir.to_str().unwrap(), 283 | path_parts.last().unwrap() 284 | ); 285 | let mut glob_files = glob::glob(&glob_pattern_index).unwrap(); 286 | match glob_files.next().is_some() { 287 | true => { 288 | let replacement_path = vec!["core".to_owned()] 289 | .into_iter() 290 | .chain(path_parts.into_iter().skip(1).map(|n| n.clone())) 291 | .collect(); 292 | return Some(replacement_path); 293 | } 294 | false => None, 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/ext.rs: -------------------------------------------------------------------------------- 1 | use cargo_metadata::{Dependency, DependencyKind, Metadata, Package}; 2 | use std::collections::HashSet; 3 | 4 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 5 | pub struct Feature { 6 | pub package_id: String, 7 | pub name: String, 8 | pub causes: Vec, 9 | } 10 | 11 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 12 | pub enum FeatureCause { 13 | /// Feature is triggered by another feature. 14 | // (package_id, feature_name) 15 | Feature(Box), 16 | /// Feature is activated by default in its respective package. 17 | // (package_id) 18 | Default(String), 19 | /// Feature is activated by explicityly by the provided package_id. 20 | // (package_id) 21 | Explicit(String), 22 | /// Feature has been activated via a --features flag. 23 | CliFlag(String), 24 | // Unknown, 25 | } 26 | 27 | impl Feature { 28 | pub fn new(package_id: String, feature: String) -> Self { 29 | Self { 30 | package_id, 31 | name: feature, 32 | causes: Vec::new(), 33 | } 34 | } 35 | 36 | pub fn print(&self, metadata: &Metadata, offset: usize) { 37 | let package_print_name = |package_id| { 38 | let package = metadata.find_package(package_id); 39 | if package.is_none() { 40 | return "UNPRINTABLE".to_owned(); 41 | } 42 | let package = package.unwrap(); 43 | format!("{}:{}", package.name, package.version) 44 | }; 45 | for _ in 0..offset { 46 | print!(" "); 47 | } 48 | println!( 49 | "- Caused by feature flag \"{}\" in crate \"{}\"", 50 | self.name, 51 | package_print_name(&self.package_id) 52 | ); 53 | for cause in self.causes.iter() { 54 | cause.print(metadata, offset + 1); 55 | } 56 | } 57 | } 58 | 59 | impl FeatureCause { 60 | pub fn print(&self, metadata: &Metadata, offset: usize) { 61 | let print_offset = || { 62 | for _ in 0..offset { 63 | print!(" "); 64 | } 65 | }; 66 | let package_print_name = |package_id| { 67 | let package = metadata.find_package(package_id); 68 | if package.is_none() { 69 | return "UNPRINTABLE".to_owned(); 70 | } 71 | let package = package.unwrap(); 72 | format!("{}:{}", package.name, package.version) 73 | }; 74 | match self { 75 | FeatureCause::Feature(feat) => feat.print(metadata, offset), 76 | FeatureCause::CliFlag(flag) => { 77 | print_offset(); 78 | println!("- Caused by providing CLI --features flag \"{}\"", flag) 79 | } 80 | FeatureCause::Default(package_id) => { 81 | print_offset(); 82 | println!( 83 | "- Caused by implicitly enabled default feature from \"{}\"", 84 | package_print_name(package_id) 85 | ) 86 | } 87 | FeatureCause::Explicit(package_id) => { 88 | print_offset(); 89 | println!( 90 | "- Explicityly enabled feature from \"{}\"", 91 | package_print_name(package_id) 92 | ) 93 | } 94 | } 95 | } 96 | } 97 | 98 | pub trait PackageExt { 99 | /// Receives a list of activated features (features activated by other features have already 100 | /// been calculate), and should return a list of dependencies that are activated for that 101 | /// featureset. 102 | fn active_dependencies(&self, features: &[Feature]) -> Vec; 103 | 104 | fn always_on_dependencies(&self) -> Vec; 105 | /// Active dependencies for a single feature. 106 | fn active_dependencies_for_feature(&self, feature: &Feature) -> Vec; 107 | /// Resolve all features that are activated by the provided feature. Also includes the 108 | /// provided feature. 109 | fn active_features_for_feature(&self, feature: &Feature) -> Vec; 110 | fn active_features_for_features(&self, features: &[Feature]) -> Vec { 111 | let mut resolved_features = HashSet::new(); 112 | for feature in features { 113 | for resolved_feature in self.active_features_for_feature(feature) { 114 | resolved_features.insert(resolved_feature); 115 | } 116 | } 117 | resolved_features.into_iter().collect() 118 | } 119 | 120 | /// Tries to turn a feature like "serde/std" into a feature flag on "serde". 121 | fn dependency_feature_for_feature( 122 | &self, 123 | metadata: &Metadata, 124 | feature: &Feature, 125 | ) -> Option; 126 | 127 | fn dependency_features_for_features( 128 | &self, 129 | metadata: &Metadata, 130 | features: &[Feature], 131 | ) -> Vec { 132 | features 133 | .iter() 134 | .filter_map(|feature| self.dependency_feature_for_feature(metadata, feature)) 135 | .collect() 136 | } 137 | 138 | fn all_dependency_features( 139 | &self, 140 | metadata: &Metadata, 141 | external_features: &[Feature], 142 | ) -> Vec { 143 | let mut features = self.fixed_dependency_features(metadata); 144 | for feat in self.dependency_features_for_features(metadata, external_features) { 145 | features.push(feat); 146 | } 147 | 148 | features 149 | } 150 | 151 | /// Fixed dependency features. Those are hardcoded in the Cargo.toml of the package and can not 152 | /// be deactivated by turning off its default features. 153 | fn fixed_dependency_features(&self, metadata: &Metadata) -> Vec; 154 | 155 | fn lib_target_sources(&self) -> Vec; 156 | fn bin_target_sources(&self) -> Vec; 157 | 158 | fn is_proc_macro(&self) -> bool; 159 | } 160 | 161 | impl PackageExt for Package { 162 | fn active_dependencies(&self, features: &[Feature]) -> Vec { 163 | let mut dependencies = Vec::new(); 164 | for dep in self.always_on_dependencies() { 165 | dependencies.push(dep); 166 | } 167 | 168 | for feature in features { 169 | let deps = self.active_dependencies_for_feature(feature); 170 | for dep in deps.into_iter() { 171 | dependencies.push(dep); 172 | } 173 | } 174 | dependencies.dedup_by(|a, b| a.name == b.name); 175 | dependencies = dependencies 176 | .into_iter() 177 | .filter(|dep| dep.kind == DependencyKind::Normal) 178 | .collect(); 179 | 180 | dependencies 181 | } 182 | 183 | fn active_dependencies_for_feature(&self, feature: &Feature) -> Vec { 184 | let activated_features = self.active_features_for_feature(feature); 185 | 186 | self.dependencies 187 | .iter() 188 | .filter(|dependency| { 189 | for feature in activated_features.iter() { 190 | if feature.name == dependency.name { 191 | return true; 192 | } 193 | } 194 | return false; 195 | }) 196 | .map(|n| n.to_owned()) 197 | .collect() 198 | } 199 | 200 | fn active_features_for_feature(&self, feature: &Feature) -> Vec { 201 | let mut resolved_features: HashSet = HashSet::new(); 202 | let mut unresolved_features: HashSet = HashSet::new(); 203 | unresolved_features.insert(feature.to_owned()); 204 | 205 | while !unresolved_features.is_empty() { 206 | for unresolved in unresolved_features.clone().iter() { 207 | let activated_features: Vec = self 208 | .features 209 | .get(&unresolved.name) 210 | .map(|features| { 211 | features 212 | .clone() 213 | .into_iter() 214 | .map(|raw_feature| { 215 | let mut new_feature = Feature::new(self.id.repr.clone(), raw_feature); 216 | new_feature 217 | .causes 218 | .push(FeatureCause::Feature(Box::new(feature.clone()))); 219 | 220 | new_feature 221 | }) 222 | .collect() 223 | }) 224 | .unwrap_or_default(); 225 | unresolved_features.remove(&unresolved); 226 | resolved_features.insert(unresolved.to_owned()); 227 | for activated in activated_features { 228 | if !resolved_features.contains(&activated) { 229 | unresolved_features.insert(activated); 230 | } 231 | } 232 | } 233 | } 234 | 235 | resolved_features.into_iter().collect() 236 | } 237 | 238 | fn dependency_feature_for_feature( 239 | &self, 240 | metadata: &Metadata, 241 | feature: &Feature, 242 | ) -> Option { 243 | if !feature.name.contains("/") { 244 | return None; 245 | } 246 | 247 | let dependency_feature_parts: Vec<_> = feature.name.split("/").collect(); 248 | let dependency_name = dependency_feature_parts[0]; 249 | let dependency_feature_name = dependency_feature_parts[1]; 250 | let dependency = self.dependencies.iter().find(|n| n.name == dependency_name); 251 | if dependency.is_none() { 252 | return None; 253 | } 254 | 255 | let dep_package_id = metadata.dependency_package_id(self, dependency.unwrap()); 256 | // package_id of dependency might not be findable if we try to activate the feature of a 257 | // optional dependency 258 | if dep_package_id.is_none() { 259 | return None; 260 | } 261 | 262 | let mut new_feature = 263 | Feature::new(dep_package_id.unwrap(), dependency_feature_name.to_owned()); 264 | new_feature 265 | .causes 266 | .push(FeatureCause::Feature(Box::new(feature.clone()))); 267 | 268 | Some(new_feature) 269 | } 270 | 271 | fn fixed_dependency_features(&self, metadata: &Metadata) -> Vec { 272 | self.dependencies 273 | .iter() 274 | .flat_map(|dependency| { 275 | let dep_package_id = metadata.dependency_package_id(self, dependency); 276 | // package_id of dependency might not be findable if we try to activate the feature of a 277 | // optional dependency 278 | if dep_package_id.is_none() { 279 | return Vec::new(); 280 | } 281 | let dep_package_id = dep_package_id.unwrap(); 282 | // features activated via 283 | // serde = { version = "*", features = ["std"] } 284 | // ^^^^^ 285 | let mut explicit_dependency_features = dependency 286 | .features 287 | .clone() 288 | .into_iter() 289 | .map(|raw_feature| { 290 | let mut feature = Feature::new(dep_package_id.to_owned(), raw_feature); 291 | feature.causes.push(FeatureCause::Explicit(self.id.repr.clone())); 292 | feature 293 | }) 294 | .collect::>(); 295 | // features activated via 296 | // serde = { version = "*", default-features = true } 297 | // ^^^^ 298 | // or the absence of the default-features option 299 | if dependency.uses_default_features { 300 | let mut feature = Feature::new(dep_package_id.to_owned(), "default".to_owned()); 301 | feature.causes.push(FeatureCause::Default(self.id.repr.clone())); 302 | 303 | explicit_dependency_features.push(feature); 304 | } 305 | explicit_dependency_features 306 | }) 307 | .collect() 308 | } 309 | 310 | fn always_on_dependencies(&self) -> Vec { 311 | self.dependencies 312 | .iter() 313 | .filter(|dep| !dep.optional) 314 | .map(|n| n.to_owned()) 315 | .collect() 316 | } 317 | 318 | fn lib_target_sources(&self) -> Vec { 319 | self.targets 320 | .iter() 321 | .filter(|target| target.kind.contains(&"lib".to_string())) 322 | .flat_map(|target| target.src_path.to_str()) 323 | .map(|target| target.into()) 324 | .collect() 325 | } 326 | 327 | fn bin_target_sources(&self) -> Vec { 328 | self.targets 329 | .iter() 330 | .filter(|target| target.kind.contains(&"bin".to_string())) 331 | .flat_map(|target| target.src_path.to_str()) 332 | .map(|target| target.into()) 333 | .collect() 334 | } 335 | 336 | fn is_proc_macro(&self) -> bool { 337 | self.targets 338 | .iter() 339 | .filter(|target| target.kind.contains(&"proc-macro".to_string())) 340 | .next() 341 | .is_some() 342 | } 343 | } 344 | 345 | pub trait MetadataExt { 346 | fn find_package(&self, package_id: &str) -> Option<&Package>; 347 | fn dependency_package_id(&self, package: &Package, dependency: &Dependency) -> Option; 348 | } 349 | 350 | impl MetadataExt for Metadata { 351 | fn find_package(&self, package_id: &str) -> Option<&Package> { 352 | self.packages 353 | .iter() 354 | .find(|package| package.id.repr == package_id) 355 | } 356 | 357 | fn dependency_package_id(&self, package: &Package, dependency: &Dependency) -> Option { 358 | let resolve_node = self 359 | .resolve 360 | .clone() 361 | .unwrap() 362 | .nodes 363 | .into_iter() 364 | .find(|n| n.id == package.id) 365 | .unwrap(); 366 | // All dependency packages of the package 367 | let dependency_packages: Vec = self 368 | .packages 369 | .iter() 370 | .filter(|n| resolve_node.dependencies.contains(&n.id)) 371 | .map(|n| n.clone()) 372 | .collect(); 373 | 374 | dependency_packages 375 | .into_iter() 376 | .find(|package| package.name == dependency.name) 377 | .map(|n| n.id.repr) 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.13" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "ansi_term" 13 | version = "0.11.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "assert_cmd" 21 | version = "0.11.1" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "escargot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "predicates 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 27 | "predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 28 | ] 29 | 30 | [[package]] 31 | name = "atty" 32 | version = "0.2.14" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | dependencies = [ 35 | "hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 38 | ] 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.2.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | 45 | [[package]] 46 | name = "cargo-nono" 47 | version = "0.1.8" 48 | dependencies = [ 49 | "assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "cargo_metadata 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "proc-macro2 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", 59 | ] 60 | 61 | [[package]] 62 | name = "cargo_metadata" 63 | version = "0.8.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | dependencies = [ 66 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "serde_derive 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "cfg-if" 74 | version = "0.1.10" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | 77 | [[package]] 78 | name = "clap" 79 | version = "2.33.3" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | dependencies = [ 82 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "clicolors-control" 93 | version = "1.0.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 100 | ] 101 | 102 | [[package]] 103 | name = "cloudabi" 104 | version = "0.1.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | dependencies = [ 107 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "console" 112 | version = "0.7.7" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "clicolors-control 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "parking_lot 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "termios 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "difference" 129 | version = "2.0.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | 132 | [[package]] 133 | name = "encode_unicode" 134 | version = "0.3.6" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | 137 | [[package]] 138 | name = "escargot" 139 | version = "0.4.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | dependencies = [ 142 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", 146 | ] 147 | 148 | [[package]] 149 | name = "glob" 150 | version = "0.3.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | 153 | [[package]] 154 | name = "hermit-abi" 155 | version = "0.1.16" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | dependencies = [ 158 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 159 | ] 160 | 161 | [[package]] 162 | name = "instant" 163 | version = "0.1.7" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | dependencies = [ 166 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 167 | ] 168 | 169 | [[package]] 170 | name = "itoa" 171 | version = "0.4.6" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | 174 | [[package]] 175 | name = "lazy_static" 176 | version = "1.4.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | 179 | [[package]] 180 | name = "libc" 181 | version = "0.2.77" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | 184 | [[package]] 185 | name = "lock_api" 186 | version = "0.4.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | dependencies = [ 189 | "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 190 | ] 191 | 192 | [[package]] 193 | name = "log" 194 | version = "0.4.11" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | dependencies = [ 197 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 198 | ] 199 | 200 | [[package]] 201 | name = "memchr" 202 | version = "2.3.3" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | 205 | [[package]] 206 | name = "parking_lot" 207 | version = "0.11.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | dependencies = [ 210 | "instant 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 211 | "lock_api 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 212 | "parking_lot_core 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 213 | ] 214 | 215 | [[package]] 216 | name = "parking_lot_core" 217 | version = "0.8.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | dependencies = [ 220 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "cloudabi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 222 | "instant 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 223 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 224 | "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "smallvec 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 227 | ] 228 | 229 | [[package]] 230 | name = "predicates" 231 | version = "1.0.5" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | dependencies = [ 234 | "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 235 | "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 236 | ] 237 | 238 | [[package]] 239 | name = "predicates-core" 240 | version = "1.0.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | 243 | [[package]] 244 | name = "predicates-tree" 245 | version = "1.0.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | dependencies = [ 248 | "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 249 | "treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 250 | ] 251 | 252 | [[package]] 253 | name = "proc-macro2" 254 | version = "1.0.23" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 258 | ] 259 | 260 | [[package]] 261 | name = "quote" 262 | version = "1.0.7" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "proc-macro2 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", 266 | ] 267 | 268 | [[package]] 269 | name = "redox_syscall" 270 | version = "0.1.57" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | 273 | [[package]] 274 | name = "regex" 275 | version = "1.3.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 282 | ] 283 | 284 | [[package]] 285 | name = "regex-syntax" 286 | version = "0.6.18" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | 289 | [[package]] 290 | name = "ryu" 291 | version = "1.0.5" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | 294 | [[package]] 295 | name = "scopeguard" 296 | version = "1.1.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | 299 | [[package]] 300 | name = "semver" 301 | version = "0.9.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | dependencies = [ 304 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "semver-parser" 310 | version = "0.7.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | 313 | [[package]] 314 | name = "serde" 315 | version = "1.0.116" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | dependencies = [ 318 | "serde_derive 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", 319 | ] 320 | 321 | [[package]] 322 | name = "serde_derive" 323 | version = "1.0.116" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | dependencies = [ 326 | "proc-macro2 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 328 | "syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", 329 | ] 330 | 331 | [[package]] 332 | name = "serde_json" 333 | version = "1.0.57" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | dependencies = [ 336 | "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", 339 | ] 340 | 341 | [[package]] 342 | name = "smallvec" 343 | version = "1.4.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | 346 | [[package]] 347 | name = "strsim" 348 | version = "0.8.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | 351 | [[package]] 352 | name = "syn" 353 | version = "1.0.42" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | dependencies = [ 356 | "proc-macro2 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 358 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 359 | ] 360 | 361 | [[package]] 362 | name = "termios" 363 | version = "0.3.2" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | dependencies = [ 366 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 367 | ] 368 | 369 | [[package]] 370 | name = "textwrap" 371 | version = "0.11.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | dependencies = [ 374 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 375 | ] 376 | 377 | [[package]] 378 | name = "thread_local" 379 | version = "1.0.1" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | dependencies = [ 382 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 383 | ] 384 | 385 | [[package]] 386 | name = "treeline" 387 | version = "0.1.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | 390 | [[package]] 391 | name = "unicode-width" 392 | version = "0.1.8" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | 395 | [[package]] 396 | name = "unicode-xid" 397 | version = "0.2.1" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | 400 | [[package]] 401 | name = "vec_map" 402 | version = "0.8.2" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | 405 | [[package]] 406 | name = "winapi" 407 | version = "0.3.9" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | dependencies = [ 410 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 411 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 412 | ] 413 | 414 | [[package]] 415 | name = "winapi-i686-pc-windows-gnu" 416 | version = "0.4.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | 419 | [[package]] 420 | name = "winapi-x86_64-pc-windows-gnu" 421 | version = "0.4.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | 424 | [metadata] 425 | "checksum aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 426 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 427 | "checksum assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2dc477793bd82ec39799b6f6b3df64938532fdf2ab0d49ef817eac65856a5a1e" 428 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 429 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 430 | "checksum cargo_metadata 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "700b3731fd7d357223d0000f4dbf1808401b694609035c3c411fbc0cd375c426" 431 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 432 | "checksum clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 433 | "checksum clicolors-control 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90082ee5dcdd64dc4e9e0d37fbf3ee325419e39c0092191e0393df65518f741e" 434 | "checksum cloudabi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" 435 | "checksum console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8ca57c2c14b8a2bf3105bc9d15574aad80babf6a9c44b1058034cdf8bd169628" 436 | "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 437 | "checksum encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 438 | "checksum escargot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ceb9adbf9874d5d028b5e4c5739d22b71988252b25c9c98fe7cf9738bee84597" 439 | "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 440 | "checksum hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" 441 | "checksum instant 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66" 442 | "checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 443 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 444 | "checksum libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" 445 | "checksum lock_api 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" 446 | "checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 447 | "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 448 | "checksum parking_lot 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" 449 | "checksum parking_lot_core 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" 450 | "checksum predicates 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" 451 | "checksum predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" 452 | "checksum predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" 453 | "checksum proc-macro2 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9" 454 | "checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 455 | "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 456 | "checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 457 | "checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 458 | "checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 459 | "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 460 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 461 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 462 | "checksum serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)" = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" 463 | "checksum serde_derive 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)" = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" 464 | "checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" 465 | "checksum smallvec 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" 466 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 467 | "checksum syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228" 468 | "checksum termios 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2" 469 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 470 | "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 471 | "checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" 472 | "checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 473 | "checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 474 | "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 475 | "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 476 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 477 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 478 | --------------------------------------------------------------------------------