Skip to main content

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