Skip to content

Commit ccec549

Browse files
authored
Remove glob to collect all artifacts introduced by cargo kani (rust-lang#2246)
This change allows us to pin-point exactly which build artifacts are related to a cargo build run. This is required to re-enable the build cache (rust-lang#2036) where multiple artifacts can co-exist in the same folder. The solution implemented here is rather hacky, but it's more reliable than other alternatives I've tried (see model-checking/kani#2246). Now, `kani-compiler` will generate stubs in the place of binaries, shared, and static libraries when those types are requested. Those stubs will contain a JSON representation of the new type `CompilerArtifactStub`, which basically contain the path for the `metadata.json` file where the compiler stores the metadata related to a given crate. `kani-driver` will parse `CompilerArtifact` messages to figure out which artifacts were built for the given build. For libraries, it will derive the name of the metadata file from the `rmeta` filepath. For other types of artifacts, it will parse the output file, convert it to `CompilerArtifactStub`, and extract the path for the `rmeta` filepath.
1 parent e45fc91 commit ccec549

File tree

8 files changed

+149
-72
lines changed

8 files changed

+149
-72
lines changed

kani-compiler/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ clap = { version = "4.1.3", features = ["cargo"] }
1717
home = "0.5"
1818
itertools = "0.10"
1919
kani_queries = {path = "kani_queries"}
20-
kani_metadata = { path = "../kani_metadata", optional = true }
20+
kani_metadata = {path = "../kani_metadata"}
2121
lazy_static = "1.4.0"
2222
libc = { version = "0.2", optional = true }
2323
num = { version = "0.4.0", optional = true }
@@ -36,7 +36,7 @@ tracing-tree = "0.2.2"
3636
# Future proofing: enable backend dependencies using feature.
3737
[features]
3838
default = ['cprover']
39-
cprover = ['ar', 'bitflags', 'cbmc', 'kani_metadata', 'libc', 'num', 'object', 'rustc-demangle', 'serde',
39+
cprover = ['ar', 'bitflags', 'cbmc', 'libc', 'num', 'object', 'rustc-demangle', 'serde',
4040
'serde_json', "strum", "strum_macros"]
4141
unsound_experiments = ["kani_queries/unsound_experiments"]
4242

kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs

+36-21
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::kani_middle::reachability::{
1515
use bitflags::_core::any::Any;
1616
use cbmc::goto_program::Location;
1717
use cbmc::{InternedString, MachineModel};
18+
use kani_metadata::CompilerArtifactStub;
1819
use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata};
1920
use kani_queries::{QueryDb, ReachabilityType, UserInput};
2021
use rustc_codegen_ssa::back::metadata::create_wrapper_file;
@@ -205,7 +206,7 @@ impl CodegenBackend for GotocCodegenBackend {
205206
.unwrap())
206207
}
207208

208-
/// Emit `rlib` files during the link stage if it was requested.
209+
/// Emit output files during the link stage if it was requested.
209210
///
210211
/// We need to emit `rlib` files normally if requested. Cargo expects these in some
211212
/// circumstances and sends them to subsequent builds with `-L`.
@@ -215,34 +216,48 @@ impl CodegenBackend for GotocCodegenBackend {
215216
/// Types such as `bin`, `cdylib`, `dylib` will trigger the native linker.
216217
///
217218
/// Thus, we manually build the rlib file including only the `rmeta` file.
219+
///
220+
/// For cases where no metadata file was requested, we stub the file requested by writing the
221+
/// path of the `kani-metadata.json` file so `kani-driver` can safely find the latest metadata.
222+
/// See <https://github.com/model-checking/kani/issues/2234> for more details.
218223
fn link(
219224
&self,
220225
sess: &Session,
221226
codegen_results: CodegenResults,
222227
outputs: &OutputFilenames,
223228
) -> Result<(), ErrorGuaranteed> {
224229
let requested_crate_types = sess.crate_types();
225-
if !requested_crate_types.contains(&CrateType::Rlib) {
226-
// Quit successfully if we don't need an `rlib`:
227-
return Ok(());
230+
for crate_type in requested_crate_types {
231+
let out_path = out_filename(
232+
sess,
233+
*crate_type,
234+
outputs,
235+
codegen_results.crate_info.local_crate_name,
236+
);
237+
debug!(?crate_type, ?out_path, "link");
238+
if *crate_type == CrateType::Rlib {
239+
// Emit the `rlib` that contains just one file: `<crate>.rmeta`
240+
let mut builder = ArchiveBuilder::new(sess);
241+
let tmp_dir = TempFileBuilder::new().prefix("kani").tempdir().unwrap();
242+
let path = MaybeTempDir::new(tmp_dir, sess.opts.cg.save_temps);
243+
let (metadata, _metadata_position) = create_wrapper_file(
244+
sess,
245+
b".rmeta".to_vec(),
246+
codegen_results.metadata.raw_data(),
247+
);
248+
let metadata = emit_wrapper_file(sess, &metadata, &path, METADATA_FILENAME);
249+
builder.add_file(&metadata);
250+
builder.build(&out_path);
251+
} else {
252+
// Write the location of the kani metadata file in the requested compiler output file.
253+
let base_filename = outputs.output_path(OutputType::Object);
254+
let content_stub = CompilerArtifactStub {
255+
metadata_path: base_filename.with_extension(ArtifactType::Metadata),
256+
};
257+
let out_file = File::create(out_path).unwrap();
258+
serde_json::to_writer(out_file, &content_stub).unwrap();
259+
}
228260
}
229-
230-
// Emit the `rlib` that contains just one file: `<crate>.rmeta`
231-
let mut builder = ArchiveBuilder::new(sess);
232-
let tmp_dir = TempFileBuilder::new().prefix("kani").tempdir().unwrap();
233-
let path = MaybeTempDir::new(tmp_dir, sess.opts.cg.save_temps);
234-
let (metadata, _metadata_position) =
235-
create_wrapper_file(sess, b".rmeta".to_vec(), codegen_results.metadata.raw_data());
236-
let metadata = emit_wrapper_file(sess, &metadata, &path, METADATA_FILENAME);
237-
builder.add_file(&metadata);
238-
239-
let rlib = out_filename(
240-
sess,
241-
CrateType::Rlib,
242-
outputs,
243-
codegen_results.crate_info.local_crate_name,
244-
);
245-
builder.build(&rlib);
246261
Ok(())
247262
}
248263
}

kani-driver/src/call_cargo.rs

+87-42
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33

44
use crate::args::KaniArgs;
55
use crate::call_single_file::to_rustc_arg;
6+
use crate::project::Artifact;
67
use crate::session::KaniSession;
78
use anyhow::{bail, Context, Result};
89
use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel};
9-
use cargo_metadata::{Message, Metadata, MetadataCommand, Package};
10+
use cargo_metadata::{Message, Metadata, MetadataCommand, Package, Target};
11+
use kani_metadata::{ArtifactType, CompilerArtifactStub};
1012
use std::ffi::{OsStr, OsString};
11-
use std::fs;
13+
use std::fs::{self, File};
1214
use std::io::BufReader;
13-
use std::path::{Path, PathBuf};
15+
use std::path::PathBuf;
1416
use std::process::Command;
1517
use tracing::{debug, trace};
1618

@@ -29,12 +31,8 @@ pub struct CargoOutputs {
2931
/// The directory where compiler outputs should be directed.
3032
/// Usually 'target/BUILD_TRIPLE/debug/deps/'
3133
pub outdir: PathBuf,
32-
/// The collection of *.symtab.out goto binary files written.
33-
pub symtab_gotos: Vec<PathBuf>,
34-
/// The location of vtable restrictions files (a directory of *.restrictions.json)
35-
pub restrictions: Option<PathBuf>,
3634
/// The kani-metadata.json files written by kani-compiler.
37-
pub metadata: Vec<PathBuf>,
35+
pub metadata: Vec<Artifact>,
3836
/// Recording the cargo metadata from the build
3937
pub cargo_metadata: Metadata,
4038
}
@@ -105,20 +103,21 @@ impl KaniSession {
105103

106104
let mut found_target = false;
107105
let packages = packages_to_verify(&self.args, &metadata);
106+
let mut artifacts = vec![];
108107
for package in packages {
109-
for target in package_targets(&self.args, package) {
108+
for verification_target in package_targets(&self.args, package) {
110109
let mut cmd = Command::new("cargo");
111110
cmd.args(&cargo_args)
112111
.args(vec!["-p", &package.name])
113-
.args(&target.to_args())
112+
.args(&verification_target.to_args())
114113
.args(&pkg_args)
115114
.env("RUSTC", &self.kani_compiler)
116115
// Use CARGO_ENCODED_RUSTFLAGS instead of RUSTFLAGS is preferred. See
117116
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
118117
.env("CARGO_ENCODED_RUSTFLAGS", rustc_args.join(OsStr::new("\x1f")))
119118
.env("CARGO_TERM_PROGRESS_WHEN", "never");
120119

121-
self.run_cargo(cmd)?;
120+
artifacts.extend(self.run_cargo(cmd, verification_target.target())?.into_iter());
122121
found_target = true;
123122
}
124123
}
@@ -127,13 +126,7 @@ impl KaniSession {
127126
bail!("No supported targets were found.");
128127
}
129128

130-
Ok(CargoOutputs {
131-
outdir: outdir.clone(),
132-
symtab_gotos: glob(&outdir.join("*.symtab.out"))?,
133-
metadata: glob(&outdir.join("*.kani-metadata.json"))?,
134-
restrictions: self.args.restrict_vtable().then_some(outdir),
135-
cargo_metadata: metadata,
136-
})
129+
Ok(CargoOutputs { outdir, metadata: artifacts, cargo_metadata: metadata })
137130
}
138131

139132
fn cargo_metadata(&self, build_target: &str) -> Result<Metadata> {
@@ -165,9 +158,10 @@ impl KaniSession {
165158
}
166159

167160
/// Run cargo and collect any error found.
168-
/// TODO: We should also use this to collect the artifacts generated by cargo.
169-
fn run_cargo(&self, cargo_cmd: Command) -> Result<()> {
161+
/// We also collect the metadata file generated during compilation if any.
162+
fn run_cargo(&self, cargo_cmd: Command, target: &Target) -> Result<Option<Artifact>> {
170163
let support_color = atty::is(atty::Stream::Stdout);
164+
let mut artifact = None;
171165
if let Some(mut cargo_process) = self.run_piped(cargo_cmd)? {
172166
let reader = BufReader::new(cargo_process.stdout.take().unwrap());
173167
let mut error_count = 0;
@@ -196,9 +190,16 @@ impl KaniSession {
196190
}
197191
}
198192
},
199-
Message::CompilerArtifact(_)
200-
| Message::BuildScriptExecuted(_)
201-
| Message::BuildFinished(_) => {
193+
Message::CompilerArtifact(rustc_artifact) => {
194+
if rustc_artifact.target == *target {
195+
debug_assert!(
196+
artifact.is_none(),
197+
"expected only one artifact for `{target:?}`",
198+
);
199+
artifact = Some(rustc_artifact);
200+
}
201+
}
202+
Message::BuildScriptExecuted(_) | Message::BuildFinished(_) => {
202203
// do nothing
203204
}
204205
Message::TextLine(msg) => {
@@ -222,7 +223,11 @@ impl KaniSession {
222223
);
223224
}
224225
}
225-
Ok(())
226+
// We generate kani specific artifacts only for the build target. The build target is
227+
// always the last artifact generated in a build, and all the other artifacts are related
228+
// to dependencies or build scripts. Hence, we need to invoke `map_kani_artifact` only
229+
// for the last compiler artifact.
230+
Ok(artifact.and_then(map_kani_artifact))
226231
}
227232
}
228233

@@ -236,15 +241,6 @@ fn print_msg(diagnostic: &Diagnostic, use_rendered: bool) -> Result<()> {
236241
Ok(())
237242
}
238243

239-
/// Given a `path` with glob characters in it (e.g. `*.json`), return a vector of matching files
240-
fn glob(path: &Path) -> Result<Vec<PathBuf>> {
241-
let results = glob::glob(path.to_str().context("Non-UTF-8 path enountered")?)?;
242-
// the logic to turn "Iter<Result<T, E>>" into "Result<Vec<T>, E>" doesn't play well
243-
// with anyhow, so a type annotation is required
244-
let v: core::result::Result<Vec<PathBuf>, glob::GlobError> = results.collect();
245-
Ok(v?)
246-
}
247-
248244
/// Extract the packages that should be verified.
249245
/// If `--package <pkg>` is given, return the list of packages selected.
250246
/// If `--workspace` is given, return the list of workspace members.
@@ -276,20 +272,69 @@ fn packages_to_verify<'b>(args: &KaniArgs, metadata: &'b Metadata) -> Vec<&'b Pa
276272
packages
277273
}
278274

275+
/// Extract Kani artifact that might've been generated from a given rustc artifact.
276+
/// Not every rustc artifact will map to a kani artifact, hence the `Option<>`.
277+
///
278+
/// Unfortunately, we cannot always rely on the messages to get the path for the original artifact
279+
/// that `rustc` produces. So we hack the content of the output path to point to the original
280+
/// metadata file. See <https://github.com/model-checking/kani/issues/2234> for more details.
281+
fn map_kani_artifact(rustc_artifact: cargo_metadata::Artifact) -> Option<Artifact> {
282+
debug!(?rustc_artifact, "map_kani_artifact");
283+
if rustc_artifact.target.is_custom_build() {
284+
// We don't verify custom builds.
285+
return None;
286+
}
287+
let result = rustc_artifact.filenames.iter().find_map(|path| {
288+
if path.extension() == Some("rmeta") {
289+
let file_stem = path.file_stem()?.strip_prefix("lib")?;
290+
let parent =
291+
path.parent().map(|p| p.as_std_path().to_path_buf()).unwrap_or(PathBuf::new());
292+
let mut meta_path = parent.join(file_stem);
293+
meta_path.set_extension(ArtifactType::Metadata);
294+
trace!(rmeta=?path, kani_meta=?meta_path.display(), "map_kani_artifact");
295+
296+
// This will check if the file exists and we just skip if it doesn't.
297+
Artifact::try_new(&meta_path, ArtifactType::Metadata).ok()
298+
} else if path.extension() == Some("rlib") {
299+
// We skip `rlib` files since we should also generate a `rmeta`.
300+
trace!(rlib=?path, "map_kani_artifact");
301+
None
302+
} else {
303+
// For all the other cases we write the path of the metadata into the output file.
304+
// The compiler should always write a valid stub into the artifact file, however the
305+
// kani-metadata file only exists if there were valid targets.
306+
trace!(artifact=?path, "map_kani_artifact");
307+
let input_file = File::open(path).ok()?;
308+
let stub: CompilerArtifactStub = serde_json::from_reader(input_file).unwrap();
309+
Artifact::try_new(&stub.metadata_path, ArtifactType::Metadata).ok()
310+
}
311+
});
312+
debug!(?result, "map_kani_artifact");
313+
result
314+
}
315+
279316
/// Possible verification targets.
280317
enum VerificationTarget {
281-
Bin(String),
282-
Lib,
283-
Test(String),
318+
Bin(Target),
319+
Lib(Target),
320+
Test(Target),
284321
}
285322

286323
impl VerificationTarget {
287324
/// Convert to cargo argument that select the specific target.
288325
fn to_args(&self) -> Vec<String> {
289326
match self {
290-
VerificationTarget::Test(name) => vec![String::from("--test"), name.clone()],
291-
VerificationTarget::Bin(name) => vec![String::from("--bin"), name.clone()],
292-
VerificationTarget::Lib => vec![String::from("--lib")],
327+
VerificationTarget::Test(target) => vec![String::from("--test"), target.name.clone()],
328+
VerificationTarget::Bin(target) => vec![String::from("--bin"), target.name.clone()],
329+
VerificationTarget::Lib(_) => vec![String::from("--lib")],
330+
}
331+
}
332+
333+
fn target(&self) -> &Target {
334+
match self {
335+
VerificationTarget::Test(target)
336+
| VerificationTarget::Bin(target)
337+
| VerificationTarget::Lib(target) => target,
293338
}
294339
}
295340
}
@@ -318,7 +363,7 @@ fn package_targets(args: &KaniArgs, package: &Package) -> Vec<VerificationTarget
318363
match kind.as_str() {
319364
CRATE_TYPE_BIN => {
320365
// Binary targets.
321-
verification_targets.push(VerificationTarget::Bin(target.name.clone()));
366+
verification_targets.push(VerificationTarget::Bin(target.clone()));
322367
}
323368
CRATE_TYPE_LIB | CRATE_TYPE_RLIB | CRATE_TYPE_CDYLIB | CRATE_TYPE_DYLIB
324369
| CRATE_TYPE_STATICLIB => {
@@ -331,7 +376,7 @@ fn package_targets(args: &KaniArgs, package: &Package) -> Vec<VerificationTarget
331376
CRATE_TYPE_TEST => {
332377
// Test target.
333378
if args.tests {
334-
verification_targets.push(VerificationTarget::Test(target.name.clone()));
379+
verification_targets.push(VerificationTarget::Test(target.clone()));
335380
} else {
336381
ignored_tests.push(target.name.as_str());
337382
}
@@ -347,7 +392,7 @@ fn package_targets(args: &KaniArgs, package: &Package) -> Vec<VerificationTarget
347392
`proc-macro`.",
348393
target.name,
349394
),
350-
(true, false) => verification_targets.push(VerificationTarget::Lib),
395+
(true, false) => verification_targets.push(VerificationTarget::Lib(target.clone())),
351396
(_, _) => {}
352397
}
353398
}

kani-driver/src/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fn cargokani_main(input_args: Vec<OsString>) -> Result<()> {
7070
}
7171

7272
let project = project::cargo_project(&session)?;
73+
debug!(?project, "cargokani_main");
7374
if session.args.only_codegen { Ok(()) } else { verify_project(project, session) }
7475
}
7576

@@ -80,6 +81,7 @@ fn standalone_main() -> Result<()> {
8081
let session = session::KaniSession::new(args.common_opts)?;
8182

8283
let project = project::standalone_project(&args.input, &session)?;
84+
debug!(?project, "standalone_main");
8385
if session.args.only_codegen { Ok(()) } else { verify_project(project, session) }
8486
}
8587

kani-driver/src/metadata.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use anyhow::Result;
55
use std::path::Path;
6-
use tracing::debug;
6+
use tracing::{debug, trace};
77

88
use kani_metadata::{
99
HarnessAttributes, HarnessMetadata, InternedString, KaniMetadata, TraitDefinedMethod,
@@ -178,6 +178,8 @@ fn find_proof_harnesses<'a>(
178178
|| targets.iter().any(|target| md.pretty_name.contains(*target))
179179
{
180180
result.push(*md);
181+
} else {
182+
trace!(skip = md.pretty_name, "find_proof_harnesses");
181183
}
182184
}
183185
result

0 commit comments

Comments
 (0)