diff --git a/build-action/build.gradle.kts b/build-action/build.gradle.kts new file mode 100644 index 0000000..a38bb91 --- /dev/null +++ b/build-action/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + java +} + +dependencies { + implementation(libs.gradle.tooling.api) + implementation(libs.gradle.declarative.dsl.tooling.models) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} \ No newline at end of file diff --git a/build-action/src/main/java/org/gradle/client/build/action/GetResolvedDomAction.java b/build-action/src/main/java/org/gradle/client/build/action/GetResolvedDomAction.java new file mode 100644 index 0000000..3fc03dd --- /dev/null +++ b/build-action/src/main/java/org/gradle/client/build/action/GetResolvedDomAction.java @@ -0,0 +1,59 @@ +package org.gradle.client.build.action; + +import org.gradle.client.build.model.ResolvedDomPrerequisites; +import org.gradle.declarative.dsl.schema.AnalysisSchema; +import org.gradle.declarative.dsl.tooling.models.DeclarativeSchemaModel; +import org.gradle.tooling.BuildAction; +import org.gradle.tooling.BuildController; +import org.gradle.tooling.model.gradle.GradleBuild; + +import java.io.File; + +public class GetResolvedDomAction implements BuildAction { + + @Override + public ResolvedDomPrerequisites execute(BuildController controller) { + AnalysisSchema projectSchema = getProjectSchema(controller); + String buildFileContent = getBuildFileContent(controller); + return new ResolvedDomPrerequisitesImpl(projectSchema, buildFileContent); + } + + private static AnalysisSchema getProjectSchema(BuildController controller) { + DeclarativeSchemaModel declarativeSchemaModel = controller.getModel(DeclarativeSchemaModel.class); + return declarativeSchemaModel.getProjectSchema(); + } + + private static String getBuildFileContent(BuildController controller) { + GradleBuild gradleBuild = controller.getModel(GradleBuild.class); + File randomProjectBuildFile = gradleBuild.getProjects().getAll().stream() + .map(p -> new File(p.getProjectDirectory(), "build.gradle.dcl")) + .filter(File::exists) + .findFirst() + .orElseThrow(() -> new RuntimeException("Declarative project file not found")); + + return randomProjectBuildFile.getAbsolutePath(); + + + } + + private static final class ResolvedDomPrerequisitesImpl implements ResolvedDomPrerequisites { + + private final AnalysisSchema analysisSchema; + private final String buildFilePath; + + public ResolvedDomPrerequisitesImpl(AnalysisSchema analysisSchema, String buildFilePath) { + this.analysisSchema = analysisSchema; + this.buildFilePath = buildFilePath; + } + + @Override + public AnalysisSchema getAnalysisSchema() { + return analysisSchema; + } + + @Override + public String getBuildFilePath() { + return buildFilePath; + } + } +} \ No newline at end of file diff --git a/build-action/src/main/java/org/gradle/client/build/model/ResolvedDomPrerequisites.java b/build-action/src/main/java/org/gradle/client/build/model/ResolvedDomPrerequisites.java new file mode 100644 index 0000000..78691ba --- /dev/null +++ b/build-action/src/main/java/org/gradle/client/build/model/ResolvedDomPrerequisites.java @@ -0,0 +1,13 @@ +package org.gradle.client.build.model; + +import org.gradle.declarative.dsl.schema.AnalysisSchema; + +import java.io.Serializable; + +public interface ResolvedDomPrerequisites extends Serializable { + + AnalysisSchema getAnalysisSchema(); + + String getBuildFilePath(); + +} diff --git a/gradle-client/build.gradle.kts b/gradle-client/build.gradle.kts index 2d7ecf7..2708377 100644 --- a/gradle-client/build.gradle.kts +++ b/gradle-client/build.gradle.kts @@ -35,6 +35,8 @@ kotlin { jvmMain.dependencies { + implementation(project(":build-action")) + implementation(libs.gradle.tooling.api) implementation(libs.sqldelight.extensions.coroutines) @@ -64,6 +66,7 @@ kotlin { implementation(libs.slf4j.api) implementation(libs.logback.classic) + implementation(libs.gradle.declarative.dsl.core) implementation(libs.gradle.declarative.dsl.tooling.models) runtimeOnly(libs.kotlinx.coroutines.swing) diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedComponent.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedComponent.kt index 099f49c..3e541e8 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedComponent.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedComponent.kt @@ -12,6 +12,7 @@ import org.gradle.client.core.gradle.GradleConnectionParameters import org.gradle.client.core.gradle.GradleDistribution import org.gradle.client.ui.AppDispatchers import org.gradle.client.ui.connected.actions.* +import org.gradle.tooling.BuildAction import org.gradle.tooling.GradleConnector import org.gradle.tooling.ProjectConnection import org.gradle.tooling.events.OperationType @@ -21,7 +22,6 @@ import org.slf4j.LoggerFactory import java.io.File import java.time.Duration import java.time.Instant -import kotlin.reflect.KClass private val logger = LoggerFactory.getLogger(ConnectedComponent::class.java) @@ -59,7 +59,8 @@ class ConnectedComponent( GetBuildEnvironment(), GetGradleBuild(), GetGradleProject(), - GetProjectSchema() + GetProjectSchema(), + GetResolvedDom() ) private val scope = coroutineScope(appDispatchers.main + SupervisorJob()) @@ -104,30 +105,53 @@ class ConnectedComponent( } } - fun getModel(modelType: KClass<*>) { + fun getModel(modelAction: GetModelAction) { scope.launch { model.value.requireConnected { current -> mutableModel.value = current.copy(events = emptyList(), outcome = Outcome.Building) withContext(appDispatchers.io) { - logger.atDebug().log { "Get ${modelType.simpleName} model!" } @Suppress("TooGenericExceptionCaught") try { - val result = connection.model(modelType.java) - .addArguments( - when (parameters.javaHomeDir) { - null -> "-Dorg.gradle.java.home=${System.getenv("JAVA_HOME")}" - else -> "-Dorg.gradle.java.home=${parameters.javaHomeDir}" + if (modelAction is GetModelAction.GetCompositeModelAction) { + logger.atDebug().log { "Run ${modelAction.javaClass.simpleName} build action!" } + val result = connection.action(modelAction.buildAction) + .addArguments( + when (parameters.javaHomeDir) { + null -> "-Dorg.gradle.java.home=${System.getenv("JAVA_HOME")}" + else -> "-Dorg.gradle.java.home=${parameters.javaHomeDir}" + } + ) + .addProgressListener( + newEventListener(), + OperationType.entries.toSet() - OperationType.GENERIC + ) + .run() + logger.atInfo().log { "Ran ${modelAction.javaClass.simpleName} build action: $result" } + model.value.requireConnected { model -> + withContext(appDispatchers.main) { + mutableModel.value = model.copy(outcome = Outcome.Result(result)) + } + } + } else { // todo: refactor, very ugly + val modelType = modelAction.modelType + logger.atDebug().log { "Get ${modelType.simpleName} model!" } + val result = connection.model(modelType.java) + .addArguments( + when (parameters.javaHomeDir) { + null -> "-Dorg.gradle.java.home=${System.getenv("JAVA_HOME")}" + else -> "-Dorg.gradle.java.home=${parameters.javaHomeDir}" + } + ) + .addProgressListener( + newEventListener(), + OperationType.entries.toSet() - OperationType.GENERIC + ) + .get() + logger.atInfo().log { "Got ${modelType.simpleName} model: $result" } + model.value.requireConnected { model -> + withContext(appDispatchers.main) { + mutableModel.value = model.copy(outcome = Outcome.Result(result)) } - ) - .addProgressListener( - newEventListener(), - OperationType.entries.toSet() - OperationType.GENERIC - ) - .get() - logger.atInfo().log { "Got ${modelType.simpleName} model: $result" } - model.value.requireConnected { model -> - withContext(appDispatchers.main) { - mutableModel.value = model.copy(outcome = Outcome.Result(result)) } } } catch (ex: Exception) { diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedContent.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedContent.kt index 92e9ee6..d8acf77 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedContent.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/ConnectedContent.kt @@ -64,7 +64,7 @@ private fun ConnectedMainContent(component: ConnectedComponent, model: Connectio ListItem( modifier = Modifier.selectable( selected = false, - onClick = { component.getModel(action.modelType) } + onClick = { component.getModel(action) } ), leadingContent = { Icon(Icons.Default.PlayCircle, action.displayName) }, headlineContent = { Text(action.displayName, style = MaterialTheme.typography.titleSmall) }, diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetGradleBuild.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetGradleBuild.kt index 952d3fa..f4b1150 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetGradleBuild.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetGradleBuild.kt @@ -5,6 +5,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import org.gradle.tooling.model.gradle.GradleBuild +import java.io.File +import java.io.IOException +import java.nio.file.Files class GetGradleBuild : GetModelAction { diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetModelAction.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetModelAction.kt index 2d76089..c8baaac 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetModelAction.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetModelAction.kt @@ -2,6 +2,8 @@ package org.gradle.client.ui.connected.actions import androidx.compose.foundation.layout.ColumnScope import androidx.compose.runtime.Composable +import org.gradle.declarative.dsl.schema.FqName.Empty.simpleName +import org.gradle.tooling.BuildAction import kotlin.reflect.KClass interface GetModelAction { @@ -13,4 +15,12 @@ interface GetModelAction { @Composable fun ColumnScope.ModelContent(model: T) + + interface GetCompositeModelAction : GetModelAction { + + val buildAction : BuildAction + + override val displayName: String + get() = "Run ${buildAction::class.simpleName}" + } } diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetProjectSchema.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetProjectSchema.kt index 232bbd8..5cd0897 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetProjectSchema.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetProjectSchema.kt @@ -14,7 +14,7 @@ class GetProjectSchema : GetModelAction { @Composable override fun ColumnScope.ModelContent(model: DeclarativeSchemaModel) { Text( - text = "Gradle Project", + text = "Gradle Project Schema", style = MaterialTheme.typography.titleMedium ) Text( diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetResolvedDom.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetResolvedDom.kt new file mode 100644 index 0000000..848135c --- /dev/null +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetResolvedDom.kt @@ -0,0 +1,43 @@ +package org.gradle.client.ui.connected.actions + +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import org.gradle.client.build.action.GetResolvedDomAction +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.internal.declarativedsl.analysis.analyzeEverything +import org.gradle.internal.declarativedsl.dom.resolvedDocument +import org.gradle.internal.declarativedsl.language.LanguageTreeResult +import org.gradle.internal.declarativedsl.language.SourceIdentifier +import org.gradle.internal.declarativedsl.parsing.DefaultLanguageTreeBuilder +import org.gradle.internal.declarativedsl.parsing.parse +import org.gradle.tooling.BuildAction +import java.io.File + +class GetResolvedDom : GetModelAction.GetCompositeModelAction { + + override val modelType = ResolvedDomPrerequisites::class + + override val buildAction: BuildAction = GetResolvedDomAction() + + @Composable + override fun ColumnScope.ModelContent(model: ResolvedDomPrerequisites) { + val buildFile = File(model.buildFilePath) + val buildFileContent = buildFile.readText() + val parsedLightTree = parse(buildFileContent) + val languageTreeResult = DefaultLanguageTreeBuilder().build(parsedLightTree, SourceIdentifier(buildFile.name)) + + val resolvedDocument = resolvedDocument(model.analysisSchema, languageTreeResult, analyzeEverything, true) + + Text( + text = "Gradle Resolved DOM", + style = MaterialTheme.typography.titleMedium + ) + Text( + text = "Schema: $resolvedDocument", + // todo: this is just a useless println, but the content is there, can be seen in the debugger + style = MaterialTheme.typography.labelSmall + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d33cd68..cf0fa83 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlinx-coroutines = "1.7.3" kotlinx-serialization = "1.6.2" compose-plugin = "1.6.1" decompose = "3.0.0-alpha07" -gradle-tooling = "8.9-20240515001350+0000" +gradle-tooling = "8.9-branch-jb_declarative_dsl_fix_misc_issue_related_to_models-20240517104245+0000" declarative-dsl = "8.9-branch-jb_declarative_dsl_fix_misc_issue_related_to_models-20240517104245+0000" sqldelight = "2.0.1" ktor = "2.3.9" @@ -25,6 +25,7 @@ decompose-decompose = { module = "com.arkivanov.decompose:decompose", version.re decompose-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" } essenty-lifecycle-coroutines = { module = "com.arkivanov.essenty:lifecycle-coroutines", version = "1.3.0" } gradle-tooling-api = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle-tooling" } +gradle-declarative-dsl-core = { module = "org.gradle:gradle-declarative-dsl-tooling-models", version.ref = "declarative-dsl" } gradle-declarative-dsl-tooling-models = { module = "org.gradle:gradle-declarative-dsl-core", version.ref = "declarative-dsl" } material3WindowSizeClassMultiplatform = { module = "dev.chrisbanes.material3:material3-window-size-class-multiplatform", version = "0.3.1" } materialKolor = { module = "com.materialkolor:material-kolor", version = "1.4.4" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 91590c0..d3dd671 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,6 +6,7 @@ pluginManagement { gradlePluginPortal { content { includeGroupAndSubgroups("com.gradle") + includeGroupAndSubgroups("org.gradle") includeGroupAndSubgroups("io.github.gradle") } } @@ -15,6 +16,7 @@ pluginManagement { plugins { id("com.gradle.enterprise") version "3.16.2" id("io.github.gradle.gradle-enterprise-conventions-plugin") version "0.9.1" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } dependencyResolutionManagement { @@ -47,3 +49,4 @@ require(JavaVersion.current() == JavaVersion.VERSION_17) { rootProject.name = "gradle-client-root" include(":gradle-client") +include(":build-action")