1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use changepacks_core::{Project, ProjectFinder};
4use regex::Regex;
5use std::{
6 collections::HashMap,
7 path::{Path, PathBuf},
8 process::Stdio,
9};
10use tokio::process::Command;
11
12use crate::{package::GradlePackage, workspace::GradleWorkspace};
13
14#[derive(Debug)]
15pub struct GradleProjectFinder {
16 projects: HashMap<PathBuf, Project>,
17 project_files: Vec<&'static str>,
18}
19
20impl Default for GradleProjectFinder {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl GradleProjectFinder {
27 #[must_use]
28 pub fn new() -> Self {
29 Self {
30 projects: HashMap::new(),
31 project_files: vec!["build.gradle.kts", "build.gradle"],
32 }
33 }
34}
35
36#[derive(Debug, Default)]
38struct GradleProperties {
39 name: Option<String>,
40 version: Option<String>,
41 has_subprojects: bool,
42}
43
44fn find_gradlew(start_dir: &Path) -> Option<(PathBuf, PathBuf)> {
52 let gradlew_name = if cfg!(windows) {
53 "gradlew.bat"
54 } else {
55 "gradlew"
56 };
57
58 let mut current = start_dir.to_path_buf();
59 loop {
60 let gradlew = current.join(gradlew_name);
61 if gradlew.exists() {
62 return Some((gradlew, current));
63 }
64 if !current.pop() {
65 return None;
66 }
67 }
68}
69
70async fn get_gradle_properties(project_dir: &Path) -> Option<GradleProperties> {
76 let (gradlew, gradlew_dir) = find_gradlew(project_dir)?;
77
78 let args: Vec<String> = if gradlew_dir == project_dir {
80 vec!["properties".to_string(), "-q".to_string()]
82 } else {
83 let relative = project_dir.strip_prefix(&gradlew_dir).ok()?;
85 let gradle_path = relative
86 .components()
87 .filter_map(|c| c.as_os_str().to_str())
88 .collect::<Vec<_>>()
89 .join(":");
90 vec![format!(":{gradle_path}:properties"), "-q".to_string()]
91 };
92
93 let output = Command::new(&gradlew)
94 .args(&args)
95 .current_dir(&gradlew_dir)
96 .stdout(Stdio::piped())
97 .stderr(Stdio::null())
98 .output()
99 .await
100 .ok()?;
101
102 if !output.status.success() {
103 return None;
104 }
105
106 let stdout = String::from_utf8_lossy(&output.stdout);
107 let mut props = GradleProperties::default();
108
109 let name_pattern = Regex::new(r"(?m)^name:\s*(.+)$").ok()?;
112 let version_pattern = Regex::new(r"(?m)^version:\s*(.+)$").ok()?;
113 let subprojects_pattern = Regex::new(r"(?m)^subprojects:\s*(.+)$").ok()?;
114
115 if let Some(caps) = name_pattern.captures(&stdout) {
116 let name = caps.get(1).map(|m| m.as_str().trim().to_string());
117 if name.as_deref() != Some("unspecified") {
118 props.name = name;
119 }
120 }
121
122 if let Some(caps) = version_pattern.captures(&stdout) {
123 let version = caps.get(1).map(|m| m.as_str().trim().to_string());
124 if version.as_deref() != Some("unspecified") {
125 props.version = version;
126 }
127 }
128
129 if let Some(caps) = subprojects_pattern.captures(&stdout) {
131 let value = caps.get(1).map(|m| m.as_str().trim()).unwrap_or("");
132 props.has_subprojects = value != "[]";
133 }
134
135 Some(props)
136}
137
138#[async_trait]
139impl ProjectFinder for GradleProjectFinder {
140 fn projects(&self) -> Vec<&Project> {
141 self.projects.values().collect::<Vec<_>>()
142 }
143
144 fn projects_mut(&mut self) -> Vec<&mut Project> {
145 self.projects.values_mut().collect::<Vec<_>>()
146 }
147
148 fn project_files(&self) -> &[&str] {
149 &self.project_files
150 }
151
152 async fn visit(&mut self, path: &Path, relative_path: &Path) -> Result<()> {
153 if path.is_file()
154 && self.project_files().contains(
155 &path
156 .file_name()
157 .context(format!("File name not found - {}", path.display()))?
158 .to_str()
159 .context(format!("File name not found - {}", path.display()))?,
160 )
161 {
162 if self.projects.contains_key(path) {
163 return Ok(());
164 }
165
166 let project_dir = path
167 .parent()
168 .context(format!("Parent not found - {}", path.display()))?;
169
170 let props = get_gradle_properties(project_dir).await.unwrap_or_default();
172
173 let name = props.name.or_else(|| {
175 project_dir
176 .file_name()
177 .and_then(|n| n.to_str())
178 .map(std::string::ToString::to_string)
179 });
180
181 let version = props.version;
182
183 let is_workspace = props.has_subprojects;
187
188 let (path, project) = if is_workspace {
189 (
190 path.to_path_buf(),
191 Project::Workspace(Box::new(GradleWorkspace::new(
192 name,
193 version,
194 path.to_path_buf(),
195 relative_path.to_path_buf(),
196 ))),
197 )
198 } else {
199 (
200 path.to_path_buf(),
201 Project::Package(Box::new(GradlePackage::new(
202 name,
203 version,
204 path.to_path_buf(),
205 relative_path.to_path_buf(),
206 ))),
207 )
208 };
209
210 self.projects.insert(path, project);
211 }
212 Ok(())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use changepacks_core::Project;
220 use std::fs;
221 use tempfile::TempDir;
222
223 #[test]
224 fn test_gradle_project_finder_new() {
225 let finder = GradleProjectFinder::new();
226 assert_eq!(
227 finder.project_files(),
228 &["build.gradle.kts", "build.gradle"]
229 );
230 assert_eq!(finder.projects().len(), 0);
231 }
232
233 #[test]
234 fn test_gradle_project_finder_default() {
235 let finder = GradleProjectFinder::default();
236 assert_eq!(
237 finder.project_files(),
238 &["build.gradle.kts", "build.gradle"]
239 );
240 assert_eq!(finder.projects().len(), 0);
241 }
242
243 #[tokio::test]
244 async fn test_gradle_project_finder_visit_kts_package() {
245 let temp_dir = TempDir::new().unwrap();
246 let project_dir = temp_dir.path().join("myproject");
247 fs::create_dir_all(&project_dir).unwrap();
248
249 let build_gradle = project_dir.join("build.gradle.kts");
250 fs::write(
251 &build_gradle,
252 r#"
253plugins {
254 id("java")
255}
256
257group = "com.example"
258version = "1.0.0"
259"#,
260 )
261 .unwrap();
262
263 let mut finder = GradleProjectFinder::new();
264 finder
265 .visit(&build_gradle, &PathBuf::from("myproject/build.gradle.kts"))
266 .await
267 .unwrap();
268
269 let projects = finder.projects();
270 assert_eq!(projects.len(), 1);
271 match projects[0] {
272 Project::Package(pkg) => {
273 assert_eq!(pkg.name(), Some("myproject"));
275 assert_eq!(pkg.version(), None);
277 }
278 _ => panic!("Expected Package"),
279 }
280
281 temp_dir.close().unwrap();
282 }
283
284 #[tokio::test]
285 async fn test_gradle_project_finder_visit_groovy_package() {
286 let temp_dir = TempDir::new().unwrap();
287 let project_dir = temp_dir.path().join("groovyproject");
288 fs::create_dir_all(&project_dir).unwrap();
289
290 let build_gradle = project_dir.join("build.gradle");
291 fs::write(
292 &build_gradle,
293 r#"
294plugins {
295 id 'java'
296}
297
298group = 'com.example'
299version = '2.0.0'
300"#,
301 )
302 .unwrap();
303
304 let mut finder = GradleProjectFinder::new();
305 finder
306 .visit(&build_gradle, &PathBuf::from("groovyproject/build.gradle"))
307 .await
308 .unwrap();
309
310 let projects = finder.projects();
311 assert_eq!(projects.len(), 1);
312 match projects[0] {
313 Project::Package(pkg) => {
314 assert_eq!(pkg.name(), Some("groovyproject"));
316 }
317 _ => panic!("Expected Package"),
318 }
319
320 temp_dir.close().unwrap();
321 }
322
323 #[tokio::test]
324 async fn test_gradle_project_finder_visit_workspace() {
325 let temp_dir = TempDir::new().unwrap();
326 let project_dir = temp_dir.path().join("multiproject");
327 fs::create_dir_all(&project_dir).unwrap();
328
329 let build_gradle = project_dir.join("build.gradle.kts");
330 fs::write(
331 &build_gradle,
332 r#"
333plugins {
334 id("java")
335}
336
337group = "com.example"
338version = "1.0.0"
339"#,
340 )
341 .unwrap();
342
343 if cfg!(windows) {
345 fs::write(
346 project_dir.join("gradlew.bat"),
347 "@echo off\necho name: multiproject\necho version: 1.0.0\necho subprojects: [project ':subproject1', project ':subproject2']\n",
348 )
349 .unwrap();
350 } else {
351 let gradlew_path = project_dir.join("gradlew");
352 fs::write(
353 &gradlew_path,
354 "#!/bin/sh\necho 'name: multiproject'\necho 'version: 1.0.0'\necho \"subprojects: [project ':subproject1', project ':subproject2']\"\n",
355 )
356 .unwrap();
357 #[cfg(unix)]
358 {
359 use std::os::unix::fs::PermissionsExt;
360 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
361 }
362 }
363
364 let mut finder = GradleProjectFinder::new();
365 finder
366 .visit(
367 &build_gradle,
368 &PathBuf::from("multiproject/build.gradle.kts"),
369 )
370 .await
371 .unwrap();
372
373 let projects = finder.projects();
374 assert_eq!(projects.len(), 1);
375 match projects[0] {
376 Project::Workspace(ws) => {
377 assert_eq!(ws.name(), Some("multiproject"));
378 assert_eq!(ws.version(), Some("1.0.0"));
379 }
380 _ => panic!("Expected Workspace"),
381 }
382
383 temp_dir.close().unwrap();
384 }
385
386 #[tokio::test]
387 async fn test_gradle_project_finder_settings_file_does_not_make_workspace() {
388 let temp_dir = TempDir::new().unwrap();
391 let project_dir = temp_dir.path().join("myproject");
392 fs::create_dir_all(&project_dir).unwrap();
393
394 let build_gradle = project_dir.join("build.gradle.kts");
395 fs::write(&build_gradle, "version = \"1.0.0\"\n").unwrap();
396
397 fs::write(
399 project_dir.join("settings.gradle.kts"),
400 "rootProject.name = \"myproject\"\n",
401 )
402 .unwrap();
403
404 let mut finder = GradleProjectFinder::new();
405 finder
406 .visit(&build_gradle, &PathBuf::from("myproject/build.gradle.kts"))
407 .await
408 .unwrap();
409
410 let projects = finder.projects();
411 assert_eq!(projects.len(), 1);
412 match projects[0] {
413 Project::Package(_) => {} _ => panic!("Expected Package, not Workspace"),
415 }
416
417 temp_dir.close().unwrap();
418 }
419
420 #[tokio::test]
421 async fn test_gradle_project_finder_empty_subprojects_is_package() {
422 let temp_dir = TempDir::new().unwrap();
424 let project_dir = temp_dir.path().join("standalone");
425 fs::create_dir_all(&project_dir).unwrap();
426
427 let build_gradle = project_dir.join("build.gradle.kts");
428 fs::write(&build_gradle, "version = \"1.0.0\"\n").unwrap();
429
430 if cfg!(windows) {
431 fs::write(
432 project_dir.join("gradlew.bat"),
433 "@echo off\necho name: standalone\necho version: 1.0.0\necho subprojects: []\n",
434 )
435 .unwrap();
436 } else {
437 let gradlew_path = project_dir.join("gradlew");
438 fs::write(
439 &gradlew_path,
440 "#!/bin/sh\necho 'name: standalone'\necho 'version: 1.0.0'\necho 'subprojects: []'\n",
441 )
442 .unwrap();
443 #[cfg(unix)]
444 {
445 use std::os::unix::fs::PermissionsExt;
446 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
447 }
448 }
449
450 let mut finder = GradleProjectFinder::new();
451 finder
452 .visit(&build_gradle, &PathBuf::from("standalone/build.gradle.kts"))
453 .await
454 .unwrap();
455
456 let projects = finder.projects();
457 assert_eq!(projects.len(), 1);
458 match projects[0] {
459 Project::Package(pkg) => {
460 assert_eq!(pkg.name(), Some("standalone"));
461 }
462 _ => panic!("Expected Package, not Workspace"),
463 }
464
465 temp_dir.close().unwrap();
466 }
467
468 #[tokio::test]
469 async fn test_gradle_project_finder_visit_non_gradle_file() {
470 let temp_dir = TempDir::new().unwrap();
471 let other_file = temp_dir.path().join("other.txt");
472 fs::write(&other_file, "some content").unwrap();
473
474 let mut finder = GradleProjectFinder::new();
475 finder
476 .visit(&other_file, &PathBuf::from("other.txt"))
477 .await
478 .unwrap();
479
480 assert_eq!(finder.projects().len(), 0);
481
482 temp_dir.close().unwrap();
483 }
484
485 #[tokio::test]
486 async fn test_gradle_project_finder_visit_duplicate() {
487 let temp_dir = TempDir::new().unwrap();
488 let project_dir = temp_dir.path().join("myproject");
489 fs::create_dir_all(&project_dir).unwrap();
490
491 let build_gradle = project_dir.join("build.gradle.kts");
492 fs::write(
493 &build_gradle,
494 r#"
495group = "com.example"
496version = "1.0.0"
497"#,
498 )
499 .unwrap();
500
501 let mut finder = GradleProjectFinder::new();
502 finder
503 .visit(&build_gradle, &PathBuf::from("myproject/build.gradle.kts"))
504 .await
505 .unwrap();
506
507 assert_eq!(finder.projects().len(), 1);
508
509 finder
511 .visit(&build_gradle, &PathBuf::from("myproject/build.gradle.kts"))
512 .await
513 .unwrap();
514
515 assert_eq!(finder.projects().len(), 1);
516
517 temp_dir.close().unwrap();
518 }
519
520 #[tokio::test]
521 async fn test_gradle_project_finder_projects_mut() {
522 let temp_dir = TempDir::new().unwrap();
523 let project_dir = temp_dir.path().join("myproject");
524 fs::create_dir_all(&project_dir).unwrap();
525
526 let build_gradle = project_dir.join("build.gradle.kts");
527 fs::write(
528 &build_gradle,
529 r#"
530group = "com.example"
531version = "1.0.0"
532"#,
533 )
534 .unwrap();
535
536 let mut finder = GradleProjectFinder::new();
537 finder
538 .visit(&build_gradle, &PathBuf::from("myproject/build.gradle.kts"))
539 .await
540 .unwrap();
541
542 let mut_projects = finder.projects_mut();
543 assert_eq!(mut_projects.len(), 1);
544
545 temp_dir.close().unwrap();
546 }
547
548 #[test]
549 fn test_find_gradlew_in_same_dir() {
550 let temp_dir = TempDir::new().unwrap();
551
552 if cfg!(windows) {
553 fs::write(temp_dir.path().join("gradlew.bat"), "@echo off").unwrap();
554 } else {
555 fs::write(temp_dir.path().join("gradlew"), "#!/bin/sh").unwrap();
556 }
557
558 let result = find_gradlew(temp_dir.path());
559 assert!(result.is_some());
560 let (_, gradlew_dir) = result.unwrap();
561 assert_eq!(gradlew_dir, temp_dir.path());
562
563 temp_dir.close().unwrap();
564 }
565
566 #[test]
567 fn test_find_gradlew_in_parent_dir() {
568 let temp_dir = TempDir::new().unwrap();
569 let subproject = temp_dir.path().join("libs").join("core");
570 fs::create_dir_all(&subproject).unwrap();
571
572 if cfg!(windows) {
574 fs::write(temp_dir.path().join("gradlew.bat"), "@echo off").unwrap();
575 } else {
576 fs::write(temp_dir.path().join("gradlew"), "#!/bin/sh").unwrap();
577 }
578
579 let result = find_gradlew(&subproject);
580 assert!(result.is_some());
581 let (_, gradlew_dir) = result.unwrap();
582 assert_eq!(gradlew_dir, temp_dir.path().to_path_buf());
583
584 temp_dir.close().unwrap();
585 }
586
587 #[test]
588 fn test_find_gradlew_not_found() {
589 let temp_dir = TempDir::new().unwrap();
590 let subdir = temp_dir.path().join("no_gradlew_here");
591 fs::create_dir_all(&subdir).unwrap();
592
593 let _ = find_gradlew(&subdir);
598
599 temp_dir.close().unwrap();
600 }
601
602 #[tokio::test]
603 async fn test_get_gradle_properties_no_gradlew() {
604 let temp_dir = TempDir::new().unwrap();
605 let _result = get_gradle_properties(temp_dir.path()).await;
609 temp_dir.close().unwrap();
610 }
611
612 #[tokio::test]
613 async fn test_get_gradle_properties_with_mock() {
614 let temp_dir = TempDir::new().unwrap();
615
616 if cfg!(windows) {
618 let gradlew_path = temp_dir.path().join("gradlew.bat");
619 fs::write(
620 &gradlew_path,
621 "@echo off\necho name: myproject\necho version: 1.2.3\n",
622 )
623 .unwrap();
624 } else {
625 let gradlew_path = temp_dir.path().join("gradlew");
626 fs::write(
627 &gradlew_path,
628 "#!/bin/sh\necho 'name: myproject'\necho 'version: 1.2.3'\n",
629 )
630 .unwrap();
631 #[cfg(unix)]
633 {
634 use std::os::unix::fs::PermissionsExt;
635 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
636 }
637 }
638
639 let result = get_gradle_properties(temp_dir.path()).await;
640 assert!(result.is_some());
641 let props = result.unwrap();
642 assert_eq!(props.name, Some("myproject".to_string()));
643 assert_eq!(props.version, Some("1.2.3".to_string()));
644 assert!(!props.has_subprojects);
645
646 temp_dir.close().unwrap();
647 }
648
649 #[tokio::test]
650 async fn test_get_gradle_properties_with_subprojects() {
651 let temp_dir = TempDir::new().unwrap();
652
653 if cfg!(windows) {
654 fs::write(
655 temp_dir.path().join("gradlew.bat"),
656 "@echo off\necho name: root\necho version: 1.0.0\necho subprojects: [project ':app', project ':lib']\n",
657 )
658 .unwrap();
659 } else {
660 let gradlew_path = temp_dir.path().join("gradlew");
661 fs::write(
662 &gradlew_path,
663 "#!/bin/sh\necho 'name: root'\necho 'version: 1.0.0'\necho \"subprojects: [project ':app', project ':lib']\"\n",
664 )
665 .unwrap();
666 #[cfg(unix)]
667 {
668 use std::os::unix::fs::PermissionsExt;
669 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
670 }
671 }
672
673 let result = get_gradle_properties(temp_dir.path()).await;
674 assert!(result.is_some());
675 let props = result.unwrap();
676 assert_eq!(props.name, Some("root".to_string()));
677 assert!(props.has_subprojects);
678
679 temp_dir.close().unwrap();
680 }
681
682 #[tokio::test]
683 async fn test_get_gradle_properties_empty_subprojects() {
684 let temp_dir = TempDir::new().unwrap();
685
686 if cfg!(windows) {
687 fs::write(
688 temp_dir.path().join("gradlew.bat"),
689 "@echo off\necho name: leaf\necho version: 1.0.0\necho subprojects: []\n",
690 )
691 .unwrap();
692 } else {
693 let gradlew_path = temp_dir.path().join("gradlew");
694 fs::write(
695 &gradlew_path,
696 "#!/bin/sh\necho 'name: leaf'\necho 'version: 1.0.0'\necho 'subprojects: []'\n",
697 )
698 .unwrap();
699 #[cfg(unix)]
700 {
701 use std::os::unix::fs::PermissionsExt;
702 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
703 }
704 }
705
706 let result = get_gradle_properties(temp_dir.path()).await;
707 assert!(result.is_some());
708 let props = result.unwrap();
709 assert_eq!(props.name, Some("leaf".to_string()));
710 assert!(!props.has_subprojects);
711
712 temp_dir.close().unwrap();
713 }
714
715 #[tokio::test]
716 async fn test_get_gradle_properties_from_parent_gradlew() {
717 let temp_dir = TempDir::new().unwrap();
718 let subproject = temp_dir.path().join("sub1");
719 fs::create_dir_all(&subproject).unwrap();
720
721 if cfg!(windows) {
723 let gradlew_path = temp_dir.path().join("gradlew.bat");
724 fs::write(
726 &gradlew_path,
727 "@echo off\necho name: sub1\necho version: 2.0.0\n",
728 )
729 .unwrap();
730 } else {
731 let gradlew_path = temp_dir.path().join("gradlew");
732 fs::write(
733 &gradlew_path,
734 "#!/bin/sh\necho 'name: sub1'\necho 'version: 2.0.0'\n",
735 )
736 .unwrap();
737 #[cfg(unix)]
738 {
739 use std::os::unix::fs::PermissionsExt;
740 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
741 }
742 }
743
744 let result = get_gradle_properties(&subproject).await;
745 assert!(result.is_some());
746 let props = result.unwrap();
747 assert_eq!(props.name, Some("sub1".to_string()));
748 assert_eq!(props.version, Some("2.0.0".to_string()));
749
750 temp_dir.close().unwrap();
751 }
752
753 #[tokio::test]
754 async fn test_get_gradle_properties_nested_subproject() {
755 let temp_dir = TempDir::new().unwrap();
756 let subproject = temp_dir.path().join("libs").join("core");
757 fs::create_dir_all(&subproject).unwrap();
758
759 if cfg!(windows) {
761 let gradlew_path = temp_dir.path().join("gradlew.bat");
762 fs::write(
764 &gradlew_path,
765 "@echo off\necho name: core\necho version: 3.1.0\n",
766 )
767 .unwrap();
768 } else {
769 let gradlew_path = temp_dir.path().join("gradlew");
770 fs::write(
771 &gradlew_path,
772 "#!/bin/sh\necho 'name: core'\necho 'version: 3.1.0'\n",
773 )
774 .unwrap();
775 #[cfg(unix)]
776 {
777 use std::os::unix::fs::PermissionsExt;
778 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
779 }
780 }
781
782 let result = get_gradle_properties(&subproject).await;
783 assert!(result.is_some());
784 let props = result.unwrap();
785 assert_eq!(props.name, Some("core".to_string()));
786 assert_eq!(props.version, Some("3.1.0".to_string()));
787
788 temp_dir.close().unwrap();
789 }
790
791 #[tokio::test]
792 async fn test_get_gradle_properties_unspecified() {
793 let temp_dir = TempDir::new().unwrap();
794
795 if cfg!(windows) {
796 let gradlew_path = temp_dir.path().join("gradlew.bat");
797 fs::write(
798 &gradlew_path,
799 "@echo off\necho name: unspecified\necho version: unspecified\n",
800 )
801 .unwrap();
802 } else {
803 let gradlew_path = temp_dir.path().join("gradlew");
804 fs::write(
805 &gradlew_path,
806 "#!/bin/sh\necho 'name: unspecified'\necho 'version: unspecified'\n",
807 )
808 .unwrap();
809 #[cfg(unix)]
810 {
811 use std::os::unix::fs::PermissionsExt;
812 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
813 }
814 }
815
816 let result = get_gradle_properties(temp_dir.path()).await;
817 assert!(result.is_some());
818 let props = result.unwrap();
819 assert!(props.name.is_none());
820 assert!(props.version.is_none());
821
822 temp_dir.close().unwrap();
823 }
824
825 #[tokio::test]
826 async fn test_get_gradle_properties_gradlew_fails() {
827 let temp_dir = TempDir::new().unwrap();
828
829 if cfg!(windows) {
830 let gradlew_path = temp_dir.path().join("gradlew.bat");
831 fs::write(&gradlew_path, "@echo off\nexit /b 1\n").unwrap();
832 } else {
833 let gradlew_path = temp_dir.path().join("gradlew");
834 fs::write(&gradlew_path, "#!/bin/sh\nexit 1\n").unwrap();
835 #[cfg(unix)]
836 {
837 use std::os::unix::fs::PermissionsExt;
838 fs::set_permissions(&gradlew_path, fs::Permissions::from_mode(0o755)).unwrap();
839 }
840 }
841
842 let result = get_gradle_properties(temp_dir.path()).await;
843 assert!(result.is_none());
844
845 temp_dir.close().unwrap();
846 }
847}