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 let mut args = env::args()
40 .skip_while(|arg| arg != "flux")
41 .skip(1)
42 .collect::<Vec<_>>();
43
44 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}