How to get a crate's version number? [duplicate] - rust

How do you access a Cargo package's metadata (e.g. version) from the Rust code in the package? In my case, I am building a command line tool that I'd like to have a standard --version flag, and I'd like the implementation to read the version of the package from Cargo.toml so I don't have to maintain it in two places. I can imagine there are other reasons someone might want to access Cargo metadata from the program as well.

Cargo passes some metadata to the compiler through environment variables, a list of which can be found in the Cargo documentation pages.
The compiler environment is populated by fill_env in Cargo's code. This code has become more complex since earlier versions, and the entire list of variables is no longer obvious from it because it can be dynamic. However, at least the following variables are set there (from the list in the docs):
CARGO_MANIFEST_DIR
CARGO_PKG_AUTHORS
CARGO_PKG_DESCRIPTION
CARGO_PKG_HOMEPAGE
CARGO_PKG_NAME
CARGO_PKG_REPOSITORY
CARGO_PKG_VERSION
CARGO_PKG_VERSION_MAJOR
CARGO_PKG_VERSION_MINOR
CARGO_PKG_VERSION_PATCH
CARGO_PKG_VERSION_PRE
You can access environment variables using the env!() macro. To insert the version number of your program you can do this:
const VERSION: &str = env!("CARGO_PKG_VERSION");
// ...
println!("MyProgram v{}", VERSION);
If you want your program to compile even without Cargo, you can use option_env!():
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
// ...
println!("MyProgram v{}", VERSION.unwrap_or("unknown"));

The built-crate helps with serializing a lot of Cargo's environment without all the boilerplate.

At build-time (as in build.rs), cargo_metadata may be useful. For example:
let path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let meta = MetadataCommand::new()
.manifest_path("./Cargo.toml")
.current_dir(&path)
.exec()
.unwrap();
let root = meta.root_package().unwrap();
let option = root.metadata["my"]["option"].as_str().unwrap();
let version = &root.version;
...

Related

Is it possible to use .env file at build time?

I'm coming to Rust from the JavaScript world, where you could have a .env file (git-ignored), and environmental values from this file could be made available at runtime via an npm package (say, dotenv).
I was able to use a dotenv crate to make this work similarly when I just cargo run the project, but after I build a release version, the value of the environment variable is lost and the app panics.
I understand that cargo doesn't evaluate env variables at build time, and using env!-like macros won't work either, at least to my best understanding.
What I'm asking is this: how do I store a variable in a file (that I can gitignore), make Rust pick up values from this file, and use them at build time so that these values are available to the release-built app?
I'm sure that there's a well-established way of doing this, but I struggle to figure it out.
There is the dotenv-codegen crate that provides a dotenv! macro that works like the env! macro except it will load from a .env file.
I'm not so sure this is well-established, but you can use a build script that will read the file and use println!("cargo:rustc-env=VAR=VALUE") to send the environment variables to Cargo, allowing you to retrieve them in the code with env!() or option_env!().
For example, to use a .env file, add dotenv to build-dependencies, and use it like so in build.rs:
fn main() {
let dotenv_path = dotenv::dotenv().expect("failed to find .env file");
println!("cargo:rerun-if-changed={}", dotenv_path.display());
// Warning: `dotenv_iter()` is deprecated! Roll your own or use a maintained fork such as `dotenvy`.
for env_var in dotenv::dotenv_iter().unwrap() {
let (key, value) = env_var.unwrap();
println!("cargo:rustc-env={key}={value}");
}
}

Is there a problem with naming a crate containing the string ".rs"?

Could there be any future problem in naming a dependency .rs for example,
[dependencies]
gccjit.rs = { git = "https://github.com/swgillespie/gccjit.rs.git" }
In the above code, I use .rs for something which is not a Rust source code file. Is that not ideal or is it okay because it would be easier to default to the same name as the repository?
If an object is named .rs it could maybe be automatically recognized as Rust source code but in this case it is not.
Yes, there is a problem. Do not do this.
How you could have determined this for yourself
Try to use the code that you've proposed, you'll see:
$ cargo build
error: failed to parse manifest at `.../Cargo.toml`
Caused by:
could not parse input as TOML
Caused by:
expected an equals, found a period at line 9
You could have also attempted to create a package with the same name. You would have then seen:
$ cargo new 'gccjit.rs.git'
error: Invalid character `.` in crate name: `gccjit.rs.git`
use --name to override crate name
Who controls crate names
You don't get to control the name of an imported crate that way; the crate determines it and it's already picked one:
[package]
name = "gccjit"
If you want to rename an existing package on import, you have to use the package key to match the real name:
some_name = { package = "gccjit", git = "https://github.com/swgillespie/gccjit.rs.git" }
See How to idiomatically alias a crate in Rust 2018? for more.
To use a period in the name, you can seemingly use a string key (although I think this is a bug):
"gcc.jit" = { package = "gccjit", git = "https://github.com/swgillespie/gccjit.rs.git" }
However, a package name has to be a valid Rust identifier, which periods are not. If you do this, there's no way to use the package.
It's redundant anyway
More opinion based, such a name is completely pointless. You don't need to say "rs" or "rust" in the name because of course it's Rust code. If it wasn't, you couldn't use it as a dependency in the first place.
We don't call our packages "computer-source-code-awesome-tool" for the same reason — that much is implied.
Package names are different from source control
Cargo and Rust don't care what the name of your source control repository is. It's separate from the package name. While it's better to have them be somewhat similar, there's no real reason they have to be whatsoever related.
Package names are different from library names
This is a feature with a very small number of uses, but the name of your package (a.k.a. name of the crate on Crates.io) can be different from the name of your library (what is imported into the code).
Piston is the biggest "offender" of this that I know of:
[package]
name = "piston2d-graphics"
version = "0.30.0"
[lib]
name = "graphics"
Please do not use this as it's simply maddening to attempt to debug.

Could not find `Options` in `getopts`

I am trying to use the Options struct in the getopts crate, which exists according to the Rust book.
The code I am running is this.
let mut opts = getopts::Options::new();
While building the project the compiler gives the error
let mut opts = getopts::Options::new();
^^^^^^^^^^^^^^^^^^^^^ Could not find `Options in `getopts`
How do I resolve this? I am using the nightly version of Rust.
You need to use the crates.io version instead. You can see here that the built-in version of getopts has been flagged as a compiler internal with rustc_private. The version on crates.io is basically the same crate. The compiler-internal version is just kept segregated for various reasons.

How to programmatically compile Rust programs?

Given metadata about the library I am trying to create in the form of an expression tree, I would like to be able to convert this into some sort of Rust-specific syntax tree that can be given to the Rust compiler.
I found a related question but it is outdated.
Yes there is. The Rust compiler itself. The entire compiler is a library and rustc is just a small crate that calls into the compiler. As an example there's the stupid-stats crate. It runs the rust compiler to generate some statistics about the code.
All you need is to import the rustc and rustc_driver crates (with extern crate) and implement the rustc_driver::CompilerCalls trait for a type (lets call it MyDriver). Then you can run rustc like this:
let args: Vec<_> = std::env::args().collect();
let mut my_driver = MyDriver::new();
rustc_driver::run_compiler(&args, &mut my_driver);
You need to make sure that the path to the standard library and core library is passed. In my case I added
"-L $HOME/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-gnu/lib"
to the command line. You cannot simply add this to the args vector, because $HOME isn't parsed here. So you need some more code that extracts the $HOME env var and builds your command.

How can a Rust program access metadata from its Cargo package?

How do you access a Cargo package's metadata (e.g. version) from the Rust code in the package? In my case, I am building a command line tool that I'd like to have a standard --version flag, and I'd like the implementation to read the version of the package from Cargo.toml so I don't have to maintain it in two places. I can imagine there are other reasons someone might want to access Cargo metadata from the program as well.
Cargo passes some metadata to the compiler through environment variables, a list of which can be found in the Cargo documentation pages.
The compiler environment is populated by fill_env in Cargo's code. This code has become more complex since earlier versions, and the entire list of variables is no longer obvious from it because it can be dynamic. However, at least the following variables are set there (from the list in the docs):
CARGO_MANIFEST_DIR
CARGO_PKG_AUTHORS
CARGO_PKG_DESCRIPTION
CARGO_PKG_HOMEPAGE
CARGO_PKG_NAME
CARGO_PKG_REPOSITORY
CARGO_PKG_VERSION
CARGO_PKG_VERSION_MAJOR
CARGO_PKG_VERSION_MINOR
CARGO_PKG_VERSION_PATCH
CARGO_PKG_VERSION_PRE
You can access environment variables using the env!() macro. To insert the version number of your program you can do this:
const VERSION: &str = env!("CARGO_PKG_VERSION");
// ...
println!("MyProgram v{}", VERSION);
If you want your program to compile even without Cargo, you can use option_env!():
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
// ...
println!("MyProgram v{}", VERSION.unwrap_or("unknown"));
The built-crate helps with serializing a lot of Cargo's environment without all the boilerplate.
At build-time (as in build.rs), cargo_metadata may be useful. For example:
let path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let meta = MetadataCommand::new()
.manifest_path("./Cargo.toml")
.current_dir(&path)
.exec()
.unwrap();
let root = meta.root_package().unwrap();
let option = root.metadata["my"]["option"].as_str().unwrap();
let version = &root.version;
...

Resources