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

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?

Related

rust libraries with cargo (rlib)

I am trying to create a library in rust to be used with rust executables. In C you can just create your .a or .so (or .lib or .dll on windows) and use tools like CMake to link everything, however rust does not seem to have this kind of infrastructure?
It is possible to make an executable with cargo (cargo new ) and create a library by adding the --lib flag (cargo new --lib), but then how would you use the resulting .rlib file (from the library cargo project)? I managed to link the .rlib file as follows:
rustc main.rs --extern foo=libfoo.rlib
and that works beautifully, though, I am not interested in writing a thousand rustc commands to build the final executable (which depends on the .rlib) if there is cargo that can do that for you. I tried working with a build script (which works perfectly for any C library, static or dynamic), but if I try it with the .rlib file, cargo says that it cannot find "foo" (-lfoo), the build script:
fn main() {
println!("cargo:rustc-link-search=.");
println!("cargo:rustc-link-lib=foo");
}
I tried replacing the path (search) to different directories (whilst also moving the .rlib file to the correct directory), also tried different combinations of libfoo, libfoo.rlib, ... (note that for the C libaries, foo is sufficient).
So my question really is: How can you create a rust library for private use, and how do you use it with a rust executable in a proper way, avoiding manual rustc commands? Are there tools that do this? Am I missing something in the build script? Perhaps there exists something like CMake for rust?
I suppose it is possible to just create a C interface over the rust code and compile another C project as that does work with cargo.
I do NOT want to publish the code to crates.io as I want this library strictly for private use.
Cargo does not support using pre-compiled .rlibs. Cargo is designed to compile programs fully from source (not counting native libraries).
How can you create a rust library for private use … I do NOT want to publish the code to crates.io as I want this library strictly for private use.
To use a private library, you write a dependency using a path or git dependency (or use a private package registry, but that's more work to set up).
[dependencies]
my-lib-a = { path = "../my-lib-a/" }
my-lib-b = { git = "https://my-git-host.example/my-lib-b", branch = "stable" }
Your private library is now compiled exactly like a “public” one.

Why Rust build.rs is unable to find C headers?

Basically I am trying to cargo build a crate which has a build.rs file.
This crate is located inside a bigger project and it's supposed to be a lib crate.
And inside this build.rs file, I am trying to compile a .C file which includes a couple of headers.
Fun fact: I got this build.rs file and crate structure from another little demo crate, and in that demo crate I had no problem to compile this exact C file with these headers.
FULL ERROR HERE:
Here is a link to github: https://github.com/mihaidogaru2537/FirecrackerPlayground/tree/dpdk_component/firecracker/src/dpdk_component
There is the crate I am talking about and you can also see the bigger project in which it resides. In the README file you can see the full error.
Either I do cargo build from the root of the big project or from the root of this problematic crate, the error is the same.
"cargo:warning=/usr/include/asm-generic/errno.h:5:10: fatal error: asm-generic/errno-base.h: No such file or directory
cargo:warning= 5 | #include <asm-generic/errno-base.h>"
The missing file might change depending on the .flag("-I/path/..") calls I am doing inside the build.rs
As you can see, right now it's unable to find errno-base.h, but I am including the path to asm-generic.
Here is the code of the build.rs file from the crate where the compilation of this C file works, as you can see, I did not have to add any include flags before calling compile.
fn main() {
// Tell cargo to tell rustc to link the system bzip2
// shared library.
// println!("cargo:rustc-link-lib=rte_ring");
// println!("cargo:rustc-link-lib=rte_mempool");
// Tell cargo to invalidate the built crate whenever the wrapper changes
// println!("cargo:rerun-if-changed=wrapper.h");
let _src = ["src/static-functions.c"];
println!("cargo:rerun-if-changed=build.rs");
let mut builder = cc::Build::new();
let build = builder
.file("src/static-functions.c")
.flag("-Wno-unused-parameter");
build.compile("foo");
}
Additional info:
The problematic crate is pretty small, see the link above. There is the build.rs file, C file and header file is inside the include directory.
One thing that I suspect, is that the target of the bigger project:
TARGET = Some("x86_64-unknown-linux-musl")
might affect the way the C file is compiled.
In the project where the compilation is working, I am not using that linux-musl stuff.
I am a total noob when it comes to Rust, but I do have a decent understanding of how C/C++ works.
I am running the project on Ubuntu 20.04
Those missing headers are a result of importing DPDK headers, I have DPDK libraries installed on the machine in question.
Let me know if you have any questions, sorry for the long read and thank you.
I somehow managed to fix it by adjusting the cargo build command to use x86_64-unknown-linux-gnu as target instead of x86_64-unknown-linux-musl (By default cargo build was doing the musl target somehow)
So if you are trying to build a rust app which is using DPDK libraries and you are getting missing headers, make sure to try the following:
cargo build --target=x86_64-unknown-linux-gnu
Well if you have to use musl and there is no alternative, I don't have an answer. But to me this was enough.
If someone has an explanation why musl is not working in this scenario, please let us know.
Reddit Rust community helped me as well, check this link out if you are interested:
https://www.reddit.com/r/rust/comments/mo3i08/unable_to_compile_c_file_inside_buildrs_headers/
So Why build.rs was unable to find .C headers?
ANSWER
Because I was using x86_64-unknown-linux-musl as target.

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.

How can the location of Cargo's configuration directory be overridden?

It seems that the default behavior of Cargo when searching for its configuration directory is to look in the current user's home directory (~/.cargo on my system). How can this behavior be modified to make Cargo look in a user-supplied directory instead?
Environment variables Cargo reads
You can override these environment variables to change Cargo's
behavior on your system:
CARGO_HOME — Cargo maintains a local cache of the registry index and of git checkouts of crates. By default these are stored under
$HOME/.cargo, but this variable overrides the location of this
directory. Once a crate is cached it is not removed by the clean
command.
CARGO_TARGET_DIR — Location of where to place all generated artifacts, relative to the current working directory.
RUSTC — Instead of running rustc, Cargo will execute this specified compiler instead.
RUSTC_WRAPPER — Instead of simply running rustc, Cargo will execute this specified wrapper instead, passing as its commandline
arguments the rustc invocation, with the first argument being rustc.
RUSTDOC — Instead of running rustdoc, Cargo will execute this specified rustdoc instance instead.
RUSTDOCFLAGS — A space-separated list of custom flags to pass to all rustdoc invocations that Cargo performs. In contrast with
cargo rustdoc, this is useful for passing a flag to all
rustdoc instances.
RUSTFLAGS — A space-separated list of custom flags to pass to all compiler invocations that Cargo performs. In contrast with cargo rustc,
this is useful for passing a flag to all compiler
instances.
CARGO_INCREMENTAL — If this is set to 1 then Cargo will force incremental compilation to be enabled for the current compilation,
and when set to 0 it will force disabling it. If this env var isn't
present then cargo's defaults will otherwise be used.
CARGO_CACHE_RUSTC_INFO — If this is set to 0 then Cargo will not try to cache compiler version information.
The Cargo documentation
Hierarchical structure
Cargo allows local configuration for a particular package as well as
global configuration, like git. Cargo extends this to a hierarchical
strategy. If, for example, Cargo were invoked in
/projects/foo/bar/baz, then the following configuration files would
be probed for and unified in this order:
/projects/foo/bar/baz/.cargo/config
/projects/foo/bar/.cargo/config
/projects/foo/.cargo/config
/projects/.cargo/config
/.cargo/config
$HOME/.cargo/config
With this structure, you can specify configuration per-package, and
even possibly check it into version control. You can also specify
personal defaults with a configuration file in your home directory.
The Cargo documentation
The default location can be changed by means of the environment variable $CARGO_HOME, it overrides the default location which is $HOME/.cargo.

Resources