Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
720 changes: 706 additions & 14 deletions tools/Cargo.lock

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion tools/hermes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,39 @@ license.workspace = true
publish.workspace = true

[dependencies]
anyhow = "1.0.101"
cargo_metadata = "0.23.1"
clap = { version = "4.5.57", features = ["derive"] }
clap-cargo = { version = "0.18.3", features = ["cargo_metadata"] }
dashmap = "6.1.0"
env_logger = "0.11.8"
log = "0.4.29"
miette = { version = "7.6.0", features = ["derive", "fancy"] }
proc-macro2 = { version = "1.0.105", features = ["span-locations"] }
rayon = "1.11.0"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
syn = { version = "2.0.114", features = ["full", "visit", "extra-traits", "parsing"] }
syn = { version = "2.0.114", features = ["full", "visit", "extra-traits", "parsing", "printing"] }
quote = "1.0"
thiserror = "2.0.18"
walkdir = "2.5.0"
indicatif = { version = "0.18.3", features = ["improved_unicode"] }
console = "0.16.2"

[dev-dependencies]
syn = { version = "2.0.114", features = ["printing", "full", "visit", "extra-traits", "parsing"] }
proc-macro2 = { version = "1.0.105", features = ["span-locations"] }
ui_test = "0.30.4"
assert_cmd = "2.1.2"
tempfile = "3.24.0"
predicates = "3.1.3"
datatest-stable = "0.3.3"
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
which = "6.0"
regex.workspace = true
strip-ansi-escapes = "0.2.1"

[[test]]
name = "integration"
harness = false
186 changes: 186 additions & 0 deletions tools/hermes/src/charon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use std::{
io::{BufRead, BufReader},
process::Command,
};

use anyhow::{bail, Context as _, Result};
use cargo_metadata::{diagnostic::DiagnosticLevel, Message};

use crate::{
resolve::{Args, HermesTargetKind, Roots},
shadow::HermesArtifact,
};

pub fn run_charon(args: &Args, roots: &Roots, packages: &[HermesArtifact]) -> Result<()> {
let charon_root = roots.charon_root();

std::fs::create_dir_all(&charon_root).context("Failed to create charon output directory")?;

for artifact in packages {
if artifact.start_from.is_empty() {
continue;
}

log::info!("Invoking Charon on package '{}'...", artifact.name.package_name);

let mut cmd = Command::new("charon");
cmd.arg("cargo");

// Output artifacts to target/hermes/<hash>/charon
let llbc_path = charon_root.join(artifact.llbc_file_name());
log::debug!("Writing .llbc file to {}", llbc_path.display());
cmd.arg("--dest-file").arg(llbc_path);

// Fail fast on errors
cmd.arg("--abort-on-error");

// Start translation from specific entry points. Sort to ensure
// deterministic ordering for testing (not important in production).
let mut start_from = artifact.start_from.clone();
start_from.sort();
cmd.arg("--start-from").arg(start_from.join(","));

// Separator for the underlying cargo command
cmd.arg("--");

// Ensure cargo emits json msgs which charon-driver natively generates
cmd.arg("--message-format=json");

cmd.arg("--manifest-path").arg(&artifact.shadow_manifest_path);

match artifact.target_kind {
HermesTargetKind::Lib
| HermesTargetKind::RLib
| HermesTargetKind::ProcMacro
| HermesTargetKind::CDyLib
| HermesTargetKind::DyLib
| HermesTargetKind::StaticLib => {
cmd.arg("--lib");
}
HermesTargetKind::Bin => {
cmd.arg("--bin").arg(&artifact.name.target_name);
}
HermesTargetKind::Example => {
cmd.arg("--example").arg(&artifact.name.target_name);
}
HermesTargetKind::Test => {
cmd.arg("--test").arg(&artifact.name.target_name);
}
}

// Forward all feature-related flags.
if args.features.all_features {
cmd.arg("--all-features");
}
if args.features.no_default_features {
cmd.arg("--no-default-features");
}
for feature in &args.features.features {
cmd.arg("--features").arg(feature);
}

// Reuse the main target directory for dependencies to save time.
cmd.env("CARGO_TARGET_DIR", &roots.cargo_target_dir);

log::debug!("Command: {:?}", cmd);

cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().context("Failed to spawn charon")?;

let mut output_error = false;

let safety_buffer = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let safety_buffer_clone = std::sync::Arc::clone(&safety_buffer);
if let Some(stderr) = child.stderr.take() {
std::thread::spawn(move || {
use std::io::{BufRead, BufReader};
let reader = BufReader::new(stderr);
for line in reader.lines() {
if let Ok(line) = line {
if let Ok(mut buf) = safety_buffer_clone.lock() {
buf.push(line);
}
}
}
});
}

let pb = indicatif::ProgressBar::new_spinner();
pb.set_style(
indicatif::ProgressStyle::default_spinner().template("{spinner:.green} {msg}").unwrap(),
);
pb.enable_steady_tick(std::time::Duration::from_millis(100));
pb.set_message("Compiling...");

if let Some(stdout) = child.stdout.take() {
let reader = BufReader::new(stdout);

// TODO: We shouldn't hard-code assumptions about the integration
// testing environment.
//
// When resolving mapped diagnostic paths, the original workspace is
// either test_case_root/source or workspace root.
let user_root = if roots.workspace.join("source").exists() {
roots.workspace.join("source")
} else {
roots.workspace.clone()
};
let mut mapper = crate::diagnostics::DiagnosticMapper::new(
artifact.shadow_manifest_path.parent().unwrap().to_path_buf(),
user_root,
);

for line in reader.lines() {
if let Ok(line) = line {
if let Ok(msg) = serde_json::from_str::<cargo_metadata::Message>(&line) {
match msg {
Message::CompilerArtifact(a) => {
pb.set_message(format!("Compiling {}", a.target.name));
}
Message::CompilerMessage(msg) => {
pb.suspend(|| {
mapper.render_miette(&msg.message, |s| eprintln!("{}", s));
});
if matches!(
msg.message.level,
DiagnosticLevel::Error | DiagnosticLevel::Ice
) {
output_error = true;
}
}
Message::TextLine(t) => {
if let Ok(mut buf) = safety_buffer.lock() {
buf.push(t);
}
}
_ => {}
}
} else {
if let Ok(mut buf) = safety_buffer.lock() {
buf.push(line);
}
}
}
}
}

pb.finish_and_clear();

let status = child.wait().context("Failed to wait for charon")?;

if output_error {
bail!("Diagnostic error in charon");
} else if !status.success() {
// "Silent Death" dump
if let Ok(buf) = safety_buffer.lock() {
for line in buf.iter() {
eprintln!("{}", line);
}
}
bail!("Charon failed with status: {}", status);
}
}

Ok(())
}
Loading