cargo_limit/
lib.rs

1//! **Documentation is [here](https://github.com/cargo-limit/cargo-limit#readme).**
2
3#[doc(hidden)]
4pub mod env_vars;
5#[doc(hidden)]
6pub mod models;
7#[doc(hidden)]
8pub mod process;
9
10mod cargo_toml;
11mod io;
12mod messages;
13mod options;
14
15pub use process::NO_EXIT_CODE;
16
17use crate::models::{EditorData, Location};
18use anyhow::{Context, Result};
19use cargo_metadata::{Message, MetadataCommand};
20use io::Buffers;
21use messages::{Messages, transform_and_process_messages};
22use options::Options;
23use process::{CargoProcess, failed_to_execute_error_text};
24use std::{
25    io::Write,
26    path::Path,
27    process::{Command, Stdio},
28};
29
30const ADDITIONAL_ENVIRONMENT_VARIABLES: &str =
31    include_str!("../additional_environment_variables.txt");
32
33#[doc(hidden)]
34pub fn run_cargo_filtered(current_exe: String) -> Result<i32> {
35    let workspace_root = MetadataCommand::new()
36        .no_deps()
37        .exec()
38        .ok()
39        .map(|m| m.workspace_root);
40    let workspace_root = workspace_root.as_ref().map(|w| w.as_ref());
41    let options = Options::from_os_env(current_exe, workspace_root)?;
42
43    let mut cargo_process = CargoProcess::run(&options)?;
44    let mut buffers = cargo_process.buffers()?;
45
46    let process_messages = |buffers: &mut Buffers,
47                            messages: Vec<Message>,
48                            locations_in_consistent_order: Vec<Location>,
49                            workspace_root: &Path|
50     -> Result<()> {
51        let messages = messages.into_iter();
52        if options.json_message_format {
53            for message in messages {
54                buffers.writeln_to_stdout(&serde_json::to_string(&message)?)?;
55            }
56        } else {
57            for message in messages.filter_map(|message| match message {
58                Message::CompilerMessage(compiler_message) => compiler_message.message.rendered,
59                _ => None,
60            }) {
61                buffers.write_to_stderr(message)?;
62            }
63        }
64        open_affected_files_in_external_app(
65            buffers,
66            locations_in_consistent_order,
67            &options,
68            workspace_root,
69        )
70    };
71
72    let mut parsed_messages =
73        Messages::parse_with_timeout_on_error(&mut buffers, Some(&cargo_process), &options)?;
74
75    let exit_code = if parsed_messages.child_killed {
76        buffers.writeln_to_stdout("")?;
77        let exit_code = cargo_process.wait()?;
78        parsed_messages.merge(Messages::parse_with_timeout_on_error(
79            &mut buffers,
80            None,
81            &options,
82        )?);
83        transform_and_process_messages(
84            &mut buffers,
85            parsed_messages,
86            &options,
87            workspace_root,
88            process_messages,
89        )?;
90        buffers.copy_from_child_stdout_reader_to_stdout_writer()?;
91
92        exit_code
93    } else {
94        transform_and_process_messages(
95            &mut buffers,
96            parsed_messages,
97            &options,
98            workspace_root,
99            process_messages,
100        )?;
101        buffers.copy_from_child_stdout_reader_to_stdout_writer()?;
102        cargo_process.wait()?
103    };
104
105    if options.help {
106        buffers.writeln_to_stdout("")?;
107        buffers.write_to_stdout(ADDITIONAL_ENVIRONMENT_VARIABLES)?;
108    }
109
110    Ok(exit_code)
111}
112
113fn open_affected_files_in_external_app(
114    buffers: &mut Buffers,
115    locations_in_consistent_order: Vec<Location>,
116    options: &Options,
117    workspace_root: &Path,
118) -> Result<()> {
119    let app = &options.open_in_external_app;
120    if !app.is_empty() {
121        let editor_data = EditorData::new(workspace_root, locations_in_consistent_order);
122        let mut child = Command::new(app).stdin(Stdio::piped()).spawn()?;
123        child
124            .stdin
125            .take()
126            .context("no stdin")?
127            .write_all(serde_json::to_string(&editor_data)?.as_bytes())?;
128
129        let error_text = failed_to_execute_error_text(app);
130        let output = child.wait_with_output().context(error_text)?;
131
132        buffers.write_all_to_stderr(&output.stdout)?;
133        buffers.write_all_to_stderr(&output.stderr)?;
134    }
135    Ok(())
136}
137
138#[doc(hidden)]
139#[macro_export]
140macro_rules! run_subcommand {
141    () => {
142        #[doc(hidden)]
143        fn main() -> anyhow::Result<()> {
144            use anyhow::Context;
145            let current_exe = std::env::current_exe()?
146                .file_stem()
147                .context("invalid executable")?
148                .to_string_lossy()
149                .to_string();
150            std::process::exit(cargo_limit::run_cargo_filtered(current_exe)?);
151        }
152    };
153}