How can a Rust program access metadata from its Cargo package? - 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 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.

Issue when replacing a crates dependency with a local version

So I am trying to serialize a struct using bincode following these instructions, and I was able to get that to work.
But then I wanted to serialize a struct with an IpAddr enum. Since IpAddr doesn't implement the Encodable trait needed, I downloaded the rustc_serialize crate from git and implemented encodable for IpAddr myself. I then changed my Cargo.toml file to:
[dependencies]
# rustc-serialize = "0.3"
byteorder = "0.3"
bincode = "0.4"
[dependencies.rustc-serialize]
path = "src/rustc-serialize-master"
But now, the same code from the struct I was using doesn't compile saying that
rustc_serialize::serialize::Encodable is not implemented for my struct even though i have #[derive(RustcEncodable)] above the struct.
Even when I get rid of the code I added to the local version of rustc_serialize, I still get that error.
I think it might be due to something being screwed up with the way bincode and a local rustc_serialize interact, but I'm not sure.
Please review the Cargo documentation on overriding dependencies:
To specify overrides, create a .cargo/config file in some ancestor of your project's directory (common places to put it is in the root of your code directory or in your home directory).
Inside that file, put this:
paths = ["/path/to/project/rand"]
Going deeper, you are likely running into issue 22750 - two different versions of a crate interacting leads to unhelpful error messages. When you add rustc-serialize to your dependencies, you aren't replacing the old version, you are adding a new one.
In general, this is a good feature. If my project relies on crates A and B and they both rely on crate Z but with different versions, Rust can handle that just fine. The problem arises when they re-export items from those crates.

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

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