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 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 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
85 let mut file = NamedTempFile::new()?;
86 {
87 let mut w = BufWriter::new(&mut file);
88 write!(
89 w,
90 r#"
91[unstable]
92profile-rustflags = true
93
94[env]
95FLUX_BUILD_SYSROOT = "1"
96FLUX_CARGO = "1"
97
98[profile.flux]
99inherits = "dev"
100incremental = false
101 "#
102 )?;
103
104 for package in metadata.packages {
105 let flux_metadata: FluxMetadata = config::Config::builder()
106 .add_source(FluxMetadataSource::new(
107 package.manifest_path.to_string(),
108 package.metadata,
109 ))
110 .add_source(flux_toml.clone())
111 .build()?
112 .try_deserialize()?;
113
114 if flux_metadata.enabled {
115 let manifest_dir_relative_to_workspace = package
119 .manifest_path
120 .strip_prefix(&metadata.workspace_root)
121 .ok()
122 .and_then(Utf8Path::parent);
123
124 let sysroot_flag = format!("-Fsysroot={}", sysroot.display());
125 write!(
126 w,
127 r#"
128[profile.flux.package."{}"]
129rustflags = [{:?}]
130 "#,
131 package.id,
132 flux_metadata
133 .into_flags(
134 &metadata.target_directory,
135 manifest_dir_relative_to_workspace,
136 cargo_flux_cmd
137 .only_check()
138 .filter(|_| targeted_package_ids.contains(&package.id))
139 )
140 .iter()
141 .chain(flux_flags.iter().flatten())
142 .map(|s| s.as_str())
143 .chain(["-Fverify=on", "-Ffull-compilation=on", sysroot_flag.as_str()])
144 .format(", ")
145 )?;
146 }
147 }
148 }
149 Ok(file)
150}
151
152#[derive(Clone, Debug)]
153struct FluxMetadataSource {
154 origin: String,
155 value: serde_json::Value,
156}
157
158impl FluxMetadataSource {
159 fn new(origin: String, value: serde_json::Value) -> Self {
160 Self { origin, value }
161 }
162}
163
164impl config::Source for FluxMetadataSource {
165 fn clone_into_box(&self) -> Box<dyn config::Source + Send + Sync> {
166 Box::new(self.clone())
167 }
168
169 fn collect(&self) -> Result<config::Map<String, config::Value>, config::ConfigError> {
170 if let serde_json::Value::Object(metadata) = &self.value
171 && let Some(flux_metadata) = metadata.get("flux")
172 {
173 let config_value = serde_json_to_config(flux_metadata, &self.origin)?;
174 if let config::ValueKind::Table(table) = config_value.kind {
175 Ok(table)
176 } else {
177 Err(config::ConfigError::Message("expected a table".to_string()))
178 }
179 } else {
180 Ok(Default::default())
181 }
182 }
183}
184
185fn serde_json_to_config(
186 value: &serde_json::Value,
187 origin: &String,
188) -> Result<config::Value, config::ConfigError> {
189 let kind = match value {
190 serde_json::Value::Null => config::ValueKind::Nil,
191 serde_json::Value::Bool(b) => config::ValueKind::Boolean(*b),
192 serde_json::Value::Number(number) => {
193 if let Some(n) = number.as_u128() {
194 config::ValueKind::U128(n)
195 } else if let Some(n) = number.as_i128() {
196 config::ValueKind::I128(n)
197 } else if let Some(n) = number.as_u64() {
198 config::ValueKind::U64(n)
199 } else if let Some(n) = number.as_i64() {
200 config::ValueKind::I64(n)
201 } else if let Some(n) = number.as_f64() {
202 config::ValueKind::Float(n)
203 } else {
204 return Err(config::ConfigError::Message("invalid number".to_string()));
205 }
206 }
207 serde_json::Value::String(s) => config::ValueKind::String(s.clone()),
208 serde_json::Value::Array(values) => {
209 config::ValueKind::Array(
210 values
211 .iter()
212 .map(|v| serde_json_to_config(v, origin))
213 .try_collect()?,
214 )
215 }
216 serde_json::Value::Object(map) => {
217 config::ValueKind::Table(
218 map.iter()
219 .map(|(k, v)| Ok((k.clone(), serde_json_to_config(v, origin)?)))
220 .try_collect()?,
221 )
222 }
223 };
224 Ok(config::Value::new(Some(origin), kind))
225}