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 },
16};
17use itertools::Itertools;
18use tempfile::NamedTempFile;
19
20fn main() {
21 let Cli::Flux { check_opts, command } = Cli::parse();
22
23 match run(command.unwrap_or(CargoFluxCommand::Check(check_opts))) {
24 Ok(exit_code) => exit(exit_code),
25 Err(e) => {
26 println!("Failed to run `cargo-flux`, error={e}");
27 exit(EXIT_ERR)
28 }
29 };
30}
31
32fn run(cargo_flux_cmd: CargoFluxCommand) -> anyhow::Result<i32> {
33 let toolchain = get_rust_toolchain()?;
34 let cargo_path = get_binary_path(&toolchain, "cargo")?;
35
36 let metadata = cargo_flux_cmd.metadata().cargo_path(&cargo_path).exec()?;
37 let config_file = write_cargo_config(metadata)?;
38
39 let sysroot = flux_sysroot_dir();
40 let flux_driver_path = get_flux_driver_path(&sysroot)?;
41
42 let mut cargo_command = Command::new("cargo");
43
44 cargo_command
49 .env("RUSTC", flux_driver_path)
50 .env("RUSTC_WRAPPER", "")
51 .arg(format!("+{toolchain}"));
52
53 cargo_flux_cmd.forward_args(&mut cargo_command, config_file.path());
54
55 Ok(cargo_command.status()?.code().unwrap_or(EXIT_ERR))
56}
57
58fn write_cargo_config(metadata: Metadata) -> anyhow::Result<NamedTempFile> {
59 let flux_flags: Option<Vec<String>> = if let Ok(flags) = env::var("FLUXFLAGS") {
60 Some(flags.split(" ").map(Into::into).collect())
61 } else {
62 None
63 };
64
65 let flux_toml = config::Config::builder()
66 .add_source(config::File::with_name("flux.toml").required(false))
67 .build()?;
68
69 if flux_toml.get_bool("enabled").is_ok() {
70 return Err(anyhow!("`enabled` cannot be set in `flux.toml`"));
71 }
72
73 let mut file = NamedTempFile::new()?;
74 {
75 let mut w = BufWriter::new(&mut file);
76 write!(
77 w,
78 r#"
79[unstable]
80profile-rustflags = true
81
82[env]
83FLUX_BUILD_SYSROOT = "1"
84FLUX_CARGO = "1"
85
86[profile.flux]
87inherits = "dev"
88incremental = false
89 "#
90 )?;
91
92 for package in metadata.packages {
93 let flux_metadata: FluxMetadata = config::Config::builder()
94 .add_source(FluxMetadataSource::new(
95 package.manifest_path.to_string(),
96 package.metadata,
97 ))
98 .add_source(flux_toml.clone())
99 .build()?
100 .try_deserialize()?;
101
102 if flux_metadata.enabled {
103 let manifest_dir_relative_to_workspace = package
107 .manifest_path
108 .strip_prefix(&metadata.workspace_root)
109 .ok()
110 .and_then(Utf8Path::parent);
111 write!(
112 w,
113 r#"
114[profile.flux.package."{}"]
115rustflags = [{:?}]
116 "#,
117 package.id,
118 flux_metadata
119 .into_flags(&metadata.target_directory, manifest_dir_relative_to_workspace)
120 .iter()
121 .chain(flux_flags.iter().flatten())
122 .map(|s| s.as_ref())
123 .chain(["-Fverify=on", "-Ffull-compilation=on"])
124 .format(", ")
125 )?;
126 }
127 }
128 }
129 Ok(file)
130}
131
132#[derive(Clone, Debug)]
133struct FluxMetadataSource {
134 origin: String,
135 value: serde_json::Value,
136}
137
138impl FluxMetadataSource {
139 fn new(origin: String, value: serde_json::Value) -> Self {
140 Self { origin, value }
141 }
142}
143
144impl config::Source for FluxMetadataSource {
145 fn clone_into_box(&self) -> Box<dyn config::Source + Send + Sync> {
146 Box::new(self.clone())
147 }
148
149 fn collect(&self) -> Result<config::Map<String, config::Value>, config::ConfigError> {
150 if let serde_json::Value::Object(metadata) = &self.value
151 && let Some(flux_metadata) = metadata.get("flux")
152 {
153 let config_value = serde_json_to_config(flux_metadata, &self.origin)?;
154 if let config::ValueKind::Table(table) = config_value.kind {
155 Ok(table)
156 } else {
157 Err(config::ConfigError::Message("expected a table".to_string()))
158 }
159 } else {
160 Ok(Default::default())
161 }
162 }
163}
164
165fn serde_json_to_config(
166 value: &serde_json::Value,
167 origin: &String,
168) -> Result<config::Value, config::ConfigError> {
169 let kind = match value {
170 serde_json::Value::Null => config::ValueKind::Nil,
171 serde_json::Value::Bool(b) => config::ValueKind::Boolean(*b),
172 serde_json::Value::Number(number) => {
173 if let Some(n) = number.as_u128() {
174 config::ValueKind::U128(n)
175 } else if let Some(n) = number.as_i128() {
176 config::ValueKind::I128(n)
177 } else if let Some(n) = number.as_u64() {
178 config::ValueKind::U64(n)
179 } else if let Some(n) = number.as_i64() {
180 config::ValueKind::I64(n)
181 } else if let Some(n) = number.as_f64() {
182 config::ValueKind::Float(n)
183 } else {
184 return Err(config::ConfigError::Message("invalid number".to_string()));
185 }
186 }
187 serde_json::Value::String(s) => config::ValueKind::String(s.clone()),
188 serde_json::Value::Array(values) => {
189 config::ValueKind::Array(
190 values
191 .iter()
192 .map(|v| serde_json_to_config(v, origin))
193 .try_collect()?,
194 )
195 }
196 serde_json::Value::Object(map) => {
197 config::ValueKind::Table(
198 map.iter()
199 .map(|(k, v)| Ok((k.clone(), serde_json_to_config(v, origin)?)))
200 .try_collect()?,
201 )
202 }
203 };
204 Ok(config::Value::new(Some(origin), kind))
205}