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

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.

Related

How to get the binary output of cargo run <rust.rs>?

When we compile a c file using gcc test.c -o test.
We can get the binary file as test.
But while running a file using cargo run test.rs in rust.
can we get the binary like we got in the C program?
The original hello.c file:
void main() {
// printf() displays the string inside quotation
printf("Hello, World!");
}
The rust program:
extern "C" {
fn printf(_: *const libc::c_char, _: ...) -> libc::c_int;
}
unsafe fn main_0() {
// printf() displays the string inside quotation
printf(b"Hello, World!\x00" as *const u8 as *const libc::c_char);
}
pub fn main() { unsafe { main_0() } ::std::process::exit(0i32); }
When using cargo it compiles and runs perfectly.
└─$ cargo run hello.rs
Compiling Rust_testing v0.1.0 (/home/pegasus/Documents/Rust_testing)
warning: crate `Rust_testing` should have a snake case name
|
= note: `#[warn(non_snake_case)]` on by default
= help: convert the identifier to snake case: `rust_testing`
warning: `Rust_testing` (bin "Rust_testing") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/Rust_testing hello.rs`
Hello, world!
Here's my Cargo.toml file:
[package]
name = "Rust_testing"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2"
I have a rust program named hello.rs.
The program is I'm unable to compile it using rustc. I generated the hello.rs using c2rust online transpiler. But if I use cargo run hello.rs the program runs smoothly.
while using rustc new.rs -o test,
I can get the x86 test binary.
How to get similar kind of file while using the cargo run new.rs?
I looked into the target/debug directory.
But there are so many directories and so many files there. How to know which on is created for which .rs file?
┌──(pegasus㉿pegasus)-[~/Documents/Rust_testing/target/debug]
└─$ ls
build deps examples incremental Rust_testing Rust_testing.d
If you do cargo build, you will find the binary in target/debug/. If you build in release via cargo build --release, you will find it in target/release/.
Be aware that cargo run hello.rs does not compile hello.rs. It will always compile src/main.rs. hello.rs will be passed to the compiled program as a command line argument.
How to know which on is created for which .rs file?
There isn't one file for one .rs file. If your crate is a binary crate, then there will be exactly one executable with the name of your crate. In your case it's Rust_testing. You can run it with ./target/debug/Rust_testing, or copy it somewhere else and execute it directly.
You can add multiple binaries per crate by putting them in the src/bin folder. For example, if you put your hello.rs file in src/bin and then execute cargo build --all, it will create a target/debug/hello executable that you can run.
For more information about cargo's folder layout, read the cargo documentation.
If you are new to Rust, I highly recommend reading the Rust book. It will guide you through how to use rustup, rustc and cargo step by step.

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

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.

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.

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

How to specify an environment variable using the rustc-env flag?

I want to set rustc-env=VAR=VALUE so that I could access it using env::var("VAR") in my code. However, I'm not clear on where to specify it. Can I set the environment variable VAR in the Makefile?
TL;DR
build.rs
fn main() {
println!("cargo:rustc-env=VAR=VALUE");
}
src/main.rs
fn main() {
let var = env!("VAR");
}
The documentation that you linked is for a Cargo build script:
The Rust file designated by the build command (relative to the package root) will be compiled and invoked before anything else is compiled in the package, allowing your Rust code to depend on the built or generated artifacts. By default Cargo looks up for "build.rs" file in a package root (even if you do not specify a value for build). Use build = "custom_build_name.rs" to specify a custom build name or build = false to disable automatic detection of the build script.
On the same page, there's a section that describes outputs of build.rs
All the lines printed to stdout by a build script are written to a file [...] Any line that starts with cargo: is interpreted directly by Cargo. This line must be of the form cargo:key=value, like the examples below:
cargo:rustc-env=FOO=bar
It then details rustc-env:
rustc-env=VAR=VALUE indicates that the specified environment variable will be added to the environment which the compiler is run within. The value can be then retrieved by the env! macro in the compiled crate. This is useful for embedding additional metadata in crate's code, such as the hash of Git HEAD or the unique identifier of a continuous integration server.
env! is a macro.
access it using env::var("VAR")
No. env::var is for reading environment variables set when the program runs, not when the program is compiled.
See also:
Is it possible to initialize a variable from an environment variable at compilation time when using no_std?

Resources