1use anyhow::Result;
2use async_trait::async_trait;
3use changepacks_core::{Language, Package, UpdateType};
4use changepacks_utils::next_version;
5use std::collections::HashSet;
6use std::path::{Path, PathBuf};
7use tokio::fs::{read_to_string, write};
8
9use crate::{update_version_in_groovy, update_version_in_kts};
10
11#[derive(Debug)]
12pub struct GradlePackage {
13 name: Option<String>,
14 version: Option<String>,
15 path: PathBuf,
16 relative_path: PathBuf,
17 is_changed: bool,
18 dependencies: HashSet<String>,
19}
20
21impl GradlePackage {
22 #[must_use]
23 pub fn new(
24 name: Option<String>,
25 version: Option<String>,
26 path: PathBuf,
27 relative_path: PathBuf,
28 ) -> Self {
29 Self {
30 name,
31 version,
32 path,
33 relative_path,
34 is_changed: false,
35 dependencies: HashSet::new(),
36 }
37 }
38}
39
40#[async_trait]
41impl Package for GradlePackage {
42 fn name(&self) -> Option<&str> {
43 self.name.as_deref()
44 }
45
46 fn version(&self) -> Option<&str> {
47 self.version.as_deref()
48 }
49
50 fn path(&self) -> &Path {
51 &self.path
52 }
53
54 fn relative_path(&self) -> &Path {
55 &self.relative_path
56 }
57
58 async fn update_version(&mut self, update_type: UpdateType) -> Result<()> {
59 let current_version = self.version.as_deref().unwrap_or("0.0.0");
60 let new_version = next_version(current_version, update_type)?;
61
62 let content = read_to_string(&self.path).await?;
63 let file_name = self
64 .path
65 .file_name()
66 .and_then(|f| f.to_str())
67 .unwrap_or_default();
68 let is_kts = Path::new(file_name)
69 .extension()
70 .is_some_and(|ext| ext.eq_ignore_ascii_case("kts"));
71
72 let updated_content = if is_kts {
73 update_version_in_kts(&content, &new_version)
74 } else {
75 update_version_in_groovy(&content, &new_version)
76 };
77
78 write(&self.path, updated_content).await?;
79 self.version = Some(new_version);
80 Ok(())
81 }
82
83 fn language(&self) -> Language {
84 Language::Java
85 }
86
87 fn set_changed(&mut self, changed: bool) {
88 self.is_changed = changed;
89 }
90
91 fn is_changed(&self) -> bool {
92 self.is_changed
93 }
94
95 fn set_name(&mut self, name: String) {
96 self.name = Some(name);
97 }
98
99 #[cfg(windows)]
100 fn default_publish_command(&self) -> String {
101 ".\\gradlew.bat publish".to_string()
102 }
103
104 #[cfg(not(windows))]
105 fn default_publish_command(&self) -> String {
106 "./gradlew publish".to_string()
107 }
108
109 #[cfg(windows)]
116 fn default_dry_run_publish_command(&self) -> Option<String> {
117 Some(".\\gradlew.bat publishToMavenLocal".to_string())
118 }
119
120 #[cfg(not(windows))]
121 fn default_dry_run_publish_command(&self) -> Option<String> {
122 Some("./gradlew publishToMavenLocal".to_string())
123 }
124
125 fn dependencies(&self) -> &HashSet<String> {
126 &self.dependencies
127 }
128
129 fn add_dependency(&mut self, dependency: &str) {
130 self.dependencies.insert(dependency.to_string());
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use changepacks_core::UpdateType;
138 use std::fs;
139 use tempfile::TempDir;
140 use tokio::fs::read_to_string;
141
142 #[tokio::test]
143 async fn test_gradle_package_new() {
144 let package = GradlePackage::new(
145 Some("test-package".to_string()),
146 Some("1.0.0".to_string()),
147 PathBuf::from("/test/build.gradle.kts"),
148 PathBuf::from("test/build.gradle.kts"),
149 );
150
151 assert_eq!(package.name(), Some("test-package"));
152 assert_eq!(package.version(), Some("1.0.0"));
153 assert_eq!(package.path(), PathBuf::from("/test/build.gradle.kts"));
154 assert_eq!(
155 package.relative_path(),
156 PathBuf::from("test/build.gradle.kts")
157 );
158 assert_eq!(package.language(), Language::Java);
159 assert!(!package.is_changed());
160 #[cfg(windows)]
161 {
162 assert_eq!(package.default_publish_command(), ".\\gradlew.bat publish");
163 assert_eq!(
164 package.default_dry_run_publish_command().as_deref(),
165 Some(".\\gradlew.bat publishToMavenLocal")
166 );
167 }
168 #[cfg(not(windows))]
169 {
170 assert_eq!(package.default_publish_command(), "./gradlew publish");
171 assert_eq!(
172 package.default_dry_run_publish_command().as_deref(),
173 Some("./gradlew publishToMavenLocal")
174 );
175 }
176 }
177
178 #[tokio::test]
179 async fn test_gradle_package_set_changed() {
180 let mut package = GradlePackage::new(
181 Some("test-package".to_string()),
182 Some("1.0.0".to_string()),
183 PathBuf::from("/test/build.gradle.kts"),
184 PathBuf::from("test/build.gradle.kts"),
185 );
186
187 assert!(!package.is_changed());
188 package.set_changed(true);
189 assert!(package.is_changed());
190 package.set_changed(false);
191 assert!(!package.is_changed());
192 }
193
194 #[tokio::test]
195 async fn test_gradle_package_update_version_kts_patch() {
196 let temp_dir = TempDir::new().unwrap();
197 let project_dir = temp_dir.path().join("myproject");
198 fs::create_dir_all(&project_dir).unwrap();
199
200 let build_gradle = project_dir.join("build.gradle.kts");
201 fs::write(
202 &build_gradle,
203 r#"
204plugins {
205 id("java")
206}
207
208group = "com.example"
209version = "1.0.0"
210"#,
211 )
212 .unwrap();
213
214 let mut package = GradlePackage::new(
215 Some("myproject".to_string()),
216 Some("1.0.0".to_string()),
217 build_gradle.clone(),
218 PathBuf::from("myproject/build.gradle.kts"),
219 );
220
221 package.update_version(UpdateType::Patch).await.unwrap();
222
223 let content = read_to_string(&build_gradle).await.unwrap();
224 assert!(content.contains(r#"version = "1.0.1""#));
225
226 temp_dir.close().unwrap();
227 }
228
229 #[tokio::test]
230 async fn test_gradle_package_update_version_kts_minor() {
231 let temp_dir = TempDir::new().unwrap();
232 let project_dir = temp_dir.path().join("myproject");
233 fs::create_dir_all(&project_dir).unwrap();
234
235 let build_gradle = project_dir.join("build.gradle.kts");
236 fs::write(
237 &build_gradle,
238 r#"
239plugins {
240 id("java")
241}
242
243group = "com.example"
244version = "1.0.0"
245"#,
246 )
247 .unwrap();
248
249 let mut package = GradlePackage::new(
250 Some("myproject".to_string()),
251 Some("1.0.0".to_string()),
252 build_gradle.clone(),
253 PathBuf::from("myproject/build.gradle.kts"),
254 );
255
256 package.update_version(UpdateType::Minor).await.unwrap();
257
258 let content = read_to_string(&build_gradle).await.unwrap();
259 assert!(content.contains(r#"version = "1.1.0""#));
260
261 temp_dir.close().unwrap();
262 }
263
264 #[tokio::test]
265 async fn test_gradle_package_update_version_kts_major() {
266 let temp_dir = TempDir::new().unwrap();
267 let project_dir = temp_dir.path().join("myproject");
268 fs::create_dir_all(&project_dir).unwrap();
269
270 let build_gradle = project_dir.join("build.gradle.kts");
271 fs::write(
272 &build_gradle,
273 r#"
274plugins {
275 id("java")
276}
277
278group = "com.example"
279version = "1.0.0"
280"#,
281 )
282 .unwrap();
283
284 let mut package = GradlePackage::new(
285 Some("myproject".to_string()),
286 Some("1.0.0".to_string()),
287 build_gradle.clone(),
288 PathBuf::from("myproject/build.gradle.kts"),
289 );
290
291 package.update_version(UpdateType::Major).await.unwrap();
292
293 let content = read_to_string(&build_gradle).await.unwrap();
294 assert!(content.contains(r#"version = "2.0.0""#));
295
296 temp_dir.close().unwrap();
297 }
298
299 #[tokio::test]
300 async fn test_gradle_package_update_version_groovy() {
301 let temp_dir = TempDir::new().unwrap();
302 let project_dir = temp_dir.path().join("myproject");
303 fs::create_dir_all(&project_dir).unwrap();
304
305 let build_gradle = project_dir.join("build.gradle");
306 fs::write(
307 &build_gradle,
308 r#"
309plugins {
310 id 'java'
311}
312
313group = 'com.example'
314version = '1.0.0'
315"#,
316 )
317 .unwrap();
318
319 let mut package = GradlePackage::new(
320 Some("myproject".to_string()),
321 Some("1.0.0".to_string()),
322 build_gradle.clone(),
323 PathBuf::from("myproject/build.gradle"),
324 );
325
326 package.update_version(UpdateType::Patch).await.unwrap();
327
328 let content = read_to_string(&build_gradle).await.unwrap();
329 assert!(content.contains("version = '1.0.1'"));
330
331 temp_dir.close().unwrap();
332 }
333
334 #[tokio::test]
335 async fn test_gradle_package_update_version_with_fallback() {
336 let temp_dir = TempDir::new().unwrap();
337 let project_dir = temp_dir.path().join("myproject");
338 fs::create_dir_all(&project_dir).unwrap();
339
340 let build_gradle = project_dir.join("build.gradle.kts");
341 fs::write(
342 &build_gradle,
343 r#"
344group = "com.devfive"
345version = project.findProperty("releaseVersion") ?: "1.0.11"
346"#,
347 )
348 .unwrap();
349
350 let mut package = GradlePackage::new(
351 Some("myproject".to_string()),
352 Some("1.0.11".to_string()),
353 build_gradle.clone(),
354 PathBuf::from("myproject/build.gradle.kts"),
355 );
356
357 package.update_version(UpdateType::Patch).await.unwrap();
358
359 let content = read_to_string(&build_gradle).await.unwrap();
360 assert!(content.contains(r#"?: "1.0.12""#));
361
362 temp_dir.close().unwrap();
363 }
364
365 #[test]
366 fn test_gradle_package_dependencies() {
367 let mut package = GradlePackage::new(
368 Some("test-package".to_string()),
369 Some("1.0.0".to_string()),
370 PathBuf::from("/test/build.gradle.kts"),
371 PathBuf::from("test/build.gradle.kts"),
372 );
373
374 assert!(package.dependencies().is_empty());
376
377 package.add_dependency("core");
379 package.add_dependency("utils");
380
381 let deps = package.dependencies();
382 assert_eq!(deps.len(), 2);
383 assert!(deps.contains("core"));
384 assert!(deps.contains("utils"));
385
386 package.add_dependency("core");
388 assert_eq!(package.dependencies().len(), 2);
389 }
390
391 #[test]
392 fn test_set_name() {
393 let mut package = GradlePackage::new(
394 None,
395 Some("1.0.0".to_string()),
396 PathBuf::from("/test/build.gradle.kts"),
397 PathBuf::from("build.gradle.kts"),
398 );
399 assert_eq!(package.name(), None);
400 package.set_name("my-project".to_string());
401 assert_eq!(package.name(), Some("my-project"));
402 }
403}