Skip to content

Commit 4b616de

Browse files
committed
feat: add AVD, system image, Gradle caches, daemons, and JDKs to workspace analyzer and cleanups
1 parent 338ca3a commit 4b616de

14 files changed

Lines changed: 383 additions & 31 deletions

File tree

‎composeApp/build.gradle.kts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ compose {
197197
iconFile.set(iconsRoot.resolve("app_logo.png"))
198198
shortcut = true
199199
appCategory = "Productivity"
200+
modules("jdk.security.auth")
200201
}
201202

202203
}

‎composeApp/compose-desktop.pro‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,13 @@
144144
-dontwarn org.codehaus.mojo.animal_sniffer.**
145145
-dontwarn com.jetbrains.SharedTextures
146146

147-
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
147+
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
148+
149+
# Required on JVM for JNA-based integrations.
150+
-keep class com.sun.jna.** { *; }
151+
-keep class * implements com.sun.jna.** { *; }
152+
153+
# Required when using FileKit Dialogs on Linux (XDG Desktop Portal / DBus).
154+
-keep class org.freedesktop.dbus.** { *; }
155+
-keep class io.github.vinceglb.filekit.dialogs.platform.xdg.** { *; }
156+
-keepattributes Signature,InnerClasses,RuntimeVisibleAnnotations

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/data/models/project/ProjectOverviewInfo.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ data class ProjectOverviewInfo(
1616
val ndkVersion: String?,
1717
val cmakeVersion: String?,
1818
val platformList: List<String>,
19+
val jdkVersion: String?,
20+
val buildDsl: String?,
1921
)

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/data/models/workspace/WorkspaceUsageAnalysis.kt‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,34 @@ enum class ResourceCategory(
4141
ANDROID_SDK_SOURCES(
4242
displayName = "Android SDK Sources",
4343
description = "Source code downloads for Android APIs, used for reference in the IDE. Safe to delete if not used."
44+
),
45+
ANDROID_AVD(
46+
displayName = "Android Virtual Devices (AVD)",
47+
description = "Configured emulator virtual devices. Deleting them removes the AVD and its virtual storage disk."
48+
),
49+
ANDROID_SYSTEM_IMAGE(
50+
displayName = "Android System Images",
51+
description = "Downloaded Android system images (grouped by API level) used to run AVDs. Older or unused images can be safely deleted."
52+
),
53+
GRADLE_DAEMON(
54+
displayName = "Gradle Daemons",
55+
description = "Gradle daemon process logs and registry files. Deleting them is completely safe and cleans up system clutter."
56+
),
57+
GRADLE_BUILD_CACHE(
58+
displayName = "Gradle Build Cache",
59+
description = "Cached task outputs for Gradle builds. Safe to delete; Gradle will rebuild tasks on demand."
60+
),
61+
GRADLE_TRANSFORMS_CACHE(
62+
displayName = "Gradle Transforms & Jars Cache",
63+
description = "Intermediate transformed dependency artifacts. Safe to delete; Gradle will regenerate them on the next compilation."
64+
),
65+
GRADLE_TEMP_FILES(
66+
displayName = "Gradle Temporary Files",
67+
description = "Leftover temporary files generated during project builds. Completely safe to delete."
68+
),
69+
GRADLE_JDK(
70+
displayName = "Gradle Toolchain JDKs",
71+
description = "JDK versions automatically downloaded by Gradle to run your projects. Deleting them is safe; Gradle will re-download them if needed."
4472
)
4573
}
4674

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/data/repository/project/helpers/ProjectOverviewAnalyzer.kt‎

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.meet.dev.analyzer.data.models.project.ModuleBuildFileInfo
55
import com.meet.dev.analyzer.data.models.project.ProjectOverviewInfo
66
import com.meet.dev.analyzer.data.models.project.SettingsGradleFileInfo
77
import com.meet.dev.analyzer.data.models.project.VersionCatalog
8+
import com.meet.dev.analyzer.data.models.project.BuildFileType
89
import com.meet.dev.analyzer.utility.crash_report.AppLogger
910
import com.meet.dev.analyzer.utility.crash_report.AppLogger.tagName
1011
import com.meet.dev.analyzer.utility.platform.FolderFileUtils
@@ -24,11 +25,16 @@ class ProjectOverviewAnalyzer {
2425

2526
fun findProjectName(): String {
2627
if (settingsGradleFileInfo == null) return projectDir.name
27-
val readLines = settingsGradleFileInfo.readLines
28-
val projectNameLine = readLines.find { it.startsWith("rootProject.name") }
29-
if (projectNameLine == null) return projectDir.name
30-
val projectName = projectNameLine.substringAfter("=").replace("\"", "").trim()
31-
return projectName
28+
val regex = Regex("""rootProject\.name\s*=\s*['"]([^'"]+)['"]""")
29+
settingsGradleFileInfo.readLines.forEach { line ->
30+
val trimmed = line.trim()
31+
if (!trimmed.startsWith("//") && !trimmed.startsWith("/*")) {
32+
regex.find(trimmed)?.let { match ->
33+
return match.groupValues[1].trim()
34+
}
35+
}
36+
}
37+
return projectDir.name
3238
}
3339

3440
val rootModuleBuildFileInfo =
@@ -208,6 +214,58 @@ class ProjectOverviewAnalyzer {
208214
return platforms.toList()
209215
}
210216

217+
fun extractJdkVersion(): String? {
218+
moduleBuildFileInfos.forEach { file ->
219+
val content = file.content
220+
221+
// Case 1: jvmToolchain(17) or jvmToolchain = 17
222+
Regex("""jvmToolchain\s*\(?\s*(\d+)\s*\)?""").find(content)?.let {
223+
return it.groupValues[1]
224+
}
225+
226+
// Case 2: languageVersion = JavaLanguageVersion.of(17)
227+
Regex("""languageVersion\s*=\s*JavaLanguageVersion\.of\s*\(\s*(\d+)\s*\)""").find(content)?.let {
228+
return it.groupValues[1]
229+
}
230+
231+
// Case 3: JavaVersion.VERSION_1_X or JavaVersion.VERSION_X
232+
Regex("""JavaVersion\.VERSION_1_(\d+)""").find(content)?.let {
233+
return it.groupValues[1]
234+
}
235+
Regex("""JavaVersion\.VERSION_(\d+)""").find(content)?.let {
236+
return it.groupValues[1]
237+
}
238+
239+
// Case 4: jvmTarget = "17" or jvmTarget = "1.8"
240+
Regex("""jvmTarget\s*=\s*["']1\.(\d+)["']""").find(content)?.let {
241+
return it.groupValues[1]
242+
}
243+
Regex("""jvmTarget\s*=\s*["'](\d+)["']""").find(content)?.let {
244+
return it.groupValues[1]
245+
}
246+
}
247+
248+
val propertiesFile = File(projectDir, "gradle.properties")
249+
if (propertiesFile.exists()) {
250+
try {
251+
val lines = propertiesFile.readLines()
252+
lines.find { it.contains("org.gradle.java.home") }?.let { line ->
253+
val path = line.substringAfter("=").trim()
254+
val dirName = File(path).name
255+
val versionRegex = Regex("""(\d+)""")
256+
versionRegex.find(dirName)?.value?.let { return it }
257+
}
258+
} catch (_: Exception) {}
259+
}
260+
261+
return null
262+
}
263+
264+
fun extractBuildDsl(): String {
265+
val hasKts = moduleBuildFileInfos.any { it.type == BuildFileType.BUILD_GRADLE_KTS }
266+
return if (hasKts) "Kotlin DSL" else "Groovy DSL"
267+
}
268+
211269
val sizeBytes = FolderFileUtils.calculateFolderSize(projectDir)
212270

213271
val projectOverviewInfo = ProjectOverviewInfo(
@@ -225,7 +283,9 @@ class ProjectOverviewAnalyzer {
225283
compileSdkVersion = extractCompileSdk(),
226284
ndkVersion = extractNdkVersion(),
227285
cmakeVersion = extractCmakeVersion(),
228-
platformList = getPlatforms()
286+
platformList = getPlatforms(),
287+
jdkVersion = extractJdkVersion(),
288+
buildDsl = extractBuildDsl()
229289
)
230290
AppLogger.d(tag = tag) { "Found project info." }
231291
AppLogger.i(tag = tag) {

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/data/repository/workspace/WorkspaceRepositoryImpl.kt‎

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.meet.dev.analyzer.data.repository.project.helpers.ProjectFileScanner
88
import com.meet.dev.analyzer.data.repository.project.helpers.ProjectOverviewAnalyzer
99
import com.meet.dev.analyzer.data.repository.project.helpers.VersionCatalogParser
1010
import com.meet.dev.analyzer.data.repository.storage.helpers.AndroidSdkAnalyzer
11+
import com.meet.dev.analyzer.data.repository.storage.helpers.AvdAnalyzer
1112
import com.meet.dev.analyzer.data.repository.storage.helpers.GradleAnalyzer
1213
import com.meet.dev.analyzer.data.repository.storage.helpers.KonanAnalyzer
1314
import com.meet.dev.analyzer.utility.crash_report.AppLogger
@@ -23,7 +24,8 @@ class WorkspaceRepositoryImpl(
2324
private val projectOverviewAnalyzer: ProjectOverviewAnalyzer,
2425
private val androidSdkAnalyzer: AndroidSdkAnalyzer,
2526
private val gradleAnalyzer: GradleAnalyzer,
26-
private val konanAnalyzer: KonanAnalyzer
27+
private val konanAnalyzer: KonanAnalyzer,
28+
private val avdAnalyzer: AvdAnalyzer
2729
) : WorkspaceRepository {
2830

2931
private val TAG = tagName(javaClass)
@@ -58,6 +60,7 @@ class WorkspaceRepositoryImpl(
5860
val usedKotlinVersions = mutableSetOf<String>()
5961
val usedNdkVersions = mutableSetOf<String>()
6062
val usedCmakeVersions = mutableSetOf<String>()
63+
val usedJdkVersions = mutableSetOf<String>()
6164

6265
projectDirs.forEachIndexed { index, projectDir ->
6366
val progressVal = 0.1f + (0.5f * (index.toFloat() / totalProjects))
@@ -93,6 +96,7 @@ class WorkspaceRepositoryImpl(
9396
overview.kotlinVersion?.let { usedKotlinVersions.add(it) }
9497
overview.ndkVersion?.let { usedNdkVersions.add(it) }
9598
overview.cmakeVersion?.let { usedCmakeVersions.add(it) }
99+
overview.jdkVersion?.let { usedJdkVersions.add(it) }
96100
}
97101

98102
AppLogger.i(TAG) {
@@ -102,7 +106,8 @@ class WorkspaceRepositoryImpl(
102106
"Gradle: $usedGradleVersions\n" +
103107
"Kotlin: $usedKotlinVersions\n" +
104108
"NDK: $usedNdkVersions\n" +
105-
"CMake: $usedCmakeVersions"
109+
"CMake: $usedCmakeVersions\n" +
110+
"JDK: $usedJdkVersions"
106111
}
107112

108113
// 2. Scan Local System Resources
@@ -316,6 +321,130 @@ class WorkspaceRepositoryImpl(
316321
if (isUsed) activeResources.add(resource) else unusedResources.add(resource)
317322
}
318323

324+
// 2.i Android Virtual Devices
325+
onProgress(0.97f, "Scanning Android Virtual Devices...")
326+
val avdInfo = avdAnalyzer.analyzeAvdData()
327+
avdInfo.avdItemList.forEach { item ->
328+
unusedResources.add(
329+
UnusedResourceItem(
330+
name = item.name,
331+
version = item.apiLevel ?: "Unknown",
332+
category = ResourceCategory.ANDROID_AVD,
333+
path = item.path,
334+
sizeBytes = item.sizeBytes,
335+
sizeFormatted = item.actualStorage,
336+
usedByProjects = emptyList()
337+
)
338+
)
339+
}
340+
341+
// 2.j Android System Images
342+
onProgress(0.98f, "Scanning Android System Images...")
343+
sdkInfo.systemImageInfo.systemImages.forEach { item ->
344+
val apiVal = apiRegex.find(item.name)?.groupValues?.get(1)
345+
?: item.name.substringAfter("android-")
346+
347+
val usedByAnyProject = usedCompileSdks.contains(apiVal) ||
348+
usedCompileSdks.any { it.contains(apiVal) } ||
349+
usedTargetSdks.contains(apiVal) ||
350+
usedTargetSdks.any { it.contains(apiVal) } ||
351+
usedMinSdks.contains(apiVal) ||
352+
usedMinSdks.any { it.contains(apiVal) }
353+
354+
val usedByAnyAvd = avdInfo.avdItemList.any { avd ->
355+
avd.apiLevel == apiVal || avd.apiLevel == "android-$apiVal"
356+
}
357+
358+
val isUsed = usedByAnyProject || usedByAnyAvd
359+
val usingProjects = if (usedByAnyProject) {
360+
projects.filter {
361+
it.compileSdkVersion == apiVal || it.compileSdkVersion?.contains(apiVal) == true ||
362+
it.targetSdkVersion == apiVal || it.targetSdkVersion?.contains(apiVal) == true ||
363+
it.minSdkVersion == apiVal || it.minSdkVersion?.contains(apiVal) == true
364+
}.map { it.projectName }
365+
} else emptyList()
366+
367+
val resource = UnusedResourceItem(
368+
name = "System Image ${item.name}",
369+
version = apiVal,
370+
category = ResourceCategory.ANDROID_SYSTEM_IMAGE,
371+
path = item.path,
372+
sizeBytes = item.sizeBytes,
373+
sizeFormatted = item.sizeReadable,
374+
usedByProjects = usingProjects
375+
)
376+
if (isUsed) activeResources.add(resource) else unusedResources.add(resource)
377+
}
378+
379+
// 2.k Gradle Daemons
380+
onProgress(0.86f, "Scanning Gradle daemons...")
381+
gradleInfo.daemonInfo.daemonItems.forEach { item ->
382+
unusedResources.add(
383+
UnusedResourceItem(
384+
name = "Gradle Daemon Logs ${item.name}",
385+
version = item.name,
386+
category = ResourceCategory.GRADLE_DAEMON,
387+
path = item.path,
388+
sizeBytes = item.sizeBytes,
389+
sizeFormatted = item.sizeReadable
390+
)
391+
)
392+
}
393+
394+
// 2.l Gradle Toolchain JDKs
395+
onProgress(0.87f, "Scanning Gradle JDKs...")
396+
gradleInfo.jdkInfo.jdkItems.forEach { item ->
397+
val version = item.name
398+
val isUsed = usedJdkVersions.any { usedVer ->
399+
version.contains(usedVer) || usedVer.contains(version)
400+
}
401+
val usingProjects = if (isUsed) {
402+
projects.filter {
403+
it.jdkVersion != null && (version.contains(it.jdkVersion) || it.jdkVersion.contains(version))
404+
}.map { it.projectName }
405+
} else emptyList()
406+
407+
val resource = UnusedResourceItem(
408+
name = "Gradle Toolchain JDK ${item.name}",
409+
version = item.name,
410+
category = ResourceCategory.GRADLE_JDK,
411+
path = item.path,
412+
sizeBytes = item.sizeBytes,
413+
sizeFormatted = item.sizeReadable,
414+
usedByProjects = usingProjects
415+
)
416+
if (isUsed) activeResources.add(resource) else unusedResources.add(resource)
417+
}
418+
419+
// 2.m Other Gradle Caches & Temp Files
420+
onProgress(0.88f, "Scanning other Gradle caches...")
421+
gradleInfo.otherGradleFolderInfo.otherGradleFolderItems.forEach { item ->
422+
val category = when (item.version) {
423+
".tmp" -> ResourceCategory.GRADLE_TEMP_FILES
424+
"build-cache-1" -> ResourceCategory.GRADLE_BUILD_CACHE
425+
"transforms-3", "jars-9" -> ResourceCategory.GRADLE_TRANSFORMS_CACHE
426+
else -> null
427+
}
428+
if (category != null) {
429+
unusedResources.add(
430+
UnusedResourceItem(
431+
name = when (item.version) {
432+
".tmp" -> "Gradle Temporary Files"
433+
"build-cache-1" -> "Gradle Local Build Cache"
434+
"transforms-3" -> "Gradle Dependency Transforms Cache"
435+
"jars-9" -> "Gradle Cached Jars"
436+
else -> item.version
437+
},
438+
version = "Global",
439+
category = category,
440+
path = item.path,
441+
sizeBytes = item.sizeBytes,
442+
sizeFormatted = item.sizeReadable
443+
)
444+
)
445+
}
446+
}
447+
319448
onProgress(1f, "Analysis Complete")
320449
WorkspaceAnalysisResult(
321450
projects = projects,
@@ -337,6 +466,13 @@ class WorkspaceRepositoryImpl(
337466
} else {
338467
val success = file.deleteRecursively()
339468
if (success) {
469+
if (file.name.endsWith(".avd", ignoreCase = true)) {
470+
val parent = file.parentFile
471+
val iniFile = File(parent, file.nameWithoutExtension + ".ini")
472+
if (iniFile.exists()) {
473+
iniFile.delete()
474+
}
475+
}
340476
Pair(true, null)
341477
} else {
342478
Pair(

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/screen/cleanbuild/CleanBuildViewModel.kt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ class CleanBuildViewModel(
278278
expandedProjects = emptySet(),
279279
selectedPath = "",
280280
deletionProgressList = emptyList(),
281-
deletionResult = ""
281+
deletionResult = "",
282+
isProjectSelectionExpanded = true
282283
)
283284
}
284285
}
@@ -344,7 +345,8 @@ class CleanBuildViewModel(
344345
isAnalyzing = false,
345346
scanProgress = 1f,
346347
scanStatus = if (result.isEmpty()) "No Gradle projects found" else "Done",
347-
expandedProjects = expandedProjects
348+
expandedProjects = expandedProjects,
349+
isProjectSelectionExpanded = if (result.isEmpty()) true else it.isProjectSelectionExpanded
348350
)
349351
}
350352
val durationMs = mark.elapsedNow().inWholeMilliseconds

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/screen/project/components/ProjectOverviewTabContent.kt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ private fun ProjectConfigurationCard(projectOverviewInfo: ProjectOverviewInfo) {
175175
ProjectDetail("Build Tools", projectOverviewInfo.buildToolsSdk),
176176
ProjectDetail("Multi-Module", if (projectOverviewInfo.isMultiModule) "Yes" else "No"),
177177
ProjectDetail("NDK", projectOverviewInfo.ndkVersion),
178-
ProjectDetail("CMake", projectOverviewInfo.cmakeVersion)
178+
ProjectDetail("CMake", projectOverviewInfo.cmakeVersion),
179+
ProjectDetail("JDK", projectOverviewInfo.jdkVersion),
180+
ProjectDetail("Build DSL", projectOverviewInfo.buildDsl)
179181
)
180182

181183
OutlinedCard(
@@ -195,7 +197,7 @@ private fun ProjectConfigurationCard(projectOverviewInfo: ProjectOverviewInfo) {
195197
)
196198
DetailGrid(
197199
items = configurationItems,
198-
columnCount = 5
200+
columnCount = 4
199201
)
200202
}
201203

0 commit comments

Comments
 (0)