Skip to content

Commit 96c6b3b

Browse files
authored
Refactor kani-verifier (rust-lang#1218)
1 parent 885546c commit 96c6b3b

File tree

3 files changed

+269
-218
lines changed

3 files changed

+269
-218
lines changed

src/cmd.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This module contains small helper functions for running Commands.
5+
//! We could possibly eliminate this if we find a small-enough dependency.
6+
7+
use std::ffi::OsString;
8+
use std::process::Command;
9+
10+
use anyhow::{bail, Context, Result};
11+
12+
/// Helper trait to fallibly run commands
13+
pub trait AutoRun {
14+
fn run(&mut self) -> Result<()>;
15+
}
16+
impl AutoRun for Command {
17+
fn run(&mut self) -> Result<()> {
18+
// This can sometimes fail during the set-up of the forked process before exec,
19+
// for example by setting `current_dir` to a directory that does not exist.
20+
let status = self.status().with_context(|| {
21+
format!(
22+
"Internal failure before invoking command: {}",
23+
render_command(self).to_string_lossy()
24+
)
25+
})?;
26+
if !status.success() {
27+
bail!("Failed command: {}", render_command(self).to_string_lossy());
28+
}
29+
Ok(())
30+
}
31+
}
32+
33+
/// Render a Command as a string, to log it
34+
fn render_command(cmd: &Command) -> OsString {
35+
let mut str = OsString::new();
36+
37+
for (k, v) in cmd.get_envs() {
38+
if let Some(v) = v {
39+
str.push(k);
40+
str.push("=\"");
41+
str.push(v);
42+
str.push("\" ");
43+
}
44+
}
45+
46+
str.push(cmd.get_program());
47+
48+
for a in cmd.get_args() {
49+
str.push(" ");
50+
if a.to_string_lossy().contains(' ') {
51+
str.push("\"");
52+
str.push(a);
53+
str.push("\"");
54+
} else {
55+
str.push(a);
56+
}
57+
}
58+
59+
str
60+
}

src/lib.rs

Lines changed: 16 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -14,63 +14,37 @@
1414
1515
#![warn(clippy::all, clippy::cargo)]
1616

17+
mod cmd;
18+
mod setup;
19+
1720
use std::env;
1821
use std::ffi::OsString;
1922
use std::os::unix::prelude::CommandExt;
20-
use std::path::{Path, PathBuf};
23+
use std::path::PathBuf;
2124
use std::process::Command;
2225

2326
use anyhow::{bail, Context, Result};
2427

25-
/// Comes from our Cargo.toml manifest file. Must correspond to our release verion.
26-
const VERSION: &str = env!("CARGO_PKG_VERSION");
27-
/// Set by our `build.rs`, reflects the Rust target triple we're building for
28-
const TARGET: &str = env!("TARGET");
29-
30-
/// Typically `~/.kani/kani-1.x/`
31-
fn kani_dir() -> PathBuf {
32-
home::home_dir()
33-
.expect("Couldn't find home dir?")
34-
.join(".kani")
35-
.join(format!("kani-{}", VERSION))
36-
}
37-
38-
/// The filename of the release bundle
39-
fn download_filename() -> String {
40-
format!("kani-{}-{}.tar.gz", VERSION, TARGET)
41-
}
42-
43-
/// Helper to find the download URL for this version of Kani
44-
fn download_url() -> String {
45-
let tag: &str = &format!("kani-{}", VERSION);
46-
let file: &str = &download_filename();
47-
format!("https://github.com/model-checking/kani/releases/download/{}/{}", tag, file)
48-
}
49-
5028
/// Effectively the entry point (i.e. `main` function) for both our proxy binaries.
29+
/// `bin` should be either `kani` or `cargo-kani`
5130
pub fn proxy(bin: &str) -> Result<()> {
5231
// In an effort to keep our dependencies minimal, we do the bare minimum argument parsing
53-
let args: Vec<_> = std::env::args_os().collect();
32+
let args: Vec<_> = env::args_os().collect();
5433
if args.len() >= 2 && args[1] == "setup" {
5534
if args.len() >= 4 && args[2] == "--use-local-bundle" {
56-
setup(Some(args[3].clone()))
35+
setup::setup(Some(args[3].clone()))
5736
} else {
58-
setup(None)
37+
setup::setup(None)
5938
}
6039
} else {
6140
fail_if_in_dev_environment()?;
62-
if !appears_setup() {
63-
setup(None)?;
41+
if !setup::appears_setup() {
42+
setup::setup(None)?;
6443
}
6544
exec(bin)
6645
}
6746
}
6847

69-
/// Fast check to see if we look setup already
70-
fn appears_setup() -> bool {
71-
kani_dir().exists()
72-
}
73-
7448
/// In dev environments, this proxy shouldn't be used.
7549
/// But accidentally using it (with the test suite) can fire off
7650
/// hundreds of HTTP requests trying to download a non-existent release bundle.
@@ -90,157 +64,31 @@ fn fail_if_in_dev_environment() -> Result<()> {
9064
Ok(())
9165
}
9266

93-
/// Give users a better error message than "404" if we're on an unsupported platform.
94-
fn fail_if_unsupported_target() -> Result<()> {
95-
// This is basically going to be reduced to a compile-time constant
96-
match TARGET {
97-
"x86_64-unknown-linux-gnu" | "x86_64-apple-darwin" => Ok(()),
98-
_ => bail!("Kani does not support this platform (Rust target {})", TARGET),
99-
}
100-
}
101-
102-
/// Sets up Kani by unpacking/installing to `~/.kani/kani-VERSION`
103-
fn setup(use_local_bundle: Option<OsString>) -> Result<()> {
104-
let kani_dir = kani_dir();
105-
// e.g. `~/.kani/`
106-
let base_dir = kani_dir.parent().expect("No base directory?");
107-
108-
println!("[0/6] Running Kani first-time setup...");
109-
110-
println!("[1/6] Ensuring the existence of: {}", base_dir.display());
111-
std::fs::create_dir_all(&base_dir)?;
112-
113-
if let Some(pathstr) = use_local_bundle {
114-
println!("[2/6] Installing local Kani bundle: {}", pathstr.to_string_lossy());
115-
let path = Path::new(&pathstr).canonicalize()?;
116-
// When given a local bundle, it's often "-latest" but we expect "-1.0" or something.
117-
// tar supports "stripping" the first directory from the bundle, so do that and
118-
// extract it directly into the expected (kani_dir) directory (instead of base_dir).
119-
if !kani_dir.exists() {
120-
std::fs::create_dir(&kani_dir)?;
121-
}
122-
Command::new("tar")
123-
.arg("--strip-components=1")
124-
.arg("-zxf")
125-
.arg(&path)
126-
.current_dir(&kani_dir)
127-
.run()?;
128-
} else {
129-
let filename = download_filename();
130-
println!("[2/6] Downloading Kani release bundle: {}", &filename);
131-
fail_if_unsupported_target()?;
132-
let bundle = base_dir.join(filename);
133-
Command::new("curl")
134-
.args(&["-sSLf", "-o"])
135-
.arg(&bundle)
136-
.arg(download_url())
137-
.run()
138-
.context("Failed to download Kani release bundle")?;
139-
140-
Command::new("tar").arg("zxf").arg(&bundle).current_dir(base_dir).run()?;
141-
142-
std::fs::remove_file(bundle)?;
143-
}
144-
145-
let toolchain_version = std::fs::read_to_string(kani_dir.join("rust-toolchain-version"))
146-
.context("Reading release bundle rust-toolchain-version")?;
147-
println!("[3/6] Installing rust toolchain version: {}", &toolchain_version);
148-
Command::new("rustup").args(&["toolchain", "install", &toolchain_version]).run()?;
149-
150-
let toolchain = home::rustup_home()?.join("toolchains").join(&toolchain_version);
151-
152-
symlink_rust_toolchain(&toolchain, &kani_dir)?;
153-
154-
println!("[4/6] Installing Kani python dependencies...");
155-
let pyroot = kani_dir.join("pyroot");
156-
157-
// TODO: this is a repetition of versions from elsewhere
158-
Command::new("python3")
159-
.args(&["-m", "pip", "install", "cbmc-viewer==3.2", "--target"])
160-
.arg(&pyroot)
161-
.run()?;
162-
Command::new("python3")
163-
.args(&["-m", "pip", "install", "colorama==0.4.3", "--target"])
164-
.arg(&pyroot)
165-
.run()?;
166-
167-
println!("[5/6] Building Kani library prelude...");
168-
// We need a workspace to build them in, otherwise repeated builds generate different hashes and `kani` can't find `kani_macros`
169-
let contents = "[workspace]\nmembers = [\"kani\",\"kani_macros\",\"std\"]";
170-
std::fs::write(kani_dir.join("library").join("Cargo.toml"), contents)?;
171-
172-
// A little helper for invoking Cargo repeatedly here
173-
let cargo = |crate_name: &str| -> Result<()> {
174-
let manifest = format!("library/{}/Cargo.toml", crate_name);
175-
Command::new("cargo")
176-
.args(&[
177-
&format!("+{}", toolchain_version),
178-
"build",
179-
"-Z",
180-
"unstable-options",
181-
"--manifest-path",
182-
&manifest,
183-
"--out-dir",
184-
"lib",
185-
"--target-dir",
186-
"target",
187-
])
188-
.current_dir(&kani_dir)
189-
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
190-
.env("CARGO_ENCODED_RUSTFLAGS", "--cfg=kani")
191-
.run()
192-
.with_context(|| format!("Failed to build Kani prelude library {}", crate_name))
193-
};
194-
195-
// We seem to need 3 invocations because of the behavior of the `--out-dir` flag.
196-
// It only seems to produce the requested artifact, not its dependencies.
197-
cargo("kani")?;
198-
cargo("kani_macros")?;
199-
cargo("std")?;
200-
201-
std::fs::remove_dir_all(kani_dir.join("target"))?;
202-
203-
println!("[6/6] Successfully completed Kani first-time setup.");
204-
205-
Ok(())
206-
}
207-
208-
/// Creates a `kani_dir/toolchain` symlink pointing to `toolchain`.
209-
fn symlink_rust_toolchain(toolchain: &Path, kani_dir: &Path) -> Result<()> {
210-
let path = kani_dir.join("toolchain");
211-
// We want to be idempotent, so if the symlink already exists, delete it first
212-
if path.exists() && path.is_symlink() {
213-
std::fs::remove_file(&path)?;
214-
}
215-
std::os::unix::fs::symlink(toolchain, path)?;
216-
Ok(())
217-
}
218-
21967
/// Executes `kani-driver` in `bin` mode (kani or cargo-kani)
22068
/// augmenting environment variables to accomodate our release environment
22169
fn exec(bin: &str) -> Result<()> {
222-
let kani_dir = kani_dir();
70+
let kani_dir = setup::kani_dir();
22371
let program = kani_dir.join("bin").join("kani-driver");
22472
let pyroot = kani_dir.join("pyroot");
22573
let bin_kani = kani_dir.join("bin");
22674
let bin_pyroot = pyroot.join("bin");
22775
let bin_toolchain = kani_dir.join("toolchain").join("bin");
22876

22977
// Allow python scripts to find dependencies under our pyroot
230-
let pythonpath = augment_search(&[pyroot], env::var_os("PYTHONPATH"))?;
78+
let pythonpath = prepend_search_path(&[pyroot], env::var_os("PYTHONPATH"))?;
23179
// Add: kani, cbmc, viewer (pyroot), and our rust toolchain directly to our PATH
232-
let path = augment_search(&[bin_kani, bin_pyroot, bin_toolchain], env::var_os("PATH"))?;
80+
let path = prepend_search_path(&[bin_kani, bin_pyroot, bin_toolchain], env::var_os("PATH"))?;
23381

23482
let mut cmd = Command::new(program);
235-
cmd.args(std::env::args_os().skip(1)).env("PYTHONPATH", pythonpath).env("PATH", path).arg0(bin);
83+
cmd.args(env::args_os().skip(1)).env("PYTHONPATH", pythonpath).env("PATH", path).arg0(bin);
23684

23785
let result = cmd.status().context("Failed to invoke kani-driver")?;
23886

23987
std::process::exit(result.code().expect("No exit code?"));
24088
}
24189

242-
/// Prepend paths to an environment variable
243-
fn augment_search(paths: &[PathBuf], original: Option<OsString>) -> Result<OsString> {
90+
/// Prepend paths to an environment variable search string like PATH
91+
fn prepend_search_path(paths: &[PathBuf], original: Option<OsString>) -> Result<OsString> {
24492
match original {
24593
None => Ok(env::join_paths(paths)?),
24694
Some(original) => {
@@ -250,53 +98,3 @@ fn augment_search(paths: &[PathBuf], original: Option<OsString>) -> Result<OsStr
25098
}
25199
}
252100
}
253-
254-
/// Helper trait to fallibly run commands
255-
trait AutoRun {
256-
fn run(&mut self) -> Result<()>;
257-
}
258-
impl AutoRun for Command {
259-
fn run(&mut self) -> Result<()> {
260-
// This can sometimes fail during the set-up of the forked process before exec,
261-
// for example by setting `current_dir` to a directory that does not exist.
262-
let status = self.status().with_context(|| {
263-
format!(
264-
"Internal failure before invoking command: {}",
265-
render_command(self).to_string_lossy()
266-
)
267-
})?;
268-
if !status.success() {
269-
bail!("Failed command: {}", render_command(self).to_string_lossy());
270-
}
271-
Ok(())
272-
}
273-
}
274-
275-
/// Render a Command as a string, to log it
276-
pub fn render_command(cmd: &Command) -> OsString {
277-
let mut str = OsString::new();
278-
279-
for (k, v) in cmd.get_envs() {
280-
if let Some(v) = v {
281-
str.push(k);
282-
str.push("=\"");
283-
str.push(v);
284-
str.push("\" ");
285-
}
286-
}
287-
288-
str.push(cmd.get_program());
289-
290-
for a in cmd.get_args() {
291-
str.push(" ");
292-
if a.to_string_lossy().contains(' ') {
293-
str.push("\"");
294-
str.push(a);
295-
str.push("\"");
296-
} else {
297-
str.push(a);
298-
}
299-
}
300-
301-
str
302-
}

0 commit comments

Comments
 (0)