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