Is there a simpler way to run clippy on my build script? - rust

In a Cargo project, I can easily run clippy on my src code using this command:
rustup run nightly cargo clippy
However, if I'm using a build script, I'd like to run clippy on that as well. For instance, if my build.rs file looks like this:
fn main() {
let foo = "Hello, world!";
println!("{}", foo);
}
I'd like to see this when I run clippy:
warning: use of a blacklisted/placeholder name `foo`, #[warn(blacklisted_name)] on by default
--> build.rs:2:9
|
2 | let foo = "Hello, world!";
| ^^^
|
= help: for further information visit https://github.com/Manishearth/rust-clippy/wiki#blacklisted_name
The only way I can think of to run clippy on my build script is to copy it into a cargo new temporary project, run clippy, make my changes there, and copy back, but this is horribly inconvenient and quickly becomes infeasible when build dependencies and the like are added to the mix.
Is there a simpler way to analyze my build script with clippy?

Note: This solution no longer works. The clippy plugin feature has been removed (source).
There are two ways to use Clippy: the cargo clippy command and the clippy compiler plugin. cargo clippy detects the build script as a dependency of the main project, so it doesn't load the compiler plugin.
Therefore, the other option is to use the compiler plugin directly. The instructions for doing this are in clippy's README. We need to make a few adaptations for using it on the build script, though.
First, we need to add clippy as a build dependency:
[build-dependencies]
clippy = { version = "*", optional = true }
[features]
default = []
Adding it to [dependencies] instead will not work (the result is error[E0463]: can't find crate for `clippy`), as Cargo will not pass the path to dependencies to the compiler when building the build script.
Then, we need to add this at the top of build.rs:
#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
Finally, we need to build with the clippy feature enabled:
$ cargo build --features clippy
If you want to run clippy on both the build script and on the main project when you use the command above, add the same clippy dependency to [dependencies], then add the cfg_attr attributes to the crate root(s) (lib.rs, main.rs, etc.).

Related

Ignore a test based on environment variable

There are a few tests that's running locally but not on Github workflows. I've spent quite some time but not able to debug and fix them. For now, I want to ignore them on ci/cd but run them locally. Since, Github provides a handy environment variable CI, which always remains true, can I use that to ignore test?
I don't want to wrap the whole function code under if(env::var("CI").is_ok()). Is there any better way?
One way you could go about doing this is by adding a feature to your Cargo.toml file, like ci or something similar. And then have your GitHub action compile with that feature enabled, and have a conditional compilation attribute on the tests in question.
To do this, you would first add a new feature to your Cargo.toml file:
[features]
ci = []
Then in your Rust code on a test you could write something like this:
#[test]
#[cfg_attr(feature = "ci", ignore)]
fn test_some_code() {
println!("This is a test");
}
Now locally if you run cargo test you will see the test run, but if you run cargo test --features ci you will see the test shows ignored, like below:
Now you just have to change your GitHub action to compile with this feature. If your action looks something like this:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
Then add --features ci to the end of both the cargo build and cargo test commands.
For more information on conditional compilation, this is in the Rust book: https://doc.rust-lang.org/reference/conditional-compilation.html
For more information on features in Cargo, this is in the Cargo book: https://doc.rust-lang.org/cargo/reference/features.html
To add to pokeyOne's answer, you can include a build script that checks for the CI environment variable and enables a ci feature when it's present. To do this, you would add this build.rs to your project root:
fn main() {
if std::env::var("CI").is_ok() {
println!("cargo:rustc-cfg=feature=\"ci\"");
}
}
Note that build scripts are only run on clean builds, so to test this locally you'll need to run cargo clean between runs (this won't be an issue on CI since you're doing a clean build there).
$ cargo test # run all tests
$ cargo clean # make sure build.rs is re-run...
$ CI=1 cargo test # skip CI tests
Features are a special type of --cfg flag, used for conditional compilation. Instead of using a feature, you could instead use a single identifier --cfg flag, like so:
// build.rs
fn main() {
if std::env::var("CI").is_ok() {
println!("cargo:rustc-cfg=ci");
}
}
// src/main.rs
#[cfg(test)]
mod tests {
// ..(snip)..
#[test]
#[cfg(not(ci))]
fn breaks_on_ci() { ... }
}
This build.rs is equivalent to running
$ RUSTFLAGS="--cfg ci" cargo test
You can read more about build scripts, --cfg flags, and conditional compilation in the Rust book here.

Is it possible to run a single rust file as a script while linking to other libraries without generating cargo projects

I'd like to run a one off rust "script" without going through creating a cargo project for a single run (since I am providing this script to colleagues).
Ideally I could build directly with the command line avoiding creating cargo projects etc.
for instance:
use serde_json::Value;
use some_private_packege_i_own_locally_in_another_directory;
fn main() {
// do some stuff with these packages and die
}
I would need to depend on the serde_json and my some_private_packege_i_own_locally_in_another_directory.
(A bit similar to rust playground I suppose for a single time use)
Something similar to this from the command line would be great:
rustc /path/to/main.rs --dependency serde_json, my_package ...
You can specify a dependency with with extern flag, and you can specify the location of transitive dependencies, with -L dependency. You will have to compile each dependency, and all of it's dependencies manually:
// compile all of serde's dependencies
// compile all of hyper's dependencies
// compile serde
// compile hyper
rustc script.rs --crate-type bin -L dependency=~/tmp/deps --extern serde_json=~/tmp/deps/serde_json.rlib --extern hyper=~/tmp/deps/hyper.rlib
As you can tell, this would get very difficult, even with two direct dependencies. Instead, you can use cargo-script, which handles all of this for you:
cargo install cargo-script
cargo script -D hyper -D serde_json script.rs

How to run cargo with features flag

I'm trying to learn rust by writing CLI but i can't do cargo run with features passed and i don't understand why. I read docs / stack and i still don't see why this is happening. It feels like it should work this way https://doc.rust-lang.org/cargo/commands/cargo-run.html
I'm trying to run this code
https://github.com/clap-rs/clap/blob/master/examples/17_yaml.rs
with command cargo run --features=yaml or cargo run --features yaml. I tried many combinations, none of them worked.
My Cargo.toml looks like that:
[dependencies.clap]
version = "*"
default-features = false
features = ["yaml"]
When i run i have error:
:!cargo run --features=yaml
error: Package `fun v0.1.0 (/Users/XXX/Projekty/rust/fun)` does not have these fe
atures: `yaml`
shell returned 101
What am i doing wrong?
Their code expects you to have cloned the clap repository, changed into its directory, and then run cargo run --features yaml --example 17_yaml from there. You can read more about how the cargo examples feature works here.
If you’re planning on copying their code, as noted in that example code, you have to remove this conditional compilation attribute:
// Note: If you're using clap as a dependency and don't have a feature for your users called
// "yaml", you'll need to remove the #[cfg(feature = "yaml")] conditional compilation attribute
#[cfg(feature = "yaml")]
fn main() {
Otherwise it will load this other main implementation and emit that error:
#[cfg(not(feature = "yaml"))]
fn main() {
// As stated above, if clap is not compiled with the YAML feature, it is disabled.
println!("YAML feature is disabled.");
println!("Pass --features yaml to cargo when trying this example.");
}
You don’t actually need to pass --features on the command line unless you are running their example within their crate as described above. You should also remove this whole function if you’re copying their code! It is only relevant when run as an example.

Conditional compilation for 'nightly' vs 'stable' Rust or compiler version

I am using some Rust unstable features, but I still want to be able to compile a reduced version of my library with stable Rust. I am happy to only include those unstable features when the compiler supports them, and exclude them when they are not supported.
I thought it would be easy to achieve this goal using conditional compilation like #[cfg(rust_version = "nightly")], but it seems like 'stable' vs 'nightly' are not cfg options.
How do you guys perform conditional compilation based on 'stable' vs 'nightly', or based on the compiler version?
I recommend creating a feature for your nightly-only code that is disabled by default, for example
Cargo.toml
[features]
default = []
nightly-features = []
Since the nightly-features feature is not default, compilation with the stable toolchain works out of the box. You can use attributes #[cfg(feature = "nightly-features")] and #[cfg(not(feature = "nightly-features"))] to include or exclude code from nightly-specialized versions. This method has the added benefit of allowing testing of the nightly features independently of the compiler (i.e. answer the question: did the compiler break my code, or does code enabled by nightly-features contain bugs?).
Despite the risks, I want to enable nightly features automatically
Use build scripts, sometimes called build.rs in addition to the nightly feature described above. (note: the following should NEVER be used in a library, otherwise switching compilers could become a breaking change. prefer the solution explained above)
build.rs (goes in package root)
use std::env;
fn main() {
let rust_toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap();
if rust_toolchain.starts_with("stable") {
// do nothing
} else if rust_toolchain.starts_with("nightly") {
//enable the 'nightly-features' feature flag
println!("cargo:rustc-cfg=feature=\"nightly-features\"");
} else {
panic!("Unexpected value for rustc toolchain")
}
}
this build script checks the toolchain environment variable set by rustup (some rust installations do not use rustup) and enables the nightly feature flag if the compiler is nightly.
src/main.rs
fn main() {
#[cfg(feature = "nightly-features")]
println!("Hello, nightly!");
#[cfg(not(feature = "nightly-features"))]
println!("Hello, stable!");
}
now, running
➜ cargo +stable run
Hello, stable!
➜ cargo +nightly run
Hello, nightly!
Is it possible to turn this feature off when build.rs turns it on?
As far as I can tell, no. Running cargo +nightly run --no-default-features leaves the feature on, due to how cargo passes flags to rustc. A programmer could create a specific environmental variable that build.rs checks for to skip the automatic version detection, but that is more complicated than the alternative with no build script - cargo build --features=nightly-features
Crate alternative
Instead of the proposed solution, you can use the rustversion crate, which works in a very similar way (but parses the output of rustc --version).
fn main() {
#[rustversion(nightly)]
println!("Hello, nightly!");
#[rustversion::not(nightly)]
println!("Hello, stable! (or beta)");
}

How do I use conditional compilation with `cfg` and Cargo?

I want to conditionally compile my source code using cfg with Cargo,
after Googling for a while,
it seems that the solution is to use cargo --features.
http://doc.crates.io/manifest.html
I tried adding a few
#[cfg(feature = "foo")]
in the source code and
cargo build --features foo
, but it says
Package `xxx v0.0.1 (file:///C:/yyy/xxx)` does not have these features: `foo`
How can I let cargo identify the features? Do I have to add something in Cargo.toml?
Here's the version of rustc and cargo I am using:
C:\>rustc --version
rustc 0.13.0-nightly (42deaa5e4 2014-12-16 17:51:23 +0000)
C:\>cargo --version
cargo 0.0.1-pre-nightly (5af754d 2014-12-18 01:50:48 +0000)
You have to introduce the existing features in your Cargo.toml.
I was able to conditionally compile by doing the following:
In Cargo.toml, create a features section and introduce a certain feature name:
[features]
customfeature = [] # feature has no explicit dependencies
If you want your feature to have specific dependencies check the examples in the documentation.
In your code, use #[cfg(feature="customfeature")]
Run cargo build --features customfeature
Since your steps 2 & 3 seem to be fine, there must probably be a problem with your Cargo.toml.
As stated in other answers, you can use features for this. I would like to add that features do not only allow you to conditionally compile parts of your code but also to conditionally include dependencies that may be part of that code. Consider the following snippets:
You can activate the conditional code using a feature flag as already described in other anwsers:
cargo build --features customfeature
You need to mark your conditional code to exist only when your customfeature is enabled:
#[cfg(feature = "customfeature")]
fn my_func() {
my_optional_dependency::do_something();
}
// This includes dependencies only when customfeature is enabled
#[cfg(feature = "customfeature")]
extern crate my_optional_dependency;
....
#[cfg(feature = "customfeature")]
use my_optional_dependency::*;
....
Your Cargo.toml needs to have the following sections:
[dependencies.my_optional_dependency]
version = "1.2.3"
optional = true
[features]
customfeature = ["my_optional_dependency"]
This allows you to activate certain parts of your code along with their dependencies only if a feature is enabled.
Alternatively, you could create a cargo configuration file in your project, by creating a .cargo subdir in your project main folder, adding in it a config.toml file,
then inserting this section in .cargo/config.toml:
[build]
rustflags = "--cfg my_cfg_flag"
This will make cargo call rustc with flags --cfg my_cfg_flag
See here for details:
https://doc.rust-lang.org/cargo/reference/config.html

Resources