Skip to main content

cargo_flux/
cargo-flux.rs

1use std::{
2    self, env,
3    io::{BufWriter, Write},
4    process::{Command, exit},
5};
6
7use anyhow::anyhow;
8use cargo_metadata::{Metadata, camino::Utf8Path};
9use clap::Parser as _;
10use flux_bin::{
11    FluxMetadata,
12    cargo_flux_opts::{CargoFluxCommand, Cli},
13    utils::{
14        EXIT_ERR, flux_sysroot_dir, get_binary_path, get_flux_driver_path, get_rust_toolchain,
15        print_version_and_exit,
16    },
17};
18use itertools::Itertools;
19use tempfile::NamedTempFile;
20
21fn main() {
22    let Cli::Flux { check_opts, command, version, verbose } = Cli::parse();
23
24    // Handle version flag (-V or --version with optional -v for verbose)
25    if version {
26        print_version_and_exit("cargo-flux", verbose > 0);
27    }
28
29    let command = command.unwrap_or(CargoFluxCommand::Check(check_opts));
30
31    match run(command) {
32        Ok(exit_code) => exit(exit_code),
33        Err(e) => {
34            println!("Failed to run `cargo-flux`, error={e}");
35            exit(EXIT_ERR)
36        }
37    };
38}
39
40fn run(cargo_flux_cmd: CargoFluxCommand) -> anyhow::Result<i32> {
41    let toolchain = get_rust_toolchain()?;
42    let cargo_path = get_binary_path(&toolchain, "cargo")?;
43
44    let metadata = cargo_flux_cmd.metadata().cargo_path(&cargo_path).exec()?;
45    let sysroot = flux_sysroot_dir();
46    let flux_driver_path = get_flux_driver_path(&sysroot)?;
47    let config_file = write_cargo_config(metadata, &sysroot, &cargo_flux_cmd)?;
48
49    let mut cargo_command = Command::new("cargo");
50
51    // We set `RUSTC` as an environment variable and not in in the [build]
52    // section of the config file to make sure we run flux even when the
53    // variable is already set. We also unset `RUSTC_WRAPPER` to avoid
54    // conflicts, e.g., see https://github.com/flux-rs/flux/issues/1155
55    cargo_command
56        .env("RUSTC", flux_driver_path)
57        .env("RUSTC_WRAPPER", "")
58        .arg(format!("+{toolchain}"));
59
60    cargo_flux_cmd.forward_args(&mut cargo_command, config_file.path());
61
62    Ok(cargo_command.status()?.code().unwrap_or(EXIT_ERR))
63}
64
65fn write_cargo_config(
66    metadata: Metadata,
67    sysroot: &std::path::Path,
68    cargo_flux_cmd: &CargoFluxCommand,
69) -> anyhow::Result<NamedTempFile> {
70    let flux_flags: Option<Vec<String>> = if let Ok(flags) = env::var("FLUXFLAGS") {
71        Some(flags.split(" ").map(Into::into).collect())
72    } else {
73        None
74    };
75
76    let flux_toml = config::Config::builder()
77        .add_source(config::File::with_name("flux.toml").required(false))
78        .build()?;
79
80    if flux_toml.get_bool("enabled").is_ok() {
81        return Err(anyhow!("`enabled` cannot be set in `flux.toml`"));
82    }
83    let targeted_package_ids = cargo_flux_cmd.targeted_package_ids(&metadata);
84    let cli_flags = cargo_flux_cmd.rustflags();
85
86    let mut file = NamedTempFile::new()?;
87    {
88        let mut w = BufWriter::new(&mut file);
89        write!(
90            w,
91            r#"
92[unstable]
93profile-rustflags = true
94
95[env]
96FLUX_BUILD_SYSROOT = "1"
97FLUX_CARGO = "1"
98
99[profile.flux]
100inherits = "dev"
101incremental = false
102        "#
103        )?;
104
105        for package in metadata.packages {
106            let flux_metadata: FluxMetadata = config::Config::builder()
107                .add_source(FluxMetadataSource::new(
108                    package.manifest_path.to_string(),
109                    package.metadata,
110                ))
111                .add_source(flux_toml.clone())
112                .build()?
113                .try_deserialize()?;
114
115            if flux_metadata.enabled {
116                // For workspace members, cargo sets the workspace's root as the working dir
117                // when running flux. Paths will be relative to that, so we must normalize
118                // glob patterns to be relative to the workspace's root.
119                let manifest_dir_relative_to_workspace = package
120                    .manifest_path
121                    .strip_prefix(&metadata.workspace_root)
122                    .ok()
123                    .and_then(Utf8Path::parent);
124
125                let sysroot_flag = format!("-Fsysroot={}", sysroot.display());
126                write!(
127                    w,
128                    r#"
129[profile.flux.package."{}"]
130rustflags = [{:?}]
131                        "#,
132                    package.id,
133                    flux_metadata
134                        .into_flags(
135                            &metadata.target_directory,
136                            manifest_dir_relative_to_workspace,
137                            cargo_flux_cmd
138                                .only_check()
139                                .filter(|_| targeted_package_ids.contains(&package.id))
140                        )
141                        .iter()
142                        .chain(flux_flags.iter().flatten())
143                        .chain(cli_flags.iter())
144                        .map(|s| s.as_str())
145                        .chain(["-Fverify=on", "-Ffull-compilation=on", sysroot_flag.as_str()])
146                        .format(", ")
147                )?;
148            }
149        }
150    }
151    Ok(file)
152}
153
154#[derive(Clone, Debug)]
155struct FluxMetadataSource {
156    origin: String,
157    value: serde_json::Value,
158}
159
160impl FluxMetadataSource {
161    fn new(origin: String, value: serde_json::Value) -> Self {
162        Self { origin, value }
163    }
164}
165
166impl config::Source for FluxMetadataSource {
167    fn clone_into_box(&self) -> Box<dyn config::Source + Send + Sync> {
168        Box::new(self.clone())
169    }
170
171    fn collect(&self) -> Result<config::Map<String, config::Value>, config::ConfigError> {
172        if let serde_json::Value::Object(metadata) = &self.value
173            && let Some(flux_metadata) = metadata.get("flux")
174        {
175            let config_value = serde_json_to_config(flux_metadata, &self.origin)?;
176            if let config::ValueKind::Table(table) = config_value.kind {
177                Ok(table)
178            } else {
179                Err(config::ConfigError::Message("expected a table".to_string()))
180            }
181        } else {
182            Ok(Default::default())
183        }
184    }
185}
186
187fn serde_json_to_config(
188    value: &serde_json::Value,
189    origin: &String,
190) -> Result<config::Value, config::ConfigError> {
191    let kind = match value {
192        serde_json::Value::Null => config::ValueKind::Nil,
193        serde_json::Value::Bool(b) => config::ValueKind::Boolean(*b),
194        serde_json::Value::Number(number) => {
195            if let Some(n) = number.as_u128() {
196                config::ValueKind::U128(n)
197            } else if let Some(n) = number.as_i128() {
198                config::ValueKind::I128(n)
199            } else if let Some(n) = number.as_u64() {
200                config::ValueKind::U64(n)
201            } else if let Some(n) = number.as_i64() {
202                config::ValueKind::I64(n)
203            } else if let Some(n) = number.as_f64() {
204                config::ValueKind::Float(n)
205            } else {
206                return Err(config::ConfigError::Message("invalid number".to_string()));
207            }
208        }
209        serde_json::Value::String(s) => config::ValueKind::String(s.clone()),
210        serde_json::Value::Array(values) => {
211            config::ValueKind::Array(
212                values
213                    .iter()
214                    .map(|v| serde_json_to_config(v, origin))
215                    .try_collect()?,
216            )
217        }
218        serde_json::Value::Object(map) => {
219            config::ValueKind::Table(
220                map.iter()
221                    .map(|(k, v)| Ok((k.clone(), serde_json_to_config(v, origin)?)))
222                    .try_collect()?,
223            )
224        }
225    };
226    Ok(config::Value::new(Some(origin), kind))
227}