From 1a63a0e6a05f9186f0babe9ab6000d254b020937 Mon Sep 17 00:00:00 2001 From: AhmadKharfan Date: Thu, 30 Jul 2026 01:19:35 +0300 Subject: [PATCH 1/3] move the active build contract into the domain. --- .../androidstudiolite/di/BuildRunModule.kt | 2 +- .../data/remote/ActiveBuildStore.kt | 25 ++------------- .../domain/buildsystem/ActiveBuild.kt | 32 +++++++++++++++++++ .../install/InstallStatusReceiver.kt | 2 +- .../feature/buildrun/BuildRunCoordinator.kt | 12 +++---- .../buildrun/BuildInstallOrchestrationTest.kt | 4 +-- ...BuildStartAdmissionCharacterizationTest.kt | 4 +-- 7 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/ActiveBuild.kt diff --git a/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/BuildRunModule.kt b/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/BuildRunModule.kt index a69ee67c..bf931c73 100644 --- a/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/BuildRunModule.kt +++ b/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/BuildRunModule.kt @@ -7,7 +7,7 @@ import androidx.datastore.preferences.preferencesDataStore import com.ahmadkharfan.androidstudiolite.data.buildsystem.install.ApkInstaller import com.ahmadkharfan.androidstudiolite.data.buildsystem.signing.AndroidKeystoreManager import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuildStore -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuildRepository +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuildRepository import com.ahmadkharfan.androidstudiolite.data.remote.RemoteBuildSystem import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildSystem import com.ahmadkharfan.androidstudiolite.domain.repository.GitRepository diff --git a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/ActiveBuildStore.kt b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/ActiveBuildStore.kt index c65a3dd6..b25ba9db 100644 --- a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/ActiveBuildStore.kt +++ b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/ActiveBuildStore.kt @@ -1,5 +1,7 @@ package com.ahmadkharfan.androidstudiolite.data.remote +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuild +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuildRepository import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey @@ -11,29 +13,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map -data class ActiveBuild( - val buildId: String, - val operationId: String = buildId, - val projectId: String, - val projectRootPath: String, - val projectName: String, - val installAfterSuccess: Boolean, - val autoLaunchAfterInstall: Boolean = true, - val startedAtEpochMs: Long, - val modulePath: String = "", - val variantName: String = "", - val kind: String = "ASSEMBLE", - val taskPath: String? = null, - val buildType: String? = null, -) - -interface ActiveBuildRepository { - fun observe(): Flow - suspend fun get(): ActiveBuild? - suspend fun save(build: ActiveBuild) - suspend fun clear(buildId: String? = null) -} - class ActiveBuildStore( private val dataStore: DataStore, ) : ActiveBuildRepository { diff --git a/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/ActiveBuild.kt b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/ActiveBuild.kt new file mode 100644 index 00000000..f5f9cb3c --- /dev/null +++ b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/ActiveBuild.kt @@ -0,0 +1,32 @@ +package com.ahmadkharfan.androidstudiolite.domain.buildsystem + +import kotlinx.coroutines.flow.Flow + +/** + * A build that has been admitted and is expected to still be running, persisted so the app can + * re-attach to it after process death. + */ +data class ActiveBuild( + val buildId: String, + val operationId: String = buildId, + val projectId: String, + val projectRootPath: String, + val projectName: String, + val installAfterSuccess: Boolean, + val autoLaunchAfterInstall: Boolean = true, + val startedAtEpochMs: Long, + val modulePath: String = "", + val variantName: String = "", + val kind: String = "ASSEMBLE", + val taskPath: String? = null, + val buildType: String? = null, +) + +interface ActiveBuildRepository { + fun observe(): Flow + suspend fun get(): ActiveBuild? + suspend fun save(build: ActiveBuild) + + /** Clears the stored build, or only [buildId] when given, so a stale writer cannot clobber a newer build. */ + suspend fun clear(buildId: String? = null) +} diff --git a/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/install/InstallStatusReceiver.kt b/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/install/InstallStatusReceiver.kt index 47f9dcad..109078e7 100644 --- a/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/install/InstallStatusReceiver.kt +++ b/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/install/InstallStatusReceiver.kt @@ -6,7 +6,7 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller import android.os.Build -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuildRepository +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuildRepository import com.ahmadkharfan.androidstudiolite.feature.buildrun.BuildRunCoordinator import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.CoroutineScope diff --git a/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildRunCoordinator.kt b/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildRunCoordinator.kt index 7ef3b761..c2dd7ae9 100644 --- a/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildRunCoordinator.kt +++ b/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildRunCoordinator.kt @@ -3,9 +3,9 @@ package com.ahmadkharfan.androidstudiolite.feature.buildrun import android.content.Context import com.ahmadkharfan.androidstudiolite.data.buildsystem.install.ApkInstaller import com.ahmadkharfan.androidstudiolite.data.buildsystem.install.UninstallEvent -import com.ahmadkharfan.androidstudiolite.data.gradle.GradleProjectReader -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuild -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuildRepository +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.GradleProjectInspector +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuild +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuildRepository import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildEvent import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildKind import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildRequest @@ -51,7 +51,7 @@ class BuildRunCoordinator internal constructor( private val buildSystem: BuildSystem, private val keystoreManager: KeystoreManager, private val installOperations: BuildInstallOperations, - private val gradleReader: GradleProjectReader, + private val gradleReader: GradleProjectInspector, private val notifier: BuildNotifier, private val activeBuildStore: ActiveBuildRepository, private val clock: AslClock, @@ -65,7 +65,7 @@ class BuildRunCoordinator internal constructor( buildSystem: BuildSystem, keystoreManager: KeystoreManager, apkInstaller: ApkInstaller, - gradleReader: GradleProjectReader, + gradleReader: GradleProjectInspector, notifier: BuildNotifier, activeBuildStore: ActiveBuildRepository, clock: AslClock = SystemAslClock, @@ -447,7 +447,7 @@ class BuildRunCoordinator internal constructor( override suspend fun preflight(projectRoot: File): BuildPreflightResult = withContext(Dispatchers.IO) { val versions = runCatching { - val read = gradleReader.read(projectRoot) + val read = gradleReader.inspect(projectRoot) ToolchainVersions( gradle = read.gradleVersion, agp = read.agpVersion, diff --git a/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallOrchestrationTest.kt b/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallOrchestrationTest.kt index d4c058b1..28b8ba8a 100644 --- a/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallOrchestrationTest.kt +++ b/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallOrchestrationTest.kt @@ -3,8 +3,8 @@ package com.ahmadkharfan.androidstudiolite.feature.buildrun import android.content.ContextWrapper import com.ahmadkharfan.androidstudiolite.data.buildsystem.install.InstallEvent import com.ahmadkharfan.androidstudiolite.data.gradle.GradleProjectReader -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuild -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuildRepository +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuild +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuildRepository import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildEvent import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildKind import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildRequest diff --git a/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildStartAdmissionCharacterizationTest.kt b/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildStartAdmissionCharacterizationTest.kt index cf37202f..e227a348 100644 --- a/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildStartAdmissionCharacterizationTest.kt +++ b/feature/buildrun/src/test/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildStartAdmissionCharacterizationTest.kt @@ -4,8 +4,8 @@ import android.content.ComponentName import android.content.ContextWrapper import android.content.Intent import com.ahmadkharfan.androidstudiolite.data.gradle.GradleProjectReader -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuild -import com.ahmadkharfan.androidstudiolite.data.remote.ActiveBuildRepository +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuild +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ActiveBuildRepository import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildEvent import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildKind import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildRequest From b155d5c1cb17c56ea416c3972c636d2bde139725 Mon Sep 17 00:00:00 2001 From: AhmadKharfan Date: Thu, 30 Jul 2026 01:19:36 +0300 Subject: [PATCH 2/3] move the ai provider catalog into the domain. --- .../com/ahmadkharfan/androidstudiolite/data/ai/AiLlmGateway.kt | 1 + .../androidstudiolite/data/ai/RealAiAgentRepository.kt | 1 + .../androidstudiolite/data/ai/RealAiChatRepository.kt | 1 + .../androidstudiolite/data/ai/llm/LlmProviderRegistry.kt | 2 +- .../androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt | 2 +- .../androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt | 2 +- .../androidstudiolite/domain/model}/AiProviderCatalog.kt | 2 +- feature/settings/build.gradle.kts | 1 - .../feature/settings/root/SettingsSearchIndex.kt | 2 +- 9 files changed, 8 insertions(+), 6 deletions(-) rename {data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai => domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/model}/AiProviderCatalog.kt (97%) diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/AiLlmGateway.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/AiLlmGateway.kt index e3bcecab..eb175865 100644 --- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/AiLlmGateway.kt +++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/AiLlmGateway.kt @@ -1,5 +1,6 @@ package com.ahmadkharfan.androidstudiolite.data.ai +import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderCatalog import com.ahmadkharfan.androidstudiolite.data.ai.agent.AiAgentLog import com.ahmadkharfan.androidstudiolite.data.ai.llm.LlmChatRequest import com.ahmadkharfan.androidstudiolite.data.ai.llm.LlmProviderRegistry diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiAgentRepository.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiAgentRepository.kt index 331d27ed..ba1b93d2 100644 --- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiAgentRepository.kt +++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiAgentRepository.kt @@ -1,5 +1,6 @@ package com.ahmadkharfan.androidstudiolite.data.ai +import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderCatalog import com.ahmadkharfan.androidstudiolite.domain.model.AiAgentSettings import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderConfig import com.ahmadkharfan.androidstudiolite.domain.model.ApiKeyStatus diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiChatRepository.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiChatRepository.kt index d2fd2776..65c55ce6 100644 --- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiChatRepository.kt +++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/RealAiChatRepository.kt @@ -1,5 +1,6 @@ package com.ahmadkharfan.androidstudiolite.data.ai +import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderCatalog import com.ahmadkharfan.androidstudiolite.data.ai.agent.AgentProtocol import com.ahmadkharfan.androidstudiolite.data.ai.agent.AgentToolExecutor import com.ahmadkharfan.androidstudiolite.data.ai.agent.AiAgentLog diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt index 35164ff3..67d1beb0 100644 --- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt +++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt @@ -1,7 +1,7 @@ package com.ahmadkharfan.androidstudiolite.data.ai.llm import com.ahmadkharfan.androidstudiolite.data.ai.AiLlmException -import com.ahmadkharfan.androidstudiolite.data.ai.AiProviderCatalog +import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderCatalog internal class LlmProviderRegistry(http: LlmHttpClient) { diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt index 1ecff3ba..76bdcf69 100644 --- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt +++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt @@ -1,7 +1,7 @@ package com.ahmadkharfan.androidstudiolite.data.ai.llm import com.ahmadkharfan.androidstudiolite.data.ai.AiLlmException -import com.ahmadkharfan.androidstudiolite.data.ai.AiProviderCatalog +import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderCatalog import com.ahmadkharfan.androidstudiolite.data.ai.LlmChatTurn import com.ahmadkharfan.androidstudiolite.domain.model.ChatRole import kotlinx.serialization.Serializable diff --git a/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt b/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt index b479c72c..3598f859 100644 --- a/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt +++ b/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt @@ -2,7 +2,7 @@ package com.ahmadkharfan.androidstudiolite.data.ai.llm import com.ahmadkharfan.androidstudiolite.data.ai.AiLlmException import com.ahmadkharfan.androidstudiolite.data.ai.AiLlmGateway -import com.ahmadkharfan.androidstudiolite.data.ai.AiProviderCatalog +import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderCatalog import com.ahmadkharfan.androidstudiolite.data.ai.LlmChatTurn import com.ahmadkharfan.androidstudiolite.domain.model.ChatRole import okhttp3.Request diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/AiProviderCatalog.kt b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/model/AiProviderCatalog.kt similarity index 97% rename from data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/AiProviderCatalog.kt rename to domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/model/AiProviderCatalog.kt index 3daf1e16..992dda05 100644 --- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/AiProviderCatalog.kt +++ b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/model/AiProviderCatalog.kt @@ -1,4 +1,4 @@ -package com.ahmadkharfan.androidstudiolite.data.ai +package com.ahmadkharfan.androidstudiolite.domain.model data class AiProviderDefinition( val id: String, diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 98f88860..ae19b76f 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -13,7 +13,6 @@ dependencies { implementation(projects.domain) implementation(projects.core.common) implementation(projects.designsystem) - implementation(projects.data.ai) implementation(projects.feature.git) implementation(libs.koin.android) implementation(libs.koin.androidx.compose) diff --git a/feature/settings/src/main/java/com/ahmadkharfan/androidstudiolite/feature/settings/root/SettingsSearchIndex.kt b/feature/settings/src/main/java/com/ahmadkharfan/androidstudiolite/feature/settings/root/SettingsSearchIndex.kt index 952edf50..c7998257 100644 --- a/feature/settings/src/main/java/com/ahmadkharfan/androidstudiolite/feature/settings/root/SettingsSearchIndex.kt +++ b/feature/settings/src/main/java/com/ahmadkharfan/androidstudiolite/feature/settings/root/SettingsSearchIndex.kt @@ -3,7 +3,7 @@ package com.ahmadkharfan.androidstudiolite.feature.settings.root import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.ahmadkharfan.androidstudiolite.core.common.R as CommonR -import com.ahmadkharfan.androidstudiolite.data.ai.AiProviderCatalog +import com.ahmadkharfan.androidstudiolite.domain.model.AiProviderCatalog import com.ahmadkharfan.androidstudiolite.feature.settings.R data class SettingsSearchEntry( From 95104fc9c06a81e0d7005d50c969788b437820d2 Mon Sep 17 00:00:00 2001 From: AhmadKharfan Date: Thu, 30 Jul 2026 01:19:36 +0300 Subject: [PATCH 3/3] read gradle projects through a domain contract. --- .../androidstudiolite/di/GradleModule.kt | 5 ++++ .../data/gradle/GradleProjectReader.kt | 9 +++++-- .../buildsystem/GradleProjectInspector.kt | 24 +++++++++++++++++++ feature/buildrun/build.gradle.kts | 2 +- feature/buildrun/detekt-baseline.xml | 4 ++-- .../feature/buildrun/BuildInstallRunner.kt | 6 ++--- feature/editor/build.gradle.kts | 2 +- feature/editor/detekt-baseline.xml | 4 ++-- .../feature/editor/EditorBuildController.kt | 6 ++--- .../feature/editor/EditorViewModel.kt | 6 ++--- 10 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/GradleProjectInspector.kt diff --git a/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/GradleModule.kt b/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/GradleModule.kt index 9a9b1884..7aeddfb8 100644 --- a/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/GradleModule.kt +++ b/app/src/main/java/com/ahmadkharfan/androidstudiolite/di/GradleModule.kt @@ -3,10 +3,15 @@ package com.ahmadkharfan.androidstudiolite.di import com.ahmadkharfan.androidstudiolite.data.gradle.GradleProjectReader import com.ahmadkharfan.androidstudiolite.data.gradle.deps.DependencyManager import com.ahmadkharfan.androidstudiolite.data.gradle.deps.MavenDependencySearch +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.GradleProjectInspector import org.koin.dsl.module val gradleModule = module { single { GradleProjectReader() } + + // Features depend on the narrow domain contract; the data layer keeps the richer reader for its + // own use, so both resolve to the same instance rather than two parsers. + single { get() } single { DependencyManager() } single { MavenDependencySearch() } } diff --git a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/GradleProjectReader.kt b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/GradleProjectReader.kt index 20c17303..d416c14b 100644 --- a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/GradleProjectReader.kt +++ b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/GradleProjectReader.kt @@ -15,6 +15,8 @@ import com.ahmadkharfan.androidstudiolite.data.gradle.parse.GradlePropertiesPars import com.ahmadkharfan.androidstudiolite.data.gradle.parse.SettingsGradleParser import com.ahmadkharfan.androidstudiolite.data.gradle.parse.VersionCatalogParser import com.ahmadkharfan.androidstudiolite.domain.buildsystem.DependencyModel +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.GradleProjectInspector +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.GradleProjectSummary import com.ahmadkharfan.androidstudiolite.domain.buildsystem.DependencyScope import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ModuleModel import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ModuleType @@ -32,11 +34,14 @@ data class GradleProjectReadResult( val agpVersion: String? = null, ) -class GradleProjectReader { +class GradleProjectReader : GradleProjectInspector { - fun isGradleProject(dir: File): Boolean = + override fun isGradleProject(dir: File): Boolean = File(dir, "settings.gradle.kts").isFile || File(dir, "settings.gradle").isFile + override fun inspect(projectRoot: File): GradleProjectSummary = + read(projectRoot).let { GradleProjectSummary(it.model, it.gradleVersion, it.agpVersion) } + fun read(projectRoot: File): GradleProjectReadResult { val diagnostics = ArrayList() val catalog = readCatalog(projectRoot, diagnostics) diff --git a/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/GradleProjectInspector.kt b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/GradleProjectInspector.kt new file mode 100644 index 00000000..619a867b --- /dev/null +++ b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/buildsystem/GradleProjectInspector.kt @@ -0,0 +1,24 @@ +package com.ahmadkharfan.androidstudiolite.domain.buildsystem + +import java.io.File + +/** + * What a feature needs to know about a Gradle project on disk. + * + * Deliberately narrower than the data layer's full parse result: features only ever need the module + * model and the toolchain versions, so parser-specific types (diagnostics, the version catalog, raw + * `gradle.properties`) stay inside the data layer instead of leaking across the boundary. + */ +data class GradleProjectSummary( + val model: ProjectModel, + val gradleVersion: String? = null, + val agpVersion: String? = null, +) + +interface GradleProjectInspector { + + /** Whether [dir] looks like the root of a Gradle project. */ + fun isGradleProject(dir: File): Boolean + + fun inspect(projectRoot: File): GradleProjectSummary +} diff --git a/feature/buildrun/build.gradle.kts b/feature/buildrun/build.gradle.kts index f2871f82..262173a5 100644 --- a/feature/buildrun/build.gradle.kts +++ b/feature/buildrun/build.gradle.kts @@ -6,7 +6,6 @@ dependencies { implementation(platform(libs.koin.bom)) implementation(platform(libs.androidx.compose.bom)) implementation(projects.domain) - implementation(projects.data.build) implementation(libs.koin.android) implementation(libs.koin.androidx.compose) implementation(libs.androidx.compose.material3) @@ -15,5 +14,6 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.datastore.preferences) implementation(libs.kotlinx.coroutines.android) + testImplementation(projects.data.build) testImplementation(libs.junit) } diff --git a/feature/buildrun/detekt-baseline.xml b/feature/buildrun/detekt-baseline.xml index eec23d31..399f4150 100644 --- a/feature/buildrun/detekt-baseline.xml +++ b/feature/buildrun/detekt-baseline.xml @@ -5,8 +5,8 @@ CyclomaticComplexMethod:ApkInstaller.kt$ApkInstaller$fun install( apk: File, applicationId: String?, autoLaunch: Boolean, requestToken: String = UUID.randomUUID().toString(), ): Flow<InstallEvent> LongMethod:ApkInstaller.kt$ApkInstaller$fun install( apk: File, applicationId: String?, autoLaunch: Boolean, requestToken: String = UUID.randomUUID().toString(), ): Flow<InstallEvent> LongMethod:RemoteBuildKeepAliveService.kt$RemoteBuildKeepAliveService$override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int - LongParameterList:BuildRunCoordinator.kt$BuildRunCoordinator$( context: Context, buildSystem: BuildSystem, keystoreManager: KeystoreManager, apkInstaller: ApkInstaller, gradleReader: GradleProjectReader, notifier: BuildNotifier, activeBuildStore: ActiveBuildRepository, clock: AslClock = SystemAslClock, ids: IdGenerator = UuidIdGenerator, ) - LongParameterList:BuildRunCoordinator.kt$BuildRunCoordinator$( private val context: Context, private val buildSystem: BuildSystem, private val keystoreManager: KeystoreManager, private val installOperations: BuildInstallOperations, private val gradleReader: GradleProjectReader, private val notifier: BuildNotifier, private val activeBuildStore: ActiveBuildRepository, private val clock: AslClock, private val ids: IdGenerator, ) + LongParameterList:BuildRunCoordinator.kt$BuildRunCoordinator$( context: Context, buildSystem: BuildSystem, keystoreManager: KeystoreManager, apkInstaller: ApkInstaller, gradleReader: GradleProjectInspector, notifier: BuildNotifier, activeBuildStore: ActiveBuildRepository, clock: AslClock = SystemAslClock, ids: IdGenerator = UuidIdGenerator, ) + LongParameterList:BuildRunCoordinator.kt$BuildRunCoordinator$( private val context: Context, private val buildSystem: BuildSystem, private val keystoreManager: KeystoreManager, private val installOperations: BuildInstallOperations, private val gradleReader: GradleProjectInspector, private val notifier: BuildNotifier, private val activeBuildStore: ActiveBuildRepository, private val clock: AslClock, private val ids: IdGenerator, ) TooGenericExceptionCaught:ApkInstaller.kt$ApkInstaller$e: Exception TooGenericExceptionCaught:ApkInstaller.kt$ApkInstaller$error: Exception TooGenericExceptionCaught:BuildRunCoordinator.kt$BuildRunCoordinator$error: Exception diff --git a/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallRunner.kt b/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallRunner.kt index fd8052e1..1629eec3 100644 --- a/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallRunner.kt +++ b/feature/buildrun/src/main/java/com/ahmadkharfan/androidstudiolite/feature/buildrun/BuildInstallRunner.kt @@ -2,7 +2,7 @@ package com.ahmadkharfan.androidstudiolite.feature.buildrun import com.ahmadkharfan.androidstudiolite.data.buildsystem.install.InstallEvent import com.ahmadkharfan.androidstudiolite.data.buildsystem.install.UninstallEvent -import com.ahmadkharfan.androidstudiolite.data.gradle.GradleProjectReader +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.GradleProjectInspector import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildEvent import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildRequest import com.ahmadkharfan.androidstudiolite.domain.buildsystem.ModuleType @@ -27,7 +27,7 @@ internal class BuildInstallOperations( internal class BuildInstallRunner( private val operations: BuildInstallOperations, - private val gradleReader: GradleProjectReader, + private val gradleReader: GradleProjectInspector, private val ids: IdGenerator, ) { suspend fun install( @@ -55,7 +55,7 @@ internal class BuildInstallRunner( private suspend fun resolveApplicationId(projectRoot: File, modulePath: String): String? = withContext(Dispatchers.IO) { runCatching { - val modules = gradleReader.read(projectRoot).model.modules + val modules = gradleReader.inspect(projectRoot).model.modules val module = modules.firstOrNull { it.path == modulePath } ?: modules.firstOrNull { it.type == ModuleType.ANDROID_APP } module?.applicationId diff --git a/feature/editor/build.gradle.kts b/feature/editor/build.gradle.kts index 2e0547b2..cc949c69 100644 --- a/feature/editor/build.gradle.kts +++ b/feature/editor/build.gradle.kts @@ -7,7 +7,6 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(projects.domain) implementation(projects.core.common) - implementation(projects.data.build) implementation(projects.designsystem) implementation(projects.feature.buildrun) implementation(projects.feature.git) @@ -22,6 +21,7 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.kotlinx.coroutines.android) implementation(libs.androidx.datastore.preferences) + testImplementation(projects.data.build) testImplementation(projects.data.local) testImplementation(libs.junit) testImplementation(libs.kotlinx.coroutines.test) diff --git a/feature/editor/detekt-baseline.xml b/feature/editor/detekt-baseline.xml index 9cf08a87..f16c9f02 100644 --- a/feature/editor/detekt-baseline.xml +++ b/feature/editor/detekt-baseline.xml @@ -27,10 +27,10 @@ LongMethod:EditorScreen.kt$@Composable private fun EditorFileOperationDialog( dialog: EditorFileOperationDialogUiState, interactionListener: EditorInteractionListener, ) LongMethod:FileTreeSearchPanel.kt$@Composable fun FileTreeSearchPanel( fileTree: List<EditorFileNodeUiModel>, onOpenFile: (id: String, name: String) -> Unit, onRevealFolder: (id: String) -> Unit, onClose: () -> Unit, modifier: Modifier = Modifier, ) LongParameterList:AslEditableCodeEditor.kt$( session: EditorSession, fontSizeSp: Int, tabSize: Int, onEdited: () -> Unit, onCaretMoved: (line: Int, column: Int) -> Unit, modifier: Modifier = Modifier, colorSchemeId: String = "darcula", fontFamilyId: String = "jetbrains", gitLineStatus: Map<Int, AslLineGit> = emptyMap(), breakpoints: Set<Int> = emptySet(), findQuery: String = "", findCurrentMatch: Int = 0, revealNonce: Int = 0, revealOffset: Int = 0, enableVolumeKeys: Boolean = true, projectIndex: ProjectSymbolIndex = ProjectSymbolIndex.EMPTY, ) - LongParameterList:EditorBuildController.kt$EditorBuildController$( private val projectId: String, private val scope: CoroutineScope, private val buildRunCoordinator: BuildRunApi, private val gradleProjectReader: GradleProjectReader, private val networkMonitor: NetworkMonitor?, private val projectRootPath: () -> String?, private val state: () -> EditorUiState, private val updateState: (EditorUiState.() -> EditorUiState) -> Unit, private val emitEffect: (EditorEffect) -> Unit, private val shouldLaunchAfterInstall: () -> Boolean, private val cancelAutoSave: () -> Unit, private val flushDirtyFiles: suspend () -> Boolean, ) + LongParameterList:EditorBuildController.kt$EditorBuildController$( private val projectId: String, private val scope: CoroutineScope, private val buildRunCoordinator: BuildRunApi, private val gradleProjectReader: GradleProjectInspector, private val networkMonitor: NetworkMonitor?, private val projectRootPath: () -> String?, private val state: () -> EditorUiState, private val updateState: (EditorUiState.() -> EditorUiState) -> Unit, private val emitEffect: (EditorEffect) -> Unit, private val shouldLaunchAfterInstall: () -> Boolean, private val cancelAutoSave: () -> Unit, private val flushDirtyFiles: suspend () -> Boolean, ) LongParameterList:EditorGitGutterController.kt$EditorGitGutterController$( private val scope: CoroutineScope, private val gitRepository: () -> GitRepository?, private val projectRootPath: () -> String?, private val latestGitFiles: () -> Map<String, GitFileState>, private val activeTabId: () -> String?, private val bufferText: (tabId: String) -> String?, private val isBufferUnchanged: (tabId: String, buffer: String) -> Boolean, private val applyMarkers: (tabId: String, markers: Map<Int, AslLineGit>) -> Unit, private val clearMarkers: (tabId: String) -> Unit, ) LongParameterList:EditorTabManager.kt$EditorTabManager$( private val scope: CoroutineScope, private val flushScope: CoroutineScope, private val fileContentRepository: FileContentRepository, private val state: () -> EditorUiState, private val updateState: (EditorUiState.() -> EditorUiState) -> Unit, private val projectRootPath: () -> String?, private val isAutoSaveEnabled: () -> Boolean, private val requestGutter: (tabId: String, immediate: Boolean) -> Unit, private val showSnackbar: (message: String) -> Unit, ) - LongParameterList:EditorViewModel.kt$EditorViewModel$( private val projectId: String, private val projectRepository: ProjectRepository, private val fileTreeRepository: FileTreeRepository, private val fileContentRepository: FileContentRepository, private val preferencesRepository: PreferencesRepository, private val gradleProjectReader: GradleProjectReader, private val buildRunCoordinator: BuildRunApi, private val networkMonitor: NetworkMonitor? = null, private val gitRepository: GitRepository? = null, private val workspaceWriteGate: WorkspaceWriteGate? = null, ) + LongParameterList:EditorViewModel.kt$EditorViewModel$( private val projectId: String, private val projectRepository: ProjectRepository, private val fileTreeRepository: FileTreeRepository, private val fileContentRepository: FileContentRepository, private val preferencesRepository: PreferencesRepository, private val gradleProjectReader: GradleProjectInspector, private val buildRunCoordinator: BuildRunApi, private val networkMonitor: NetworkMonitor? = null, private val gitRepository: GitRepository? = null, private val workspaceWriteGate: WorkspaceWriteGate? = null, ) LongParameterList:EditorWorkspaceSync.kt$EditorWorkspaceSync$( private val sessions: MutableMap<String, EditorSession>, private val fileContentRepository: FileContentRepository, private val projectRootPath: () -> String?, private val state: () -> EditorUiState, private val updateState: (EditorUiState.() -> EditorUiState) -> Unit, private val latestRootGenerations: () -> Map<String, Long>, private val refreshFileTree: suspend (expandIds: Set<String>) -> Unit, private val closeTabsUnder: (path: String) -> Unit, private val remapOpenTabs: (oldPath: String, newPath: String) -> Unit, private val requestGutter: (tabId: String, immediate: Boolean) -> Unit, ) LoopWithTooManyJumpStatements:ProjectSymbolIndexer.kt$ProjectSymbolIndexer$while LoopWithTooManyJumpStatements:SyntaxLexer.kt$XmlLexer$while diff --git a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorBuildController.kt b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorBuildController.kt index 0e9ed5ef..c00ece5a 100644 --- a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorBuildController.kt +++ b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorBuildController.kt @@ -1,7 +1,7 @@ package com.ahmadkharfan.androidstudiolite.feature.editor import com.ahmadkharfan.androidstudiolite.core.network.NetworkMonitor -import com.ahmadkharfan.androidstudiolite.data.gradle.GradleProjectReader +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.GradleProjectInspector import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildEvent import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildKind import com.ahmadkharfan.androidstudiolite.domain.buildsystem.BuildRequest @@ -27,7 +27,7 @@ class EditorBuildController( private val projectId: String, private val scope: CoroutineScope, private val buildRunCoordinator: BuildRunApi, - private val gradleProjectReader: GradleProjectReader, + private val gradleProjectReader: GradleProjectInspector, private val networkMonitor: NetworkMonitor?, private val projectRootPath: () -> String?, private val state: () -> EditorUiState, @@ -243,7 +243,7 @@ class EditorBuildController( private fun projectModelFor(root: File): ProjectModel? = cachedProjectModel ?.takeIf { it.first == root.absolutePath } ?.second - ?: runCatching { gradleProjectReader.read(root).model } + ?: runCatching { gradleProjectReader.inspect(root).model } .getOrNull() ?.also { cachedProjectModel = root.absolutePath to it } diff --git a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorViewModel.kt b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorViewModel.kt index 2d1502fd..723a20fe 100644 --- a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorViewModel.kt +++ b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorViewModel.kt @@ -10,7 +10,7 @@ import com.ahmadkharfan.androidstudiolite.domain.repository.FileTreeRepository import com.ahmadkharfan.androidstudiolite.domain.repository.PreferencesRepository import com.ahmadkharfan.androidstudiolite.domain.repository.ProjectRepository import com.ahmadkharfan.androidstudiolite.domain.repository.GitRepository -import com.ahmadkharfan.androidstudiolite.data.gradle.GradleProjectReader +import com.ahmadkharfan.androidstudiolite.domain.buildsystem.GradleProjectInspector import com.ahmadkharfan.androidstudiolite.domain.repository.WorkspaceWriteGate import com.ahmadkharfan.androidstudiolite.feature.buildrun.BuildProblem import com.ahmadkharfan.androidstudiolite.feature.buildrun.BuildRunApi @@ -47,7 +47,7 @@ class EditorViewModel( private val fileTreeRepository: FileTreeRepository, private val fileContentRepository: FileContentRepository, private val preferencesRepository: PreferencesRepository, - private val gradleProjectReader: GradleProjectReader, + private val gradleProjectReader: GradleProjectInspector, private val buildRunCoordinator: BuildRunApi, private val networkMonitor: NetworkMonitor? = null, private val gitRepository: GitRepository? = null, @@ -259,7 +259,7 @@ class EditorViewModel( selectedVariant = "", ) } - val model = gradleProjectReader.read(root).model + val model = gradleProjectReader.inspect(root).model buildController.cacheProjectModel(root.absolutePath to model) val app = RunTargetResolver.resolveAppModule(model) val remembered = preferencesRepository.getSelectedVariant(projectId)?.takeIf { it.isNotBlank() }