How to programmatically compile Rust programs? - rust

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.

Related

How is it possible to keep Rust module documentation in separate Markdown files?

This section of the Rust book seems to imply that it is possible to keep Rust documentation in separate .md files, but it does not say how these .md files can then be included back. How does this work?
The syntax for putting Rust module documentation in separate Markdown files is:
#![doc = include_str!("path/to/some-documentation.md")]
/* content of the module */
This is supported since stable Rust 1.54.0.
On old nightly compilers from 1.50.0-nightly through 1.53.0-nightly, an unstable feature is required in order for the above to be available.
#![feature(extended_key_value_attributes)]
#![doc = include_str!("path/to/some-documentation.md")]
On nightly compilers 1.24.0-nightly through 1.53.0-nightly, the following alternative syntax is available, but has since been removed.
#![feature(external_doc)]
#![doc(include = "path/to/some-documentation.md")]
It doesn't. That section on describing the functionality of rustdoc is saying that it can process individual .md files. The third paragraph touches on this:
Documentation can be generated in two ways: from source code, and from standalone Markdown files.
Insofar as I am aware, there is no extant way to put code documentation in external files. It would be theoretically possible to do so using a procedural derive macro, but I'm not aware of any crate that actually does this.
In stable Rust, you can mimic the unstable external-doc feature through clever macros.
An easy way to do this is to use the doc-comment crate:
#[macro_use]
extern crate doc_comment;
// If you want to test examples in your README file.
doctest!("../README.md");
// If you want to document an item:
doc_comment!(include_str!("another_file.md"), pub struct Foo {});
You can see a complicated version of this in my crate SNAFU, where I use it for the user's guide.
The "by-hand" version involves passing the thing to be documented along with the included markdown:
macro_rules! inner {
($text:expr, $($rest: tt)*) => {
#[doc = $text]
$($rest)*
};
}
inner! {
include_str!("/etc/hosts"),
mod dummy {}
}
See also:
Is it possible to emit Rust attributes from within macros?
How to embed a Rust macro variable into documentation?
Generating documentation in macros

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 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;
...

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;
...

How to compile multiple Rust files into a single C library?

You can compile a Rust file to a C library like this:
rustc --crate-type=staticlib file.rs
But what if you have multiple Rust files, can you compile them into a single C library, or does each one have to be a different library? How does the Rust module system interact with building staticlibs?
Here, file.rs is your crate root, which is what results in the static library. So to add other files, you have to reference them from the crate root. That is, assuming you have a file other.rs, you could do something like mod other; in file.rs to effectively 'bring in' the contents of other.rs into file.rs.
See the guide for more information.

Resources