我想这样做,以便在程序启动时打印到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
在C / C ++中,我会用Makefile实现这一点,例如:
VERSION = 0.1.0
FLAGS = -Wall -pipe -O3 -funroll-loops -Wall -DVERSION="\"$(VERSION)\"" -DCOMPILED_AT="\"`date +%Y%m%d`\""
然后,在源代码中,我会喜欢使用这些常量,也许是在调用fprintf
时。当然,检查它们是否确实存在#ifdef
。
如何在Rust中实现这一目标?我需要使用程序宏吗?我能以某种方式使用cargo
吗?
我知道env!("CARGO_PKG_VERSION")
可以用作VERSION
的替代品,但是COMPILED_AT
呢?
答案 0 :(得分:5)
有两种方法可以做到这一点。
build.rs
脚本使用build.rs
脚本的好处是,编译程序的其他用户不必以特殊方式调用cargo
或设置其环境。这是一个如何做到这一点的最小例子。
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]
显然,可以调整它以使其在没有/bin/date
的其他平台上工作,或者在其他东西(例如Git版本号)中编译。此脚本基于Jmb提供的示例,该示例演示了如何在编译时将Mercurial信息添加到程序中。
这可以保证COMPILED_AT
将被设置或构建失败。这样,其他Rust程序员可以通过执行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
env!()
,然后要求用户在构建在我提出此问题后尝试以下内容时,我发现它确实有用(vim
正在使用中,因此转义的%
s),cargo
确实通过了我的问题环境低至rustc
:
COMPILED_AT=`date +\%Y\%m\%d` cargo run
然后,在Rust:
fn main() {
eprintln!("{}", env!("COMPILED_AT"));
}
如果我不提供环境变量,Rust编译器拒绝编译代码,这是一个不错的选择。
error: environment variable `COMPILED_AT` not defined
--> src/main.rs:147:21
|
147 | eprintln!("{}", env!("COMPILED_AT"));
| ^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
这种方式非常hacky并且保证会惹恼其他希望使用cargo build
构建的Rust程序员,但它确实有效。如果可以,建议改为使用build.rs
。
答案 1 :(得分:1)
您可以使用build.rs
脚本将环境变量添加到货物环境中,或者创建包含所需信息的额外源文件。这样,您可以添加有关构建环境的大量信息。这是一个full example,它创建一个build_info.rs
源文件,其中包含:
debug
或release
),感谢rustc_version
包。Cargo.toml
和源代码之间重复。#[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();
}