Cargo build --verbose --target=i686-linux-android makes target_os NOT android, why? - rust

If I build my project with
cargo build --verbose --target=i686-linux-android
where build.rs looks like this
fn main() {
#[cfg(target_os = "linux")]
{
panic!("target_os is linux!!!!!!!!!!!!!");
}
I get the panic at panic!("target_os is linux!!!!!!!!!!!!!");, but the target is android.
Why?

The build.rs script is compiled and run locally and thus its #[cfg(...)] attributes will reflect the local system. If you want to know the operating system that you're ultimately building for, use the CARGO_CFG_TARGET_OS environment variable.
Others can be seen in Environment variables Cargo sets for build scripts in the Rust Reference.

Related

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

Why does a procedural macro not see environment variables set by dotenv in my build script?

I have a procedural macro crate Proc and binary crate Bin. Bin has a dependency on Proc. Proc needs a filled environment variable to function properly.
This is some code inside my build.rs in Bin. Proc can successfully find the env value when using the following code:
fn main() {
println!("cargo:rustc-env=SOME_ENV_VALUE=somevalue");
}
However, Proc fails to find the environment variable when using this code inside my build.rs in Bin (note: when checking the existence right after the dotenv call, I can verify the key is actually present):
fn main() {
dotenv::dotenv().unwrap();
}
This is my Proc crate:
use proc_macro::TokenStream;
#[proc_macro_derive(MyProcMacro)]
pub fn my_proc_macro(input: TokenStream) -> TokenStream {
if std::env::var("SOME_ENV_VALUE").is_err() {
panic!("Failed to retrieve env value")
}
TokenStream::new()
}
Why won't it fail with the println! command? Can it work with dotenv? Else I need to write some code that copies the keys from my env file to the println! command...
All the code is in my minimal reproduction project.
I encourage you to re-read the Build Scripts chapter of the Cargo docs. A build script is a separate executable that Cargo builds and executes before starting to build your source code.
Cargo starts
Cargo executes rustc to build the build script
Cargo executes the build script
Cargo executes rustc to build your code
Cargo exits
[dotenv] loads environment variables from a .env file, if available, and mashes those with the actual environment variables provided by the operating system.
Variables that it loads are placed into the current processes environment variables. In your example, that's the build script executable.
[cargo:rustc-env] tells Cargo to set the given environment variable when compiling the package
The stdout of the build script interacts with Cargo, which then modifies how the code is compiled, including what environment variables to set.
You need to load the dotenv file and set the environment variables for the subsequent compilation. Something like this compiling-but-untested example:
fn main() {
if let Ok(env) = dotenv::dotenv_iter() {
for (k,v) in env.flatten() {
println!("cargo:rustc-env={}={}", k, v);
}
}
}
Don't worry that this method is marked as deprecated. The maintainers changed their minds.

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)");
}

Is it possible to invoke a build.rs only for release mode?

In a Rust crate, is it possible to invoke a build.rs only for release mode?
One can specify it in Cargo.toml:
[package]
build = "build.rs"
The issue is, for development, it delays the beginning of compiling the crate's sources. The Cargo guide doesn't seem to offer such an option.
You can use this build script. Cargo passes PROFILE environment variable to rustc invokation, which can be used to determinate active profile.
// build.rs
use std::env;
pub fn main() {
if Ok("release".to_owned()) == env::var("PROFILE") {
panic!("I'm only panicking in release mode")
}
}

Resources