Add message caching.

This commit is contained in:
Eric Huss 2019-05-11 17:35:25 -07:00
parent 719781be1a
commit dcd4999d42
15 changed files with 590 additions and 75 deletions

View file

@ -39,6 +39,7 @@ matrix:
before_script:
- rustup target add $ALT
- rustup component add clippy || echo "clippy not available"
script:
- cargo test --features=deny-warnings

View file

@ -54,6 +54,7 @@ serde = { version = "1.0.82", features = ['derive'] }
serde_ignored = "0.0.4"
serde_json = { version = "1.0.30", features = ["raw_value"] }
shell-escape = "0.1.4"
strip-ansi-escapes = "0.1.0"
tar = { version = "0.4.18", default-features = false }
tempfile = "3.0"
termcolor = "1.0"

View file

@ -9,6 +9,7 @@ install:
- rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- if defined OTHER_TARGET rustup target add %OTHER_TARGET%
- rustup component add clippy || exit 0
- rustc -V
- cargo -V
- git submodule update --init

View file

@ -35,6 +35,7 @@ Available unstable (nightly-only) flags:
-Z unstable-options -- Allow the usage of unstable options such as --registry
-Z config-profile -- Read profiles from .cargo/config files
-Z install-upgrade -- `cargo install` will upgrade instead of failing
-Z cache-messages -- Cache compiler messages
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
);

View file

@ -27,6 +27,8 @@ pub struct BuildConfig {
/// An optional wrapper, if any, used to wrap rustc invocations
pub rustc_wrapper: Option<ProcessBuilder>,
pub rustfix_diagnostic_server: RefCell<Option<RustfixDiagnosticServer>>,
/// Whether or not Cargo should cache compiler output on disk.
cache_messages: bool,
}
impl BuildConfig {
@ -87,6 +89,7 @@ impl BuildConfig {
}
let cfg_jobs: Option<u32> = config.get("build.jobs")?;
let jobs = jobs.or(cfg_jobs).unwrap_or(::num_cpus::get() as u32);
Ok(BuildConfig {
requested_target: target,
jobs,
@ -97,10 +100,23 @@ impl BuildConfig {
build_plan: false,
rustc_wrapper: None,
rustfix_diagnostic_server: RefCell::new(None),
cache_messages: config.cli_unstable().cache_messages,
})
}
pub fn json_messages(&self) -> bool {
/// Whether or not Cargo should cache compiler messages on disk.
pub fn cache_messages(&self) -> bool {
self.cache_messages
&& match self.message_format {
MessageFormat::Human | MessageFormat::Json => true,
// short is currently not supported
MessageFormat::Short => false,
}
}
/// Whether or not the *user* wants JSON output. Whether or not rustc
/// actually uses JSON is decided in `add_error_format`.
pub fn emit_json(&self) -> bool {
self.message_format == MessageFormat::Json
}

View file

@ -192,6 +192,11 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
self.layout(unit.kind).fingerprint().join(dir)
}
/// Path where compiler output is cached.
pub fn message_cache_path(&self, unit: &Unit<'a>) -> PathBuf {
self.fingerprint_dir(unit).join("output")
}
/// Returns the directory where a compiled build script is stored.
/// `/path/to/target/{debug,release}/build/PKG-HASH`
pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf {

View file

@ -112,7 +112,7 @@ fn emit_build_output(state: &JobState<'_>, output: &BuildOutput, package_id: Pac
env: &output.env,
}
.to_json_string();
state.stdout(&msg);
state.stdout(msg);
}
fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<Job> {
@ -248,7 +248,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
);
let build_scripts = super::load_build_deps(cx, unit);
let kind = unit.kind;
let json_messages = bcx.build_config.json_messages();
let json_messages = bcx.build_config.emit_json();
let extra_verbose = bcx.config.extra_verbose();
let (prev_output, prev_script_out_dir) = prev_build_output(cx, unit);
@ -315,13 +315,13 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
.exec_with_streaming(
&mut |stdout| {
if extra_verbose {
state.stdout(&format!("{}{}", prefix, stdout));
state.stdout(format!("{}{}", prefix, stdout));
}
Ok(())
},
&mut |stderr| {
if extra_verbose {
state.stderr(&format!("{}{}", prefix, stderr));
state.stderr(format!("{}{}", prefix, stderr));
}
Ok(())
},

View file

@ -105,12 +105,12 @@ impl<'a> JobState<'a> {
.send(Message::BuildPlanMsg(module_name, cmd, filenames));
}
pub fn stdout(&self, stdout: &str) {
drop(self.tx.send(Message::Stdout(stdout.to_string())));
pub fn stdout(&self, stdout: String) {
drop(self.tx.send(Message::Stdout(stdout)));
}
pub fn stderr(&self, stderr: &str) {
drop(self.tx.send(Message::Stderr(stderr.to_string())));
pub fn stderr(&self, stderr: String) {
drop(self.tx.send(Message::Stderr(stderr)));
}
/// A method used to signal to the coordinator thread that the rmeta file

View file

@ -13,11 +13,13 @@ mod unit;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use failure::{bail, Error};
use lazycell::LazyCell;
use log::debug;
use same_file::is_same_file;
use serde::Serialize;
@ -139,8 +141,21 @@ fn compile<'a, 'cfg: 'a>(
};
work.then(link_targets(cx, unit, false)?)
} else {
let work = if cx.bcx.build_config.cache_messages()
&& cx.bcx.show_warnings(unit.pkg.package_id())
{
replay_output_cache(
unit.pkg.package_id(),
unit.target,
cx.files().message_cache_path(unit),
cx.bcx.build_config.message_format,
cx.bcx.config.shell().supports_color(),
)
} else {
Work::noop()
};
// Need to link targets on both the dirty and fresh.
link_targets(cx, unit, true)?
work.then(link_targets(cx, unit, true)?)
});
job
@ -202,6 +217,9 @@ fn rustc<'a, 'cfg>(
let dep_info_loc = fingerprint::dep_info_loc(cx, unit);
rustc.args(cx.bcx.rustflags_args(unit));
let emit_json = cx.bcx.build_config.emit_json();
let mut cache_cell = new_cache_cell(cx, unit);
let color = cx.bcx.config.shell().supports_color();
let package_id = unit.pkg.package_id();
let target = unit.target.clone();
let mode = unit.mode;
@ -218,47 +236,6 @@ fn rustc<'a, 'cfg>(
let fingerprint_dir = cx.files().fingerprint_dir(unit);
let rmeta_produced = cx.rmeta_required(unit);
// If this unit is producing a required rmeta file then we need to know
// when the rmeta file is ready so we can signal to the rest of Cargo that
// it can continue dependant compilations. To do this we are currently
// required to switch the compiler into JSON message mode, but we still
// want to present human readable errors as well. (this rabbit hole just
// goes and goes)
//
// All that means is that if we're not already in JSON mode we need to
// switch to JSON mode, ensure that rustc error messages can be rendered
// prettily, and then when parsing JSON messages from rustc we need to
// internally understand that we should extract the `rendered` field and
// present it if we can.
let extract_rendered_errors = if rmeta_produced {
match cx.bcx.build_config.message_format {
MessageFormat::Json => {
rustc.arg("-Zemit-artifact-notifications");
false
}
MessageFormat::Human => {
rustc
.arg("--error-format=json")
.arg("--json-rendered=termcolor")
.arg("-Zunstable-options")
.arg("-Zemit-artifact-notifications");
true
}
// FIXME(rust-lang/rust#60419): right now we have no way of turning
// on JSON messages from the compiler and also asking the rendered
// field to be in the `short` format.
MessageFormat::Short => {
bail!(
"currently `--message-format short` is incompatible with \
pipelined compilation"
);
}
}
} else {
false
};
return Ok(Work::new(move |state| {
// Only at runtime have we discovered what the extra -L and -l
// arguments are for native libraries, so we process those here. We
@ -325,8 +302,10 @@ fn rustc<'a, 'cfg>(
line,
package_id,
&target,
extract_rendered_errors,
!emit_json,
rmeta_produced,
color,
&mut cache_cell,
)
},
)
@ -440,7 +419,7 @@ fn link_targets<'a, 'cfg>(
.into_iter()
.map(|s| s.to_owned())
.collect();
let json_messages = bcx.build_config.json_messages();
let json_messages = bcx.build_config.emit_json();
let executable = cx.get_executable(unit)?;
let mut target = unit.target.clone();
if let TargetSourcePath::Metabuild = target.src_path() {
@ -500,7 +479,7 @@ fn link_targets<'a, 'cfg>(
fresh,
}
.to_json_string();
state.stdout(&msg);
state.stdout(msg);
}
Ok(())
}))
@ -657,7 +636,7 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
rustdoc.arg("--cfg").arg(&format!("feature=\"{}\"", feat));
}
add_error_format(cx, &mut rustdoc);
add_error_format(cx, &mut rustdoc, false, false)?;
if let Some(args) = bcx.extra_args_for(unit) {
rustdoc.args(args);
@ -670,8 +649,11 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
let name = unit.pkg.name().to_string();
let build_state = cx.build_state.clone();
let key = (unit.pkg.package_id(), unit.kind);
let emit_json = bcx.build_config.emit_json();
let color = bcx.config.shell().supports_color();
let package_id = unit.pkg.package_id();
let target = unit.target.clone();
let mut cache_cell = new_cache_cell(cx, unit);
Ok(Work::new(move |state| {
if let Some(output) = build_state.outputs.lock().unwrap().get(&key) {
@ -687,7 +669,18 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
rustdoc
.exec_with_streaming(
&mut |line| on_stdout_line(state, line, package_id, &target),
&mut |line| on_stderr_line(state, line, package_id, &target, false, false),
&mut |line| {
on_stderr_line(
state,
line,
package_id,
&target,
!emit_json,
false,
color,
&mut cache_cell,
)
},
false,
)
.chain_err(|| format!("Could not document `{}`.", name))?;
@ -753,16 +746,53 @@ fn add_color(bcx: &BuildContext<'_, '_>, cmd: &mut ProcessBuilder) {
cmd.args(&["--color", color]);
}
fn add_error_format(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder) {
match cx.bcx.build_config.message_format {
MessageFormat::Human => (),
MessageFormat::Json => {
cmd.arg("--error-format").arg("json");
fn add_error_format(
cx: &Context<'_, '_>,
cmd: &mut ProcessBuilder,
pipelined: bool,
supports_termcolor: bool,
) -> CargoResult<()> {
// If this unit is producing a required rmeta file then we need to know
// when the rmeta file is ready so we can signal to the rest of Cargo that
// it can continue dependent compilations. To do this we are currently
// required to switch the compiler into JSON message mode, but we still
// want to present human readable errors as well. (this rabbit hole just
// goes and goes)
//
// All that means is that if we're not already in JSON mode we need to
// switch to JSON mode, ensure that rustc error messages can be rendered
// prettily, and then when parsing JSON messages from rustc we need to
// internally understand that we should extract the `rendered` field and
// present it if we can.
if cx.bcx.build_config.cache_messages() || pipelined {
cmd.arg("--error-format=json").arg("-Zunstable-options");
if supports_termcolor {
cmd.arg("--json-rendered=termcolor");
}
MessageFormat::Short => {
cmd.arg("--error-format").arg("short");
if pipelined {
cmd.arg("-Zemit-artifact-notifications");
if cx.bcx.build_config.message_format == MessageFormat::Short {
// FIXME(rust-lang/rust#60419): right now we have no way of
// turning on JSON messages from the compiler and also asking
// the rendered field to be in the `short` format.
bail!(
"currently `--message-format short` is incompatible with \
pipelined compilation"
);
}
}
} else {
match cx.bcx.build_config.message_format {
MessageFormat::Human => (),
MessageFormat::Json => {
cmd.arg("--error-format").arg("json");
}
MessageFormat::Short => {
cmd.arg("--error-format").arg("short");
}
}
}
Ok(())
}
fn build_base_args<'a, 'cfg>(
@ -792,7 +822,7 @@ fn build_base_args<'a, 'cfg>(
add_path_args(bcx, unit, cmd);
add_color(bcx, cmd);
add_error_format(cx, cmd);
add_error_format(cx, cmd, cx.rmeta_required(unit), true)?;
if !test {
for crate_type in crate_types.iter() {
@ -1086,7 +1116,7 @@ fn on_stdout_line(
_package_id: PackageId,
_target: &Target,
) -> CargoResult<()> {
state.stdout(line);
state.stdout(line.to_string());
Ok(())
}
@ -1097,25 +1127,36 @@ fn on_stderr_line(
target: &Target,
extract_rendered_messages: bool,
look_for_metadata_directive: bool,
color: bool,
cache_cell: &mut Option<(PathBuf, LazyCell<File>)>,
) -> CargoResult<()> {
if let Some((path, cell)) = cache_cell {
// The file is created lazily so that in the normal case, lots of
// empty files are not created.
let f = cell.try_borrow_mut_with(|| File::create(path))?;
f.write_all(line.as_bytes())?;
f.write_all(&[b'\n'])?;
}
// We primarily want to use this function to process JSON messages from
// rustc. The compiler should always print one JSON message per line, and
// otherwise it may have other output intermingled (think RUST_LOG or
// something like that), so skip over everything that doesn't look like a
// JSON message.
if !line.starts_with('{') {
state.stderr(line);
state.stderr(line.to_string());
return Ok(());
}
let compiler_message: Box<serde_json::value::RawValue> = match serde_json::from_str(line) {
let mut compiler_message: Box<serde_json::value::RawValue> = match serde_json::from_str(line) {
Ok(msg) => msg,
// If the compiler produced a line that started with `{` but it wasn't
// valid JSON, maybe it wasn't JSON in the first place! Forward it along
// to stderr.
Err(_) => {
state.stderr(line);
Err(e) => {
debug!("failed to parse json: {:?}", e);
state.stderr(line.to_string());
return Ok(());
}
};
@ -1130,10 +1171,43 @@ fn on_stderr_line(
struct CompilerMessage {
rendered: String,
}
if let Ok(error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
state.stderr(&error.rendered);
if let Ok(mut error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
// state.stderr will add a newline
if error.rendered.ends_with('\n') {
error.rendered.pop();
}
let rendered = if color {
error.rendered
} else {
strip_ansi_escapes::strip(&error.rendered)
.map(|v| String::from_utf8(v).expect("utf8"))
.unwrap_or(error.rendered)
};
state.stderr(rendered);
return Ok(());
}
} else {
// Remove color information from the rendered string. rustc has not
// included color in the past, so to avoid breaking anything, strip it
// out when --json-rendered=termcolor is used. This runs
// unconditionally under the assumption that Cargo will eventually
// move to this as the default mode. Perhaps in the future, cargo
// could allow the user to enable/disable color (such as with a
// `--json-rendered` or `--color` or `--message-format` flag).
#[derive(serde::Deserialize, serde::Serialize)]
struct CompilerMessage {
rendered: String,
#[serde(flatten)]
other: std::collections::BTreeMap<String, serde_json::Value>,
}
if let Ok(mut error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
error.rendered = strip_ansi_escapes::strip(&error.rendered)
.map(|v| String::from_utf8(v).expect("utf8"))
.unwrap_or(error.rendered);
let new_line = serde_json::to_string(&error)?;
let new_msg: Box<serde_json::value::RawValue> = serde_json::from_str(&new_line)?;
compiler_message = new_msg;
}
}
// In some modes of execution we will execute rustc with `-Z
@ -1172,6 +1246,56 @@ fn on_stderr_line(
// Switch json lines from rustc/rustdoc that appear on stderr to stdout
// instead. We want the stdout of Cargo to always be machine parseable as
// stderr has our colorized human-readable messages.
state.stdout(&msg);
state.stdout(msg);
Ok(())
}
fn replay_output_cache(
package_id: PackageId,
target: &Target,
path: PathBuf,
format: MessageFormat,
color: bool,
) -> Work {
let target = target.clone();
let extract_rendered_messages = match format {
MessageFormat::Human => true,
MessageFormat::Json => false,
// FIXME: short not supported.
MessageFormat::Short => false,
};
Work::new(move |state| {
if path.exists() {
let mut f = BufReader::new(File::open(&path)?);
let mut line = String::new();
loop {
if f.read_line(&mut line)? == 0 {
break;
}
on_stderr_line(
state,
&line,
package_id,
&target,
extract_rendered_messages,
false, // look_for_metadata_directive
color,
&mut None,
)?;
line.clear();
}
}
Ok(())
})
}
fn new_cache_cell(cx: &Context<'_, '_>, unit: &Unit<'_>) -> Option<(PathBuf, LazyCell<File>)> {
if cx.bcx.build_config.cache_messages() {
let path = cx.files().message_cache_path(unit);
// Remove old cache, ignore ENOENT, which is the common case.
drop(fs::remove_file(&path));
Some((path, LazyCell::new()))
} else {
None
}
}

View file

@ -331,6 +331,7 @@ pub struct CliUnstable {
pub dual_proc_macros: bool,
pub mtime_on_use: bool,
pub install_upgrade: bool,
pub cache_messages: bool,
}
impl CliUnstable {
@ -375,6 +376,7 @@ impl CliUnstable {
"dual-proc-macros" => self.dual_proc_macros = true,
"mtime-on-use" => self.mtime_on_use = true,
"install-upgrade" => self.install_upgrade = true,
"cache-messages" => self.cache_messages = true,
_ => failure::bail!("unknown `-Z` flag specified: {}", k),
}

View file

@ -592,6 +592,11 @@ impl FixArgs {
ret.enabled_edition = Some(s[prefix.len()..].to_string());
continue;
}
if s.starts_with("--error-format=") || s.starts_with("--json-rendered=") {
// Cargo may add error-format in some cases, but `cargo
// fix` wants to add its own.
continue;
}
}
ret.other.push(path.into());
}

View file

@ -212,7 +212,10 @@ impl ProcessBuilder {
///
/// If any invocations of these function return an error, it will be propagated.
///
/// Optionally, output can be passed to errors using `print_output`
/// If `capture_output` is true, then all the output will also be buffered
/// and stored in the returned `Output` object. If it is false, no caching
/// is done, and the callbacks are solely responsible for handling the
/// output.
pub fn exec_with_streaming(
&self,
on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,

View file

@ -255,7 +255,7 @@ installed.
### public-dependency
* Tracking Issue: [#44663](https://github.com/rust-lang/rust/issues/44663)
The 'public-dependency' features allows marking dependencies as 'public'
The 'public-dependency' feature allows marking dependencies as 'public'
or 'private'. When this feature is enabled, additional information is passed to rustc to allow
the 'exported_private_dependencies' lint to function properly.
@ -268,3 +268,22 @@ cargo-features = ["public-dependency"]
my_dep = { version = "1.2.3", public = true }
private_dep = "2.0.0" # Will be 'private' by default
```
### cache-messages
* Tracking Issue: TODO
The `cache-messages` feature causes Cargo to cache the messages generated by
the compiler. This is primarily useful if a crate compiles successfully with
warnings. Previously, re-running Cargo would not display any output. With the
`cache-messages` feature, it will quickly redisplay the previous warnings.
```
cargo +nightly check -Z cache-messages
```
This works with any command that runs the compiler (`build`, `check`, `test`,
etc.).
This also changes the way Cargo interacts with the compiler, helping to
prevent interleaved messages when multiple crates attempt to display a message
at the same time.

View file

@ -0,0 +1,336 @@
use crate::support::{is_nightly, process, project, registry::Package};
use std::path::Path;
fn as_str(bytes: &[u8]) -> &str {
std::str::from_utf8(bytes).expect("valid utf-8")
}
#[test]
fn simple() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
// A simple example that generates two warnings (unused functions).
let p = project()
.file(
"src/lib.rs",
"
fn a() {}
fn b() {}
",
)
.build();
let agnostic_path = Path::new("src").join("lib.rs");
let agnostic_path_s = agnostic_path.to_str().unwrap();
// Capture what rustc actually emits. This is done to avoid relying on the
// exact message formatting in rustc.
let rustc_output = process("rustc")
.cwd(p.root())
.args(&["--crate-type=lib", agnostic_path_s])
.exec_with_output()
.expect("rustc to run");
assert!(rustc_output.stdout.is_empty());
assert!(rustc_output.status.success());
// -q so the output is the same as rustc (no "Compiling" or "Finished").
let cargo_output1 = p
.cargo("check -Zcache-messages -q --color=never")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("cargo to run");
assert_eq!(as_str(&rustc_output.stderr), as_str(&cargo_output1.stderr));
assert!(cargo_output1.stdout.is_empty());
// Check that the cached version is exactly the same.
let cargo_output2 = p
.cargo("check -Zcache-messages -q")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("cargo to run");
assert_eq!(as_str(&rustc_output.stderr), as_str(&cargo_output2.stderr));
assert!(cargo_output2.stdout.is_empty());
}
#[test]
fn color() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
// Check enabling/disabling color.
let p = project().file("src/lib.rs", "fn a() {}").build();
let agnostic_path = Path::new("src").join("lib.rs");
let agnostic_path_s = agnostic_path.to_str().unwrap();
// Capture the original color output.
let rustc_output = process("rustc")
.cwd(p.root())
.args(&["--crate-type=lib", agnostic_path_s, "--color=always"])
.exec_with_output()
.expect("rustc to run");
assert!(rustc_output.status.success());
let rustc_color = as_str(&rustc_output.stderr);
assert!(rustc_color.contains("\x1b["));
// Capture the original non-color output.
let rustc_output = process("rustc")
.cwd(p.root())
.args(&["--crate-type=lib", agnostic_path_s])
.exec_with_output()
.expect("rustc to run");
let rustc_nocolor = as_str(&rustc_output.stderr);
assert!(!rustc_nocolor.contains("\x1b["));
// First pass, non-cached, with color, should be the same.
let cargo_output1 = p
.cargo("check -Zcache-messages -q --color=always")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("cargo to run");
assert_eq!(rustc_color, as_str(&cargo_output1.stderr));
// Replay cached, with color.
let cargo_output2 = p
.cargo("check -Zcache-messages -q --color=always")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("cargo to run");
assert_eq!(rustc_color, as_str(&cargo_output2.stderr));
// Replay cached, no color.
let cargo_output_nocolor = p
.cargo("check -Zcache-messages -q --color=never")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("cargo to run");
assert_eq!(rustc_nocolor, as_str(&cargo_output_nocolor.stderr));
}
#[test]
fn cached_as_json() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
// Check that cached JSON output is the same.
let p = project().file("src/lib.rs", "fn a() {}").build();
// Grab the non-cached output, feature disabled.
// NOTE: When stabilizing, this will need to be redone.
let cargo_output = p
.cargo("check --message-format=json")
.exec_with_output()
.expect("cargo to run");
assert!(cargo_output.status.success());
let orig_cargo_out = as_str(&cargo_output.stdout);
assert!(orig_cargo_out.contains("compiler-message"));
p.cargo("clean").run();
// Check JSON output, not fresh.
let cargo_output1 = p
.cargo("check -Zcache-messages --message-format=json")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("cargo to run");
assert_eq!(as_str(&cargo_output1.stdout), orig_cargo_out);
// Check JSON output, fresh.
let cargo_output2 = p
.cargo("check -Zcache-messages --message-format=json")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("cargo to run");
// The only difference should be this field.
let fix_fresh = as_str(&cargo_output2.stdout).replace("\"fresh\":true", "\"fresh\":false");
assert_eq!(fix_fresh, orig_cargo_out);
}
#[test]
fn clears_cache_after_fix() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
// Make sure the cache is invalidated when there is no output.
let p = project().file("src/lib.rs", "fn asdf() {}").build();
// Fill the cache.
p.cargo("check -Zcache-messages")
.masquerade_as_nightly_cargo()
.with_stderr_contains("[..]asdf[..]")
.run();
let cpath = p
.glob("target/debug/.fingerprint/foo-*/output")
.next()
.unwrap()
.unwrap();
assert!(std::fs::read_to_string(cpath).unwrap().contains("asdf"));
// Fix it.
p.change_file("src/lib.rs", "");
p.cargo("check -Zcache-messages")
.masquerade_as_nightly_cargo()
.with_stdout("")
.with_stderr(
"\
[CHECKING] foo [..]
[FINISHED] [..]
",
)
.run();
assert_eq!(p.glob("target/debug/.fingerprint/foo-*/output").count(), 0);
// And again, check the cache is correct.
p.cargo("check -Zcache-messages")
.masquerade_as_nightly_cargo()
.with_stdout("")
.with_stderr(
"\
[FINISHED] [..]
",
)
.run();
}
#[test]
fn rustdoc() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
// Create a warning in rustdoc.
let p = project()
.file(
"src/lib.rs",
"
#![warn(private_doc_tests)]
/// asdf
/// ```
/// let x = 1;
/// ```
fn f() {}
",
)
.build();
// At this time, rustdoc does not support --json-rendered=termcolor. So it
// will always be uncolored with -Zcache-messages.
let rustdoc_output = p
.cargo("doc -Zcache-messages -q")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("rustdoc to run");
assert!(rustdoc_output.status.success());
let rustdoc_stderr = as_str(&rustdoc_output.stderr);
assert!(rustdoc_stderr.contains("private"));
// Invert this when --json-rendered is added.
assert!(!rustdoc_stderr.contains("\x1b["));
assert_eq!(p.glob("target/debug/.fingerprint/foo-*/output").count(), 1);
// Check the cached output.
let rustdoc_output = p
.cargo("doc -Zcache-messages -q")
.masquerade_as_nightly_cargo()
.exec_with_output()
.expect("rustdoc to run");
assert_eq!(as_str(&rustdoc_output.stderr), rustdoc_stderr);
}
#[test]
fn clippy() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
if let Err(e) = process("clippy-driver").arg("-V").exec_with_output() {
eprintln!("clippy-driver not available, skipping clippy test");
eprintln!("{:?}", e);
return;
}
// Caching clippy output.
// This is just a random clippy lint (assertions_on_constants) that
// hopefully won't change much in the future.
let p = project()
.file("src/lib.rs", "pub fn f() { assert!(true); }")
.build();
p.cargo("clippy-preview -Zunstable-options -Zcache-messages")
.masquerade_as_nightly_cargo()
.with_stderr_contains("[..]assert!(true)[..]")
.run();
// Again, reading from the cache.
p.cargo("clippy-preview -Zunstable-options -Zcache-messages")
.masquerade_as_nightly_cargo()
.with_stderr_contains("[..]assert!(true)[..]")
.run();
// FIXME: Unfortunately clippy is sharing the same hash with check. This
// causes the cache to be reused when it shouldn't.
p.cargo("check -Zcache-messages")
.masquerade_as_nightly_cargo()
.with_stderr_contains("[..]assert!(true)[..]") // This should not be here.
.run();
}
#[test]
fn fix() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
// Make sure `fix` is not broken by caching.
let p = project().file("src/lib.rs", "pub fn try() {}").build();
p.cargo("fix --edition --allow-no-vcs -Zcache-messages")
.masquerade_as_nightly_cargo()
.run();
assert_eq!(p.read_file("src/lib.rs"), "pub fn r#try() {}");
}
#[test]
fn very_verbose() {
if !is_nightly() {
// --json-rendered is unstable
return;
}
// Handle cap-lints in dependencies.
Package::new("bar", "1.0.0")
.file("src/lib.rs", "fn not_used() {}")
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcache-messages -vv")
.masquerade_as_nightly_cargo()
.with_stderr_contains("[..]not_used[..]")
.run();
p.cargo("check -Zcache-messages")
.masquerade_as_nightly_cargo()
.with_stderr("[FINISHED] [..]")
.run();
p.cargo("check -Zcache-messages -vv")
.masquerade_as_nightly_cargo()
.with_stderr_contains("[..]not_used[..]")
.run();
}

View file

@ -19,6 +19,7 @@ mod build_lib;
mod build_plan;
mod build_script;
mod build_script_env;
mod cache_messages;
mod cargo_alias_config;
mod cargo_command;
mod cargo_features;