1use crate::core::{Artifact, ArtifactKind, ArtifactMetadata, MarkerKind, ProjectKind, ProjectMarker};
4use crate::error::Result;
5use crate::plugins::Plugin;
6use std::path::Path;
7
8pub struct SwiftPlugin;
10
11impl Plugin for SwiftPlugin {
12 fn id(&self) -> &'static str {
13 "swift"
14 }
15
16 fn name(&self) -> &'static str {
17 "Swift/Xcode"
18 }
19
20 fn supported_kinds(&self) -> &[ProjectKind] {
21 &[ProjectKind::SwiftSpm, ProjectKind::SwiftXcode]
22 }
23
24 fn markers(&self) -> Vec<ProjectMarker> {
25 vec![
26 ProjectMarker {
27 indicator: MarkerKind::File("Package.swift"),
28 kind: ProjectKind::SwiftSpm,
29 priority: 60,
30 },
31 ProjectMarker {
32 indicator: MarkerKind::Extension("xcodeproj"),
33 kind: ProjectKind::SwiftXcode,
34 priority: 55,
35 },
36 ProjectMarker {
37 indicator: MarkerKind::Extension("xcworkspace"),
38 kind: ProjectKind::SwiftXcode,
39 priority: 55,
40 },
41 ]
42 }
43
44 fn detect(&self, path: &Path) -> Option<ProjectKind> {
45 if path.join("Package.swift").is_file() {
47 return Some(ProjectKind::SwiftSpm);
48 }
49
50 if let Ok(entries) = std::fs::read_dir(path) {
52 for entry in entries.filter_map(|e| e.ok()) {
53 if let Some(name) = entry.file_name().to_str() {
54 if name.ends_with(".xcodeproj") || name.ends_with(".xcworkspace") {
55 return Some(ProjectKind::SwiftXcode);
56 }
57 }
58 }
59 }
60
61 None
62 }
63
64 fn find_artifacts(&self, project_root: &Path) -> Result<Vec<Artifact>> {
65 let mut artifacts = Vec::new();
66
67 let build = project_root.join(".build");
69 if build.exists() {
70 artifacts.push(Artifact {
71 path: build,
72 kind: ArtifactKind::BuildOutput,
73 size: 0,
74 file_count: 0,
75 age: None,
76 metadata: ArtifactMetadata::restorable("swift build"),
77 });
78 }
79
80 let swiftpm = project_root.join(".swiftpm");
82 if swiftpm.exists() {
83 artifacts.push(Artifact {
84 path: swiftpm,
85 kind: ArtifactKind::Cache,
86 size: 0,
87 file_count: 0,
88 age: None,
89 metadata: ArtifactMetadata::default(),
90 });
91 }
92
93 let pods = project_root.join("Pods");
95 if pods.exists() {
96 artifacts.push(Artifact {
97 path: pods,
98 kind: ArtifactKind::Dependencies,
99 size: 0,
100 file_count: 0,
101 age: None,
102 metadata: ArtifactMetadata {
103 restorable: true,
104 restore_command: Some("pod install".into()),
105 lockfile: Some(project_root.join("Podfile.lock")),
106 ..Default::default()
107 },
108 });
109 }
110
111 let derived_data = project_root.join("DerivedData");
113 if derived_data.exists() {
114 artifacts.push(Artifact {
115 path: derived_data,
116 kind: ArtifactKind::BuildOutput,
117 size: 0,
118 file_count: 0,
119 age: None,
120 metadata: ArtifactMetadata::restorable("xcodebuild"),
121 });
122 }
123
124 let build_dir = project_root.join("build");
126 if build_dir.exists() {
127 artifacts.push(Artifact {
128 path: build_dir,
129 kind: ArtifactKind::BuildOutput,
130 size: 0,
131 file_count: 0,
132 age: None,
133 metadata: ArtifactMetadata::restorable("xcodebuild"),
134 });
135 }
136
137 Ok(artifacts)
138 }
139
140 fn cleanable_dirs(&self) -> &[&'static str] {
141 &[".build", ".swiftpm", "Pods", "DerivedData", "build"]
142 }
143
144 fn priority(&self) -> u8 {
145 55
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use tempfile::TempDir;
153
154 #[test]
155 fn test_detect_spm() {
156 let temp = TempDir::new().unwrap();
157 std::fs::write(
158 temp.path().join("Package.swift"),
159 "// swift-tools-version:5.5\nimport PackageDescription\n",
160 )
161 .unwrap();
162
163 let plugin = SwiftPlugin;
164 assert_eq!(plugin.detect(temp.path()), Some(ProjectKind::SwiftSpm));
165 }
166
167 #[test]
168 fn test_detect_xcode() {
169 let temp = TempDir::new().unwrap();
170 std::fs::create_dir(temp.path().join("MyApp.xcodeproj")).unwrap();
171
172 let plugin = SwiftPlugin;
173 assert_eq!(plugin.detect(temp.path()), Some(ProjectKind::SwiftXcode));
174 }
175
176 #[test]
177 fn test_find_artifacts() {
178 let temp = TempDir::new().unwrap();
179 std::fs::write(temp.path().join("Package.swift"), "").unwrap();
180 std::fs::create_dir(temp.path().join(".build")).unwrap();
181 std::fs::create_dir(temp.path().join("Pods")).unwrap();
182
183 let plugin = SwiftPlugin;
184 let artifacts = plugin.find_artifacts(temp.path()).unwrap();
185
186 assert_eq!(artifacts.len(), 2);
187 }
188}