cargo_flux/
cargo-flux.rs

1#![feature(let_chains)]
2
3use std::{
4    self, env,
5    io::{BufWriter, Write},
6    process::{Command, exit},
7};
8
9use anyhow::anyhow;
10use cargo_metadata::{Metadata, MetadataCommand};
11use flux_bin::{
12    FluxMetadata,
13    utils::{
14        EXIT_ERR, LIB_PATH, get_flux_driver_path, get_rust_toolchain, get_rustc_driver_lib_path,
15        prepend_path_to_env_var, sysroot_dir,
16    },
17};
18use itertools::Itertools;
19use tempfile::NamedTempFile;
20
21fn main() {
22    let exit_code = match run() {
23        Ok(code) => code,
24        Err(e) => {
25            println!("Failed to run `cargo-flux`, error={e}");
26            EXIT_ERR
27        }
28    };
29    exit(exit_code)
30}
31
32fn run() -> anyhow::Result<i32> {
33    let toolchain = get_rust_toolchain()?;
34
35    let metadata = MetadataCommand::new().exec()?;
36    let config_file = write_cargo_config(&toolchain, metadata)?;
37
38    // Cargo can be called like `cargo [OPTIONS] flux`, so we skip all arguments until `flux` is found.
39    let mut args = env::args()
40        .skip_while(|arg| arg != "flux")
41        .skip(1)
42        .collect::<Vec<_>>();
43
44    // Unless we are calling `cargo flux clean` add a `check`
45    match &args[..] {
46        [subcommand, ..] if subcommand == "clean" => {}
47        _ => {
48            args.insert(0, "check".to_string());
49        }
50    }
51    args.extend(["--profile".to_string(), "flux".to_string()]);
52
53    let exit_code = Command::new("cargo")
54        .arg(format!("+{toolchain}"))
55        .arg("-Zprofile-rustflags")
56        .arg("--config")
57        .arg(config_file.path())
58        .args(args)
59        .status()?
60        .code();
61
62    Ok(exit_code.unwrap_or(EXIT_ERR))
63}
64
65fn write_cargo_config(toolchain: &str, metadata: Metadata) -> anyhow::Result<NamedTempFile> {
66    let sysroot = sysroot_dir();
67    let flux_driver_path = get_flux_driver_path(&sysroot)?;
68    let ld_library_path = get_rustc_driver_lib_path(toolchain)?;
69    let extended_lib_path = prepend_path_to_env_var(LIB_PATH, ld_library_path)?;
70
71    let flux_flags: Option<Vec<String>> = if let Ok(flags) = env::var("FLUXFLAGS") {
72        Some(flags.split(" ").map(Into::into).collect())
73    } else {
74        None
75    };
76
77    let flux_toml = config::Config::builder()
78        .add_source(config::File::with_name("flux.toml").required(false))
79        .build()?;
80
81    if flux_toml.get_bool("enabled").is_ok() {
82        return Err(anyhow!("`enabled` cannot be set in `flux.toml`"));
83    }
84
85    let mut file = NamedTempFile::new()?;
86    {
87        let mut w = BufWriter::new(&mut file);
88        write!(
89            w,
90            r#"
91[build]
92rustc = "{flux_driver}"
93
94[env]
95LIB_PATH = "{lib_path}"
96FLUX_BUILD_SYSROOT = "1"
97FLUX_CARGO = "1"
98
99[profile.flux]
100inherits = "dev"
101incremental = false
102        "#,
103            flux_driver = flux_driver_path.display(),
104            lib_path = extended_lib_path.display(),
105        )?;
106
107        for package in metadata.packages {
108            let flux_metadata: FluxMetadata = config::Config::builder()
109                .add_source(FluxMetadataSource::new(package.manifest_path, package.metadata))
110                .add_source(flux_toml.clone())
111                .build()?
112                .try_deserialize()?;
113
114            if flux_metadata.enabled {
115                write!(
116                    w,
117                    r#"
118[profile.flux.package."{}"]
119rustflags = [{:?}]
120                        "#,
121                    package.id,
122                    flux_metadata
123                        .into_flags(&metadata.target_directory)
124                        .iter()
125                        .chain(flux_flags.iter().flatten())
126                        .map(|s| s.as_ref())
127                        .chain(["-Fverify=on", "-Ffull-compilation=on"])
128                        .format(", ")
129                )?;
130            }
131        }
132    }
133    Ok(file)
134}
135
136#[derive(Clone, Debug)]
137struct FluxMetadataSource {
138    origin: String,
139    value: serde_json::Value,
140}
141
142impl FluxMetadataSource {
143    fn new(origin: impl Into<String>, value: serde_json::Value) -> Self {
144        Self { origin: origin.into(), value }
145    }
146}
147
148impl config::Source for FluxMetadataSource {
149    fn clone_into_box(&self) -> Box<dyn config::Source + Send + Sync> {
150        Box::new(self.clone())
151    }
152
153    fn collect(&self) -> Result<config::Map<String, config::Value>, config::ConfigError> {
154        if let serde_json::Value::Object(metadata) = &self.value
155            && let Some(flux_metadata) = metadata.get("flux")
156        {
157            let config_value = serde_json_to_config(flux_metadata, &self.origin)?;
158            if let config::ValueKind::Table(table) = config_value.kind {
159                Ok(table)
160            } else {
161                Err(config::ConfigError::Message("expected a table".to_string()))
162            }
163        } else {
164            Ok(Default::default())
165        }
166    }
167}
168
169fn serde_json_to_config(
170    value: &serde_json::Value,
171    origin: &String,
172) -> Result<config::Value, config::ConfigError> {
173    let kind = match value {
174        serde_json::Value::Null => config::ValueKind::Nil,
175        serde_json::Value::Bool(b) => config::ValueKind::Boolean(*b),
176        serde_json::Value::Number(number) => {
177            if let Some(n) = number.as_u128() {
178                config::ValueKind::U128(n)
179            } else if let Some(n) = number.as_i128() {
180                config::ValueKind::I128(n)
181            } else if let Some(n) = number.as_u64() {
182                config::ValueKind::U64(n)
183            } else if let Some(n) = number.as_i64() {
184                config::ValueKind::I64(n)
185            } else if let Some(n) = number.as_f64() {
186                config::ValueKind::Float(n)
187            } else {
188                return Err(config::ConfigError::Message("invalid number".to_string()));
189            }
190        }
191        serde_json::Value::String(s) => config::ValueKind::String(s.to_string()),
192        serde_json::Value::Array(values) => {
193            config::ValueKind::Array(
194                values
195                    .iter()
196                    .map(|v| serde_json_to_config(v, origin))
197                    .try_collect()?,
198            )
199        }
200        serde_json::Value::Object(map) => {
201            config::ValueKind::Table(
202                map.iter()
203                    .map(|(k, v)| Ok((k.to_string(), serde_json_to_config(v, origin)?)))
204                    .try_collect()?,
205            )
206        }
207    };
208    Ok(config::Value::new(Some(origin), kind))
209}