I get this warnig from the rust compiler, when running the following code:
fn main() {
let test = 3;
let bar = Bar(test);
(&bar).foo();
}
#[derive(Debug, Copy, Clone)]
struct Bar(i32);
impl Bar {
fn foo(self) -> () {
println!("{:?}", self);
}
}
I wanted to try out the auto-ref rule on fn method_name(self).
I run cargo 1.61 on termux.
Can anyone explain to me what the actual warning is or how I can get it to be displayed?
EDIT:
Here's the termux console output
~/auto-ref/src $ cargo run
warning: Hard linking files in the incremental compilation cache failed. Copying files instead. Consider moving the cache directory to a file system which supports hard linking in session dir `/data/data/com.termux/files/home/auto-ref/target/debug/incremental/auto_ref-2c1jpkoha1tv/s-gbbxgywkr4-kv6r95-working`
warning: `auto-ref` (bin "auto-ref") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 1.11s
Running `/data/data/com.termux/files/home/auto-ref/target/debug/auto-ref` Bar(3)
~/auto-ref/src $
Ok, I understand now. What I thought was a warning was just the warnings summary. I got confused because I tried to test a rule that says that a &self method argument can automatically be referenced when using it on type self. The warning mentions auto-ref -- hence my confusion
-- but I think is actually about the linking warning.
What I tried was the following;
use the aut ref rule such that you do
bar.foo()
instead of
(&bar).foo()
.
Also the example code doesn't even do this. Here would be the correct example.
fn main() {
let test = 3;
let bar = Bar(test);
bar.foo();
}
#[derive(Debug, Copy, Clone)]
struct Bar(i32);
impl Bar {
fn foo(&self) -> () {
println!("{:?}", self);
}
}
The program takes an path to a configuration file. E.g. cargo run -- -c path/to/yaml.
This does not however work with cargo test. cargo test -- -c path/to/yaml and following error will occur: error: Unrecognized option: 'c'.
Attempts and research
Clap provide a method fn from_args() -> Self, but did not fully know how this would solve the problem. A similar problem was solved by making it a integration test and add
[[test]]
name = "cpp_test"
# path = "tests/cpp_test.rs" # This is automatic; you can use a different path if you really want to.
harness = false
to the cargo.toml file.
In my case I want to test some functions and thus unit test. I do not believe this would work.
I think the simplest way is to have a fn main_body(args: Args) that does the real work, and then just test main_body by passing the args directly in your source code instead of on the command line.
use clap::Parser; // 3.1.18
#[derive(Parser)]
struct Args {
#[clap(short, long)]
name: String,
}
fn main_body(args: Args) -> Result<(), ()> {
// Your main body here
Ok(())
}
fn main() -> Result<(), ()> {
let args = Args::parse();
main_body(args)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test() {
let args = Args { name: "Bob".into() };
assert_eq!(main_body(args), Ok(()));
}
}
I want rust code compiled when the compilation attribute debug_assertions is false (or not enabled), i.e. a "debug build".
Is this possible? What is the syntax?
For example, I can compile function build_type when compiling for debug build (i.e. option --release is not passed to command cargo build).
#[cfg(debug_assertions)]
pub fn build_type() -> String {
String::from("debug")
}
In this case, I want a "release version" of the function,
#[cfg(debug_assertions)]
pub fn build_type() -> String {
String::from("debug")
}
#[!cfg(debug_assertions)]
pub fn build_type() -> String {
String::from("release")
}
However, the syntax #[!cfg(debug_assertions)] results in cargo build error expected identifier, found '!'.
Other failed syntax variations were:
#[cfg(!debug_assertions)]
#[cfg(debug_assertions = false)]
#[cfg(debug_assertions = "false")]
In book Rust By Example, the section on cfg has this snipped code sample:
// And this function only gets compiled if the target OS is *not* linux
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
println!("You are *not* running linux!");
}
Use syntax #[cfg(not(debug_assertions))] to conditionally compile for cargo build --release.
The book The Rust Reference has the full list of conditional compilation predicates.
You can do this, based on rust book
if cfg!(not(target_os = "linux")) {
// Your code
} else {
// Your else code
}
And to check if it is a debug binary, you can do the same, but with the debug_assertions cfg, like this
if cfg!(debug_assertions) {
// Your code if is debug
}
I'm trying to setup a simple CLI program in Rust, but I want it to display the version based on the info in git describe rather than whatever is it the Cargo.toml file.
I setup some basic dependencies:
[dependencies]
structopt = "0.3.13"
[build-dependencies]
vergen = "3.1.0"
And a build.rs file does the lifting with vergen:
extern crate vergen;
use vergen::{ConstantsFlags, generate_cargo_keys};
fn main() {
// Setup the flags, toggling off the 'SEMVER_FROM_CARGO_PKG' flag
let mut flags = ConstantsFlags::all();
flags.toggle(ConstantsFlags::SEMVER_FROM_CARGO_PKG);
// Generate the 'cargo:' key output
generate_cargo_keys(flags).expect("Unable to generate the cargo keys!");
}
The result is a set of env variables usable from my main.rs:
fn main() {
println!("Build SHA: {}", env!("VERGEN_SHA_SHORT"));
}
This prints the correct git SHA I built from. The trouble is I can't figure out how to make StruckOpt use the generated version. I would have expected something like this to work:
use structopt::StructOpt;
/// A thing
#[derive(StructOpt)]
struct Cli {
version: [ version = env!("VERGEN_SEMVER") ],
}
fn main() {
let args = Cli::from_args();
println!("Build SHA: {}", env!("VERGEN_SHA_SHORT"));
}
But alas! In spite of this syntax being documented, it throws a parse error trying to build:
error: expected one of `!`, `(`, `+`, `::`, `;`, `<`, or `]`, found `=`
--> src/main.rs:6:24
|
6 | version: [ version = env!("VERGEN_SEMVER") ],
| ^ expected one of 7 possible tokens
What gives? How do I setup my CLI using StructOpt such that --version will report the version detected by vergen?
The [version = "version"] notation in the manual means that we need to write #[structopt(version = "version")] to specify a string of the version number:
$ cat src/main.rs
use structopt::StructOpt;
#[derive(StructOpt)]
#[structopt(version = env!("VERGEN_SEMVER"))]
struct Cli {}
fn main() {
let _cli = Cli::from_args();
}
$ env VERGEN_SEMVER=v1.2.3 cargo build
...
$ cargo run -- --version
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/foo --version`
foo v1.2.3
I want to make it so that when my program starts it prints to stderr:
This is Program X v. 0.1.0 compiled on 20180110. Now listening on stdin,
quit with SIGINT (^C). EOF is ignored. For licensing information, read
LICENSE. To suppress this message, supply --quiet or --suppress-greeting
In C/C++, I would achieve this with a Makefile, e.g.:
VERSION = 0.1.0
FLAGS = -Wall -pipe -O3 -funroll-loops -Wall -DVERSION="\"$(VERSION)\"" -DCOMPILED_AT="\"`date +%Y%m%d`\""
Then, in the source code, I would use those constants as I pleased, perhaps in a call to fprintf. After checking if they actually existed with #ifdef, of course.
How can this be achieved in Rust? Do I need to use a procedural macro? Can I use cargo somehow?
I know that env!("CARGO_PKG_VERSION") can be used as a replacement for VERSION, but what about COMPILED_AT?
There are two ways to do this.
Use a build.rs script
The benefit of using a build.rs script is that other users compiling your program will not have to invoke cargo in a special way or set up their environment. Here is a minimal example of how to do that.
build.rs
use std::process::{Command, exit};
use std::str;
static CARGOENV: &str = "cargo:rustc-env=";
fn main() {
let time_c = Command::new("date").args(&["+%Y%m%d"]).output();
match time_c {
Ok(t) => {
let time;
unsafe {
time = str::from_utf8_unchecked( &t.stdout );
}
println!("{}COMPILED_AT={}", CARGOENV, time);
}
Err(_) => exit(1)
}
}
src/main.rs
fn main() {
println!("This is Example Program {} compiled at {}", env!("CARGO_PKG_VERSION"), env!("COMPILED_AT"));
}
Cargo.toml
[package]
name = "compiled_at"
version = "0.1.0"
authors = ["Fredrick Brennan <copypaste#kittens.ph>"]
build = "build.rs"
[dependencies]
Obviously this can be tweaked to get it to work on other platforms which don't have a /bin/date or to compile in other things such as the Git version number. This script was based on the example provided by Jmb, which shows how to add Mercurial information into your program at compile time.
This guarantees that COMPILED_AT will either be set or the build will fail. This way other Rust programmers can build just by doing cargo build.
[osboxes#osboxes compiled_at]$ cargo run
Compiling compiled_at v0.1.0 (file:///home/osboxes/Workspace/rust/compiled_at)
Finished dev [unoptimized + debuginfo] target(s) in 1.27 secs
Running `target/debug/compiled_at`
This is Example Program 0.1.0 compiled at 20180202
Use env!(), then require users to set their environment prior to building
It occurred to me after I asked this question to try the following, and it does work (vim is in use, thus the escaped %s), cargo does pass my environment down to rustc:
COMPILED_AT=`date +\%Y\%m\%d` cargo run
Then, in Rust:
fn main() {
eprintln!("{}", env!("COMPILED_AT"));
}
The Rust compiler refuses to compile the code if I don't supply the environment variable, which is a nice touch.
error: environment variable `COMPILED_AT` not defined
--> src/main.rs:147:21
|
147 | eprintln!("{}", env!("COMPILED_AT"));
| ^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
This way is extremely hacky and is guaranteed to annoy other Rust programmers who just expect to build with cargo build, but it does work. If you can, it is recommended to use the build.rs instead.
You can use the build.rs script to add an environment variable to the cargo environment or to create an extra source file that contains the information you need. That way you can add a lot of information about the build environment. Here is a full example that creates a build_info.rs source file containing:
Version information built from the Mercurial tag, revision hash and status (including the date if the source folder is different from the Mercurial folder).
Toolchain information, including the compiler version and the build profile (eg. debug or release) thanks to the rustc_version crate.
Plus it extracts some information from cargo (like the package name) so it doesn't have to be duplicated between the Cargo.toml and the source code).
#[macro_use] extern crate map_for;
extern crate rustc_version;
extern crate time;
use rustc_version::{ Channel, version_meta };
use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::process::Command;
/// Run mercurial with the given arguments and return the output.
fn run_hg<S: AsRef<OsStr>> (args: &[S]) -> Option<String> {
Command::new ("hg")
.env ("HGRCPATH", "")
.env ("LANG", "C")
.args (args)
.output()
.ok()
.and_then (|output|
String::from_utf8 (output.stdout)
.ok())
}
/// Get the version from a mercurial repository.
///
/// Version numbers follow the Python PEP440 conventions. If the
/// current folder corresponds to a version tag, then return that tag.
/// Otherwise, identify the closest tag and return a string of the
/// form _tag_.dev_N_+_hash_. In both cases, if the current folder has
/// been modified, then add the current date as `YYYYMMDD` to the
/// local version label.
fn get_mercurial_version_tag() -> Option<String> {
let output = run_hg (&[ "id", "-i", "-t" ]);
let mut iter = output.iter().flat_map (|s| s.split_whitespace()).fuse();
let hash = match iter.next() {
Some (hash) => hash,
_ => { return None },
};
let clean = !hash.ends_with ("+");
fn mkdate() -> String { time::strftime ("%Y%m%d", &time::now()).unwrap() }
map_for!(
version <- iter.find (|s| s.chars().next()
.map (|c| ('0' <= c) && (c <= '9'))
.unwrap_or (false));
// The current folder corresponds to a version tag (i.e. a
// tag that starts with a digit).
=> (if clean { version.into() }
else { format!("{}+{}", version, mkdate()) }))
.or_else (|| {
// The current folder does not correspond to a version tag.
// Find the closest tag and build the version from that. Note
// that this may return a wrong version number if the closest
// tag is not a version tag.
let version = run_hg (
&[ "parents",
"--template",
"{latesttag}.dev{latesttagdistance}+{node|short}" ]);
if clean { version }
else { version.map (|s| format!("{}.{}", s, mkdate())) }
})
}
/// Get the version from Mercurial archive information.
///
/// The Mercurial `archive` command creates a file named
/// `.hg_archival.txt` that contains information about the archived
/// version. This function tries to use this information to create a
/// version string similar to what `get_mercurial_version_tag` would
/// have created for this version.
fn get_mercurial_archived_version_tag() -> Option<String> {
use map_for::FlatMap;
// Parse the contents of `.hg_archival.txt` into a hash map.
let info = &File::open (".hg_archival.txt")
.iter()
.flat_map (|f| BufReader::new (f).lines())
.filter_map (|l| l.ok())
.map (|l| l.splitn (2, ':')
.map (String::from)
.collect::<Vec<_>>())
.filter_map (
|v| if v.len() == 2
{ Some ((String::from (v[0].trim()),
String::from (v[1].trim()))) }
else { None })
.collect::<HashMap<_,_>>();
// Extract version information from the hash map.
map_for!(
tag <- info.get ("tag");
=> format!("{}+archive.{}", tag, time::strftime ("%Y%m%d", &time::now()).unwrap()))
.or_else (|| map_for!{
tag <- info.get ("latesttag");
distance <- info.get ("latesttagdistance");
node <- info.get ("node");
=> format!("{}.dev{}+archive.{:.12}.{}",
tag, distance, node,
time::strftime ("%Y%m%d", &time::now()).unwrap()) })
.map (String::from)
}
/// Get the version information.
///
/// This function will first try to get the version from a Mercurial
/// repository. If that fails, it will try to get the version from a
/// `.hg_archival.txt` file. If both fail, it will return a version of
/// the form: "unknown-date".
fn get_version() -> String {
get_mercurial_version_tag()
.or_else (get_mercurial_archived_version_tag)
.unwrap_or_else (
|| format!("{}+cargo.{}",
env::var ("CARGO_PKG_VERSION").unwrap(),
time::strftime ("%Y%m%d", &time::now()).unwrap())
.into())
}
fn main()
{
let mut f = File::create ("src/build_info.rs").unwrap();
let version = version_meta().unwrap();
writeln!(f, "pub const RUST_VERSION: &'static str = \"{} {} v{}\";",
env::var ("RUSTC").unwrap_or ("rustc".into()),
match version.channel {
Channel::Dev => "dev",
Channel::Nightly => "nightly",
Channel::Beta => "beta",
Channel::Stable => "stable",
},
version.semver).unwrap();
writeln!(f, "pub const PROFILE: &'static str = \"{}\";",
env::var ("PROFILE").unwrap_or ("unknown".into()))
.unwrap();
writeln!(f, "pub const TARGET: &'static str = \"{}\";",
env::var ("TARGET").unwrap_or ("unknown".into()))
.unwrap();
writeln!(f, "pub const PKG_NAME: &'static str = \"{} {} {}\";",
env::var ("CARGO_PKG_NAME").unwrap(),
get_version(),
env::var ("PROFILE").unwrap_or ("".into()))
.unwrap();
writeln!(f, "pub const PKG_VERSION: &'static str = \"{}\";",
get_version())
.unwrap();
}