diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f85e4..9c5750d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. `cargo-comp #### 3.x Releases +- `3.2.x` Releases - [3.2.0](#320) - `3.1.x` Releases - [3.1.0](#310) - `3.0.x` Releases - [3.0.0](#300) | [3.0.1](#301) @@ -18,6 +19,30 @@ All notable changes to this project will be documented in this file. `cargo-comp --- +## [3.2.0](https://github.com/bitslab/cargo-compiler-interrupts/releases/tag/3.2.0) + +Released on 2021-07-31. + +#### Added + +- `cargo-build-ci` + - Support integration for `examples` target by passing `--example` option. +- `cargo-run-ci` + - Ability to pass arguments to the binary by passing `--args` option. + +#### Updated + +- Various path-related helper functions are renamed. +- `cargo-build-ci` + - Fix a race condition between integration threads and progress indicator threads when integration thread failed unexpectedly. + - Failed integration can now be run again without using `cargo clean`. +- `cargo-lib-ci` + - Fix wrong output formatting while the progress indicator is showing. + +#### Removed + +- `deps-ci` folder has been removed. All CI-integrated artifacts are now resided in the same directory as the original one. + ## [3.1.0](https://github.com/bitslab/cargo-compiler-interrupts/releases/tag/3.1.0) Released on 2021-07-24. diff --git a/Cargo.toml b/Cargo.toml index d2cea8c..f3fe44e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "cargo-compiler-interrupts" readme = "README.md" repository = "https://github.com/bitslab/cargo-compiler-interrupts" -version = "3.1.0" +version = "3.2.0" [dependencies] anyhow = "1.0" @@ -20,12 +20,13 @@ cargo_metadata = "0.13" chrono = "0.4" clap = "3.0.0-beta.2" colored = "2" +crossbeam-utils = "0.8" dirs = "3.0" faccess = "0.2" indicatif = "0.16" md5 = "0.7" -rayon = "1.5" -serde = {version = "1.0", features = ["derive"]} +num_cpus = "1.13" +serde = "1.0" term_size = "1.0.0-beta.2" thiserror = "1.0" toml = "0.5" diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 3b363b3..c31ef7f 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -50,6 +50,7 @@ FLAGS: -V, --version Prints version information OPTIONS: + -e, --example Build an example artifact -s, --skip-crates ... Crates to skip the integration (space-delimited) -t, --target Build for the target triple ``` @@ -68,6 +69,7 @@ FLAGS: -V, --version Prints version information OPTIONS: + -a, --args ... Arguments for the binary -b, --bin Name of the binary -t, --target Target triple for the binary ``` @@ -88,9 +90,9 @@ FLAGS: -V, --version Prints version information OPTIONS: - -a, --args ... Default arguments for the library (space-delimited) - -p, --path Destination path for the library when installing - --url URL to the source code of the library when installing + -a, --args ... Default arguments for the library (space-delimited) + -p, --path Destination path for the library when installing + --url Remote URL to the source code of the library when installing ``` ## Project structure diff --git a/README.md b/README.md index f5321e4..68bd2c9 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,25 @@ # cargo-compiler-interrupts -[![crates.io](https://img.shields.io/crates/v/cargo-compiler-interrupts.svg)](https://crates.io/crates/cargo-compiler-interrupts) -[![docs.rs](https://docs.rs/cargo-compiler-interrupts/badge.svg)](https://docs.rs/cargo-compiler-interrupts) -[![license](https://img.shields.io/crates/l/cargo-compiler-interrupts.svg)](LICENSE) +[![crates.io](https://img.shields.io/crates/v/cargo-compiler-interrupts.svg)][crates.io] +[![docs.rs](https://docs.rs/cargo-compiler-interrupts/badge.svg)][docs.rs] +[![license](https://img.shields.io/crates/l/cargo-compiler-interrupts.svg)][license] -`cargo-compiler-interrupts` provides you a seamless way to integrate the [Compiler Interrupts](https://dl.acm.org/doi/10.1145/3453483.3454107) to any Rust packages. Check out the Compiler Interrupts [main repository](https://github.com/bitslab/CompilerInterrupts) for more info. +`cargo-compiler-interrupts` provides you a seamless way to integrate the +[Compiler Interrupts][compiler-interrupts-paper] to any Rust packages. +Check out the Compiler Interrupts [main repository][compiler-interrupts] for more info. ## Requirements -* [Rust 1.45.0](https://www.rust-lang.org/tools/install) or later and [LLVM 9](https://releases.llvm.org/) or later are required. Both must have the same LLVM version. -* You can check the LLVM version from Rust toolchain and LLVM toolchain by running `rustc -vV` and `llvm-config --version` respectively. -* x86-64 architecture with Linux or macOS is highly recommended. Other architectures and platforms have not been tested. +* [Rust 1.45.0][rust] or later and [LLVM 9][llvm] or later are required. +Both must have the same LLVM version. +* You can check the LLVM version from Rust toolchain and LLVM toolchain by running `rustc -vV` +and `llvm-config --version` respectively. +* x86-64 architecture with Linux or macOS is highly recommended. +Other architectures and platforms have not been tested. ## Installation -`cargo-compiler-interrupts` is a Cargo package and can be installed via `cargo install`. +`cargo-compiler-interrupts` can be installed via `cargo install`. ``` sh cargo install cargo-compiler-interrupts @@ -39,9 +44,14 @@ cargo run-ci # run the CI-integrated binary * `cargo lib-ci` — manage the Compiler Interrupts library. * `cargo build-ci` — build and integrate the Compiler Interrupts to the package. -* `cargo run-ci` — run the integrated binary. You can specify which binary to run by passing `--bin `. +* `cargo run-ci` — run the integrated binary. +You can specify which binary to run by passing `--bin `. -Make sure your program registers the Compiler Interrupts handler before running `cargo build-ci`. Compiler Interrupts APIs are provided through the [`compiler-interrupts`](https://github.com/bitslab/compiler-interrupts-rs) package. +Run `cargo lib-ci --install` to install the Compiler Interrupts library first. +Before running `cargo build-ci`, add the Compiler Interrupts API package as the dependency for +your Cargo package and registers the Compiler Interrupts handler in your program. +Compiler Interrupts API is provided through the [`compiler-interrupts`][compiler-interrupts-rs] +package. ``` rust fn interrupt_handler(ic: i64) { @@ -53,21 +63,38 @@ unsafe { } ``` -For more detailed usages and internals, run the command with `--help` option and check out the **[documentation](DOCUMENTATION.md)**. +For more detailed usages and internals, run the command with `--help` option and +check out the **[documentation]**. ## Contribution -All issue reports, feature requests, pull requests and GitHub stars are welcomed and much appreciated. Issues relating to the Compiler Interrupts integration should be reported to the [main repository](https://github.com/bitslab/CompilerInterrupts). +All issue reports, feature requests, pull requests and GitHub stars are welcomed +and much appreciated. Issues relating to the Compiler Interrupts library +should be reported to the [main repository][compiler-interrupts]. ## Author -Quan Tran ([@quanshousio](https://quanshousio.com)) +Quan Tran ([@quanshousio][quanshousio]) ## Acknowledgements -* My advisor [Jakob Eriksson](https://www.linkedin.com/in/erikssonjakob) for the enormous support for this project. -* [Nilanjana Basu](https://www.linkedin.com/in/nilanjana-basu-99027959) for implementing the Compiler Interrupts. +* My advisor [Jakob Eriksson][jakob] for the enormous support for this project. +* [Nilanjana Basu][nilanjana] for implementing the Compiler Interrupts. ## License -`cargo-compiler-interrupts` is available under the MIT license. See the [LICENSE](LICENSE) file for more info. +`cargo-compiler-interrupts` is available under the MIT license. +See the [LICENSE][license] file for more info. + +[crates.io]: https://crates.io/crates/cargo-compiler-interrupts +[docs.rs]: https://docs.rs/cargo-compiler-interrupts +[license]: https://github.com/bitslab/cargo-compiler-interrupts/blob/main/LICENSE +[documentation]: https://github.com/bitslab/cargo-compiler-interrupts/blob/main/DOCUMENTATION.md +[compiler-interrupts]: https://github.com/bitslab/CompilerInterrupts +[compiler-interrupts-rs]: https://github.com/bitslab/compiler-interrupts-rs +[compiler-interrupts-paper]: https://dl.acm.org/doi/10.1145/3453483.3454107 +[rust]: https://www.rust-lang.org/tools/install +[llvm]: https://releases.llvm.org +[quanshousio]: https://quanshousio.com +[jakob]: https://www.linkedin.com/in/erikssonjakob +[nilanjana]: https://www.linkedin.com/in/nilanjana-basu-99027959 diff --git a/src/config.rs b/src/config.rs index 3391749..fd14d3b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,26 +11,21 @@ use crate::{util, CIResult}; pub struct Config { /// Path to the library. pub library_path: String, - /// Path to the debug-enabled library. pub library_path_dbg: String, - /// LLVM version used to compile the library. pub llvm_version: String, - /// Default arguments. pub default_args: Vec, - /// Checksum of the source code. pub checksum: String, - /// Remote URL for the source code. pub url: String, } impl Config { /// Load the configuration. - pub fn load() -> CIResult { + pub fn load() -> CIResult { let mut path = util::config_path()?; path.push("default.cfg"); let file = match paths::read(&path) { @@ -38,7 +33,7 @@ impl Config { Err(e) => { info!("config file not available, default config loaded"); debug!("error: {}", e); - return Ok(Config::default()); + return Ok(Self::default()); } }; match toml::from_str(&file) { @@ -46,19 +41,19 @@ impl Config { Err(e) => { let old_path = util::append_suffix(&path, "old"); paths::copy(&path, &old_path)?; - Config::save(&Config::default())?; + Self::save(&Self::default())?; eprintln!("Incompatible config file found, replaced with default config"); eprintln!("Old config file can be found at: {}", old_path.display()); debug!("error: {}", e); - Config::load() + Self::load() } } } /// Save the configuration. - pub fn save(config: &Config) -> CIResult<()> { + pub fn save(config: &Self) -> CIResult<()> { let mut path = util::config_path()?; path.push("default.cfg"); let s = toml::to_string_pretty(config)?; diff --git a/src/error.rs b/src/error.rs index 0abc85b..e39d6e9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -//! Errors related to Compiler Interrupts integration. +//! Errors related to the Compiler Interrupts integration. // thiserror's bug #![allow(clippy::nonstandard_macro_braces)] diff --git a/src/lib.rs b/src/lib.rs index 6628c23..2d1f45a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,29 @@ -//! `cargo-compiler-interrupts` provides you a simple way to integrate the [Compiler Interrupts](https://dl.acm.org/doi/10.1145/3453483.3454107) to any Rust packages. Check out the Compiler Interrupts [main repository](https://github.com/bitslab/CompilerInterrupts) for more documentation. +//! [![crates.io](https://img.shields.io/crates/v/cargo-compiler-interrupts.svg)][crates.io] +//! [![docs.rs](https://docs.rs/cargo-compiler-interrupts/badge.svg)][docs.rs] +//! [![license](https://img.shields.io/crates/l/cargo-compiler-interrupts.svg)][license] +//! +//! `cargo-compiler-interrupts` provides you a seamless way to integrate the +//! [Compiler Interrupts][compiler-interrupts-paper] to any Rust packages. +//! Check out the Compiler Interrupts [main repository][compiler-interrupts] for more info. //! //! ## Requirements //! -//! * [Rust 1.45.0](https://www.rust-lang.org/tools/install) or later and [LLVM 9](https://releases.llvm.org/) or later are required. Both must have the same LLVM version. -//! * You can check the LLVM version from Rust toolchain and LLVM toolchain by running `rustc -vV` and `llvm-config --version` respectively. -//! * x86-64 architecture with Linux or macOS is highly recommended. Other architectures and platforms have not been tested. +//! * [Rust 1.45.0][rust] or later and [LLVM 9][llvm] or later are required. +//! Both must have the same LLVM version. +//! * You can check the LLVM version from Rust toolchain and LLVM toolchain by running `rustc -vV` +//! and `llvm-config --version` respectively. +//! * x86-64 architecture with Linux or macOS is highly recommended. +//! Other architectures and platforms have not been tested. //! //! ## Installation //! -//! `cargo-compiler-interrupts` is a Cargo package and can be installed via `cargo install`. +//! `cargo-compiler-interrupts` can be installed via `cargo install`. //! //! ``` sh //! cargo install cargo-compiler-interrupts //! ``` //! -//! You can also fetch the repo and install using `--path`. +//! You can also fetch the repo and install using `--path`. //! //! ``` sh //! git clone https://github.com/bitslab/cargo-compiler-interrupts @@ -31,29 +40,62 @@ //! cargo run-ci # run the CI-integrated binary //! ``` //! -//! * `cargo lib-ci` — install or uninstall the Compiler Interrupts library. -//! * `cargo build-ci` — build and integrate the Compiler Interrupts to the binary. -//! * `cargo run-ci` — run the integrated binary. You can specify which binary to run by passing `--bin `. +//! * `cargo lib-ci` — manage the Compiler Interrupts library. +//! * `cargo build-ci` — build and integrate the Compiler Interrupts to the package. +//! * `cargo run-ci` — run the integrated binary. +//! You can specify which binary to run by passing `--bin `. +//! +//! Run `cargo lib-ci --install` to install the Compiler Interrupts library first. +//! Before running `cargo build-ci`, add the Compiler Interrupts API package as the dependency for +//! your Cargo package and registers the Compiler Interrupts handler in your program. +//! Compiler Interrupts API is provided through the [`compiler-interrupts`][compiler-interrupts-rs] +//! package. +//! +//! ``` rust +//! fn interrupt_handler(ic: i64) { +//! println!("Compiler interrupt called with instruction count: {}", ic); +//! } +//! +//! unsafe { +//! compiler_interrupts::register(1000, 1000, interrupt_handler); +//! } +//! ``` //! -//! For more detailed usage and internals, please run the command with `--help` option and check out the **[documentation](https://github.com/bitslab/cargo-compiler-interrupts/blob/main/DOCUMENTATION.md)**. +//! For more detailed usages and internals, run the command with `--help` option and +//! check out the **[documentation]**. //! //! ## Contribution //! -//! All issue reports, feature requests, pull requests and GitHub stars are welcomed and much appreciated. +//! All issue reports, feature requests, pull requests and GitHub stars are welcomed +//! and much appreciated. Issues relating to the Compiler Interrupts library +//! should be reported to the [main repository][compiler-interrupts]. //! //! ## Author //! -//! Quan Tran ([@quanshousio](https://quanshousio.com)) +//! Quan Tran ([@quanshousio][quanshousio]) //! //! ## Acknowledgements //! -//! * My advisor [Jakob Eriksson](https://www.linkedin.com/in/erikssonjakob) for the enormous support for this project. -//! * [Nilanjana Basu](https://www.linkedin.com/in/nilanjana-basu-99027959) for implementing the Compiler Interrupts. +//! * My advisor [Jakob Eriksson][jakob] for the enormous support for this project. +//! * [Nilanjana Basu][nilanjana] for implementing the Compiler Interrupts. //! //! ## License //! -//! `cargo-compiler-interrupts` is available under the MIT license. See the [LICENSE](https://github.com/bitslab/cargo-compiler-interrupts/blob/main/LICENSE) file for more info. -//! +//! `cargo-compiler-interrupts` is available under the MIT license. +//! See the [LICENSE][license] file for more info. +//! +//! [crates.io]: https://crates.io/crates/cargo-compiler-interrupts +//! [docs.rs]: https://docs.rs/cargo-compiler-interrupts +//! [license]: https://github.com/bitslab/cargo-compiler-interrupts/blob/main/LICENSE +//! [documentation]: https://github.com/bitslab/cargo-compiler-interrupts/blob/main/DOCUMENTATION.md +//! [compiler-interrupts]: https://github.com/bitslab/CompilerInterrupts +//! [compiler-interrupts-rs]: https://github.com/bitslab/compiler-interrupts-rs +//! [compiler-interrupts-paper]: https://dl.acm.org/doi/10.1145/3453483.3454107 +//! [rust]: https://www.rust-lang.org/tools/install +//! [llvm]: https://releases.llvm.org +//! [quanshousio]: https://quanshousio.com +//! [jakob]: https://www.linkedin.com/in/erikssonjakob +//! [nilanjana]: https://www.linkedin.com/in/nilanjana-basu-99027959 #![warn( missing_copy_implementations, @@ -70,11 +112,12 @@ clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_sign_loss, + clippy::clone_on_ref_ptr, clippy::missing_docs_in_private_items, clippy::mut_mut, clippy::print_stdout, - clippy::unwrap_used, - clippy::unseparated_literal_suffix + clippy::unseparated_literal_suffix, + clippy::unwrap_used )] /// Compiler Interrupts result. diff --git a/src/ops/build.rs b/src/ops/build.rs index 458eea3..6f53e90 100644 --- a/src/ops/build.rs +++ b/src/ops/build.rs @@ -1,18 +1,17 @@ -//! Implementation of `cargo build-ci` subcommand. +//! Implementation of `cargo-build-ci`. use std::collections::{HashMap, HashSet}; -use std::io::{BufRead, BufReader}; -use std::path::{Path, PathBuf}; -use std::process::{Output, Stdio}; -use std::sync::mpsc; +use std::path::Path; +use std::process::Output; +use std::sync::{mpsc, Arc, Mutex}; use anyhow::{bail, Context}; use cargo_metadata::{Metadata, MetadataCommand}; use cargo_util::{paths, ProcessBuilder, ProcessError}; use colored::Colorize; +use crossbeam_utils::thread; use faccess::PathExt; use indicatif::{ProgressBar, ProgressStyle}; -use rayon::prelude::*; use tracing::{debug, info}; use crate::config::Config; @@ -32,20 +31,51 @@ enum State { /// Crate is skipped. Skipped, /// An error occurred. - Error, + Error(String), } -/// Information about the integration. +/// Shared context of the integration. #[derive(Debug)] -struct Integration { +struct IntegrationCx { /// Name of the crate. - crate_name: String, - /// State of the integration. + crate_name: Arc, + /// Current state. state: State, } +/// Linker invocation. +#[derive(Debug)] +struct Linker { + /// Linker program name. + program: String, + /// Arguments for the linker. + args: Vec, + /// Path to the target binary. + bin_path: String, +} + /// Main routine for `cargo-build-ci`. pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { + if let Err(e) = _exec(&config, &opts) { + // make the build dirty if the integration failed + let target_path = util::target_path(&opts.target, &opts.release)?; + let deps_path = target_path.join("deps"); + let examples_path = target_path.join("examples"); + let binary_deps_files = + util::scan_path(&deps_path, |p| p.executable() && p.is_file()).unwrap_or_default(); + let binary_examples_files = + util::scan_path(&examples_path, |p| p.executable() && p.is_file()).unwrap_or_default(); + for file in binary_deps_files.iter().chain(binary_examples_files.iter()) { + paths::remove_file(file)?; + } + return Err(e); + } + + Ok(()) +} + +/// Core routine for `cargo-build-ci`. +fn _exec(config: &Config, opts: &BuildOpts) -> CIResult<()> { if !Path::new(&config.library_path).is_file() { bail!(CIError::LibraryNotInstalled); } @@ -66,9 +96,7 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { let metadata = cargo_metadata()?; for package in metadata.packages { for target in package.targets { - let t = target.crate_types.iter(); - let k = target.kind.iter(); - if t.zip(k).all(|(t, k)| t == "bin" && k == "bin") { + if target.crate_types.iter().any(|t| t == "bin") { crate_names.push(target.name.replace("-", "_")); } } @@ -88,17 +116,18 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { let target_path = util::target_path(&opts.target, &opts.release)?; let deps_path = target_path.join("deps"); - let deps_ci_path = target_path.join("deps-ci"); - - // create new "deps-ci" directory for CI-integrated files - paths::create_dir_all(&deps_ci_path)?; + let examples_path = target_path.join("examples"); // get timestamp from output files before running `cargo build` - if let Ok(target_files) = util::scan_path(&deps_path, |path| path.is_file()) { - for file in target_files { - let mtime = paths::mtime(&file)?; - assert!(mtimes.insert(file, mtime).is_none()); - } + let deps_files = util::scan_path(&deps_path, |p| p.is_file()).unwrap_or_default(); + for file in deps_files { + let mtime = paths::mtime(&file)?; + assert!(mtimes.insert(file, mtime).is_none()); + } + let examples_files = util::scan_path(&examples_path, |p| p.is_file()).unwrap_or_default(); + for file in examples_files { + let mtime = paths::mtime(&file)?; + assert!(mtimes.insert(file, mtime).is_none()); } // run `cargo build` @@ -108,19 +137,29 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { let time = std::time::Instant::now(); // check for stale files after the compilation - let deps_files = util::scan_path(&deps_path, |path| path.is_file())?; + let deps_files = util::scan_path(&deps_path, |p| p.is_file())?; for file in &deps_files { let new_mtime = paths::mtime(&file)?; - match mtimes.get(file) { - Some(cache_mtime) => { - if new_mtime > *cache_mtime { - stale_files.push(file); - } + if let Some(cache_mtime) = mtimes.get(file) { + if new_mtime > *cache_mtime { + stale_files.push(file); } - None => { - mtimes.insert(file.clone(), new_mtime); + } else { + mtimes.insert(file.clone(), new_mtime); + stale_files.push(file); + } + } + + let examples_files = util::scan_path(&examples_path, |p| p.is_file()).unwrap_or_default(); + for file in &examples_files { + let new_mtime = paths::mtime(&file)?; + if let Some(cache_mtime) = mtimes.get(file) { + if new_mtime > *cache_mtime { stale_files.push(file); } + } else { + mtimes.insert(file.clone(), new_mtime); + stale_files.push(file); } } @@ -137,7 +176,7 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { // name of stale crates let stale_crate_names = stale_files .iter() - .filter(|file| crate_names.contains(&crate_name(file))) + .filter(|p| crate_names.contains(&crate_name(p))) .map(crate_name) .collect::>(); debug!("stale_crate_names: {:#?}", stale_crate_names); @@ -145,15 +184,17 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { // *.rcgu.ll are intermediate files generated by `rustc -C save-temps` let ll_files = deps_files .iter() - .filter(|path| { - util::file_stem_unwrapped(path).contains("rcgu") - && util::extension_unwrapped(path) == "ll" + .chain(examples_files.iter()) + .filter(|p| { + util::file_stem(p).contains("rcgu") + && !util::file_stem(p).contains("-ci") + && util::extension(p) == "ll" }) .collect::>(); - // parsing cargo build output to get the linker invocation + // parse cargo build output to get the linker invocation let iter = cargo_build.iter(); - let mut linkers: Vec<(Vec, String)> = Vec::new(); + let mut linkers = Vec::new(); 'outer: for info in iter { if !info.contains("libcompiler_builtins") { // ignore as this is not a linker invocation @@ -167,29 +208,38 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { .map(str::to_string) .collect::>(); - let mut iter = linker.iter(); + let mut iter = linker.into_iter(); + let program = iter.next().context("expected linker program name")?; + let args = iter.clone().collect::>(); let mut bin_path = String::new(); while let Some(arg) = iter.next() { if arg.contains("-o") { - bin_path = iter.next().context("expected path to binary")?.to_string(); + bin_path = iter.next().context("expected path to binary")?; let crate_name = crate_name(&bin_path); if !stale_crate_names.contains(&crate_name) { // redundant linker as the binary is still fresh - debug!("skipping linker invocation for binary \"{}\"", crate_name); + debug!("skipped linker invocation for binary \"{}\"", crate_name); continue 'outer; } } } - linkers.push((linker, bin_path)); + if bin_path.is_empty() { + debug!("bin_path is empty while parsing the linker, skipped"); + continue; + } + + linkers.push(Linker { + program, + args, + bin_path, + }); } let term_size = term_size::dimensions().unwrap_or((80, 24)); debug!("term_size: {:?}", term_size); - let (tx, rx) = mpsc::channel::(); - // progress bar let len = ll_files.len() * 2 + linkers.len() + 1; let pb = if opts.verbose == 0 { @@ -208,307 +258,343 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { ); pb.set_prefix("Building"); - // handle progress bar rendering - let progress_bar_thread = std::thread::spawn(move || { - let mut names = Vec::new(); - let mut error = false; + let opts = &opts; + let config = &config; + let crate_names = &crate_names; + let deps_path = &deps_path; - while let Ok(integration) = rx.recv() { - if error { - continue; - } + let num_cpus = num_cpus::get(); - let name = integration.crate_name; - let status_line = - |status: &str| -> String { format!("{:>12} {}", status.green().bold(), name) }; - let mut remove = |name| { - let idx = names - .iter() - .position(|e| *e == name) - .expect("failed to find crate name"); - names.remove(idx); - }; + let ll_iter = Arc::new(Mutex::new(ll_files.iter())); + let lk_iter = Arc::new(Mutex::new(linkers.iter_mut())); - match integration.state { - State::Opt(finished) => { - if finished { - remove(name); - } else { - pb.println(status_line("Integrating")); - pb.inc(1); - names.insert(0, name); - } + thread::scope(move |s| -> CIResult<()> { + let (tx, rx) = mpsc::channel::(); + + // handle progress bar rendering + let pb_thread = s.spawn(move |_| { + let mut names: Vec = Vec::new(); + let mut error = false; + + while let Ok(integration) = rx.recv() { + if error { + continue; } - State::Llc(finished) => { - let name = format!("{}(llc)", name); - if finished { - remove(name); - } else { - pb.inc(1); - names.insert(0, name); + + let name = integration.crate_name; + let status_line = + |status: &str| -> String { format!("{:>12} {}", status.green().bold(), name) }; + let mut remove = |name: &String| { + let idx = names + .iter() + .position(|e| e == name) + .expect("failed to find crate name"); + names.remove(idx); + }; + + match integration.state { + State::Opt(finished) => { + if finished { + remove(&name); + } else { + pb.println(status_line("Integrating")); + pb.inc(1); + names.insert(0, name.to_string()); + } } - } - State::Ld(finished) => { - let name = format!("{}(bin)", name); - if finished { - remove(name); - } else { - pb.println(status_line("Linking")); + State::Llc(finished) => { + let llc_name = format!("{}(llc)", name); + if finished { + remove(&llc_name); + } else { + pb.inc(1); + names.insert(0, llc_name); + } + } + State::Ld(finished) => { + let ld_name = format!("{}(bin)", name); + if finished { + remove(&ld_name); + } else { + pb.println(status_line("Linking")); + pb.inc(1); + names.insert(0, ld_name); + } + } + State::Skipped => { + // redundant to print `compiler_interrupts` status as it is always skipped + if *name != "compiler_interrupts" { + pb.println(status_line("Skipped")); + } pb.inc(1); - names.insert(0, name); } - } - State::Skipped => { - // redundant to print `compiler_interrupts` status as it is always skipped - if name != "compiler_interrupts" { - pb.println(status_line("Skipped")); + State::Error(msg) => { + pb.set_style( + ProgressStyle::default_bar().template("{prefix:>12.red.bold} {msg}"), + ); + pb.set_prefix("Error"); + pb.finish_with_message( + "Compiler Interrupts integration has unexpectedly failed", + ); + println!("{:>12} {}", "Warning".yellow().bold(), msg); + + // we must not prematurely close the channel + // channel must live until all threads are done sending signals + error = true; + continue; } - pb.inc(1); - } - State::Error => { - pb.set_style( - ProgressStyle::default_bar().template("{prefix:>12.red.bold} {msg}"), - ); - pb.set_prefix("Error"); - pb.finish_with_message( - "Compiler Interrupts integration has unexpectedly failed", - ); - println!( - "{:>12} Waiting for other jobs to finish...", - "Note".cyan().bold(), - ); - - // we must not prematurely close the channel - // channel must live until all threads are done sending signals - error = true; - continue; } - } - // message - let term_size = term_size::dimensions().unwrap_or((80, 24)); - let prefix_size = if term_size.0 > 80 { 50 } else { 20 }; - let mut msg = String::new(); - let mut iter = names.iter(); - let first = match iter.next() { - Some(first) => first, - None => "", - }; - msg.push_str(first); - for name in iter { - msg.push_str(", "); - // truncate the message if too wide - if msg.len() + name.len() < term_size.0 - prefix_size - /* padding */ 15 { - msg.push_str(name); - } else { - msg.push_str("..."); - break; + // processing crates message + let term_size = term_size::dimensions().unwrap_or((80, 24)); + let prefix_size = if term_size.0 > 80 { 50 } else { 20 }; + let mut msg = String::new(); + let mut iter = names.iter(); + let first = match iter.next() { + Some(first) => first, + None => "", + }; + msg.push_str(first); + for name in iter { + msg.push_str(", "); + // truncate the message if too wide + if msg.len() + name.len() < term_size.0 - prefix_size - /* padding */ 15 { + msg.push_str(name); + } else { + msg.push_str("..."); + break; + } } + pb.set_message(msg); } - pb.set_message(msg); - } - if !error { - pb.finish_and_clear(); - } - }); - - // run integration - ll_files - .par_iter() - .try_for_each_with(tx.clone(), |tx, file| -> CIResult<()> { - let mut integrate = true; - let crate_name = crate_name(&file); - let ci_file = util::append_suffix(r_depsci(&file), "ci"); - - // `nm -jU` displays defined symbol names - let output = ProcessBuilder::new(nm) - .arg("-jU") - .arg(file.with_extension("o")) - .exec_with_output()?; - let stdout = String::from_utf8(output.stdout)?; - if stdout.contains("intvActionHook") { - // skip the `compiler-interrupts` crate - integrate = false; + if !error { + pb.inc(1); + pb.finish_and_clear(); } - if let Some(skip_crates) = &opts.skip_crates { - for skip_crate in skip_crates { - if skip_crate.replace("-", "_").contains(&crate_name) { - // skip the given crates - integrate = false; + }); + + // integration + let mut threads = Vec::new(); + for _ in 0..num_cpus { + let tx = tx.clone(); + let iter = Arc::clone(&ll_iter); + let thread = s.spawn(move |_| -> CIResult<()> { + loop { + let file = iter.lock().expect("failed to acquire lock").next(); + if let Some(file) = file { + let mut integrate = true; + let crate_name = Arc::new(crate_name(&file)); + let ci_file = util::append_suffix(&file, "ci"); + + // `nm -jU` displays defined symbol names + let output = ProcessBuilder::new(nm) + .arg("-jU") + .arg(file.with_extension("o")) + .exec_with_output()?; + let stdout = String::from_utf8(output.stdout)?; + if stdout.contains("intvActionHook") { + // skip the `compiler-interrupts` crate + integrate = false; + } + if let Some(skip_crates) = &opts.skip_crates { + for skip_crate in skip_crates { + if skip_crate.replace("-", "_").contains(&*crate_name) { + // skip the given crates + integrate = false; + break; + } + } + } + + if integrate { + info!("integrating: {}", file.display()); + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Opt(false), + })?; + + // define `LocalLC` if it is a binary target + let def_clock = if crate_names.contains(&crate_name.to_string()) { + "-defclock=1" + } else { + "-defclock=0" + }; + + // `opt` runs the integration + let output = ProcessBuilder::new(opt) + .args(&[ + "-S", + "-load", + &config.library_path, + "-logicalclock", + def_clock, + ]) + .args(&config.default_args) + .arg(&file) + .arg("-o") + .arg(&ci_file) + .exec_with_output(); + handle_output(output, &ci_file, &tx, opts.debug_ci)?; + + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Opt(true), + })?; + } else { + info!("integration skipped: {}", file.display()); + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Skipped, + })?; + paths::copy(&file, &ci_file)?; + } + + // `llc` transforms integrated IR bitcode to object file + debug!("run llc on: {}", ci_file.display()); + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Llc(false), + })?; + + let mut llc = ProcessBuilder::new(llc); + llc.arg("-filetype=obj"); + llc.arg(&ci_file); + + // `-code-model=large` fixes mismatch relocation symbols on Linux + if std::env::consts::OS == "linux" { + llc.arg("-code-model=large"); + } + + let output = llc.exec_with_output(); + handle_output(output, &ci_file, &tx, opts.debug_ci)?; + + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Llc(true), + })?; + } else { break; } } - } - if integrate { - info!("integrating: {}", file.display()); - tx.send(Integration { - crate_name: crate_name.clone(), - state: State::Opt(false), - })?; - - // define `LocalLC` if it is a binary target - let def_clock = match crate_names.contains(&crate_name) { - true => "-defclock=1", - false => "-defclock=0", - }; - - // `opt` runs the integration - let output = ProcessBuilder::new(opt) - .args(&[ - "-S", - "-load", - &config.library_path, - "-logicalclock", - def_clock, - ]) - .args(&config.default_args) - .arg(&file) - .arg("-o") - .arg(&ci_file) - .exec_with_output(); - handle_output(output, &ci_file, tx, opts.debug_ci)?; - - tx.send(Integration { - crate_name: crate_name.clone(), - state: State::Opt(true), - })?; - } else { - info!("integration skipped: {}", file.display()); - tx.send(Integration { - crate_name: crate_name.clone(), - state: State::Skipped, - })?; - paths::copy(&file, &ci_file)?; - } - - // `llc` transforms integrated IR bitcode to object file - debug!("run llc on: {}", ci_file.display()); - tx.send(Integration { - crate_name: crate_name.clone(), - state: State::Llc(false), - })?; - - let mut llc = ProcessBuilder::new(llc); - llc.arg("-filetype=obj"); - llc.arg(&ci_file); - - // `-code-model=large` fixes mismatch relocation symbols on Linux - if std::env::consts::OS == "linux" { - llc.arg("-code-model=large"); - } - - let output = llc.exec_with_output(); - handle_output(output, &ci_file, tx, opts.debug_ci)?; - - tx.send(Integration { - crate_name, - state: State::Llc(true), - })?; - - Ok(()) - }) - .context("integration thread failed")?; - - // run linker - linkers - .par_iter_mut() - .try_for_each_with(tx, |tx, linker| -> CIResult<()> { - let crate_name = crate_name(&linker.1); - info!("linking: {}", crate_name); - tx.send(Integration { - crate_name: crate_name.clone(), - state: State::Ld(false), - })?; - let object_files = linker.0.iter_mut().filter(|e| e.contains(".o")); - for file in object_files { - // find the object file contains the symbol for memory allocator - let output = ProcessBuilder::new(nm) - .arg("-jU") - .arg(&file) - .exec_with_output()?; - let stdout = String::from_utf8(output.stdout)?; - if stdout.contains("__rust_alloc") { - debug!("found allocator shim: {}", file); - paths::copy(&file, r_depsci(&file))?; - } else { - *file = util::append_suffix(&file, "ci").display().to_string(); - } - } + Ok(()) + }); + threads.push(thread); + } + for thread in threads { + thread + .join() + .expect("integration thread panicked") + .context("integration failed")?; + } - let deps_rlib_files = linker - .0 - .iter_mut() - .filter(|e| e.contains("deps") && e.contains(".rlib")); - for file in deps_rlib_files { - debug!("replacing object file for rlib: {}", file); - - // list all object files inside rlib - let output = ProcessBuilder::new(ar) - .arg("-t") - .arg(&file) - .exec_with_output()?; - let stdout = String::from_utf8(output.stdout)?; - if let Some(rcgu_obj_file_name) = stdout.lines().find(|e| e.contains("rcgu")) { - debug!("found obj file: {}", rcgu_obj_file_name); - let rcgu_obj_file = deps_path.join(rcgu_obj_file_name); - let rcgu_obj_ci_file = util::append_suffix(r_depsci(&rcgu_obj_file), "ci"); - - // copy the rlib file to the "deps-ci" dir - let ci_file = r_depsci(&file); - paths::copy(&file, &ci_file)?; - - // replace *.o with *-ci.o - ProcessBuilder::new(ar) - .arg("-rb") - .arg(&rcgu_obj_file) - .arg(&ci_file) - .arg(&rcgu_obj_ci_file) - .exec_with_output()?; - - // delete old *.o - ProcessBuilder::new(ar) - .arg("-d") - .arg(&ci_file) - .arg(&rcgu_obj_file) - .exec_with_output()?; + // linking + let mut threads = Vec::new(); + for _ in 0..num_cpus { + let tx = tx.clone(); + let iter = Arc::clone(&lk_iter); + let thread = s.spawn(move |_| -> CIResult<()> { + loop { + let linker = iter.lock().expect("mutex failed").next(); + if let Some(linker) = linker { + let crate_name = Arc::new(crate_name(&linker.bin_path)); + info!("linking: {}", crate_name); + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Ld(false), + })?; + let object_files = linker.args.iter_mut().filter(|e| e.contains(".o")); + for file in object_files { + // find the object file contains the symbol for memory allocator + let output = ProcessBuilder::new(nm) + .arg("-jU") + .arg(&file) + .exec_with_output()?; + let stdout = String::from_utf8(output.stdout)?; + if stdout.contains("__rust_alloc") { + debug!("found allocator shim: {}", file); + } else { + *file = util::append_suffix(&file, "ci").display().to_string(); + } + } + let deps_rlib_files = linker + .args + .iter() + .filter(|e| e.contains("deps") && e.contains(".rlib")); + for file in deps_rlib_files { + debug!("replacing object file for rlib: {}", file); + // list all object files inside rlib + let output = ProcessBuilder::new(ar) + .arg("-t") + .arg(&file) + .exec_with_output()?; + let stdout = String::from_utf8(output.stdout)?; + if let Some(rcgu_obj_file_name) = stdout + .lines() + .find(|e| e.contains("rcgu") && !e.contains("-ci")) + { + debug!("found obj file: {}", rcgu_obj_file_name); + let rcgu_obj_file = deps_path.join(rcgu_obj_file_name); + let rcgu_obj_ci_file = util::append_suffix(&rcgu_obj_file, "ci"); + + // replace *.o with *-ci.o + ProcessBuilder::new(ar) + .arg("-rb") + .arg(&rcgu_obj_file) + .arg(&file) + .arg(&rcgu_obj_ci_file) + .exec_with_output()?; + + // delete old *.o + ProcessBuilder::new(ar) + .arg("-d") + .arg(&file) + .arg(&rcgu_obj_file) + .exec_with_output()?; + } + } + + // execute the linker + debug!("linker: {:#?}", linker); + let mut builder = ProcessBuilder::new(&linker.program); + builder.args(&linker.args); + let output = builder.exec_with_output(); + debug!("linker output: {:?}", output); + handle_output(output, &linker.bin_path, &tx, opts.debug_ci)?; + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Ld(true), + })?; + } else { + break; + } } - } - - for arg in linker.0.iter_mut() { - // replace all "deps" with "deps-ci" - *arg = arg.replace("deps", "deps-ci"); - } + Ok(()) + }); + threads.push(thread); + } + for thread in threads { + thread + .join() + .expect("linker thread panicked") + .context("linker failed")?; + } - // execute the linker - debug!("linker: {:#?}", linker); - let mut iter = linker.0.iter(); - let mut linker_builder = - ProcessBuilder::new(iter.next().context("expected linker binary name")?); - for arg in iter { - linker_builder.arg(arg); - } - let output = linker_builder.exec_with_output(); - debug!("linker output: {:?}", output); - handle_output(output, &linker.1, tx, opts.debug_ci)?; + drop(tx); - tx.send(Integration { - crate_name, - state: State::Ld(true), - })?; + pb_thread.join().expect("progress bar panicked"); - Ok(()) - }) - .context("linker thread failed")?; + Ok(()) + }) + .expect("thread panicked")?; // copy CI-integrated binary file to the parent directory - let deps_ci_files = util::scan_path(&deps_ci_path, |path| path.is_file())?; - let binary_files = deps_ci_files - .iter() - .filter(|path| path.executable() && path.is_file()) - .collect::>(); - for file in binary_files { + let binary_deps_files = + util::scan_path(&deps_path, |p| p.executable() && p.is_file()).unwrap_or_default(); + let binary_examples_files = + util::scan_path(&examples_path, |p| p.executable() && p.is_file()).unwrap_or_default(); + for file in binary_deps_files.iter().chain(binary_examples_files.iter()) { let file_name = crate_name(&file).replace("_", "-"); let parent = file.parent().context("failed to get parent dir")?; let path = parent.with_file_name(file_name); @@ -516,10 +602,6 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { paths::copy(file, path)?; } - progress_bar_thread - .join() - .expect("progress bar thread failed"); - println!( "{:>12} integrated {} target(s) in {}", "Finished".green().bold(), @@ -534,23 +616,27 @@ pub fn exec(config: Config, opts: BuildOpts) -> CIResult<()> { fn handle_output>( output: CIResult, output_file: P, - tx: &mut mpsc::Sender, + tx: &mpsc::Sender, debug: bool, ) -> CIResult<()> { let output_file = output_file.as_ref(); - let crate_name = crate_name(&output_file); + let crate_name = Arc::new(crate_name(&output_file)); match output { Ok(output) => { if !output_file.is_file() { // output file does not exist - tx.send(Integration { - crate_name, - state: State::Error, - })?; let stderr = String::from_utf8(output.stderr)?; + let msg = "Process returned success but output file does not exist".to_string(); + + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Error(msg), + })?; + bail!( "process returned success but output file does not exist\n\ - expected file: {}\nprocess output:\n{}", + expected file: {}\n\ + --- stderr\n{}", output_file.display(), stderr ); @@ -558,45 +644,42 @@ fn handle_output>( Ok(()) } Err(err) => { - tx.send(Integration { - crate_name, - state: State::Error, - })?; let proc_err = err .downcast_ref::() - .context("process didn't execute by exec_with_output")?; - let mut out: Vec<_>; - if debug { - out = proc_err.desc.lines().take(1).collect::>(); - let desc = proc_err.desc.clone(); - + .context("process was not executed by `exec_with_output`")?; + let mut out = proc_err.desc.lines().take(2).collect::>(); + // take last few output lines to not polluting the terminal + let mut desc = proc_err.desc.lines().rev().take(10).collect::>(); + desc.reverse(); + out.append(&mut desc); + let msg = if debug { // set log file name + let desc = &proc_err.desc; let digest = md5::compute(desc.as_bytes()); let date = chrono::Local::now().format("%y%m%dT%H%M%S").to_string(); let mut path = util::config_path()?; path.push(format!("CI-{}-{:x}.log", date, digest)); - // output to the log file + // log the entire output paths::write(&path, desc)?; - let path_str = path - .into_os_string() - .into_string() - .expect("path is not valid utf-8"); - println!( + + format!( "Consider filing an issue report on \ \"https://github.com/bitslab/CompilerInterrupts\" \ - with the LLVM IR file and log attached" - ); - println!("Path to the log: {}\n", path_str); + with the LLVM IR file and log attached. \ + Path to the log: {}", + path.display(), + ) } else { - out = proc_err.desc.lines().take(2).collect::>(); - // take last few output lines to not polluting the terminal - let mut desc = proc_err.desc.lines().rev().take(10).collect::>(); - desc.reverse(); - out.append(&mut desc); - println!("Run `cargo-build-ci` with `--debug-ci` to enable full logging\n"); - } - bail!("process didn't exit successfully: {}", out.join("\n")); + "Run `cargo-build-ci` with `--debug-ci` to enable full logging".to_string() + }; + + tx.send(IntegrationCx { + crate_name: Arc::clone(&crate_name), + state: State::Error(msg), + })?; + + bail!(out.join("\n")); } } } @@ -608,6 +691,11 @@ fn cargo_build(opts: &BuildOpts) -> CIResult> { let mut cmd = ProcessBuilder::new("cargo"); cmd.arg("build"); + if let Some(example) = &opts.example { + cmd.arg("--example"); + cmd.arg(example); + } + // release mode if opts.release { cmd.arg("--release"); @@ -649,45 +737,25 @@ fn cargo_build(opts: &BuildOpts) -> CIResult> { ]; cmd.env("RUSTFLAGS", rustflags.join(" ")); - debug!("opts: {:#?}", cmd.get_args()); - debug!("envs: {:#?}", cmd.get_envs()); - - let mut cmd = cmd.build_command(); - cmd.stderr(Stdio::piped()); - - let mut child = cmd.spawn().with_context(|| { - ProcessError::new("could not execute process `cargo build`", None, None) - })?; - - let stderr = child - .stderr - .take() - .context("failed to get stderr from `cargo build`")?; - let reader = BufReader::new(stderr); - let mut link_info = Vec::new(); - for line in reader.lines().filter_map(|x| x.ok()) { - // sample of the expected linker output - // INFO rustc_codegen_ssa::back::link preparing Executable to "/path/to/binary" - // INFO rustc_codegen_ssa::back::link "cc" "-m64 "-arch" "x86_64" ... - if line.contains("INFO rustc_codegen_ssa::back::link") { - link_info.push(line); - } else if !line.is_empty() { - println!("{}", line); - } - } + cmd.exec_with_streaming( + &mut |out| { + println!("{}", out); + Ok(()) + }, + &mut |err| { + if err.contains("INFO rustc_codegen_ssa::back::link") { + link_info.push(err.to_string()); + } else if !err.is_empty() { + eprintln!("{}", err); + } + Ok(()) + }, + false, + ) + .context("Failed to execute `cargo build`")?; - let exit = child.wait()?; - if exit.success() { - Ok(link_info) - } else { - Err(ProcessError::new( - "process didn't exit successfully: `cargo build`", - Some(exit), - None, - ) - .into()) - } + Ok(link_info) } /// Run `cargo metadata`. @@ -695,13 +763,13 @@ fn cargo_metadata() -> CIResult { info!("running cargo metadata"); let mut cmd = MetadataCommand::new(); cmd.no_deps(); - let metadata = cmd.exec().context("failed to execute `cargo metadata`")?; + let metadata = cmd.exec().context("Failed to execute `cargo metadata`")?; Ok(metadata) } /// Get the binary name from path. fn crate_name>(path: P) -> String { - util::file_stem_unwrapped(path) + util::file_stem(path) .split('.') .next() .expect("invalid crate name, expected '.'") @@ -710,14 +778,3 @@ fn crate_name>(path: P) -> String { .expect("invalid crate name, expected '-'") .to_string() } - -/// Replace "deps" with "deps-ci" for the path. -fn r_depsci>(path: P) -> PathBuf { - let path = path.as_ref(); - let file_name = util::file_name_unwrapped(path); - let mut path = PathBuf::from(path); - path.pop(); - path.set_file_name("deps-ci"); - path.push(file_name); - path -} diff --git a/src/ops/library.rs b/src/ops/library.rs index 91208e4..c44c855 100644 --- a/src/ops/library.rs +++ b/src/ops/library.rs @@ -1,4 +1,4 @@ -//! Implementation of `cargo lib-ci` subcommand. +//! Implementation of `cargo-lib-ci`. use std::io::Read; use std::path::{Path, PathBuf}; @@ -54,30 +54,29 @@ fn install(mut config: Config, opts: LibraryOpts, update: bool) -> CIResult<()> .template("{spinner:.dim.bold} {prefix:>10.cyan.bold} {wide_msg}"); pb.enable_steady_tick(200); pb.set_style(ps); - if !update { - pb.set_prefix("Installing"); - } else { + if update { pb.set_prefix("Updating"); + } else { + pb.set_prefix("Installing"); } - if !update { - pb.set_message("Fetching the source code"); - } else { + if update { pb.set_message("Checking for update"); + } else { + pb.set_message("Fetching the source code"); } // fetch the source code let mut src_path = std::env::temp_dir(); src_path.push("CompilerInterrupt.cpp"); - let src_path = src_path.to_str().expect("path is not valid utf-8"); - info!("src_path: {}", src_path); + info!("src_path: {}", src_path.display()); - let url = if !update { + let url = if update { + config.url.clone() + } else { let default_url = "https://raw.githubusercontent.com/bitslab/\ CompilerInterrupts/main/src/CompilerInterrupt.cpp"; opts.url.unwrap_or_else(|| default_url.to_string()) - } else { - config.url.clone() }; let resp = ureq::get(&url).call()?; let len = resp @@ -144,8 +143,6 @@ fn install(mut config: Config, opts: LibraryOpts, update: bool) -> CIResult<()> _ => "", }; - pb.set_message("Compiling the Compiler Interrupts library"); - info!("getting the destination library path"); let file_name = format!("CompilerInterrupt-{:x}.so", digest); let lib_path = { @@ -169,19 +166,16 @@ fn install(mut config: Config, opts: LibraryOpts, update: bool) -> CIResult<()> path } }; - let lib_path_dbg = util::append_suffix(&lib_path, "dbg") - .into_os_string() - .into_string() - .expect("path is not valid utf-8"); - let lib_path = lib_path - .into_os_string() - .into_string() - .expect("path is not valid utf-8"); - info!("lib_path: {}", lib_path); + info!("lib_path: {}", lib_path.display()); + + let lib_path = util::path_to_string(&lib_path); + let lib_path_dbg = util::path_to_string(util::append_suffix(&lib_path, "dbg")); + + pb.set_message("Compiling the Compiler Interrupts library"); // compile - info!("preparing to compile"); let mut clang = ProcessBuilder::new(clang); + clang.arg("-fdiagnostics-color=always"); clang.arg(src_path); clang.arg(llvm_version_macro); clang.args(&so_flags.split_ascii_whitespace().collect::>()); @@ -191,17 +185,41 @@ fn install(mut config: Config, opts: LibraryOpts, update: bool) -> CIResult<()> debug!("clang_base_args: {:?}", clang.get_args()); let mut clang_dbg = clang.clone(); - clang_dbg.arg("-DDBG_DETAILED"); - clang_dbg.args(&["-o".to_string(), lib_path_dbg.to_string()]); info!("compiling the library"); - clang.args(&["-o".to_string(), lib_path.to_string()]); - clang.exec().context("failed to compile the library")?; + clang.args(&["-o".to_string(), lib_path.clone()]); + clang + .exec_with_streaming( + &mut |out| { + pb.println(out); + Ok(()) + }, + &mut |err| { + pb.println(err); + Ok(()) + }, + false, + ) + .context("Failed to compile the library")?; info!("compiling the library with debugging mode"); + pb.set_message("Compiling the Compiler Interrupts library with debugging mode"); + + clang_dbg.arg("-DDBG_DETAILED"); + clang_dbg.args(&["-o".to_string(), lib_path_dbg.clone()]); clang_dbg - .exec() - .context("failed to compile the library with debugging mode")?; + .exec_with_streaming( + &mut |out| { + pb.println(out); + Ok(()) + }, + &mut |err| { + pb.println(err); + Ok(()) + }, + false, + ) + .context("Failed to compile the library with debugging mode")?; // update config info!("updating configuration"); @@ -210,7 +228,7 @@ fn install(mut config: Config, opts: LibraryOpts, update: bool) -> CIResult<()> .map(|&s| s.to_string()) .collect(); - config.library_path = lib_path.to_string(); + config.library_path = lib_path.clone(); config.library_path_dbg = lib_path_dbg; config.llvm_version = llvm_version; config.checksum = checksum; @@ -221,7 +239,7 @@ fn install(mut config: Config, opts: LibraryOpts, update: bool) -> CIResult<()> if let Err(e) = Config::save(&config).context("failed to save the configuration") { // try to remove the library - paths::remove_file(lib_path)?; + paths::remove_file(&lib_path)?; return Err(e); } @@ -261,6 +279,10 @@ fn uninstall(config: Config) -> CIResult<()> { /// Sets the default arguments for the library. fn set_default_args(mut config: Config, default_args: Vec) -> CIResult<()> { + if !Path::new(&config.library_path).is_file() { + bail!(CIError::LibraryNotInstalled) + } + config.default_args = default_args; Config::save(&config).context("failed to save the configuration")?; diff --git a/src/ops/run.rs b/src/ops/run.rs index 1b52a5b..df835e9 100644 --- a/src/ops/run.rs +++ b/src/ops/run.rs @@ -1,4 +1,4 @@ -//! Implementation of `cargo run-ci` subcommand. +//! Implementation of `cargo-run-ci`. use anyhow::bail; use cargo_util::ProcessBuilder; @@ -12,7 +12,7 @@ use crate::{util, CIResult}; pub fn exec(opts: RunOpts) -> CIResult<()> { let deps_path = util::target_path(&opts.target, &opts.release)?; let binary_paths = util::scan_path(&deps_path, |path| { - path.executable() && path.is_file() && util::file_stem_unwrapped(path).contains("-ci") + path.executable() && path.is_file() && util::file_stem(path).contains("-ci") })?; if binary_paths.is_empty() { bail!(CIError::BinaryNotFound); @@ -20,7 +20,7 @@ pub fn exec(opts: RunOpts) -> CIResult<()> { let binary_names = binary_paths .iter() - .map(util::file_stem_unwrapped) + .map(util::file_stem) .map(|mut path| { remove_ci(&mut path); path @@ -30,16 +30,20 @@ pub fn exec(opts: RunOpts) -> CIResult<()> { if let Some(binary_name) = opts.bin { for path in binary_paths { - let mut name = util::file_name_unwrapped(&path); + let mut name = util::file_name(&path); remove_ci(&mut name); if binary_name == name { - return ProcessBuilder::new(&path).exec_replace(); + return ProcessBuilder::new(&path) + .args(&opts.args.unwrap_or_default()) + .exec_replace(); } } bail!(CIError::BinaryNotAvailable(binary_name, binary_names)); } else if binary_paths.len() == 1 { - return ProcessBuilder::new(&binary_paths[0]).exec_replace(); + return ProcessBuilder::new(&binary_paths[0]) + .args(&opts.args.unwrap_or_default()) + .exec_replace(); } bail!(CIError::BinaryNotDetermine(binary_names)); diff --git a/src/opts.rs b/src/opts.rs index c03a534..6d0316e 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -19,6 +19,10 @@ pub struct BuildOpts { #[clap(short, long, value_name = "TRIPLE")] pub target: Option, + /// Build an example artifact + #[clap(short, long, value_name = "BINARY")] + pub example: Option, + /// Crates to skip the integration (space-delimited) #[clap( short, @@ -45,6 +49,7 @@ pub struct BuildOpts { version = clap::crate_version!(), author = clap::crate_authors!(), about = "Run a Compiler Interrupts-integrated binary", + setting = clap::AppSettings::TrailingVarArg, )] pub struct RunOpts { /// Run the binary in release mode @@ -59,6 +64,10 @@ pub struct RunOpts { #[clap(short, long, value_name = "BINARY")] pub bin: Option, + /// Arguments for the binary + #[clap(short, long, value_name = "ARGS")] + pub args: Option>, + /// Use verbose output (-vv very verbose output) #[clap(short, long, parse(from_occurrences))] pub verbose: i32, @@ -91,16 +100,17 @@ pub struct LibraryOpts { long, allow_hyphen_values = true, require_delimiter = true, - value_delimiter = " " + value_delimiter = " ", + value_name = "ARGS" )] pub args: Option>, - /// URL to the source code of the library when installing - #[clap(long, requires = "install")] + /// Remote URL to the source code of the library when installing + #[clap(long, value_name = "URL", requires = "install")] pub url: Option, /// Destination path for the library when installing - #[clap(short, long, requires = "install")] + #[clap(short, long, value_name = "PATH", requires = "install")] pub path: Option, /// Use verbose output (-vv very verbose output) diff --git a/src/util.rs b/src/util.rs index dbba7c1..db91eaf 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,10 +2,11 @@ use std::path::{Path, PathBuf}; -use anyhow::Context; +use anyhow::{bail, Context}; use cargo_util::{paths, ProcessBuilder}; use tracing::{debug, info}; +use crate::error::CIError::*; use crate::CIResult; /// Initializes the logger. @@ -26,10 +27,11 @@ pub fn init_logger(verbose: i32) { /// Gets the root directory of the package. fn package_root_dir() -> CIResult { - let output = ProcessBuilder::new("cargo") - .arg("locate-project") - .arg("--message-format=plain") - .exec_with_output()?; + let mut cmd = ProcessBuilder::new("cargo"); + cmd.arg("locate-project"); + cmd.arg("--message-format=plain"); + cmd.env("CARGO_TERM_COLOR", "always"); + let output = cmd.exec_with_output()?; let stdout = String::from_utf8(output.stdout)?; let path_toml = Path::new(&stdout); let root_dir = path_toml.parent().context("failed to get parent dir")?; @@ -102,17 +104,18 @@ pub fn scan_path>( /// Appends the suffix to the file stem of a path. pub fn append_suffix>(path: P, suffix: &str) -> PathBuf { let path = path.as_ref(); - let file_stem = file_stem_unwrapped(path); - let extension = extension(path); - let file_name = match extension { - Some(extension) => format!("{}-{}.{}", file_stem, suffix, extension), - None => format!("{}-{}", file_stem, suffix), + let file_stem = file_stem(path); + let extension = _extension(path); + let file_name = if let Some(extension) = extension { + format!("{}-{}.{}", file_stem, suffix, extension) + } else { + format!("{}-{}", file_stem, suffix) }; path.with_file_name(file_name) } -/// Gets the file stem of a path and unwrapped by default. -pub fn file_stem_unwrapped>(path: P) -> String { +/// Gets the file stem of a path. Empty string is returned if path was not valid UTF-8. +pub fn file_stem>(path: P) -> String { let path = path.as_ref(); path.file_stem() .map(std::ffi::OsStr::to_string_lossy) @@ -120,8 +123,8 @@ pub fn file_stem_unwrapped>(path: P) -> String { .unwrap_or_default() } -/// Gets the file name of a path and unwrapped by default. -pub fn file_name_unwrapped>(path: P) -> String { +/// Gets the file name of a path. Empty string is returned if path was not valid UTF-8. +pub fn file_name>(path: P) -> String { let path = path.as_ref(); path.file_name() .map(std::ffi::OsStr::to_string_lossy) @@ -129,24 +132,27 @@ pub fn file_name_unwrapped>(path: P) -> String { .unwrap_or_default() } +/// Get the file extension of a path. Empty string is returned if path was not valid UTF-8. +pub fn extension>(path: P) -> String { + _extension(path).unwrap_or_default() +} + /// Gets the file extension of a path. -fn extension>(path: P) -> Option { +fn _extension>(path: P) -> Option { let path = path.as_ref(); path.extension() .map(std::ffi::OsStr::to_string_lossy) .map(|e| e.to_string()) } -/// Get the file extension of a path and unwrapped by default. -pub fn extension_unwrapped>(path: P) -> String { - extension(path).unwrap_or_default() +/// Converts a path to string. Empty string is returned if path was not valid UTF-8. +pub fn path_to_string>(path: P) -> String { + let path = path.as_ref(); + path.to_str().unwrap_or_default().to_string() } /// Determines appropriate version for LLVM toolchain and its binaries. pub fn llvm_toolchain(binaries: &mut Vec) -> CIResult { - use crate::error::CIError::*; - use anyhow::bail; - let llvm_min_version_supported: i32 = 9; // get llvm version from rustc @@ -154,18 +160,17 @@ pub fn llvm_toolchain(binaries: &mut Vec) -> CIResult { let rustc_output = String::from_utf8(output.stdout)?; let rustc_ver = rustc_output .lines() - .filter_map(|line| line.strip_prefix("LLVM version: ")) - .next() + .find_map(|line| line.strip_prefix("LLVM version: ")) .context("`rustc -vV` should have the LLVM version field")? .trim() .to_string(); let major_ver = rustc_ver .split('.') .next() - .context("`rust version string is not valid")?; + .context("`rustc` llvm version is not valid")?; let major_ver_i32 = major_ver .parse::() - .context("`rust version string is not valid")?; + .context("`rustc` llvm version is not valid")?; // check if llvm version is supported if major_ver_i32 < llvm_min_version_supported { @@ -220,7 +225,7 @@ pub fn llvm_toolchain(binaries: &mut Vec) -> CIResult { *binary = if add_sf { format!("{}-{}", binary, major_ver) } else { - binary.to_string() + (*binary).to_string() } }