1use std::fs::{self, File};
2use std::io::{Read, Write};
3use std::path::PathBuf;
4
5use dfir_lang::graph::DfirGraph;
6use sha2::{Digest, Sha256};
7use stageleft::internal::quote;
8use syn::visit_mut::VisitMut;
9use trybuild_internals_api::cargo::{self, Metadata};
10use trybuild_internals_api::env::Update;
11use trybuild_internals_api::run::{PathDependency, Project};
12use trybuild_internals_api::{Runner, dependencies, features, path};
13
14use super::trybuild_rewriters::ReplaceCrateNameWithStaged;
15
16pub const HYDRO_RUNTIME_FEATURES: [&str; 1] = ["runtime_measure"];
17
18static IS_TEST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
19
20pub fn init_test() {
21 IS_TEST.store(true, std::sync::atomic::Ordering::Relaxed);
22}
23
24fn clean_name_hint(name_hint: &str) -> String {
25 name_hint
26 .replace("::", "__")
27 .replace(" ", "_")
28 .replace(",", "_")
29 .replace("<", "_")
30 .replace(">", "")
31 .replace("(", "")
32 .replace(")", "")
33}
34
35pub fn create_graph_trybuild(
36 graph: DfirGraph,
37 extra_stmts: Vec<syn::Stmt>,
38 name_hint: &Option<String>,
39) -> (String, (PathBuf, PathBuf, Option<Vec<String>>)) {
40 let source_dir = cargo::manifest_dir().unwrap();
41 let source_manifest = dependencies::get_manifest(&source_dir).unwrap();
42 let crate_name = &source_manifest.package.name.to_string().replace("-", "_");
43
44 let is_test = IS_TEST.load(std::sync::atomic::Ordering::Relaxed);
45
46 let mut generated_code = compile_graph_trybuild(graph, extra_stmts);
47
48 ReplaceCrateNameWithStaged {
49 crate_name: crate_name.clone(),
50 is_test,
51 }
52 .visit_file_mut(&mut generated_code);
53
54 let inlined_staged = if is_test {
55 stageleft_tool::gen_staged_trybuild(
56 &path!(source_dir / "src" / "lib.rs"),
57 crate_name.clone(),
58 is_test,
59 )
60 } else {
61 syn::parse_quote!()
62 };
63
64 let source = prettyplease::unparse(&syn::parse_quote! {
65 #generated_code
66
67 #[allow(
68 unused,
69 ambiguous_glob_reexports,
70 clippy::suspicious_else_formatting,
71 unexpected_cfgs,
72 reason = "generated code"
73 )]
74 pub mod __staged {
75 #inlined_staged
76 }
77 });
78
79 let hash = format!("{:X}", Sha256::digest(&source))
80 .chars()
81 .take(8)
82 .collect::<String>();
83
84 let bin_name = if let Some(name_hint) = &name_hint {
85 format!("{}_{}", clean_name_hint(name_hint), &hash)
86 } else {
87 hash
88 };
89
90 let (project_dir, target_dir, mut cur_bin_enabled_features) = create_trybuild().unwrap();
91
92 fs::create_dir_all(path!(project_dir / "src" / "bin")).unwrap();
94
95 let out_path = path!(project_dir / "src" / "bin" / format!("{bin_name}.rs"));
96 {
97 let mut out_file = File::options()
98 .read(true)
99 .write(true)
100 .create(true)
101 .truncate(false)
102 .open(&out_path)
103 .unwrap();
104 #[cfg(nightly)]
105 out_file.lock().unwrap();
106
107 let mut existing_contents = String::new();
108 out_file.read_to_string(&mut existing_contents).unwrap();
109 if existing_contents != source {
110 out_file.write_all(source.as_ref()).unwrap()
111 }
112 }
113
114 if is_test {
115 if cur_bin_enabled_features.is_none() {
116 cur_bin_enabled_features = Some(vec![]);
117 }
118
119 cur_bin_enabled_features
120 .as_mut()
121 .unwrap()
122 .push("hydro___test".to_string());
123 }
124
125 (
126 bin_name,
127 (project_dir, target_dir, cur_bin_enabled_features),
128 )
129}
130
131pub fn compile_graph_trybuild(
132 partitioned_graph: DfirGraph,
133 extra_stmts: Vec<syn::Stmt>,
134) -> syn::File {
135 let mut diagnostics = Vec::new();
136 let tokens = partitioned_graph.as_code(
137 "e! { hydro_lang::dfir_rs },
138 true,
139 quote!(),
140 &mut diagnostics,
141 );
142
143 let source_ast: syn::File = syn::parse_quote! {
144 #![allow(unused_imports, unused_crate_dependencies, missing_docs, non_snake_case)]
145 use hydro_lang::*;
146
147 #[allow(unused)]
148 fn __hydro_runtime<'a>(__hydro_lang_trybuild_cli: &'a hydro_lang::dfir_rs::util::deploy::DeployPorts<hydro_lang::deploy_runtime::HydroMeta>) -> hydro_lang::dfir_rs::scheduled::graph::Dfir<'a> {
149 #(#extra_stmts)*
150 #tokens
151 }
152
153 #[tokio::main]
154 async fn main() {
155 let ports = hydro_lang::dfir_rs::util::deploy::init_no_ack_start().await;
156 let flow = __hydro_runtime(&ports);
157 println!("ack start");
158
159 hydro_lang::runtime_support::resource_measurement::run(flow).await;
160 }
161 };
162 source_ast
163}
164
165pub fn create_trybuild()
166-> Result<(PathBuf, PathBuf, Option<Vec<String>>), trybuild_internals_api::error::Error> {
167 let Metadata {
168 target_directory: target_dir,
169 workspace_root: workspace,
170 packages,
171 } = cargo::metadata()?;
172
173 let source_dir = cargo::manifest_dir()?;
174 let mut source_manifest = dependencies::get_manifest(&source_dir)?;
175
176 let mut dev_dependency_features = vec![];
177 source_manifest.dev_dependencies.retain(|k, v| {
178 if source_manifest.dependencies.contains_key(k) {
179 for feat in &v.features {
181 dev_dependency_features.push(format!("{}/{}", k, feat));
182 }
183
184 false
185 } else {
186 dev_dependency_features.push(format!("dep:{k}"));
188
189 v.optional = true;
190 true
191 }
192 });
193
194 let mut features = features::find();
195
196 let path_dependencies = source_manifest
197 .dependencies
198 .iter()
199 .filter_map(|(name, dep)| {
200 let path = dep.path.as_ref()?;
201 if packages.iter().any(|p| &p.name == name) {
202 None
204 } else {
205 Some(PathDependency {
206 name: name.clone(),
207 normalized_path: path.canonicalize().ok()?,
208 })
209 }
210 })
211 .collect();
212
213 let crate_name = source_manifest.package.name.clone();
214 let project_dir = path!(target_dir / "hydro_trybuild" / crate_name /);
215 fs::create_dir_all(&project_dir)?;
216
217 let project_name = format!("{}-hydro-trybuild", crate_name);
218 let mut manifest = Runner::make_manifest(
219 &workspace,
220 &project_name,
221 &source_dir,
222 &packages,
223 &[],
224 source_manifest,
225 )?;
226
227 manifest.features.remove("stageleft_devel");
228
229 if let Some(enabled_features) = &mut features {
230 enabled_features
231 .retain(|feature| manifest.features.contains_key(feature) || feature == "default");
232
233 manifest
234 .features
235 .get_mut("default")
236 .iter_mut()
237 .for_each(|v| {
238 v.retain(|f| f != "stageleft_devel");
239 });
240 }
241
242 for runtime_feature in HYDRO_RUNTIME_FEATURES {
243 manifest.features.insert(
244 format!("hydro___feature_{runtime_feature}"),
245 vec![format!("hydro_lang/{runtime_feature}")],
246 );
247 }
248
249 manifest
250 .features
251 .insert("hydro___test".to_string(), dev_dependency_features);
252
253 let project = Project {
254 dir: project_dir,
255 source_dir,
256 target_dir,
257 name: project_name,
258 update: Update::env()?,
259 has_pass: false,
260 has_compile_fail: false,
261 features,
262 workspace,
263 path_dependencies,
264 manifest,
265 keep_going: false,
266 };
267
268 {
269 #[cfg(nightly)]
270 let project_lock = File::create(path!(project.dir / ".hydro-trybuild-lock"))?;
271 #[cfg(nightly)]
272 project_lock.lock()?;
273
274 let manifest_toml = toml::to_string(&project.manifest)?;
275 fs::write(path!(project.dir / "Cargo.toml"), manifest_toml)?;
276
277 let workspace_cargo_lock = path!(project.workspace / "Cargo.lock");
278 if workspace_cargo_lock.exists() {
279 let _ = fs::copy(workspace_cargo_lock, path!(project.dir / "Cargo.lock"));
280 } else {
281 let _ = cargo::cargo(&project).arg("generate-lockfile").status();
282 }
283
284 let workspace_dot_cargo_config_toml = path!(project.workspace / ".cargo" / "config.toml");
285 if workspace_dot_cargo_config_toml.exists() {
286 let dot_cargo_folder = path!(project.dir / ".cargo");
287 fs::create_dir_all(&dot_cargo_folder)?;
288
289 let _ = fs::copy(
290 workspace_dot_cargo_config_toml,
291 path!(dot_cargo_folder / "config.toml"),
292 );
293 }
294 }
295
296 Ok((
297 project.dir.as_ref().into(),
298 path!(project.target_dir / "hydro_trybuild"),
299 project.features,
300 ))
301}