From 5d31d876537848c190581ad382aef7d82034bf2a Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Sun, 3 May 2026 23:27:01 +0300 Subject: [PATCH 01/19] Introduce an agent-friendly tool for DCL introspection --- agentic-tool/build.gradle.dcl | 21 ++ .../org/gradle/client/agentic/AgenticTool.kt | 64 ++++ .../gradle/client/agentic/AnalysisUtils.kt | 22 ++ .../gradle/client/agentic/DomJsonRenderer.kt | 350 ++++++++++++++++++ .../EffectiveProjectDefinitionCommand.kt | 63 ++++ .../agentic/ProjectTypeAndFeaturesInfo.kt | 53 +++ .../gradle/client/agentic/ProjectsCommand.kt | 57 +++ .../gradle/client/agentic/SchemaCommand.kt | 223 +++++++++++ .../client/agentic/SettingsDefaultsCommand.kt | 76 ++++ .../agentic/SettingsDefinitionCommand.kt | 49 +++ build-action/build.gradle.dcl | 4 +- .../build/action/GetResolvedDomAction.java | 77 +++- .../build/model/ResolvedDomPrerequisites.java | 15 +- dcl-utils/build.gradle.dcl | 8 + .../src/main/kotlin/DocumentOverlayUtils.kt | 16 +- .../client/core/gradle/dcl/ErrorUtil.kt | 6 +- gradle-client/build.gradle.dcl | 15 +- .../client/core/gradle/dcl/AnalysisUtils.kt | 4 +- .../client/ui/composables/SourcesColumn.kt | 4 +- .../GetDeclarativeDocuments.kt | 24 +- .../GetDeclarativeDocumentsModel.kt | 8 +- .../ModelTreeRendering.kt | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- mutations-demo/build.gradle.dcl | 2 +- settings.gradle.dcl | 18 + 25 files changed, 1125 insertions(+), 58 deletions(-) create mode 100644 agentic-tool/build.gradle.dcl create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AgenticTool.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AnalysisUtils.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectsCommand.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SchemaCommand.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt create mode 100644 agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt create mode 100644 dcl-utils/build.gradle.dcl rename gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt => dcl-utils/src/main/kotlin/DocumentOverlayUtils.kt (92%) rename {gradle-client/src/jvmMain => dcl-utils/src/main}/kotlin/org/gradle/client/core/gradle/dcl/ErrorUtil.kt (99%) diff --git a/agentic-tool/build.gradle.dcl b/agentic-tool/build.gradle.dcl new file mode 100644 index 0000000..16a037a --- /dev/null +++ b/agentic-tool/build.gradle.dcl @@ -0,0 +1,21 @@ +kotlinApplication { + targets { + jvm { + mainClass = "org.gradle.client.agentic.AgenticToolKt" + jdkVersion = 17 + + dependencies { + implementation(project(":build-action")) + implementation(project(":dcl-utils")) + + implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") + + implementation("com.github.ajalt.clikt:clikt:5.0.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + } + } + } +} diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AgenticTool.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AgenticTool.kt new file mode 100644 index 0000000..0974c8a --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AgenticTool.kt @@ -0,0 +1,64 @@ +package org.gradle.client.agentic + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.core.NoOpCliktCommand +import com.github.ajalt.clikt.core.main +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import org.gradle.client.build.action.GetResolvedDomAction +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator +import org.gradle.tooling.BuildAction +import org.gradle.tooling.GradleConnector +import java.io.File + +fun main(args: Array) { + MainCommand() + .subcommands( + ProjectsCommand(), + EffectiveProjectDefinitionCommand(), + SettingsDefaultsCommand(), + SettingsDefinitionCommand(), + SchemaCommand() + ) + .main(args) +} + +class MainCommand : NoOpCliktCommand() { + override fun help(context: Context): String = + "An agent-friendly introspection tool for Declarative Gradle projects." + + "\nEach command outputs a JSON on the standard output and has its own ' --help'." +} + +abstract class DclCommand(name: String) : CliktCommand(name) { + protected val rootProjectDir: String by option(help = "Gradle build root project directory").default(".") + + private val domPrerequisites: BuildAction = GetResolvedDomAction() + + final override fun run() { + val connector = GradleConnector.newConnector().forProjectDirectory(File(rootProjectDir).absoluteFile) + val prerequisites = try { + connector.connect().use { connection -> + connection.action(domPrerequisites).addArguments("--no-scan", "--quiet").run() + } + } finally { + connector.disconnect() + } + + val analyzer = SimpleAnalysisEvaluator.withSchema( + prerequisites.settingsInterpretationSequence, + prerequisites.projectInterpretationSequence + ) + + val output = produceOutput(prerequisites, analyzer) + println(Json { prettyPrint = true }.encodeToString(output)) + } + + abstract fun produceOutput(prerequisites: ResolvedDomPrerequisites, analyzer: SimpleAnalysisEvaluator): JsonElement +} + + diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AnalysisUtils.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AnalysisUtils.kt new file mode 100644 index 0000000..8c6a037 --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AnalysisUtils.kt @@ -0,0 +1,22 @@ +package org.gradle.client.agentic + +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult +import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator + +fun getDomForProject( + analyzer: SimpleAnalysisEvaluator, + prerequisites: ResolvedDomPrerequisites, + projectInfo: ResolvedDomPrerequisites.ProjectInfo +): DocumentOverlayResult? { + val settingsScript = prerequisites.settingsFile.takeIf { it.canRead() }?.readText() ?: "" + val projectScript = projectInfo.projectBuildFile.takeIf { it.canRead() }?.readText() ?: "" + + val settingsResult = + analyzer.evaluate(prerequisites.settingsFile.path, settingsScript) + val projectResult = + analyzer.evaluate(projectInfo.projectBuildFile.path, projectScript) + + return AnalysisDocumentUtils.documentWithModelDefaults(settingsResult, projectResult) +} diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt new file mode 100644 index 0000000..c6a7e1d --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt @@ -0,0 +1,350 @@ +package org.gradle.client.agentic + +import kotlinx.serialization.json.JsonArrayBuilder +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.add +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject +import org.gradle.client.core.gradle.dcl.userFriendlyErrorMessages +import org.gradle.declarative.dsl.schema.ConfigureFromGetterOrigin +import org.gradle.declarative.dsl.schema.ContainerElementFactory +import org.gradle.declarative.dsl.schema.DataClass +import org.gradle.declarative.dsl.schema.DataProperty +import org.gradle.declarative.dsl.schema.DataType +import org.gradle.declarative.dsl.schema.DataType.ParameterizedTypeInstance.TypeArgument.ConcreteTypeArgument +import org.gradle.declarative.dsl.schema.DataType.ParameterizedTypeInstance.TypeArgument.StarProjection +import org.gradle.declarative.dsl.schema.DataTypeRef +import org.gradle.declarative.dsl.schema.FunctionSemantics.ConfigureSemantics +import org.gradle.declarative.dsl.schema.ProjectFeatureOrigin +import org.gradle.declarative.dsl.schema.SchemaItemMetadata +import org.gradle.declarative.dsl.schema.SchemaMemberFunction +import org.gradle.declarative.dsl.schema.UnsafeSchemaItem +import org.gradle.internal.declarativedsl.analysis.ref +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ErrorNode +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.PropertyNode +import org.gradle.internal.declarativedsl.dom.DocumentResolution +import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResolution.SuccessfulElementResolution +import org.gradle.internal.declarativedsl.dom.DocumentResolution.ValueNodeResolution.ValueFactoryResolution.ValueFactoryResolved +import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult +import org.gradle.internal.declarativedsl.dom.operations.overlay.OverlayNodeOrigin +import org.gradle.internal.declarativedsl.language.SourceData +import kotlin.collections.forEach +import kotlin.collections.orEmpty + +@Suppress("TooManyFunctions") +class DomRenderer( + val withUnusedMembers: Boolean, + val dom: DocumentOverlayResult +) { + @Suppress("ComplexMethod") + fun JsonArrayBuilder.visitNode(node: DeclarativeDocument.DocumentNode) { + when (node) { + is ElementNode -> addJsonObject { + val type = putElementHeaderAndGetType(node) + + val properties = node.content.filterIsInstance() + val unusedProperties = (type as? DataClass)?.properties?.toSet() + ?.minus(properties.mapNotNull { it.asResolved(dom)?.property }.toSet()) + .orEmpty() + + if (properties.isNotEmpty()) { + put("properties", buildJsonObject { + properties.forEach { putProperty(it) } + }) + } + if (withUnusedMembers) { + put("unusedProperties", buildJsonObject { + unusedProperties.forEach { property -> putUnusedProperty(property) } + }) + } + + val (features, nestedObjects) = + node.content.filterIsInstance().partition { isFeatureUsageElement(it) } + + if (features.isNotEmpty()) { + put("features", buildJsonArray { features.forEach { visitNode(it) } }) + } + if (nestedObjects.isNotEmpty()) { + putJsonArray("nestedObjects") { nestedObjects.forEach { visitNode(it) } } + } + + val usedFunctions = node.content.filterIsInstance() + .map(dom.overlayResolutionContainer::data) + .filterIsInstance() + .map { it.elementFactoryFunction } + .toSet() + + val unusedNestedObjects = (type as? DataClass)?.memberFunctions + ?.filter { it.semantics is ConfigureSemantics && it !in usedFunctions } + .orEmpty() + + if (withUnusedMembers) { + putJsonArray("unusedNestedObjects") { + unusedNestedObjects.forEach { putUnusedNestedObject(it) } + } + } + + val errors = node.content.filterIsInstance() + if (errors.isNotEmpty()) { + putJsonArray("errors") { + errors.forEach { putErrorNode(dom, it) } + } + } + } + + is PropertyNode -> Unit // no handling at top level + is ErrorNode -> putErrorNode(dom, node) + } + } + + private fun isFeatureUsageElement(node1: ElementNode): Boolean = + (dom.overlayResolutionContainer.data(node1) as? SuccessfulElementResolution) + ?.elementFactoryFunction?.metadata?.any { it is ProjectFeatureOrigin } == true + + private fun JsonObjectBuilder.putElementHeaderAndGetType( + node: ElementNode, + ): DataType? { + put("name", node.name) + putJsonArray("args") { + node.elementValues.forEach { putValueInline(it, dom) } + } + + val overlayOrigin = dom.overlayNodeOriginContainer.data(node) + val resolution = dom.overlayResolutionContainer.data(node) + val function = when (resolution) { + is SuccessfulElementResolution -> { + resolution.elementFactoryFunction + } + + is DocumentResolution.UnsuccessfulResolution -> { + put("isUnresolved", true) + putJsonArray("causes") { + userFriendlyErrorMessages(node, resolution).forEach(::add) + } + null + } + } + val type = if (resolution is SuccessfulElementResolution) { + resolution.elementType + } else null + + if (type != null) { + put("type", type.ref.typeName) + } + + elementMetadata(function) + + sourceLocation(overlayOrigin, node) + return type + } + + fun JsonObjectBuilder.putProperty(property: PropertyNode) { + val resolved = dom.overlayResolutionContainer.data(property) + put( + property.name, buildJsonObject { + if (property.augmentation == PropertyNode.PropertyAugmentation.Plus) { + put("augmentedWithPlusAssign", true) + } + when (resolved) { + is DocumentResolution.PropertyResolution.PropertyAssignmentResolved -> { + put("type", resolved.property.valueType.typeName) + } + + is DocumentResolution.PropertyResolution.PropertyNotAssigned -> { + put("isUnresolved", true) + putJsonArray("causes") { + userFriendlyErrorMessages(property, resolved).forEach(::add) + } + } + } + + val origin = dom.overlayNodeOriginContainer.data(property) + + sourceLocation(origin, property) + putValue(property.value, dom) + if (origin is OverlayNodeOrigin.MergedProperties) { + val effective = origin.effectivePropertiesFromUnderlay + + origin.effectivePropertiesFromOverlay + + if (effective.isNotEmpty()) { + putJsonArray("multipartEffectiveValue") { + effective.forEach { putValueInline(it.value, dom) } + } + } + + val shadowed = origin.shadowedPropertiesFromUnderlay + + origin.shadowedPropertiesFromOverlay + + if (shadowed.isNotEmpty()) { + putJsonArray("overridesValues") { + shadowed.forEach { putValueInline(it.value, dom) } + } + } + } + }) + } + + private fun JsonObjectBuilder.putUnusedProperty(property: DataProperty) { + put(property.name, buildJsonObject { + put("type", property.valueType.typeName) + }) + } + + private fun JsonArrayBuilder.putUnusedNestedObject(function: SchemaMemberFunction) { + addJsonObject { + put("name", function.simpleName) + put("type", (function.semantics as ConfigureSemantics).configuredType.typeName) + put( + "isBlock", + (function.semantics as ConfigureSemantics).configureBlockRequirement.requires + ) + if (function.parameters.isNotEmpty()) { + putJsonArray("parameters") { + function.parameters.forEach { param -> + addJsonObject { + put("name", param.name) + put("type", param.type.typeName) + } + } + } + } + } + } + + private fun JsonArrayBuilder.putErrorNode( + fullDom: DocumentOverlayResult, + node: ErrorNode + ) { + addJsonObject { + val errorResolution = fullDom.overlayResolutionContainer.data(node) + put("source", node.sourceData.text()) + sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) + putJsonArray("causes") { + userFriendlyErrorMessages(node, errorResolution).forEach(::add) + } + } + } + + private fun JsonArrayBuilder.putValueInline( + node: DeclarativeDocument.ValueNode, + fullDom: DocumentOverlayResult + ) { + when (node) { + is DeclarativeDocument.ValueNode.LiteralValueNode -> add(node.sourceData.text()) + is DeclarativeDocument.ValueNode.NamedReferenceNode -> add(node.referenceName) + is DeclarativeDocument.ValueNode.ValueFactoryNode -> addJsonObject { putValue(node, fullDom) } + } + } + + private fun JsonObjectBuilder.putValue( + node: DeclarativeDocument.ValueNode, + fullDom: DocumentOverlayResult + ) { + when (node) { + is DeclarativeDocument.ValueNode.LiteralValueNode -> put("value", node.sourceData.text()) + is DeclarativeDocument.ValueNode.NamedReferenceNode -> { + put("value", node.referenceName) + } + + is DeclarativeDocument.ValueNode.ValueFactoryNode -> putJsonObject("value") { + put("source", node.sourceData.text()) + val resolved = fullDom.overlayResolutionContainer.data(node) + if (resolved is ValueFactoryResolved) { + put("type", resolved.function.semantics.returnValueType.typeName) + } + put("valueFactory", node.factoryName + (if (node.isInfix) "(infix-notation)" else "")) + putJsonArray("args") { + node.values.forEach { + putValueInline(it, fullDom) + } + } + } + } + } + + private fun JsonObjectBuilder.sourceLocation( + overlayOrigin: OverlayNodeOrigin, + node: DeclarativeDocument.DocumentNode + ) { + when (overlayOrigin) { + is OverlayNodeOrigin.CopiedOrigin -> { + if (overlayOrigin is OverlayNodeOrigin.FromUnderlay) { + put("isDefault", true) + } + put("sourceLocation", node.sourceData.locationString) + } + + is OverlayNodeOrigin.MergedElements -> putJsonArray("sourceLocations") { + add(overlayOrigin.underlayElement.sourceData.locationString) + add(overlayOrigin.overlayElement.sourceData.locationString) + } + + is OverlayNodeOrigin.MergedProperties -> putJsonArray("sourceLocations") { + overlayOrigin.effectivePropertiesFromUnderlay.map { it.sourceData.locationString }.forEach(::add) + overlayOrigin.effectivePropertiesFromOverlay.map { it.sourceData.locationString }.forEach(::add) + } + } + } + + private fun PropertyNode.asResolved(inDom: DocumentOverlayResult) = + inDom.overlayResolutionContainer.data(this) as? DocumentResolution.PropertyResolution.PropertyAssignmentResolved + + + private val SourceData.locationString: String + get() = + "${this.sourceIdentifier.fileIdentifier}:${this.lineRange.first}" +} + +internal fun JsonObjectBuilder.elementMetadata(function: SchemaMemberFunction?) = + function?.metadata?.let(::elementMetadata) + +internal fun JsonObjectBuilder.elementMetadata(metadata: Iterable) { + metadata.forEach { metadata -> + when (metadata) { + is ProjectFeatureOrigin -> { + val isProjectType = + metadata.targetDefinitionClassName == "org.gradle.api.Project" + putJsonObject(if (isProjectType) "projectType" else "feature") { + put("name", metadata.featureName) + put("ecosystemPluginId", metadata.ecosystemPluginId) + } + } + + is ConfigureFromGetterOrigin -> Unit + is ContainerElementFactory -> Unit + is UnsafeSchemaItem -> Unit + } + } +} + +internal val DataTypeRef.typeName: String + get() = when (this) { + is DataTypeRef.Name -> this.fqName.qualifiedName + is DataTypeRef.NameWithArgs -> this.fqName.qualifiedName + "<${ + typeArguments.joinToString { arg -> + when (arg) { + is ConcreteTypeArgument -> { + arg.type.typeName + } + + is StarProjection -> "*" + } + } + }>" + + is DataTypeRef.Type -> when (val t = dataType) { + is DataType.BooleanDataType -> "Boolean" + is DataType.IntDataType -> "Int" + is DataType.LongDataType -> "Long" + is DataType.StringDataType -> "String" + is DataType.NullType -> "Nothing?" + is DataType.TypeVariableUsage -> "TypeVariable#${t.variableId}" + is DataType.UnitType -> "Unit" + } + } diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt new file mode 100644 index 0000000..5d5a323 --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt @@ -0,0 +1,63 @@ +package org.gradle.client.agentic + +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.boolean +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator +import java.io.File + +class EffectiveProjectDefinitionCommand : DclCommand("effective-project-definition") { + override fun help(context: Context): String = + "Prints the effective definition of a specified subproject. The effective definition is produced by applying " + + "the project's definition as written in the build file on top of the defaults for the project type. " + + "If a subproject path is not specified, prints the effective definition of the root project. " + + "By default, omits schema properties that have no values and nested objects that are not used." + + val subprojectPath: String by option( + help = "Gradle project path such as :foo:bar or filesystem path such as foo/bar" + ).default(":") + + val withUnusedMembers by option( + help = "Include members that are not present in the effective definition" + ).boolean().default(false) + + @Suppress("ReturnCount", "CyclomaticComplexMethod") + override fun produceOutput( + prerequisites: ResolvedDomPrerequisites, + analyzer: SimpleAnalysisEvaluator + ): JsonElement { + val projectInfo = matchingProjectInfo(prerequisites, subprojectPath) + ?: return buildJsonObject { put("error", "Project not found") } + + val fullDom = getDomForProject(analyzer, prerequisites, projectInfo) + ?: return buildJsonObject { put("error", "Could not analyze the definition") } + + return buildJsonObject { + put("gradlePath", projectInfo.projectPath) + put("settingsFile", prerequisites.settingsFile.path) + put("buildFile", projectInfo.projectBuildFile.path) + put("content", buildJsonArray { + with(DomRenderer(withUnusedMembers, fullDom)) { + fullDom.document.content.forEach { visitNode(it) } + } + }) + } + } +} + +internal fun matchingProjectInfo( + prerequisites: ResolvedDomPrerequisites, + projectPath: String +): ResolvedDomPrerequisites.ProjectInfo? = + prerequisites.projectInfos.find { + it.projectPath == projectPath + } ?: prerequisites.projectInfos.find { + it.projectDirectory.relativeToOrNull(File(".").absoluteFile)?.path?.equals(projectPath) == true + } ?: prerequisites.projectInfos.find { it.projectDirectory.absolutePath == projectPath } diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt new file mode 100644 index 0000000..c7e3d40 --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt @@ -0,0 +1,53 @@ +package org.gradle.client.agentic + +import org.gradle.declarative.dsl.schema.ProjectFeatureOrigin +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument +import org.gradle.internal.declarativedsl.dom.DocumentResolution +import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult + +internal data class ProjectTypeAndFeaturesInfo( + val projectType: ProjectFeatureOrigin?, + val projectFeatures: List, +) { + val projectTypeName = projectType?.featureName + val projectFeatureNames = projectFeatures.map { it.featureName }.toSet() +} + +@Suppress("NestedBlockDepth") +internal fun collectProjectTypeAndFeatures( + fullDom: DocumentOverlayResult, +): ProjectTypeAndFeaturesInfo { + var projectType: ProjectFeatureOrigin? = null + val projectFeatures = mutableSetOf() + + fun visitNode(node: DeclarativeDocument.DocumentNode) { + when (node) { + is DeclarativeDocument.DocumentNode.ElementNode -> { + when (val resolution = fullDom.overlayResolutionContainer.data(node)) { + is DocumentResolution.ElementResolution.ElementNotResolved -> Unit + is DocumentResolution.ElementResolution.SuccessfulElementResolution -> { + val function = resolution.elementFactoryFunction + function.metadata.filterIsInstance().forEach { metadata -> + val isProjectType = + metadata.targetDefinitionClassName == "org.gradle.api.Project" + + if (isProjectType) { + projectType = metadata + } else { + projectFeatures += metadata + } + } + } + } + node.content.forEach(::visitNode) + } + + is DeclarativeDocument.DocumentNode.ErrorNode -> Unit + is DeclarativeDocument.DocumentNode.PropertyNode -> Unit + } + } + + fullDom.document.content.forEach(::visitNode) + + return ProjectTypeAndFeaturesInfo(projectType, projectFeatures.toList()) +} diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectsCommand.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectsCommand.kt new file mode 100644 index 0000000..beb417b --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectsCommand.kt @@ -0,0 +1,57 @@ +package org.gradle.client.agentic + +import com.github.ajalt.clikt.core.Context +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.add +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator + +class ProjectsCommand : DclCommand("projects") { + + override fun help(context: Context): String = + "Lists projects in the Gradle build. For each project, provides the " + + "project path, project directory, project build file path. " + + "If a project is Declarative, detects its project type and all project features used in it." + + override fun produceOutput( + prerequisites: ResolvedDomPrerequisites, + analyzer: SimpleAnalysisEvaluator + ): JsonElement { + prerequisites.projectInfos + return buildJsonObject { + put("settingsFile", prerequisites.settingsFile.path) + + putJsonArray("projects") { + prerequisites.projectInfos.forEach { projectInfo -> + addJsonObject project@{ + put("gradlePath", projectInfo.projectPath) + put("projectDir", projectInfo.projectDirectory.path) + put("buildFile", projectInfo.projectBuildFile.path) + + if (!projectInfo.isDeclarative) { + put("buildFileIsDeclarative", false) + return@project + } + + val fullDom = getDomForProject(analyzer, prerequisites, projectInfo) + ?: return@project + + val projectTypeAndFeatures = collectProjectTypeAndFeatures(fullDom) + put("projectType", projectTypeAndFeatures.projectTypeName) + putJsonArray("features") { + projectTypeAndFeatures.projectFeatureNames.forEach { feature -> + add(feature) + } + } + } + } + } + } + } +} diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SchemaCommand.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SchemaCommand.kt new file mode 100644 index 0000000..407a7c5 --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SchemaCommand.kt @@ -0,0 +1,223 @@ +package org.gradle.client.agentic + +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.parameters.options.option +import kotlinx.serialization.json.* +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.declarative.dsl.schema.* +import org.gradle.declarative.dsl.schema.DataType.ParameterizedTypeInstance.TypeArgument.ConcreteTypeArgument +import org.gradle.declarative.dsl.schema.FunctionSemantics.ConfigureSemantics +import org.gradle.internal.declarativedsl.analysis.SchemaTypeRefContext +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator + +class SchemaCommand : DclCommand("project-schema") { + override fun help(context: Context): String = + "Prints the schema for the project types and features available in the build. " + + "If project features are specified, only their schema will be returned. " + + "If a subproject path is specified, only the schema for that subproject's " + + "project type (and its features) will be returned." + + val projectFeatures by option( + help = "Comma-separated project type or feature names for which to query settings defaults; " + + "e.g. javaLibrary,javaApplication,spotless" + + "If not specified, returns the schema for all project types and features" + ) + + val subprojectPath by option( + help = "Gradle project path such as :foo:bar or filesystem path such as foo/bar" + ) + + @Suppress("CyclomaticComplexMethod", "ReturnCount") + override fun produceOutput( + prerequisites: ResolvedDomPrerequisites, + analyzer: SimpleAnalysisEvaluator + ): JsonElement { + if (projectFeatures != null && subprojectPath != null) { + return buildJsonObject { put("error", "Subproject path and project features cannot be specified together") } + } + + val schema = + prerequisites.projectInterpretationSequence.steps.last().evaluationSchemaForStep.analysisSchema + + val projectFeaturePredicate: ((ProjectFeatureOrigin) -> Boolean)? = when { + projectFeatures != null -> { + val featureNames = commaSeparatedFeatureNames(projectFeatures); + { origin -> featureNames?.contains(origin.featureName) ?: true } + } + + subprojectPath != null -> { + val projectInfo = + matchingProjectInfo(prerequisites, subprojectPath!!) + ?: return buildJsonObject { put("error", "Project not found for path: $subprojectPath") } + + val dom = getDomForProject(analyzer, prerequisites, projectInfo) + ?: return buildJsonObject { put("error", "Failed to analyze project at path: $subprojectPath") } + + val features = collectProjectTypeAndFeatures(dom).run { projectFeatures + listOfNotNull(projectType) }; + { origin -> + features.any { + it.featureName == origin.featureName && + it.targetDefinitionClassName == origin.targetDefinitionClassName && + it.targetBuildModelClassName == origin.targetBuildModelClassName + } + } + } + + else -> null + } + + val initialTypes = if (projectFeaturePredicate != null) + matchingInitialTypes(schema, projectFeaturePredicate) + else listOf(schema.topLevelReceiverType) + + val allUsedTypes = collectTypesToInclude(schema, initialTypes) + + return buildJsonObject { + if (projectFeaturePredicate == null) { + put("topLevelTypeName", schema.topLevelReceiverType.name.qualifiedName) + } + + put("types", buildJsonArray { + allUsedTypes.forEach { type -> + add(typeInfo(type)) + } + }) + } + } + + private fun matchingInitialTypes( + schema: AnalysisSchema, + projectFeaturePredicate: (ProjectFeatureOrigin) -> Boolean + ): List = schema.dataClassTypesByFqName.values.filterIsInstance().filter { type -> + type.metadata.any { metadata -> + metadata is ProjectFeatureOrigin && projectFeaturePredicate(metadata) + } + } + + private fun typeInfo(type: DataType): JsonObject = buildJsonObject { + if (type is EnumClass) { + put("name", type.name.qualifiedName) + putJsonArray("enumEntries") { + type.entryNames.forEach { add(it) } + } + } + if (type is DataClass) { + put("name", type.name.qualifiedName) + if (type.properties.isNotEmpty()) { + put("properties", buildJsonArray { + type.properties.forEach { add(propertyInfo(it)) } + }) + } + + elementMetadata(type.metadata) + + val (features, nestedObjectFunctions) = type.memberFunctions + .filter { it.semantics is ConfigureSemantics } + .partition { it.metadata.filterIsInstance().any() } + + if (features.isNotEmpty()) { + put("features", buildJsonArray { + features.forEach { add(nestedObjectInfo(it)) } + }) + } + if (nestedObjectFunctions.isNotEmpty()) { + put("nestedObjects", buildJsonArray { + nestedObjectFunctions.forEach { add(nestedObjectInfo(it)) } + }) + } + val valueFactories = type.memberFunctions.filter { it.semantics is FunctionSemantics.Pure } + if (valueFactories.isNotEmpty()) { + put("valueFactories", buildJsonArray { + valueFactories.forEach { add(valueFactoryInfo(it)) } + }) + } + } + } + + private fun propertyInfo(property: DataProperty): JsonObject = buildJsonObject { + put("name", property.name) + put("type", property.valueType.typeName) + } + + + private fun nestedObjectInfo(function: SchemaMemberFunction): JsonObject = buildJsonObject { + put("name", function.simpleName) + elementMetadata(function) + putJsonArray("parameters") { + function.parameters.forEach { parameter -> + addJsonObject { + put("name", parameter.name) + put("type", parameter.type.typeName) + when (parameter.semantics) { + is ParameterSemantics.IdentityKey -> put("semantics", "identity-key") + else -> Unit + } + } + } + } + val semantics = function.semantics as ConfigureSemantics + put("type", (function.semantics as ConfigureSemantics).configuredType.typeName) + when (semantics) { + is FunctionSemantics.AccessAndConfigure -> + put("semantics", "configures-nested-object-or-feature") + + is FunctionSemantics.AddAndConfigure -> + put("semantics", "adds-new-object") + } + } + + private fun collectTypesToInclude( + schema: AnalysisSchema, + initial: Iterable + ): Set = with(SchemaTypeRefContext(schema)) { + + buildSet { + fun visitType(type: DataClass) { + fun maybeVisitRef(ref: DataTypeRef) { + when (val type = resolveRef(ref)) { + is DataClass -> { + visitType(type) + } + + is DataType.ParameterizedTypeInstance -> { + type.typeArguments.forEach { if (it is ConcreteTypeArgument) maybeVisitRef(it.type) } + } + + is EnumClass -> add(type) + is DataType.PrimitiveType -> Unit + } + } + + if (add(type)) { + type.properties.forEach { + maybeVisitRef(it.valueType) + } + type.memberFunctions.forEach { function -> + function.parameters.forEach { maybeVisitRef(it.type) } + val semantics = function.semantics + maybeVisitRef(semantics.returnValueType) + if (semantics is ConfigureSemantics) { + maybeVisitRef(semantics.configuredType) + } + } + } + } + + initial.forEach(::visitType) + } + } + + private fun valueFactoryInfo(function: SchemaMemberFunction): JsonObject = buildJsonObject { + put("name", function.simpleName) + put("returnType", (function.semantics as FunctionSemantics.Pure).returnValueType.typeName) + putJsonArray("parameters") { + function.parameters.forEach { parameter -> + addJsonObject { + put("name", parameter.name) + put("type", parameter.type.typeName) + } + } + } + } + +} diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt new file mode 100644 index 0000000..8cbf346 --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt @@ -0,0 +1,76 @@ +package org.gradle.client.agentic + +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.boolean +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode +import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResolution.SuccessfulElementResolution +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator +import settingsWithNoOverlayOrigin +import kotlin.contracts.contract + +class SettingsDefaultsCommand : DclCommand("settings-defaults") { + + override fun help(context: Context): String = + "Lists settings defaults for project types. " + + "If a project type list is specified, prints the defaults only for them. " + + "By default, omits schema properties that have no values and nested objects that are not used." + + val projectTypes by option( + help = "Comma-separated project type names for which to query settings defaults; " + + "e.g. javaLibrary,javaApplication." + + "If not specified, returns defaults for all project types" + ) + + val withUnusedMembers by option( + help = "Include members that are not present in the defaults" + ).boolean().default(false) + + override fun produceOutput( + prerequisites: ResolvedDomPrerequisites, + analyzer: SimpleAnalysisEvaluator + ): JsonElement { + val settingsScript = prerequisites.settingsFile.takeIf { it.canRead() }?.readText() ?: "" + + val projectTypeNames = commaSeparatedFeatureNames(projectTypes) + + val settingsResult = + analyzer.evaluate(prerequisites.settingsFile.path, settingsScript) + val dom = settingsWithNoOverlayOrigin(settingsResult) + ?: return buildJsonObject { put("error", "Model evaluation failed") } + + return buildJsonObject { + put("settingsFile", prerequisites.settingsFile.path) + put("content", buildJsonArray { + with(DomRenderer(withUnusedMembers, dom)) { + val defaultsBlocks = + dom.document.content.filterIsInstance() + .filter { + it.name == "defaults" && + dom.overlayResolutionContainer.data(it) is SuccessfulElementResolution + } + + defaultsBlocks.forEach { defaultsBlock -> + defaultsBlock.content.forEach { node -> + if (node is ElementNode && (projectTypeNames == null || node.name in projectTypeNames)) { + visitNode(node) + } + } + } + } + }) + } + } +} + +internal fun commaSeparatedFeatureNames(projectTypes: String?): Set? = projectTypes + ?.split(",") + ?.map { it.trim() } + ?.filter { it.isNotBlank() } + ?.toSet() diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt new file mode 100644 index 0000000..485c0d5 --- /dev/null +++ b/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt @@ -0,0 +1,49 @@ +package org.gradle.client.agentic + +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.boolean +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode +import org.gradle.internal.declarativedsl.dom.DocumentResolution +import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResolution.SuccessfulElementResolution +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator +import settingsWithNoOverlayOrigin + +class SettingsDefinitionCommand : DclCommand("settings-definition") { + + override fun help(context: Context): String = + "Prints the effective settings definition (including defaults declared for project types). " + + "By default, omits schema properties that have no values and nested objects that are not used." + + val withUnusedMembers by option( + help = "Include members that are not present in the definition or defaults" + ).boolean().default(false) + + override fun produceOutput( + prerequisites: ResolvedDomPrerequisites, + analyzer: SimpleAnalysisEvaluator + ): JsonElement { + val settingsScript = prerequisites.settingsFile.takeIf { it.canRead() }?.readText() ?: "" + + val settingsResult = + analyzer.evaluate(prerequisites.settingsFile.path, settingsScript) + val dom = settingsWithNoOverlayOrigin(settingsResult) + ?: return buildJsonObject { put("error", "Model evaluation failed") } + + return buildJsonObject { + put("settingsFile", prerequisites.settingsFile.path) + put("content", buildJsonArray { + with(DomRenderer(withUnusedMembers, dom)) { + dom.document.content.forEach { visitNode(it) } + } + }) + } + } +} diff --git a/build-action/build.gradle.dcl b/build-action/build.gradle.dcl index b985433..b92212c 100644 --- a/build-action/build.gradle.dcl +++ b/build-action/build.gradle.dcl @@ -2,7 +2,7 @@ javaLibrary { javaVersion = 8 dependencies { - implementation("org.gradle:gradle-tooling-api:9.5.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.5.0-milestone-1") + implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") } } 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 index 6f10bb5..4453ddd 100644 --- 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 @@ -10,6 +10,7 @@ import org.gradle.tooling.model.gradle.GradleBuild; import java.io.File; +import java.io.Serializable; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -22,43 +23,86 @@ public ResolvedDomPrerequisites execute(BuildController controller) { DeclarativeSchemaModel declarativeSchemaModel = controller.getModel(DeclarativeSchemaModel.class); InterpretationSequence settingsSchema = declarativeSchemaModel.getSettingsSequence(); InterpretationSequence projectSchema = declarativeSchemaModel.getProjectSequence(); - Pair> buildFiles = getDeclarativeBuildFiles(controller); + Pair> buildFiles = getProjects(controller); return new ResolvedDomPrerequisitesImpl(settingsSchema, projectSchema, buildFiles.getLeft(), buildFiles.getRight()); } - private static Pair> getDeclarativeBuildFiles(BuildController controller) { + private static Pair> getProjects(BuildController controller) { GradleBuild gradleBuild = controller.getModel(GradleBuild.class); File rootProjectDirectory = gradleBuild.getRootProject().getProjectDirectory(); - List declarativeBuildFiles = gradleBuild - .getProjects() - .getAll() - .stream() - .map(p -> new File(p.getProjectDirectory(), "build.gradle.dcl")) - .filter(File::exists).collect(Collectors.toList()); + List declarativeBuildFiles = gradleBuild + .getProjects() + .getAll() + .stream() + .map(p -> new ProjectInfoImpl(p.getPath(), getBuildFileInProjectDirectory(p.getProjectDirectory()))) + .collect(Collectors.toList()); if (declarativeBuildFiles.isEmpty()) { throw new RuntimeException("No declarative project file found"); } return Pair.of(rootProjectDirectory, declarativeBuildFiles); } + private static final class ProjectInfoImpl implements ResolvedDomPrerequisites.ProjectInfo, Serializable { + private final String projectPath; + private final File projectBuildFile; + + @Override + public String getProjectPath() { + return projectPath; + } + + @Override + public File getProjectBuildFile() { + return projectBuildFile; + } + + @Override + public File getProjectDirectory() { + return projectBuildFile.getParentFile(); + } + + @Override + public boolean isDeclarative() { + return projectBuildFile.getName().endsWith(".dcl") && projectBuildFile.canRead(); + } + + public ProjectInfoImpl(String projectPath, File projectBuildFile) { + this.projectPath = projectPath; + this.projectBuildFile = projectBuildFile; + } + } + + private static File getBuildFileInProjectDirectory(File projectDir) { + File dcl = new File(projectDir, "build.gradle.dcl"); + if (dcl.exists()) + return dcl; + File kts = new File(projectDir, "build.gradle.kts"); + if (kts.exists()) + return kts; + File groovy = new File(projectDir, "build.gradle"); + if (groovy.exists()) + return groovy; + + return dcl; + } private static final class ResolvedDomPrerequisitesImpl implements ResolvedDomPrerequisites { private final InterpretationSequence settingsSequence; private final InterpretationSequence projectSequence; private final File rootDir; - private final List declarativeBuildFiles; + private final List projectInfos; public ResolvedDomPrerequisitesImpl( InterpretationSequence settingsSequence, InterpretationSequence projectSequence, File rootDir, - List declarativeBuildFiles + List projectInfos ) { this.settingsSequence = settingsSequence; this.projectSequence = projectSequence; this.rootDir = rootDir; - this.declarativeBuildFiles = declarativeBuildFiles; + this.projectInfos = projectInfos; } @Override @@ -94,9 +138,16 @@ public File getSettingsFile() { @Override public List getDeclarativeFiles() { return Stream.concat( - declarativeBuildFiles.stream(), + projectInfos.stream() + .filter(f -> f.isDeclarative() && f.getProjectBuildFile().exists()) + .map(ProjectInfo::getProjectBuildFile), getSettingsFile().canRead() ? Stream.of(getSettingsFile()) : Stream.empty() ).collect(Collectors.toList()); } + + @Override + public List getProjectInfos() { + return projectInfos; + } } -} \ 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 index 2e9980c..1aff391 100644 --- 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 @@ -8,16 +8,25 @@ import java.util.List; public interface ResolvedDomPrerequisites extends Serializable { - + InterpretationSequence getSettingsInterpretationSequence(); - + InterpretationSequence getProjectInterpretationSequence(); AnalysisSchema getAnalysisSchema(); File getRootDir(); - + File getSettingsFile(); List getDeclarativeFiles(); + + List getProjectInfos(); + + interface ProjectInfo { + String getProjectPath(); + File getProjectBuildFile(); + File getProjectDirectory(); + boolean isDeclarative(); + } } diff --git a/dcl-utils/build.gradle.dcl b/dcl-utils/build.gradle.dcl new file mode 100644 index 0000000..6b03f6a --- /dev/null +++ b/dcl-utils/build.gradle.dcl @@ -0,0 +1,8 @@ +kotlinJvmLibrary { + dependencies { + implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") + } +} diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt b/dcl-utils/src/main/kotlin/DocumentOverlayUtils.kt similarity index 92% rename from gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt rename to dcl-utils/src/main/kotlin/DocumentOverlayUtils.kt index b932868..e3ffc83 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt +++ b/dcl-utils/src/main/kotlin/DocumentOverlayUtils.kt @@ -1,5 +1,3 @@ -package org.gradle.client.core.gradle.dcl - import org.gradle.internal.declarativedsl.dom.DeclarativeDocument import org.gradle.internal.declarativedsl.dom.DocumentResolution import org.gradle.internal.declarativedsl.dom.UnresolvedBase @@ -10,6 +8,7 @@ import org.gradle.internal.declarativedsl.dom.resolution.DocumentResolutionConta import org.gradle.internal.declarativedsl.dom.resolution.DocumentWithResolution import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils.resolvedDocument import org.gradle.internal.declarativedsl.evaluator.main.AnalysisSequenceResult +import org.gradle.internal.declarativedsl.evaluator.runner.AnalysisStepResult import org.gradle.internal.declarativedsl.evaluator.runner.stepResultOrPartialResult import org.gradle.internal.declarativedsl.language.SourceData import org.gradle.internal.declarativedsl.language.SyntheticallyProduced @@ -59,7 +58,8 @@ fun DeclarativeDocument.nodeAt(fileIdentifier: String, offset: Int): Declarative } fun settingsWithNoOverlayOrigin(analysisSequenceResult: AnalysisSequenceResult): DocumentOverlayResult? { - val docs = analysisSequenceResult.stepResults.map { it.value.stepResultOrPartialResult.resolvedDocument() } + val docs = analysisSequenceResult.stepResults.mapNotNull { + (it.value.stepResultOrPartialResult as? AnalysisStepResult.PassedAnalysisStepResult)?.resolvedDocument() } if (docs.isEmpty()) return null @@ -67,7 +67,7 @@ fun settingsWithNoOverlayOrigin(analysisSequenceResult: AnalysisSequenceResult): } -internal fun indexBasedOverlayResultFromDocuments(docs: List): DocumentOverlayResult { +fun indexBasedOverlayResultFromDocuments(docs: List): DocumentOverlayResult { val emptyDoc = DocumentWithResolution( object : DeclarativeDocument { override val content: List = emptyList() @@ -91,12 +91,12 @@ internal fun indexBasedOverlayResultFromDocuments(docs: List): DocumentResolutionContainer { +fun indexBasedMultiResolutionContainer(docs: List): DocumentResolutionContainer { val indicesMaps: Map> = docs.associateWith { buildMap { fun visitValue(valueNode: DeclarativeDocument.ValueNode) { @@ -156,7 +156,7 @@ internal fun indexBasedMultiResolutionContainer(docs: List data(node) is DeclarativeDocument.ValueNode -> data(node) diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/ErrorUtil.kt b/dcl-utils/src/main/kotlin/org/gradle/client/core/gradle/dcl/ErrorUtil.kt similarity index 99% rename from gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/ErrorUtil.kt rename to dcl-utils/src/main/kotlin/org/gradle/client/core/gradle/dcl/ErrorUtil.kt index e15b649..bdda2ec 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/ErrorUtil.kt +++ b/dcl-utils/src/main/kotlin/org/gradle/client/core/gradle/dcl/ErrorUtil.kt @@ -33,7 +33,7 @@ fun userFriendlyErrorMessages( } else listOf("Block mismatch") CrossScopeAccess -> listOf("Cross-scope access") - + IsError -> if (node is DeclarativeDocument.DocumentNode.ErrorNode) { node.errors.map { error -> when (error) { @@ -44,7 +44,7 @@ fun userFriendlyErrorMessages( } } } else listOf("Syntax error") - + OpaqueValueInIdentityKey -> listOf("Opaque value passed as identity key") UnresolvedName -> listOf("Unresolved name") UnresolvedSignature -> listOf("Unresolved function signature") @@ -53,7 +53,7 @@ fun userFriendlyErrorMessages( UnresolvedValueUsed -> listOf("Assigned value is not resolved") ValueTypeMismatch -> listOf("Value type mismatch") NonEnumValueNamedReference -> listOf("Illegal named reference. Only enum entries can be referenced by name") - + UnresolvedBase -> emptyList() } } diff --git a/gradle-client/build.gradle.dcl b/gradle-client/build.gradle.dcl index 6d3b21c..f57b71a 100644 --- a/gradle-client/build.gradle.dcl +++ b/gradle-client/build.gradle.dcl @@ -18,8 +18,9 @@ kotlinApplication { dependencies { implementation(project(":build-action")) implementation(project(":mutations-demo")) + implementation(project(":dcl-utils")) - implementation("org.gradle:gradle-tooling-api:9.5.0-milestone-1") + implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") implementation("com.arkivanov.decompose:decompose:3.3.0") implementation("com.arkivanov.decompose:extensions-compose:3.3.0") @@ -36,9 +37,9 @@ kotlinApplication { implementation("org.slf4j:slf4j-api:2.0.17") implementation("ch.qos.logback:logback-classic:1.5.18") - implementation("org.gradle:gradle-declarative-dsl-core:9.5.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-evaluator:9.5.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.5.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") implementation("org.jspecify:jspecify:1.0.0") runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.10.2") @@ -78,12 +79,6 @@ kotlinApplication { } } - detekt { - source = listOf(layout.projectDirectory.dir("src/jvmMain/kotlin"), layout.projectDirectory.dir("src/jvmTest/kotlin")) - config = listOf(layout.settingsDirectory.file("gradle/detekt/detekt.conf")) - parallel = true - } - compose { mainClass = "org.gradle.client.GradleClientMainKt" diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/AnalysisUtils.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/AnalysisUtils.kt index 39623ac..3cd2875 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/AnalysisUtils.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/AnalysisUtils.kt @@ -9,6 +9,7 @@ import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResoluti import org.gradle.internal.declarativedsl.dom.resolution.DocumentResolutionContainer import org.gradle.internal.declarativedsl.evaluator.main.AnalysisSequenceResult import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator +import org.gradle.internal.declarativedsl.evaluator.runner.AnalysisStepResult import org.gradle.internal.declarativedsl.evaluator.runner.stepResultOrPartialResult import org.gradle.internal.declarativedsl.language.SourceIdentifier @@ -20,9 +21,6 @@ fun analyzer( prerequisites.projectInterpretationSequence ) -fun AnalysisSequenceResult.sourceIdentifier(): SourceIdentifier = - stepResults.values.last().stepResultOrPartialResult.languageTreeResult.topLevelBlock.sourceData.sourceIdentifier - fun DeclarativeDocument.DocumentNode.ElementNode.type(resolutionContainer: DocumentResolutionContainer): DataType? = when (val resolution = resolutionContainer.data(this)) { is ElementNotResolved -> null diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/composables/SourcesColumn.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/composables/SourcesColumn.kt index c436384..d579511 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/composables/SourcesColumn.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/composables/SourcesColumn.kt @@ -2,6 +2,7 @@ package org.gradle.client.ui.composables +import ErrorData import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.fillMaxWidth @@ -18,7 +19,6 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextDecoration -import org.gradle.client.core.gradle.dcl.ErrorData import org.gradle.client.core.gradle.dcl.userFriendlyErrorMessages import org.gradle.client.ui.connected.actions.declarativedocuments.HighlightingEntry import org.gradle.client.ui.theme.spacing @@ -121,7 +121,7 @@ private fun SourceFileTitleAndText( val errorPopupText = selectedErrors?.takeIf { it.isNotEmpty() }?.let { it.flatMap { userFriendlyErrorMessages(it.documentNode, it.resolution) }.joinToString("\n") } - + if (trimmedSource != null) { var isTrimmed by remember { mutableStateOf(true) } diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/GetDeclarativeDocuments.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/GetDeclarativeDocuments.kt index 7f7c27e..8ae5bf1 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/GetDeclarativeDocuments.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/GetDeclarativeDocuments.kt @@ -8,10 +8,14 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon +import errorRanges +import indexBasedMultiResolutionContainer +import indexBasedOverlayResultFromDocuments import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import nodeAt import org.gradle.client.build.action.GetResolvedDomAction import org.gradle.client.build.model.ResolvedDomPrerequisites import org.gradle.client.core.gradle.dcl.* @@ -33,10 +37,13 @@ import org.gradle.internal.declarativedsl.dom.operations.overlay.OverlayNodeOrig import org.gradle.internal.declarativedsl.dom.operations.overlay.OverlayOriginContainer import org.gradle.internal.declarativedsl.dom.resolution.DocumentResolutionContainer import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils.resolvedDocument +import org.gradle.internal.declarativedsl.evaluator.runner.AnalysisStepResult import org.gradle.internal.declarativedsl.evaluator.runner.stepResultOrPartialResult import org.gradle.tooling.BuildAction import org.jetbrains.skiko.Cursor +import relevantRange import java.io.File +import kotlin.collections.orEmpty import kotlin.time.Duration.Companion.seconds @@ -79,7 +86,9 @@ class GetDeclarativeDocuments : GetModelAction.GetCompositeModelAction - val document = result.stepResultOrPartialResult.resolvedDocument() + val document = + (result.stepResultOrPartialResult as? AnalysisStepResult.PassedAnalysisStepResult)?.resolvedDocument() + ?: return /** * For multistep settings, we want to avoid document content appearing as duplicates in @@ -102,7 +111,8 @@ class GetDeclarativeDocuments : GetModelAction.GetCompositeModelAction>()) private fun readDocumentFile() = _selectedDocument.value.takeIf { it.canRead() }?.readText().orEmpty() private fun readSettingsFile() = model.settingsFile.takeIf { it.canRead() }?.readText().orEmpty() - + val typeRefContext = JointTypeRefContext( listOf(model.settingsInterpretationSequence, model.projectInterpretationSequence) ) @@ -42,7 +42,7 @@ internal class GetDeclarativeDocumentsModel(private val model: ResolvedDomPrereq _highlightedSourceRangeByFileId.value = entries _reportedErrors.value = emptyMap() } - + fun reportErrors(errorsByFileId: Map>) { _reportedErrors.value = errorsByFileId } diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/ModelTreeRendering.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/ModelTreeRendering.kt index f243f15..425b8d5 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/ModelTreeRendering.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/declarativedocuments/ModelTreeRendering.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.times -import org.gradle.client.core.gradle.dcl.isUnresolvedBase +import isUnresolvedBase import org.gradle.client.core.gradle.dcl.type import org.gradle.client.ui.composables.LabelMedium import org.gradle.client.ui.composables.TitleSmall diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0aa20c1..8b8bedc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-milestone-7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-milestone-1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/mutations-demo/build.gradle.dcl b/mutations-demo/build.gradle.dcl index 45546e3..d21cbda 100644 --- a/mutations-demo/build.gradle.dcl +++ b/mutations-demo/build.gradle.dcl @@ -1,5 +1,5 @@ kotlinJvmLibrary { dependencies { - implementation("org.gradle:gradle-declarative-dsl-core:9.5.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") } } diff --git a/settings.gradle.dcl b/settings.gradle.dcl index 9f7b75d..b61219b 100644 --- a/settings.gradle.dcl +++ b/settings.gradle.dcl @@ -32,3 +32,21 @@ rootProject.name = "gradle-client-root" include(":gradle-client") include(":build-action") include(":mutations-demo") +include(":agentic-tool") +include(":dcl-utils") + +defaults { + kotlinApplication { + detekt { + parallel = true + source = listOf(layout.projectDirectory.dir("src/jvmMain/kotlin"), layout.projectDirectory.dir("src/jvmTest/kotlin")) + config = listOf(layout.settingsDirectory.file("gradle/detekt/detekt.conf")) + } + } + kotlinJvmApplication { + javaVersion = 8 + } + javaLibrary { + javaVersion = 8 + } +} From 114b3bd969797749da2f227843a196e068c8d516 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Sun, 3 May 2026 23:31:02 +0300 Subject: [PATCH 02/19] Use `kotlinJvmApplication` for `agentic-tool`: it can build a distribution --- agentic-tool/build.gradle.dcl | 27 ++++++++----------- .../org/gradle/client/agentic/AgenticTool.kt | 0 .../gradle/client/agentic/AnalysisUtils.kt | 0 .../gradle/client/agentic/DomJsonRenderer.kt | 0 .../EffectiveProjectDefinitionCommand.kt | 0 .../agentic/ProjectTypeAndFeaturesInfo.kt | 0 .../gradle/client/agentic/ProjectsCommand.kt | 0 .../gradle/client/agentic/SchemaCommand.kt | 0 .../client/agentic/SettingsDefaultsCommand.kt | 0 .../agentic/SettingsDefinitionCommand.kt | 0 dcl-utils/build.gradle.dcl | 1 + settings.gradle.dcl | 3 +++ 12 files changed, 15 insertions(+), 16 deletions(-) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/AgenticTool.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/AnalysisUtils.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/ProjectsCommand.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/SchemaCommand.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt (100%) rename agentic-tool/src/{jvmMain => main}/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt (100%) diff --git a/agentic-tool/build.gradle.dcl b/agentic-tool/build.gradle.dcl index 16a037a..8b10f91 100644 --- a/agentic-tool/build.gradle.dcl +++ b/agentic-tool/build.gradle.dcl @@ -1,21 +1,16 @@ -kotlinApplication { - targets { - jvm { - mainClass = "org.gradle.client.agentic.AgenticToolKt" - jdkVersion = 17 +kotlinJvmApplication { + mainClass = "org.gradle.client.agentic.AgenticToolKt" - dependencies { - implementation(project(":build-action")) - implementation(project(":dcl-utils")) + dependencies { + implementation(project(":build-action")) + implementation(project(":dcl-utils")) - implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") + implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") - implementation("com.github.ajalt.clikt:clikt:5.0.1") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") - } - } + implementation("com.github.ajalt.clikt:clikt:5.0.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") } } diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AgenticTool.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AgenticTool.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AnalysisUtils.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AnalysisUtils.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/AnalysisUtils.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/AnalysisUtils.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypeAndFeaturesInfo.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectsCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectsCommand.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/ProjectsCommand.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectsCommand.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SchemaCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SchemaCommand.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SchemaCommand.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/SchemaCommand.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt diff --git a/agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt similarity index 100% rename from agentic-tool/src/jvmMain/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt diff --git a/dcl-utils/build.gradle.dcl b/dcl-utils/build.gradle.dcl index 6b03f6a..a835dce 100644 --- a/dcl-utils/build.gradle.dcl +++ b/dcl-utils/build.gradle.dcl @@ -1,4 +1,5 @@ kotlinJvmLibrary { + dependencies { implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") diff --git a/settings.gradle.dcl b/settings.gradle.dcl index b61219b..54234b5 100644 --- a/settings.gradle.dcl +++ b/settings.gradle.dcl @@ -46,6 +46,9 @@ defaults { kotlinJvmApplication { javaVersion = 8 } + kotlinJvmLibrary { + javaVersion = 8 + } javaLibrary { javaVersion = 8 } From 6f1beea17e574f8a740affe1ba9d9ed724b5ba95 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Sun, 3 May 2026 23:33:25 +0300 Subject: [PATCH 03/19] Minor rename `DomRenderer` -> `DomJsonRenderer` --- .../kotlin/org/gradle/client/agentic/DomJsonRenderer.kt | 2 +- .../client/agentic/EffectiveProjectDefinitionCommand.kt | 3 +-- .../org/gradle/client/agentic/SettingsDefaultsCommand.kt | 3 +-- .../org/gradle/client/agentic/SettingsDefinitionCommand.kt | 6 +----- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt index c6a7e1d..1eabacf 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt @@ -38,7 +38,7 @@ import kotlin.collections.forEach import kotlin.collections.orEmpty @Suppress("TooManyFunctions") -class DomRenderer( +class DomJsonRenderer( val withUnusedMembers: Boolean, val dom: DocumentOverlayResult ) { diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt index 5d5a323..1afe692 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt @@ -9,7 +9,6 @@ import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import org.gradle.client.build.model.ResolvedDomPrerequisites -import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator import java.io.File @@ -44,7 +43,7 @@ class EffectiveProjectDefinitionCommand : DclCommand("effective-project-definiti put("settingsFile", prerequisites.settingsFile.path) put("buildFile", projectInfo.projectBuildFile.path) put("content", buildJsonArray { - with(DomRenderer(withUnusedMembers, fullDom)) { + with(DomJsonRenderer(withUnusedMembers, fullDom)) { fullDom.document.content.forEach { visitNode(it) } } }) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt index 8cbf346..c4678e8 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt @@ -13,7 +13,6 @@ import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.E import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResolution.SuccessfulElementResolution import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator import settingsWithNoOverlayOrigin -import kotlin.contracts.contract class SettingsDefaultsCommand : DclCommand("settings-defaults") { @@ -48,7 +47,7 @@ class SettingsDefaultsCommand : DclCommand("settings-defaults") { return buildJsonObject { put("settingsFile", prerequisites.settingsFile.path) put("content", buildJsonArray { - with(DomRenderer(withUnusedMembers, dom)) { + with(DomJsonRenderer(withUnusedMembers, dom)) { val defaultsBlocks = dom.document.content.filterIsInstance() .filter { diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt index 485c0d5..5bb07f1 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt @@ -9,10 +9,6 @@ import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import org.gradle.client.build.model.ResolvedDomPrerequisites -import org.gradle.internal.declarativedsl.dom.DeclarativeDocument -import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode -import org.gradle.internal.declarativedsl.dom.DocumentResolution -import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResolution.SuccessfulElementResolution import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator import settingsWithNoOverlayOrigin @@ -40,7 +36,7 @@ class SettingsDefinitionCommand : DclCommand("settings-definition") { return buildJsonObject { put("settingsFile", prerequisites.settingsFile.path) put("content", buildJsonArray { - with(DomRenderer(withUnusedMembers, dom)) { + with(DomJsonRenderer(withUnusedMembers, dom)) { dom.document.content.forEach { visitNode(it) } } }) From e62cd6b87a50515158f707a2bff1594c3c6100a8 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 00:09:18 +0300 Subject: [PATCH 04/19] Introduce a `DocumentationProvider` stub --- .../org/gradle/client/agentic/AgenticTool.kt | 3 +- .../client/agentic/DocumentationProvider.kt | 13 ++ .../gradle/client/agentic/DomJsonRenderer.kt | 189 ++++++++++-------- .../EffectiveProjectDefinitionCommand.kt | 4 +- ...hemaCommand.kt => ProjectSchemaCommand.kt} | 42 ++-- .../client/agentic/SettingsDefaultsCommand.kt | 8 +- .../agentic/SettingsDefinitionCommand.kt | 4 +- 7 files changed, 159 insertions(+), 104 deletions(-) create mode 100644 agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt rename agentic-tool/src/main/kotlin/org/gradle/client/agentic/{SchemaCommand.kt => ProjectSchemaCommand.kt} (86%) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt index 0974c8a..5d91f63 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt @@ -23,7 +23,7 @@ fun main(args: Array) { EffectiveProjectDefinitionCommand(), SettingsDefaultsCommand(), SettingsDefinitionCommand(), - SchemaCommand() + ProjectSchemaCommand() ) .main(args) } @@ -36,6 +36,7 @@ class MainCommand : NoOpCliktCommand() { abstract class DclCommand(name: String) : CliktCommand(name) { protected val rootProjectDir: String by option(help = "Gradle build root project directory").default(".") + protected val documentationProvider: DocumentationProvider = NoopDocumentationProvider private val domPrerequisites: BuildAction = GetResolvedDomAction() diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt new file mode 100644 index 0000000..c11ec77 --- /dev/null +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt @@ -0,0 +1,13 @@ +package org.gradle.client.agentic + +interface DocumentationProvider { + fun classDocumentation(classFqName: String): String? + + // todo: use a member signature to disambiguate function overloads + fun memberDocumentation(classFqName: String, memberName: String): String? +} + +object NoopDocumentationProvider : DocumentationProvider { + override fun classDocumentation(classFqName: String): String? = null + override fun memberDocumentation(classFqName: String, memberName: String): String? = null +} diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt index 1eabacf..8deb979 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt @@ -1,6 +1,7 @@ package org.gradle.client.agentic import kotlinx.serialization.json.JsonArrayBuilder +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.add import kotlinx.serialization.json.addJsonObject @@ -23,6 +24,7 @@ import org.gradle.declarative.dsl.schema.ProjectFeatureOrigin import org.gradle.declarative.dsl.schema.SchemaItemMetadata import org.gradle.declarative.dsl.schema.SchemaMemberFunction import org.gradle.declarative.dsl.schema.UnsafeSchemaItem +import org.gradle.internal.declarativedsl.analysis.isReadOnly import org.gradle.internal.declarativedsl.analysis.ref import org.gradle.internal.declarativedsl.dom.DeclarativeDocument import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode @@ -40,27 +42,28 @@ import kotlin.collections.orEmpty @Suppress("TooManyFunctions") class DomJsonRenderer( val withUnusedMembers: Boolean, - val dom: DocumentOverlayResult + val dom: DocumentOverlayResult, + val documentationProvider: DocumentationProvider, ) { @Suppress("ComplexMethod") - fun JsonArrayBuilder.visitNode(node: DeclarativeDocument.DocumentNode) { + fun JsonArrayBuilder.visitNode(inClass: DataClass?, node: DeclarativeDocument.DocumentNode) { when (node) { is ElementNode -> addJsonObject { - val type = putElementHeaderAndGetType(node) + val type = putElementHeaderAndGetType(node) as? DataClass val properties = node.content.filterIsInstance() - val unusedProperties = (type as? DataClass)?.properties?.toSet() + val unusedProperties = type?.properties?.toSet() ?.minus(properties.mapNotNull { it.asResolved(dom)?.property }.toSet()) .orEmpty() if (properties.isNotEmpty()) { put("properties", buildJsonObject { - properties.forEach { putProperty(it) } + properties.forEach { put(it.name, propertyInfo(type, it)) } }) } - if (withUnusedMembers) { + if (withUnusedMembers && unusedProperties.isNotEmpty()) { put("unusedProperties", buildJsonObject { - unusedProperties.forEach { property -> putUnusedProperty(property) } + unusedProperties.forEach { put(it.name, unusedPropertyInfo(type!!, it)) } }) } @@ -68,44 +71,56 @@ class DomJsonRenderer( node.content.filterIsInstance().partition { isFeatureUsageElement(it) } if (features.isNotEmpty()) { - put("features", buildJsonArray { features.forEach { visitNode(it) } }) + put("features", buildJsonArray { features.forEach { visitNode(type, it) } }) } if (nestedObjects.isNotEmpty()) { - putJsonArray("nestedObjects") { nestedObjects.forEach { visitNode(it) } } + putJsonArray("nestedObjects") { nestedObjects.forEach { visitNode(type, it) } } } - val usedFunctions = node.content.filterIsInstance() + val usedFunctions = node.content.asSequence() + .filterIsInstance() .map(dom.overlayResolutionContainer::data) .filterIsInstance() .map { it.elementFactoryFunction } .toSet() - val unusedNestedObjects = (type as? DataClass)?.memberFunctions + val (unusedFeatures, unusedNestedObjects) = type?.memberFunctions ?.filter { it.semantics is ConfigureSemantics && it !in usedFunctions } .orEmpty() + .partition { isFeatureUsageFunction(it) } - if (withUnusedMembers) { + if (withUnusedMembers && unusedFeatures.isNotEmpty()) { + putJsonArray("unusedFeatures") { + unusedFeatures.forEach { add(unusedNestedObject(type!!, it)) } + } + } + if (withUnusedMembers && unusedNestedObjects.isNotEmpty()) { putJsonArray("unusedNestedObjects") { - unusedNestedObjects.forEach { putUnusedNestedObject(it) } + unusedNestedObjects.forEach { add(unusedNestedObject(type!!, it)) } } } val errors = node.content.filterIsInstance() if (errors.isNotEmpty()) { putJsonArray("errors") { - errors.forEach { putErrorNode(dom, it) } + errors.forEach { add(errorNode(dom, it)) } } } + + if (inClass != null) { + documentationProvider.memberDocumentation(inClass.name.qualifiedName, node.name) + ?.let { put("documentation", it) } + } } - is PropertyNode -> Unit // no handling at top level - is ErrorNode -> putErrorNode(dom, node) + is PropertyNode -> add(propertyInfo(inClass, node)) + is ErrorNode -> add(errorNode(dom, node)) } } - private fun isFeatureUsageElement(node1: ElementNode): Boolean = - (dom.overlayResolutionContainer.data(node1) as? SuccessfulElementResolution) - ?.elementFactoryFunction?.metadata?.any { it is ProjectFeatureOrigin } == true + private fun isFeatureUsageElement(node: ElementNode): Boolean = + (dom.overlayResolutionContainer.data(node) as? SuccessfulElementResolution) + ?.elementFactoryFunction?.let(::isFeatureUsageFunction) == true private fun JsonObjectBuilder.putElementHeaderAndGetType( node: ElementNode, @@ -136,98 +151,110 @@ class DomJsonRenderer( if (type != null) { put("type", type.ref.typeName) + if (type is DataType.ClassDataType) { + documentationProvider.classDocumentation(type.name.qualifiedName) + ?.let { put("typeDocumentation", it) } + } } elementMetadata(function) sourceLocation(overlayOrigin, node) + return type } - fun JsonObjectBuilder.putProperty(property: PropertyNode) { + fun propertyInfo(inType: DataClass?, property: PropertyNode): JsonObject { val resolved = dom.overlayResolutionContainer.data(property) - put( - property.name, buildJsonObject { - if (property.augmentation == PropertyNode.PropertyAugmentation.Plus) { - put("augmentedWithPlusAssign", true) - } - when (resolved) { - is DocumentResolution.PropertyResolution.PropertyAssignmentResolved -> { - put("type", resolved.property.valueType.typeName) + return buildJsonObject { + if (property.augmentation == PropertyNode.PropertyAugmentation.Plus) { + put("augmentedWithPlusAssign", true) + } + when (resolved) { + is DocumentResolution.PropertyResolution.PropertyAssignmentResolved -> { + put("type", resolved.property.valueType.typeName) + if (resolved.property.isReadOnly) { + put("isReadOnly", true) } + } - is DocumentResolution.PropertyResolution.PropertyNotAssigned -> { - put("isUnresolved", true) - putJsonArray("causes") { - userFriendlyErrorMessages(property, resolved).forEach(::add) - } + is DocumentResolution.PropertyResolution.PropertyNotAssigned -> { + put("isUnresolved", true) + putJsonArray("causes") { + userFriendlyErrorMessages(property, resolved).forEach(::add) } } + } - val origin = dom.overlayNodeOriginContainer.data(property) + val origin = dom.overlayNodeOriginContainer.data(property) - sourceLocation(origin, property) - putValue(property.value, dom) - if (origin is OverlayNodeOrigin.MergedProperties) { - val effective = origin.effectivePropertiesFromUnderlay + - origin.effectivePropertiesFromOverlay + sourceLocation(origin, property) + putValue(property.value, dom) + if (origin is OverlayNodeOrigin.MergedProperties) { + val effective = origin.effectivePropertiesFromUnderlay + + origin.effectivePropertiesFromOverlay - if (effective.isNotEmpty()) { - putJsonArray("multipartEffectiveValue") { - effective.forEach { putValueInline(it.value, dom) } - } + if (effective.isNotEmpty()) { + putJsonArray("multipartEffectiveValue") { + effective.forEach { putValueInline(it.value, dom) } } + } - val shadowed = origin.shadowedPropertiesFromUnderlay + - origin.shadowedPropertiesFromOverlay + val shadowed = origin.shadowedPropertiesFromUnderlay + + origin.shadowedPropertiesFromOverlay - if (shadowed.isNotEmpty()) { - putJsonArray("overridesValues") { - shadowed.forEach { putValueInline(it.value, dom) } - } + if (shadowed.isNotEmpty()) { + putJsonArray("overridesValues") { + shadowed.forEach { putValueInline(it.value, dom) } } } - }) + } + + if (inType != null) { + documentationProvider.memberDocumentation(inType.name.qualifiedName, property.name) + ?.let { put("documentation", it) } + } + } } - private fun JsonObjectBuilder.putUnusedProperty(property: DataProperty) { - put(property.name, buildJsonObject { - put("type", property.valueType.typeName) - }) + private fun unusedPropertyInfo(type: DataClass?, property: DataProperty): JsonObject = buildJsonObject { + put("type", property.valueType.typeName) + if (type != null) { + documentationProvider.memberDocumentation(type.name.qualifiedName, property.name) + ?.let { put("documentation", it) } + } } - private fun JsonArrayBuilder.putUnusedNestedObject(function: SchemaMemberFunction) { - addJsonObject { - put("name", function.simpleName) - put("type", (function.semantics as ConfigureSemantics).configuredType.typeName) - put( - "isBlock", - (function.semantics as ConfigureSemantics).configureBlockRequirement.requires - ) - if (function.parameters.isNotEmpty()) { - putJsonArray("parameters") { - function.parameters.forEach { param -> - addJsonObject { - put("name", param.name) - put("type", param.type.typeName) - } + private fun unusedNestedObject(type: DataClass, function: SchemaMemberFunction): JsonObject = buildJsonObject { + put("name", function.simpleName) + put("type", (function.semantics as ConfigureSemantics).configuredType.typeName) + put( + "isBlock", + (function.semantics as ConfigureSemantics).configureBlockRequirement.requires + ) + if (function.parameters.isNotEmpty()) { + putJsonArray("parameters") { + function.parameters.forEach { param -> + addJsonObject { + put("name", param.name) + put("type", param.type.typeName) } } } } + documentationProvider.memberDocumentation(type.name.qualifiedName, function.simpleName) + ?.let { put("documentation", it) } } - private fun JsonArrayBuilder.putErrorNode( + private fun errorNode( fullDom: DocumentOverlayResult, node: ErrorNode - ) { - addJsonObject { - val errorResolution = fullDom.overlayResolutionContainer.data(node) - put("source", node.sourceData.text()) - sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) - putJsonArray("causes") { - userFriendlyErrorMessages(node, errorResolution).forEach(::add) - } + ) = buildJsonObject { + val errorResolution = fullDom.overlayResolutionContainer.data(node) + put("source", node.sourceData.text()) + sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) + putJsonArray("causes") { + userFriendlyErrorMessages(node, errorResolution).forEach(::add) } } @@ -297,8 +324,7 @@ class DomJsonRenderer( private val SourceData.locationString: String - get() = - "${this.sourceIdentifier.fileIdentifier}:${this.lineRange.first}" + get() = "${this.sourceIdentifier.fileIdentifier}:${this.lineRange.first}" } internal fun JsonObjectBuilder.elementMetadata(function: SchemaMemberFunction?) = @@ -348,3 +374,6 @@ internal val DataTypeRef.typeName: String is DataType.UnitType -> "Unit" } } + +internal fun isFeatureUsageFunction(function: SchemaMemberFunction): Boolean = + function.metadata.any { it is ProjectFeatureOrigin } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt index 1afe692..9a926df 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt @@ -43,8 +43,8 @@ class EffectiveProjectDefinitionCommand : DclCommand("effective-project-definiti put("settingsFile", prerequisites.settingsFile.path) put("buildFile", projectInfo.projectBuildFile.path) put("content", buildJsonArray { - with(DomJsonRenderer(withUnusedMembers, fullDom)) { - fullDom.document.content.forEach { visitNode(it) } + with(DomJsonRenderer(withUnusedMembers, fullDom, documentationProvider)) { + fullDom.document.content.forEach { visitNode(null, it) } } }) } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SchemaCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt similarity index 86% rename from agentic-tool/src/main/kotlin/org/gradle/client/agentic/SchemaCommand.kt rename to agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt index 407a7c5..bb32e75 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SchemaCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt @@ -5,12 +5,14 @@ import com.github.ajalt.clikt.parameters.options.option import kotlinx.serialization.json.* import org.gradle.client.build.model.ResolvedDomPrerequisites import org.gradle.declarative.dsl.schema.* +import org.gradle.declarative.dsl.schema.DataType.ClassDataType import org.gradle.declarative.dsl.schema.DataType.ParameterizedTypeInstance.TypeArgument.ConcreteTypeArgument import org.gradle.declarative.dsl.schema.FunctionSemantics.ConfigureSemantics import org.gradle.internal.declarativedsl.analysis.SchemaTypeRefContext +import org.gradle.internal.declarativedsl.analysis.isReadOnly import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator -class SchemaCommand : DclCommand("project-schema") { +class ProjectSchemaCommand : DclCommand("project-schema") { override fun help(context: Context): String = "Prints the schema for the project types and features available in the build. " + "If project features are specified, only their schema will be returned. " + @@ -41,7 +43,7 @@ class SchemaCommand : DclCommand("project-schema") { val projectFeaturePredicate: ((ProjectFeatureOrigin) -> Boolean)? = when { projectFeatures != null -> { - val featureNames = commaSeparatedFeatureNames(projectFeatures); + val featureNames = splitCommaSeparatedFeatureNames(projectFeatures); { origin -> featureNames?.contains(origin.featureName) ?: true } } @@ -53,14 +55,8 @@ class SchemaCommand : DclCommand("project-schema") { val dom = getDomForProject(analyzer, prerequisites, projectInfo) ?: return buildJsonObject { put("error", "Failed to analyze project at path: $subprojectPath") } - val features = collectProjectTypeAndFeatures(dom).run { projectFeatures + listOfNotNull(projectType) }; - { origin -> - features.any { - it.featureName == origin.featureName && - it.targetDefinitionClassName == origin.targetDefinitionClassName && - it.targetBuildModelClassName == origin.targetBuildModelClassName - } - } + val used = collectProjectTypeAndFeatures(dom).run { projectFeatures + listOfNotNull(projectType) }; + { feature -> used.any(feature::matchesFeature) } } else -> null @@ -94,7 +90,7 @@ class SchemaCommand : DclCommand("project-schema") { } } - private fun typeInfo(type: DataType): JsonObject = buildJsonObject { + private fun typeInfo(type: ClassDataType): JsonObject = buildJsonObject { if (type is EnumClass) { put("name", type.name.qualifiedName) putJsonArray("enumEntries") { @@ -132,11 +128,18 @@ class SchemaCommand : DclCommand("project-schema") { }) } } + + documentationProvider.classDocumentation(type.name.qualifiedName)?.let { docs -> + put("documentation", docs) + } } private fun propertyInfo(property: DataProperty): JsonObject = buildJsonObject { put("name", property.name) put("type", property.valueType.typeName) + if (property.isReadOnly) { + put("isReadOnly", true) + } } @@ -158,8 +161,12 @@ class SchemaCommand : DclCommand("project-schema") { val semantics = function.semantics as ConfigureSemantics put("type", (function.semantics as ConfigureSemantics).configuredType.typeName) when (semantics) { - is FunctionSemantics.AccessAndConfigure -> - put("semantics", "configures-nested-object-or-feature") + is FunctionSemantics.AccessAndConfigure -> { + put( + "semantics", + if (isFeatureUsageFunction(function)) "feature-application" else "pre-existing-nested-object" + ) + } is FunctionSemantics.AddAndConfigure -> put("semantics", "adds-new-object") @@ -169,7 +176,7 @@ class SchemaCommand : DclCommand("project-schema") { private fun collectTypesToInclude( schema: AnalysisSchema, initial: Iterable - ): Set = with(SchemaTypeRefContext(schema)) { + ): Set = with(SchemaTypeRefContext(schema)) { buildSet { fun visitType(type: DataClass) { @@ -219,5 +226,10 @@ class SchemaCommand : DclCommand("project-schema") { } } } - } + +private fun ProjectFeatureOrigin.matchesFeature( + feature: ProjectFeatureOrigin +): Boolean = featureName == feature.featureName && + targetDefinitionClassName == feature.targetDefinitionClassName && + targetBuildModelClassName == feature.targetBuildModelClassName diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt index c4678e8..71cb99c 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt @@ -37,7 +37,7 @@ class SettingsDefaultsCommand : DclCommand("settings-defaults") { ): JsonElement { val settingsScript = prerequisites.settingsFile.takeIf { it.canRead() }?.readText() ?: "" - val projectTypeNames = commaSeparatedFeatureNames(projectTypes) + val projectTypeNames = splitCommaSeparatedFeatureNames(projectTypes) val settingsResult = analyzer.evaluate(prerequisites.settingsFile.path, settingsScript) @@ -47,7 +47,7 @@ class SettingsDefaultsCommand : DclCommand("settings-defaults") { return buildJsonObject { put("settingsFile", prerequisites.settingsFile.path) put("content", buildJsonArray { - with(DomJsonRenderer(withUnusedMembers, dom)) { + with(DomJsonRenderer(withUnusedMembers, dom, documentationProvider)) { val defaultsBlocks = dom.document.content.filterIsInstance() .filter { @@ -58,7 +58,7 @@ class SettingsDefaultsCommand : DclCommand("settings-defaults") { defaultsBlocks.forEach { defaultsBlock -> defaultsBlock.content.forEach { node -> if (node is ElementNode && (projectTypeNames == null || node.name in projectTypeNames)) { - visitNode(node) + visitNode(null, node) } } } @@ -68,7 +68,7 @@ class SettingsDefaultsCommand : DclCommand("settings-defaults") { } } -internal fun commaSeparatedFeatureNames(projectTypes: String?): Set? = projectTypes +internal fun splitCommaSeparatedFeatureNames(projectTypes: String?): Set? = projectTypes ?.split(",") ?.map { it.trim() } ?.filter { it.isNotBlank() } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt index 5bb07f1..42d5ad1 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt @@ -36,8 +36,8 @@ class SettingsDefinitionCommand : DclCommand("settings-definition") { return buildJsonObject { put("settingsFile", prerequisites.settingsFile.path) put("content", buildJsonArray { - with(DomJsonRenderer(withUnusedMembers, dom)) { - dom.document.content.forEach { visitNode(it) } + with(DomJsonRenderer(withUnusedMembers, dom, documentationProvider)) { + dom.document.content.forEach { visitNode(null, it) } } }) } From 4d692955cde5dce1724895453acab091b5f3f9d2 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 10:06:38 +0300 Subject: [PATCH 05/19] Add supertypes to the schema output --- .../org/gradle/client/agentic/ProjectSchemaCommand.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt index bb32e75..6040584 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt @@ -99,6 +99,12 @@ class ProjectSchemaCommand : DclCommand("project-schema") { } if (type is DataClass) { put("name", type.name.qualifiedName) + val supertypes = type.supertypes.filter { it.qualifiedName != Any::class.qualifiedName } + if (supertypes.isNotEmpty()) { + putJsonArray("supertypes") { + type.supertypes.forEach { add(it.qualifiedName) } + } + } if (type.properties.isNotEmpty()) { put("properties", buildJsonArray { type.properties.forEach { add(propertyInfo(it)) } From 1da98637a79f0eb49d84f5be5e79ea5fe2bbf13f Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 11:00:47 +0300 Subject: [PATCH 06/19] Fix Proguard rules after libs update --- gradle-client/proguard-desktop.pro | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gradle-client/proguard-desktop.pro b/gradle-client/proguard-desktop.pro index 19e8c57..6fbfc43 100644 --- a/gradle-client/proguard-desktop.pro +++ b/gradle-client/proguard-desktop.pro @@ -88,6 +88,14 @@ # Kotlin embedded compiler -keep class org.jetbrains.kotlin.** { *; } +-keep class org.jetbrains.org.objectweb.asm.** { *; } + +# Guava uses signature-polymorphic MethodHandle.invokeExact and VarHandle.{get,set,setRelease,compareAndSet,getAndSet}, +# which ProGuard cannot resolve against java.base. The call sites are valid at runtime. +-dontwarn com.google.common.hash.ChecksumHashFunction$ChecksumMethodHandles +-dontwarn com.google.common.hash.Hashing$Crc32cMethodHandles +-dontwarn com.google.common.hash.LittleEndianByteArray$VarHandleLittleEndianBytes$1 +-dontwarn com.google.common.util.concurrent.AbstractFutureState$VarHandleAtomicHelper # JNA classes -keep class com.sun.jna.** { *; } From e60687a029687fedbc2c82f37d5513490f769730 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 11:10:58 +0300 Subject: [PATCH 07/19] Add supertypes to the effective definition as well Features can be applied to supertypes, so it should help finding where to apply a feature --- .../main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt index 8deb979..158ca9c 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt @@ -151,6 +151,11 @@ class DomJsonRenderer( if (type != null) { put("type", type.ref.typeName) + if (type is DataClass && type.supertypes.any { it.qualifiedName != Any::class.qualifiedName }) { + putJsonArray("supertypes") { + type.supertypes.forEach { add(it.qualifiedName) } + } + } if (type is DataType.ClassDataType) { documentationProvider.classDocumentation(type.name.qualifiedName) ?.let { put("typeDocumentation", it) } From 81eac7c10e7ea3be12b0139ee05ecf4e1fffd3df Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 11:22:37 +0300 Subject: [PATCH 08/19] Refine value/source representation --- .../gradle/client/agentic/DomJsonRenderer.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt index 158ca9c..353826c 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt @@ -199,9 +199,11 @@ class DomJsonRenderer( val effective = origin.effectivePropertiesFromUnderlay + origin.effectivePropertiesFromOverlay - if (effective.isNotEmpty()) { + if (effective.size > 1) { putJsonArray("multipartEffectiveValue") { - effective.forEach { putValueInline(it.value, dom) } + addJsonObject { + effective.forEach { putValue(it.value, dom) } + } } } @@ -210,7 +212,9 @@ class DomJsonRenderer( if (shadowed.isNotEmpty()) { putJsonArray("overridesValues") { - shadowed.forEach { putValueInline(it.value, dom) } + addJsonObject { + shadowed.forEach { putValue(it.value, dom) } + } } } } @@ -279,9 +283,13 @@ class DomJsonRenderer( fullDom: DocumentOverlayResult ) { when (node) { - is DeclarativeDocument.ValueNode.LiteralValueNode -> put("value", node.sourceData.text()) + is DeclarativeDocument.ValueNode.LiteralValueNode -> { + put("value", node.sourceData.text()) + sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) + } is DeclarativeDocument.ValueNode.NamedReferenceNode -> { put("value", node.referenceName) + sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) } is DeclarativeDocument.ValueNode.ValueFactoryNode -> putJsonObject("value") { @@ -302,7 +310,7 @@ class DomJsonRenderer( private fun JsonObjectBuilder.sourceLocation( overlayOrigin: OverlayNodeOrigin, - node: DeclarativeDocument.DocumentNode + node: DeclarativeDocument.Node ) { when (overlayOrigin) { is OverlayNodeOrigin.CopiedOrigin -> { From 8dfa859d17f840cfed196e805d5f7bd2193f54ad Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 11:27:02 +0300 Subject: [PATCH 09/19] Remove duplicated default value in `build-actions/build.gradle.dcl` --- build-action/build.gradle.dcl | 2 -- 1 file changed, 2 deletions(-) diff --git a/build-action/build.gradle.dcl b/build-action/build.gradle.dcl index b92212c..1281327 100644 --- a/build-action/build.gradle.dcl +++ b/build-action/build.gradle.dcl @@ -1,6 +1,4 @@ javaLibrary { - javaVersion = 8 - dependencies { implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") From 63f6b0a80eca6df8756286daf390558ddf0cd775 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 21:14:57 +0300 Subject: [PATCH 10/19] Add a command for listing project types and features --- .../org/gradle/client/agentic/AgenticTool.kt | 3 +- .../client/agentic/DocumentationProvider.kt | 3 + .../agentic/ProjectTypesAndFeaturesCommand.kt | 83 +++++++++++++++++++ .../gradle/client/agentic/ProjectsCommand.kt | 3 - 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt index 5d91f63..ec70ce2 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt @@ -23,7 +23,8 @@ fun main(args: Array) { EffectiveProjectDefinitionCommand(), SettingsDefaultsCommand(), SettingsDefinitionCommand(), - ProjectSchemaCommand() + ProjectSchemaCommand(), + ProjectTypesAndFeaturesCommand() ) .main(args) } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt index c11ec77..305cb69 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt @@ -5,9 +5,12 @@ interface DocumentationProvider { // todo: use a member signature to disambiguate function overloads fun memberDocumentation(classFqName: String, memberName: String): String? + + fun featureDocumentation(pluginId: String?, featureName: String): String? } object NoopDocumentationProvider : DocumentationProvider { + override fun featureDocumentation(pluginId: String?, featureName: String): String? = null override fun classDocumentation(classFqName: String): String? = null override fun memberDocumentation(classFqName: String, memberName: String): String? = null } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt new file mode 100644 index 0000000..d329a2b --- /dev/null +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt @@ -0,0 +1,83 @@ +package org.gradle.client.agentic + +import com.github.ajalt.clikt.core.Context +import kotlinx.serialization.json.* +import org.gradle.client.build.model.ResolvedDomPrerequisites +import org.gradle.declarative.dsl.schema.DataClass +import org.gradle.declarative.dsl.schema.ProjectFeatureOrigin +import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator + +class ProjectTypesAndFeaturesCommand : DclCommand("project-types-and-features") { + + override fun help(context: Context): String = + "Lists project types and features available in this build. For each project type and feature," + + "provides the ID of the ecosystem plugin that contributes the project type or feature. " + + "For project features, specifies which definition or build model type the feature can be applied to." + + override fun produceOutput( + prerequisites: ResolvedDomPrerequisites, + analyzer: SimpleAnalysisEvaluator + ): JsonElement { + return buildJsonObject { + val schema = prerequisites.projectInterpretationSequence.steps.last().evaluationSchemaForStep.analysisSchema + + val projectTypes = schema.topLevelReceiverType.memberFunctions.flatMap { + it.metadata.filterIsInstance() + } + + putJsonArray("projectTypes") { + projectTypes.forEach { projectType -> + addJsonObject { + put("name", projectType.featureName) + put("ecosystemPluginId", projectType.ecosystemPluginId) + + val documentation = documentationProvider.featureDocumentation( + projectType.ecosystemPluginId, + projectType.featureName + ) + if (documentation != null) { + put("documentation", documentation) + } + } + } + } + + val features = schema.dataClassTypesByFqName.values.filterIsInstance() + .flatMap { + it.metadata.filterIsInstance() + .filter { it.targetDefinitionClassName != "org.gradle.api.Project" } + }.distinctBy { + listOf( + it.featureName, + it.ecosystemPluginId, + it.targetDefinitionClassName, + it.targetBuildModelClassName + ) + } + + putJsonArray("projectFeatures") { + features.forEach { + addJsonObject { + put("name", it.featureName) + put("ecosystemPluginId", it.ecosystemPluginId) + + if (it.targetDefinitionClassName != null) { + put("targetDefinitionTypeName", it.targetDefinitionClassName) + } + if (it.targetBuildModelClassName != null) { + put("targetBuildModelTypeName", it.targetBuildModelClassName) + } + + val documentation = documentationProvider.featureDocumentation( + it.ecosystemPluginId, + it.featureName + ) + if (documentation != null) { + put("documentation", documentation) + } + } + } + } + } + } +} diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectsCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectsCommand.kt index beb417b..acb7cb3 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectsCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectsCommand.kt @@ -4,12 +4,10 @@ import com.github.ajalt.clikt.core.Context import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.add import kotlinx.serialization.json.addJsonObject -import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonArray import org.gradle.client.build.model.ResolvedDomPrerequisites -import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator class ProjectsCommand : DclCommand("projects") { @@ -23,7 +21,6 @@ class ProjectsCommand : DclCommand("projects") { prerequisites: ResolvedDomPrerequisites, analyzer: SimpleAnalysisEvaluator ): JsonElement { - prerequisites.projectInfos return buildJsonObject { put("settingsFile", prerequisites.settingsFile.path) From 88ab07640c5421dd07d1c63e2dbcd84258e275f7 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 22:16:20 +0300 Subject: [PATCH 11/19] Details on configure semantics with identity key --- .../org/gradle/client/agentic/ProjectSchemaCommand.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt index 6040584..32cd1a1 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt @@ -170,7 +170,12 @@ class ProjectSchemaCommand : DclCommand("project-schema") { is FunctionSemantics.AccessAndConfigure -> { put( "semantics", - if (isFeatureUsageFunction(function)) "feature-application" else "pre-existing-nested-object" + when { + isFeatureUsageFunction(function) -> "feature-application" + function.parameters.any { it.semantics is ParameterSemantics.IdentityKey } -> + "creates-or-configures-object-by-id" + else -> "pre-existing-nested-object" + } ) } From 4775ac1ea2124442a5ba4e497c58a48f7ea0daf6 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 4 May 2026 22:25:22 +0300 Subject: [PATCH 12/19] Add info about configuring block requirement in adding functions --- .../kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt index 32cd1a1..9bde430 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt @@ -179,8 +179,10 @@ class ProjectSchemaCommand : DclCommand("project-schema") { ) } - is FunctionSemantics.AddAndConfigure -> + is FunctionSemantics.AddAndConfigure -> { put("semantics", "adds-new-object") + put("requiresConfiguringBlock", semantics.configureBlockRequirement.requires) + } } } From 53b9bbe77c83e6b2b7fbff1e1040361176f3b734 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Tue, 5 May 2026 20:03:42 +0300 Subject: [PATCH 13/19] Print full source for simple values, too --- .../gradle/client/agentic/DomJsonRenderer.kt | 71 +++++++------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt index 353826c..0af8842 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt @@ -1,43 +1,21 @@ package org.gradle.client.agentic -import kotlinx.serialization.json.JsonArrayBuilder -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonObjectBuilder -import kotlinx.serialization.json.add -import kotlinx.serialization.json.addJsonObject -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonArray -import kotlinx.serialization.json.putJsonObject +import kotlinx.serialization.json.* import org.gradle.client.core.gradle.dcl.userFriendlyErrorMessages -import org.gradle.declarative.dsl.schema.ConfigureFromGetterOrigin -import org.gradle.declarative.dsl.schema.ContainerElementFactory -import org.gradle.declarative.dsl.schema.DataClass -import org.gradle.declarative.dsl.schema.DataProperty -import org.gradle.declarative.dsl.schema.DataType +import org.gradle.declarative.dsl.schema.* import org.gradle.declarative.dsl.schema.DataType.ParameterizedTypeInstance.TypeArgument.ConcreteTypeArgument import org.gradle.declarative.dsl.schema.DataType.ParameterizedTypeInstance.TypeArgument.StarProjection -import org.gradle.declarative.dsl.schema.DataTypeRef import org.gradle.declarative.dsl.schema.FunctionSemantics.ConfigureSemantics -import org.gradle.declarative.dsl.schema.ProjectFeatureOrigin -import org.gradle.declarative.dsl.schema.SchemaItemMetadata -import org.gradle.declarative.dsl.schema.SchemaMemberFunction -import org.gradle.declarative.dsl.schema.UnsafeSchemaItem import org.gradle.internal.declarativedsl.analysis.isReadOnly import org.gradle.internal.declarativedsl.analysis.ref import org.gradle.internal.declarativedsl.dom.DeclarativeDocument -import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode -import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ErrorNode -import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.PropertyNode +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.* import org.gradle.internal.declarativedsl.dom.DocumentResolution import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResolution.SuccessfulElementResolution import org.gradle.internal.declarativedsl.dom.DocumentResolution.ValueNodeResolution.ValueFactoryResolution.ValueFactoryResolved import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult import org.gradle.internal.declarativedsl.dom.operations.overlay.OverlayNodeOrigin import org.gradle.internal.declarativedsl.language.SourceData -import kotlin.collections.forEach -import kotlin.collections.orEmpty @Suppress("TooManyFunctions") class DomJsonRenderer( @@ -127,7 +105,7 @@ class DomJsonRenderer( ): DataType? { put("name", node.name) putJsonArray("args") { - node.elementValues.forEach { putValueInline(it, dom) } + node.elementValues.forEach { putValueInObject(it, dom) } } val overlayOrigin = dom.overlayNodeOriginContainer.data(node) @@ -151,9 +129,11 @@ class DomJsonRenderer( if (type != null) { put("type", type.ref.typeName) - if (type is DataClass && type.supertypes.any { it.qualifiedName != Any::class.qualifiedName }) { + val supertypeNames = (type as? DataClass)?.supertypes + ?.filter { it.qualifiedName != Any::class.qualifiedName }.orEmpty() + if (supertypeNames.isNotEmpty()) { putJsonArray("supertypes") { - type.supertypes.forEach { add(it.qualifiedName) } + supertypeNames.forEach { add(it.qualifiedName) } } } if (type is DataType.ClassDataType) { @@ -193,8 +173,7 @@ class DomJsonRenderer( val origin = dom.overlayNodeOriginContainer.data(property) - sourceLocation(origin, property) - putValue(property.value, dom) + putValueInline(property.value, dom) if (origin is OverlayNodeOrigin.MergedProperties) { val effective = origin.effectivePropertiesFromUnderlay + origin.effectivePropertiesFromOverlay @@ -202,7 +181,7 @@ class DomJsonRenderer( if (effective.size > 1) { putJsonArray("multipartEffectiveValue") { addJsonObject { - effective.forEach { putValue(it.value, dom) } + effective.forEach { putValueInline(it.value, dom) } } } } @@ -213,7 +192,7 @@ class DomJsonRenderer( if (shadowed.isNotEmpty()) { putJsonArray("overridesValues") { addJsonObject { - shadowed.forEach { putValue(it.value, dom) } + shadowed.forEach { putValueInline(it.value, dom, it.sourceData) } } } } @@ -267,33 +246,27 @@ class DomJsonRenderer( } } - private fun JsonArrayBuilder.putValueInline( + private fun JsonArrayBuilder.putValueInObject( node: DeclarativeDocument.ValueNode, fullDom: DocumentOverlayResult - ) { - when (node) { - is DeclarativeDocument.ValueNode.LiteralValueNode -> add(node.sourceData.text()) - is DeclarativeDocument.ValueNode.NamedReferenceNode -> add(node.referenceName) - is DeclarativeDocument.ValueNode.ValueFactoryNode -> addJsonObject { putValue(node, fullDom) } - } - } + ) = addJsonObject { putValueInline(node, fullDom) } - private fun JsonObjectBuilder.putValue( + private fun JsonObjectBuilder.putValueInline( node: DeclarativeDocument.ValueNode, - fullDom: DocumentOverlayResult + fullDom: DocumentOverlayResult, + sourceLocation: SourceData? = null ) { when (node) { is DeclarativeDocument.ValueNode.LiteralValueNode -> { put("value", node.sourceData.text()) - sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) } is DeclarativeDocument.ValueNode.NamedReferenceNode -> { put("value", node.referenceName) sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) } - is DeclarativeDocument.ValueNode.ValueFactoryNode -> putJsonObject("value") { - put("source", node.sourceData.text()) + is DeclarativeDocument.ValueNode.ValueFactoryNode -> { + sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) val resolved = fullDom.overlayResolutionContainer.data(node) if (resolved is ValueFactoryResolved) { put("type", resolved.function.semantics.returnValueType.typeName) @@ -301,11 +274,17 @@ class DomJsonRenderer( put("valueFactory", node.factoryName + (if (node.isInfix) "(infix-notation)" else "")) putJsonArray("args") { node.values.forEach { - putValueInline(it, fullDom) + putValueInObject(it, fullDom) } } + put("source", node.sourceData.text()) } } + if (sourceLocation != null) { + put("sourceLocation", sourceLocation.locationString) + } else { + sourceLocation(fullDom.overlayNodeOriginContainer.data(node), node) + } } private fun JsonObjectBuilder.sourceLocation( From 2a6804814c6ed16799c1551cebc0e0cf9bdbfda0 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Tue, 12 May 2026 13:06:06 +0200 Subject: [PATCH 14/19] Use published Gradle snapshot for wrapper and libs --- agentic-tool/build.gradle.dcl | 8 ++++---- build-action/build.gradle.dcl | 4 ++-- dcl-utils/build.gradle.dcl | 8 ++++---- gradle-client/build.gradle.dcl | 8 ++++---- gradle/wrapper/gradle-wrapper.properties | 2 +- mutations-demo/build.gradle.dcl | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/agentic-tool/build.gradle.dcl b/agentic-tool/build.gradle.dcl index 8b10f91..a05b6e7 100644 --- a/agentic-tool/build.gradle.dcl +++ b/agentic-tool/build.gradle.dcl @@ -5,10 +5,10 @@ kotlinJvmApplication { implementation(project(":build-action")) implementation(project(":dcl-utils")) - implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") + implementation("org.gradle:gradle-tooling-api:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-20260511195019+0000") implementation("com.github.ajalt.clikt:clikt:5.0.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") diff --git a/build-action/build.gradle.dcl b/build-action/build.gradle.dcl index 1281327..967c52e 100644 --- a/build-action/build.gradle.dcl +++ b/build-action/build.gradle.dcl @@ -1,6 +1,6 @@ javaLibrary { dependencies { - implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") + implementation("org.gradle:gradle-tooling-api:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-20260511195019+0000") } } diff --git a/dcl-utils/build.gradle.dcl b/dcl-utils/build.gradle.dcl index a835dce..67a04f3 100644 --- a/dcl-utils/build.gradle.dcl +++ b/dcl-utils/build.gradle.dcl @@ -1,9 +1,9 @@ kotlinJvmLibrary { dependencies { - implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") + implementation("org.gradle:gradle-tooling-api:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-20260511195019+0000") } } diff --git a/gradle-client/build.gradle.dcl b/gradle-client/build.gradle.dcl index f57b71a..e11a257 100644 --- a/gradle-client/build.gradle.dcl +++ b/gradle-client/build.gradle.dcl @@ -20,7 +20,7 @@ kotlinApplication { implementation(project(":mutations-demo")) implementation(project(":dcl-utils")) - implementation("org.gradle:gradle-tooling-api:9.6.0-milestone-1") + implementation("org.gradle:gradle-tooling-api:9.6.0-20260511195019+0000") implementation("com.arkivanov.decompose:decompose:3.3.0") implementation("com.arkivanov.decompose:extensions-compose:3.3.0") @@ -37,9 +37,9 @@ kotlinApplication { implementation("org.slf4j:slf4j-api:2.0.17") implementation("ch.qos.logback:logback-classic:1.5.18") - implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-milestone-1") - implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-evaluator:9.6.0-20260511195019+0000") + implementation("org.gradle:gradle-declarative-dsl-tooling-models:9.6.0-20260511195019+0000") implementation("org.jspecify:jspecify:1.0.0") runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.10.2") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8b8bedc..1d8ae0e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-milestone-1-bin.zip +distributionUrl=https://services.gradle.org/distributions-snapshots/gradle-9.6.0-branch-eskatosDocInDclSchema-20260511195019+0000-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/mutations-demo/build.gradle.dcl b/mutations-demo/build.gradle.dcl index d21cbda..2814a24 100644 --- a/mutations-demo/build.gradle.dcl +++ b/mutations-demo/build.gradle.dcl @@ -1,5 +1,5 @@ kotlinJvmLibrary { dependencies { - implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-milestone-1") + implementation("org.gradle:gradle-declarative-dsl-core:9.6.0-20260511195019+0000") } } From 240b60327b0c33f35715151ca562e8cae675d936 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 11 May 2026 20:51:37 +0300 Subject: [PATCH 15/19] Update prototype plugins to 0.1.61 --- build-logic/plugins/build.gradle.dcl | 2 +- build-logic/settings.gradle.dcl | 2 +- settings.gradle.dcl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build-logic/plugins/build.gradle.dcl b/build-logic/plugins/build.gradle.dcl index 1172e5f..77d649a 100644 --- a/build-logic/plugins/build.gradle.dcl +++ b/build-logic/plugins/build.gradle.dcl @@ -2,7 +2,7 @@ javaGradlePlugin { description = "Declarative plugins containing custom project features for the gradle-client project." dependencies { - api("org.gradle.experimental.kmp-ecosystem:org.gradle.experimental.kmp-ecosystem.gradle.plugin:0.1.54") + api("org.gradle.experimental.kmp-ecosystem:org.gradle.experimental.kmp-ecosystem.gradle.plugin:0.1.61") api("org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin:2.3.0") api("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:2.3.0") api("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.3.0") diff --git a/build-logic/settings.gradle.dcl b/build-logic/settings.gradle.dcl index ef9a084..2730faa 100644 --- a/build-logic/settings.gradle.dcl +++ b/build-logic/settings.gradle.dcl @@ -1,5 +1,5 @@ plugins { - id("org.gradle.experimental.plugin-ecosystem").version("0.1.54") + id("org.gradle.experimental.plugin-ecosystem").version("0.1.61") } dependencyResolutionManagement { diff --git a/settings.gradle.dcl b/settings.gradle.dcl index 54234b5..6be3832 100644 --- a/settings.gradle.dcl +++ b/settings.gradle.dcl @@ -7,8 +7,8 @@ plugins { id("io.github.gradle.develocity-conventions-plugin").version("0.14.1") id("org.gradle.toolchains.foojay-resolver-convention").version("0.8.0") - id("org.gradle.experimental.jvm-ecosystem").version("0.1.54") - id("org.gradle.experimental.kmp-ecosystem").version("0.1.54") + id("org.gradle.experimental.jvm-ecosystem").version("0.1.61") + id("org.gradle.experimental.kmp-ecosystem").version("0.1.61") id("org.gradle.client.ecosystem.custom-ecosystem") } From 745e0ef4d9f8ca1bceaa4a4a71680020ec7787d4 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 11 May 2026 20:52:02 +0300 Subject: [PATCH 16/19] Use apply-action classes in `build-logic` --- .../compose/ComposeProjectFeaturePlugin.java | 96 +++++++++++-------- .../detekt/DetektProjectFeaturePlugin.java | 43 +++++---- .../SqlDelightProjectFeaturePlugin.java | 82 +++++++++------- 3 files changed, 132 insertions(+), 89 deletions(-) diff --git a/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/compose/ComposeProjectFeaturePlugin.java b/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/compose/ComposeProjectFeaturePlugin.java index e9dcf12..f8dc773 100644 --- a/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/compose/ComposeProjectFeaturePlugin.java +++ b/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/compose/ComposeProjectFeaturePlugin.java @@ -4,9 +4,9 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.experimental.kmp.KotlinMultiplatformBuildModel; +import org.gradle.api.model.ObjectFactory; import org.gradle.features.annotations.BindsProjectFeature; -import org.gradle.features.binding.ProjectFeatureBindingBuilder; -import org.gradle.features.binding.ProjectFeatureBinding; +import org.gradle.features.binding.*; import org.gradle.api.plugins.PluginManager; import org.gradle.api.provider.ProviderFactory; import org.jetbrains.compose.ComposeExtension; @@ -28,48 +28,68 @@ public void apply(Project project) { } static class Binding implements ProjectFeatureBinding { + @Override public void bind(ProjectFeatureBindingBuilder builder) { - builder.bindProjectFeatureToBuildModel("compose", Compose.class, KotlinMultiplatformBuildModel.class, - (context, definition, buildModel, parent) -> { - Services services = context.getObjectFactory().newInstance(Services.class); - services.getPluginManager().apply("org.jetbrains.kotlin.plugin.serialization"); - services.getPluginManager().apply("org.jetbrains.kotlin.plugin.compose"); - - buildModel.getPackageVersion().set( - definition.getNativeDistributions().getPackageVersion() - .orElse(services.getProviders().provider(() -> services.getProject().getVersion().toString())) - ); - - // Many of the underlying extension elements are not provider-aware, so we have to do - // this in an afterEvaluate block - services.getProject().afterEvaluate(p -> { - services.getPluginManager().apply("org.jetbrains.compose"); - ((DefaultComposeBuildModel)buildModel).setComposeExtension(services.getProject().getExtensions().getByType(ComposeExtension.class)); - DesktopExtension desktop = buildModel.getComposeExtension().getExtensions().getByType(DesktopExtension.class); - KotlinMultiplatformExtension kmpExtension = context.getBuildModel(parent).getKotlinMultiplatformExtension(); - JvmApplication application = desktop.getApplication(); - JvmApplicationDistributions nativeDistributions = application.getNativeDistributions(); - - wireNativeDistribution(definition.getNativeDistributions(), nativeDistributions, buildModel.getPackageVersion().get()); - application.setMainClass(definition.getMainClass().get()); - definition.getJvmArgs().forEach(jvmArg -> application.getJvmArgs().add(jvmArg.getName() + jvmArg.getValue().get())); - - wireBuildTypes(definition, application.getBuildTypes()); - - // Need to set the main class for the JVM run task in the KMP model from the one nested in the Compose model - kmpExtension.jvm("jvm" , jvm -> { - jvm.mainRun(kotlinJvmRunDsl -> { - kotlinJvmRunDsl.getMainClass().set(definition.getMainClass()); - return Unit.INSTANCE; - }); - }); - }); - }) + builder.bindProjectFeatureToBuildModel( + "compose", Compose.class, KotlinMultiplatformBuildModel.class, ApplyAction.class + ) .withUnsafeApplyAction() .withBuildModelImplementationType(DefaultComposeBuildModel.class); } + public static abstract class ApplyAction implements ProjectFeatureApplyAction< + Compose, + ComposeBuildModel, + Definition + > { + + @Inject + public ApplyAction() { + } + + @Inject + public abstract ObjectFactory getObjectFactory(); + + @Override + public void apply(ProjectFeatureApplicationContext context, Compose definition, ComposeBuildModel buildModel, Definition parent) { + Binding.Services services = getObjectFactory().newInstance(Binding.Services.class); + services.getPluginManager().apply("org.jetbrains.kotlin.plugin.serialization"); + services.getPluginManager().apply("org.jetbrains.kotlin.plugin.compose"); + + buildModel.getPackageVersion().set( + definition.getNativeDistributions().getPackageVersion() + .orElse(services.getProviders().provider(() -> services.getProject().getVersion().toString())) + ); + + // Many of the underlying extension elements are not provider-aware, so we have to do + // this in an afterEvaluate block + services.getProject().afterEvaluate(p -> { + services.getPluginManager().apply("org.jetbrains.compose"); + ((DefaultComposeBuildModel) buildModel).setComposeExtension(services.getProject().getExtensions().getByType(ComposeExtension.class)); + DesktopExtension desktop = buildModel.getComposeExtension().getExtensions().getByType(DesktopExtension.class); + KotlinMultiplatformExtension kmpExtension = context.getBuildModel(parent).getKotlinMultiplatformExtension(); + JvmApplication application = desktop.getApplication(); + JvmApplicationDistributions nativeDistributions = application.getNativeDistributions(); + + wireNativeDistribution(definition.getNativeDistributions(), nativeDistributions, buildModel.getPackageVersion().get()); + application.setMainClass(definition.getMainClass().get()); + definition.getJvmArgs().forEach(jvmArg -> application.getJvmArgs().add(jvmArg.getName() + jvmArg.getValue().get())); + + wireBuildTypes(definition, application.getBuildTypes()); + + // Need to set the main class for the JVM run task in the KMP model from the one nested in the Compose model + kmpExtension.jvm("jvm", jvm -> { + jvm.mainRun(kotlinJvmRunDsl -> { + kotlinJvmRunDsl.getMainClass().set(definition.getMainClass()); + return Unit.INSTANCE; + }); + }); + }); + } + + } + private static void wireBuildTypes(Compose composeBuildModel, JvmApplicationBuildTypes buildTypes) { wireBuildType(composeBuildModel.getBuildTypes().getRelease(), buildTypes.getRelease()); } diff --git a/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/detekt/DetektProjectFeaturePlugin.java b/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/detekt/DetektProjectFeaturePlugin.java index bdaf1de..932ebac 100644 --- a/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/detekt/DetektProjectFeaturePlugin.java +++ b/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/detekt/DetektProjectFeaturePlugin.java @@ -4,9 +4,9 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.experimental.kmp.KotlinMultiplatformBuildModel; +import org.gradle.api.model.ObjectFactory; import org.gradle.features.annotations.BindsProjectFeature; -import org.gradle.features.binding.ProjectFeatureBindingBuilder; -import org.gradle.features.binding.ProjectFeatureBinding; +import org.gradle.features.binding.*; import org.gradle.api.plugins.PluginManager; import javax.inject.Inject; @@ -22,24 +22,35 @@ public void apply(Project project) { static class Binding implements ProjectFeatureBinding { @Override public void bind(ProjectFeatureBindingBuilder builder) { - builder.bindProjectFeatureToBuildModel("detekt", Detekt.class, KotlinMultiplatformBuildModel.class, - (context, definition, buildModel, parent) -> { + builder.bindProjectFeatureToBuildModel("detekt", Detekt.class, KotlinMultiplatformBuildModel.class, ApplyAction.class) + .withUnsafeApplyAction() + .withBuildModelImplementationType(DefaultDetektBuildModel.class); + } + + static abstract class ApplyAction implements ProjectFeatureApplyAction> { + + @Inject + public ApplyAction() { + } - Services services = context.getObjectFactory().newInstance(Services.class); + @Inject + public abstract ObjectFactory getObjectFactory(); - services.getPluginManager().apply("io.gitlab.arturbosch.detekt"); - ((DefaultDetektBuildModel)buildModel).setDetektExtension(services.getProject().getExtensions().getByType(DetektExtension.class)); + @Override + public void apply(ProjectFeatureApplicationContext context, Detekt definition, DetektBuildModel buildModel, Definition kotlinMultiplatformBuildModelDefinition) { + Services services = getObjectFactory().newInstance(Services.class); - buildModel.getDetektExtension().getSource().from(definition.getSource()); - buildModel.getDetektExtension().getConfig().from(definition.getConfig()); + services.getPluginManager().apply("io.gitlab.arturbosch.detekt"); + ((DefaultDetektBuildModel)buildModel).setDetektExtension(services.getProject().getExtensions().getByType(DetektExtension.class)); - services.getProject().afterEvaluate(p -> { - // This is not a property, need to wire this in afterEvaluate - buildModel.getDetektExtension().setParallel(definition.getParallel().get()); - }); - }) - .withUnsafeApplyAction() - .withBuildModelImplementationType(DefaultDetektBuildModel.class); + buildModel.getDetektExtension().getSource().from(definition.getSource()); + buildModel.getDetektExtension().getConfig().from(definition.getConfig()); + + services.getProject().afterEvaluate(p -> { + // This is not a property, need to wire this in afterEvaluate + buildModel.getDetektExtension().setParallel(definition.getParallel().get()); + }); + } } interface Services { diff --git a/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/sqldelight/SqlDelightProjectFeaturePlugin.java b/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/sqldelight/SqlDelightProjectFeaturePlugin.java index 65d0e49..4fa3342 100644 --- a/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/sqldelight/SqlDelightProjectFeaturePlugin.java +++ b/build-logic/plugins/src/main/java/org/gradle/client/projectfeatures/sqldelight/SqlDelightProjectFeaturePlugin.java @@ -9,8 +9,7 @@ import org.gradle.api.artifacts.dsl.DependencyFactory; import org.gradle.api.experimental.kmp.KotlinMultiplatformBuildModel; import org.gradle.features.annotations.BindsProjectFeature; -import org.gradle.features.binding.ProjectFeatureBindingBuilder; -import org.gradle.features.binding.ProjectFeatureBinding; +import org.gradle.features.binding.*; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.PluginManager; import org.gradle.api.provider.Provider; @@ -39,47 +38,60 @@ public void apply(Project project) { } static class Binding implements ProjectFeatureBinding { - @Override - public void bind(ProjectFeatureBindingBuilder builder) { - builder.bindProjectFeatureToBuildModel("sqlDelight", SqlDelight.class, KotlinMultiplatformBuildModel.class, - (context, definition, buildModel, parent) -> { - Services services = context.getObjectFactory().newInstance(Services.class); - - buildModel.getVersion().set(definition.getVersion().orElse("2.1.0")); - - KotlinMultiplatformBuildModel parentBuildModel = context.getBuildModel(parent); - parentBuildModel.getKotlinMultiplatformExtension().jvm(jvmTarget -> { - NamedDomainObjectProvider sqlDelightConfiguration = services.getProject().getConfigurations().dependencyScope("sqlDelightTool", conf -> { - stream(SQLDELIGHT_DEPENDENCY_MODULES).forEach(module -> - conf.getDependencies().addLater(buildModel.getVersion().map(version -> services.getDependencyFactory().create(SQLDELIGHT_GROUP + ":" + module + ":" + version))) - ); - }); - - services.getProject().getConfigurations().named(jvmTarget.getCompilations().getByName("main").getDefaultSourceSet().getImplementationConfigurationName(), conf -> - conf.extendsFrom(sqlDelightConfiguration.get()) + + public static abstract class ApplyAction implements ProjectFeatureApplyAction> { + + @Inject + public ApplyAction() { + } + + @Inject + public abstract ObjectFactory getObjectFactory(); + + @Override + public void apply(ProjectFeatureApplicationContext context, SqlDelight definition, SqlDelightBuildModel buildModel, Definition parent) { + Services services = getObjectFactory().newInstance(Services.class); + + buildModel.getVersion().set(definition.getVersion().orElse("2.1.0")); + + KotlinMultiplatformBuildModel parentBuildModel = context.getBuildModel(parent); + parentBuildModel.getKotlinMultiplatformExtension().jvm(jvmTarget -> { + NamedDomainObjectProvider sqlDelightConfiguration = services.getProject().getConfigurations().dependencyScope("sqlDelightTool", conf -> { + stream(SQLDELIGHT_DEPENDENCY_MODULES).forEach(module -> + conf.getDependencies().addLater(buildModel.getVersion().map(version -> services.getDependencyFactory().create(SQLDELIGHT_GROUP + ":" + module + ":" + version))) ); }); - services.getPluginManager().apply("app.cash.sqldelight"); - ((DefaultSqlDelightBuildModel) buildModel).setSqlDelightExtension(services.getProject().getExtensions().getByType(SqlDelightExtension.class)); + services.getProject().getConfigurations().named(jvmTarget.getCompilations().getByName("main").getDefaultSourceSet().getImplementationConfigurationName(), conf -> + conf.extendsFrom(sqlDelightConfiguration.get()) + ); + }); - // https://github.com/gradle/gradle/issues/28043 - Provider> featureDatabasesProvider = services.getProviders().provider(() -> Collections.unmodifiableSet(definition.getDatabases())); + services.getPluginManager().apply("app.cash.sqldelight"); + ((DefaultSqlDelightBuildModel) buildModel).setSqlDelightExtension(services.getProject().getExtensions().getByType(SqlDelightExtension.class)); - Provider> sqlDelightDatabases = featureDatabasesProvider.map(featureDatabases -> featureDatabases.stream().map(featureDatabase -> { - SqlDelightDatabase database = services.getObjects().newInstance(SqlDelightDatabase.class, featureDatabase.getName()); + // https://github.com/gradle/gradle/issues/28043 + Provider> featureDatabasesProvider = services.getProviders().provider(() -> Collections.unmodifiableSet(definition.getDatabases())); - database.getPackageName().set(featureDatabase.getPackageName()); - database.getVerifyDefinitions().set(featureDatabase.getVerifyDefinitions()); - database.getVerifyMigrations().set(featureDatabase.getVerifyMigrations()); - database.getDeriveSchemaFromMigrations().set(featureDatabase.getDeriveSchemaFromMigrations()); - database.getGenerateAsync().set(featureDatabase.getGenerateAsync()); + Provider> sqlDelightDatabases = featureDatabasesProvider.map(featureDatabases -> featureDatabases.stream().map(featureDatabase -> { + SqlDelightDatabase database = services.getObjects().newInstance(SqlDelightDatabase.class, featureDatabase.getName()); - return database; - }).collect(Collectors.toUnmodifiableSet())); + database.getPackageName().set(featureDatabase.getPackageName()); + database.getVerifyDefinitions().set(featureDatabase.getVerifyDefinitions()); + database.getVerifyMigrations().set(featureDatabase.getVerifyMigrations()); + database.getDeriveSchemaFromMigrations().set(featureDatabase.getDeriveSchemaFromMigrations()); + database.getGenerateAsync().set(featureDatabase.getGenerateAsync()); - buildModel.getSqlDelightExtension().getDatabases().addAllLater(sqlDelightDatabases); - }) + return database; + }).collect(Collectors.toUnmodifiableSet())); + + buildModel.getSqlDelightExtension().getDatabases().addAllLater(sqlDelightDatabases); + } + } + + @Override + public void bind(ProjectFeatureBindingBuilder builder) { + builder.bindProjectFeatureToBuildModel("sqlDelight", SqlDelight.class, KotlinMultiplatformBuildModel.class, ApplyAction.class) .withUnsafeApplyAction() .withBuildModelImplementationType(DefaultSqlDelightBuildModel.class); } From 46941507e3620262d11c68b15545ab1242dab8b3 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 11 May 2026 20:52:19 +0300 Subject: [PATCH 17/19] Integrate the DCL schema-embedded documentation --- .../org/gradle/client/agentic/AgenticTool.kt | 2 +- .../client/agentic/DocumentationProvider.kt | 38 +++++++++++---- .../gradle/client/agentic/DomJsonRenderer.kt | 34 +++++++------- .../EffectiveProjectDefinitionCommand.kt | 2 +- .../client/agentic/ProjectSchemaCommand.kt | 11 ++++- .../agentic/ProjectTypesAndFeaturesCommand.kt | 46 ++++++++----------- .../client/agentic/SettingsDefaultsCommand.kt | 2 +- .../agentic/SettingsDefinitionCommand.kt | 2 +- 8 files changed, 81 insertions(+), 56 deletions(-) diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt index ec70ce2..edbb6d5 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/AgenticTool.kt @@ -37,7 +37,7 @@ class MainCommand : NoOpCliktCommand() { abstract class DclCommand(name: String) : CliktCommand(name) { protected val rootProjectDir: String by option(help = "Gradle build root project directory").default(".") - protected val documentationProvider: DocumentationProvider = NoopDocumentationProvider + protected val documentationProvider: DocumentationProvider = SchemaEmbeddedDocumentationProvider private val domPrerequisites: BuildAction = GetResolvedDomAction() diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt index 305cb69..f2cd3c0 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DocumentationProvider.kt @@ -1,16 +1,36 @@ package org.gradle.client.agentic +import org.gradle.declarative.dsl.schema.DataProperty +import org.gradle.declarative.dsl.schema.DataType +import org.gradle.declarative.dsl.schema.SchemaDocumentation +import org.gradle.declarative.dsl.schema.SchemaItemMetadata +import org.gradle.declarative.dsl.schema.SchemaMemberFunction + interface DocumentationProvider { - fun classDocumentation(classFqName: String): String? + fun classDocumentation(classDataType: DataType.ClassDataType): String? + fun functionDocumentation(schemaMemberFunction: SchemaMemberFunction): String? + fun propertyDocumentation(property: DataProperty): String? +} - // todo: use a member signature to disambiguate function overloads - fun memberDocumentation(classFqName: String, memberName: String): String? +object SchemaEmbeddedDocumentationProvider : DocumentationProvider { + override fun classDocumentation(classDataType: DataType.ClassDataType): String? = + extractDocsFromMetadata(classDataType.metadata) - fun featureDocumentation(pluginId: String?, featureName: String): String? -} + override fun functionDocumentation(schemaMemberFunction: SchemaMemberFunction): String? = + extractDocsFromMetadata(schemaMemberFunction.metadata) + + override fun propertyDocumentation(property: DataProperty): String? = + extractDocsFromMetadata(property.metadata) -object NoopDocumentationProvider : DocumentationProvider { - override fun featureDocumentation(pluginId: String?, featureName: String): String? = null - override fun classDocumentation(classFqName: String): String? = null - override fun memberDocumentation(classFqName: String, memberName: String): String? = null + private fun extractDocsFromMetadata(metadata: Iterable) = + metadata + .filterIsInstance() + .flatMap { + listOf(it.text) + + it.parts.map { (key, value) -> + "* `$key`: $value" + } + } + .joinToString("\n\n") + .ifBlank { null } } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt index 0af8842..7c05ff0 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/DomJsonRenderer.kt @@ -24,7 +24,7 @@ class DomJsonRenderer( val documentationProvider: DocumentationProvider, ) { @Suppress("ComplexMethod") - fun JsonArrayBuilder.visitNode(inClass: DataClass?, node: DeclarativeDocument.DocumentNode) { + fun JsonArrayBuilder.visitNode(node: DeclarativeDocument.DocumentNode) { when (node) { is ElementNode -> addJsonObject { val type = putElementHeaderAndGetType(node) as? DataClass @@ -36,7 +36,7 @@ class DomJsonRenderer( if (properties.isNotEmpty()) { put("properties", buildJsonObject { - properties.forEach { put(it.name, propertyInfo(type, it)) } + properties.forEach { put(it.name, propertyInfo(it)) } }) } if (withUnusedMembers && unusedProperties.isNotEmpty()) { @@ -49,10 +49,10 @@ class DomJsonRenderer( node.content.filterIsInstance().partition { isFeatureUsageElement(it) } if (features.isNotEmpty()) { - put("features", buildJsonArray { features.forEach { visitNode(type, it) } }) + put("features", buildJsonArray { features.forEach { visitNode(it) } }) } if (nestedObjects.isNotEmpty()) { - putJsonArray("nestedObjects") { nestedObjects.forEach { visitNode(type, it) } } + putJsonArray("nestedObjects") { nestedObjects.forEach { visitNode(it) } } } val usedFunctions = node.content.asSequence() @@ -69,12 +69,12 @@ class DomJsonRenderer( if (withUnusedMembers && unusedFeatures.isNotEmpty()) { putJsonArray("unusedFeatures") { - unusedFeatures.forEach { add(unusedNestedObject(type!!, it)) } + unusedFeatures.forEach { add(unusedNestedObject(it)) } } } if (withUnusedMembers && unusedNestedObjects.isNotEmpty()) { putJsonArray("unusedNestedObjects") { - unusedNestedObjects.forEach { add(unusedNestedObject(type!!, it)) } + unusedNestedObjects.forEach { add(unusedNestedObject(it)) } } } @@ -85,13 +85,14 @@ class DomJsonRenderer( } } - if (inClass != null) { - documentationProvider.memberDocumentation(inClass.name.qualifiedName, node.name) + val resolved = dom.overlayResolutionContainer.data(node) + if (resolved is SuccessfulElementResolution) { + documentationProvider.functionDocumentation(resolved.elementFactoryFunction) ?.let { put("documentation", it) } } } - is PropertyNode -> add(propertyInfo(inClass, node)) + is PropertyNode -> add(propertyInfo(node)) is ErrorNode -> add(errorNode(dom, node)) } } @@ -137,7 +138,7 @@ class DomJsonRenderer( } } if (type is DataType.ClassDataType) { - documentationProvider.classDocumentation(type.name.qualifiedName) + documentationProvider.classDocumentation(type) ?.let { put("typeDocumentation", it) } } } @@ -149,7 +150,7 @@ class DomJsonRenderer( return type } - fun propertyInfo(inType: DataClass?, property: PropertyNode): JsonObject { + fun propertyInfo(property: PropertyNode): JsonObject { val resolved = dom.overlayResolutionContainer.data(property) return buildJsonObject { if (property.augmentation == PropertyNode.PropertyAugmentation.Plus) { @@ -198,8 +199,8 @@ class DomJsonRenderer( } } - if (inType != null) { - documentationProvider.memberDocumentation(inType.name.qualifiedName, property.name) + if (resolved is DocumentResolution.PropertyResolution.PropertyAssignmentResolved) { + documentationProvider.propertyDocumentation(resolved.property) ?.let { put("documentation", it) } } } @@ -208,12 +209,12 @@ class DomJsonRenderer( private fun unusedPropertyInfo(type: DataClass?, property: DataProperty): JsonObject = buildJsonObject { put("type", property.valueType.typeName) if (type != null) { - documentationProvider.memberDocumentation(type.name.qualifiedName, property.name) + documentationProvider.propertyDocumentation(property) ?.let { put("documentation", it) } } } - private fun unusedNestedObject(type: DataClass, function: SchemaMemberFunction): JsonObject = buildJsonObject { + private fun unusedNestedObject(function: SchemaMemberFunction): JsonObject = buildJsonObject { put("name", function.simpleName) put("type", (function.semantics as ConfigureSemantics).configuredType.typeName) put( @@ -230,7 +231,7 @@ class DomJsonRenderer( } } } - documentationProvider.memberDocumentation(type.name.qualifiedName, function.simpleName) + documentationProvider.functionDocumentation(function) ?.let { put("documentation", it) } } @@ -337,6 +338,7 @@ internal fun JsonObjectBuilder.elementMetadata(metadata: Iterable Unit is ContainerElementFactory -> Unit is UnsafeSchemaItem -> Unit + is SchemaDocumentation -> Unit } } } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt index 9a926df..34ac7f7 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/EffectiveProjectDefinitionCommand.kt @@ -44,7 +44,7 @@ class EffectiveProjectDefinitionCommand : DclCommand("effective-project-definiti put("buildFile", projectInfo.projectBuildFile.path) put("content", buildJsonArray { with(DomJsonRenderer(withUnusedMembers, fullDom, documentationProvider)) { - fullDom.document.content.forEach { visitNode(null, it) } + fullDom.document.content.forEach { visitNode(it) } } }) } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt index 9bde430..0ce8fa5 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectSchemaCommand.kt @@ -135,7 +135,7 @@ class ProjectSchemaCommand : DclCommand("project-schema") { } } - documentationProvider.classDocumentation(type.name.qualifiedName)?.let { docs -> + documentationProvider.classDocumentation(type)?.let { docs -> put("documentation", docs) } } @@ -146,6 +146,9 @@ class ProjectSchemaCommand : DclCommand("project-schema") { if (property.isReadOnly) { put("isReadOnly", true) } + documentationProvider.propertyDocumentation(property)?.let { docs -> + put("documentation", docs) + } } @@ -184,6 +187,9 @@ class ProjectSchemaCommand : DclCommand("project-schema") { put("requiresConfiguringBlock", semantics.configureBlockRequirement.requires) } } + documentationProvider.functionDocumentation(function)?.let { docs -> + put("documentation", docs) + } } private fun collectTypesToInclude( @@ -238,6 +244,9 @@ class ProjectSchemaCommand : DclCommand("project-schema") { } } } + documentationProvider.functionDocumentation(function)?.let { docs -> + put("documentation", docs) + } } } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt index d329a2b..aaf26a0 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/ProjectTypesAndFeaturesCommand.kt @@ -21,22 +21,18 @@ class ProjectTypesAndFeaturesCommand : DclCommand("project-types-and-features") return buildJsonObject { val schema = prerequisites.projectInterpretationSequence.steps.last().evaluationSchemaForStep.analysisSchema - val projectTypes = schema.topLevelReceiverType.memberFunctions.flatMap { - it.metadata.filterIsInstance() + val projectTypes = schema.topLevelReceiverType.memberFunctions.flatMap { fn -> + fn.metadata.filterIsInstance().map { fn to it } } putJsonArray("projectTypes") { - projectTypes.forEach { projectType -> + projectTypes.forEach { (function, projectType) -> addJsonObject { put("name", projectType.featureName) put("ecosystemPluginId", projectType.ecosystemPluginId) - val documentation = documentationProvider.featureDocumentation( - projectType.ecosystemPluginId, - projectType.featureName - ) - if (documentation != null) { - put("documentation", documentation) + documentationProvider.functionDocumentation(function)?.let { + put("documentation", it) } } } @@ -44,9 +40,12 @@ class ProjectTypesAndFeaturesCommand : DclCommand("project-types-and-features") val features = schema.dataClassTypesByFqName.values.filterIsInstance() .flatMap { - it.metadata.filterIsInstance() - .filter { it.targetDefinitionClassName != "org.gradle.api.Project" } - }.distinctBy { + it.memberFunctions.flatMap { fn -> + fn.metadata.filterIsInstance() + .filter { it.targetDefinitionClassName != "org.gradle.api.Project" } + .map { fn to it } + } + }.distinctBy { (_, it) -> listOf( it.featureName, it.ecosystemPluginId, @@ -56,25 +55,20 @@ class ProjectTypesAndFeaturesCommand : DclCommand("project-types-and-features") } putJsonArray("projectFeatures") { - features.forEach { + features.forEach { (function, feature) -> addJsonObject { - put("name", it.featureName) - put("ecosystemPluginId", it.ecosystemPluginId) + put("name", feature.featureName) + put("ecosystemPluginId", feature.ecosystemPluginId) - if (it.targetDefinitionClassName != null) { - put("targetDefinitionTypeName", it.targetDefinitionClassName) + if (feature.targetDefinitionClassName != null) { + put("targetDefinitionTypeName", feature.targetDefinitionClassName) } - if (it.targetBuildModelClassName != null) { - put("targetBuildModelTypeName", it.targetBuildModelClassName) + if (feature.targetBuildModelClassName != null) { + put("targetBuildModelTypeName", feature.targetBuildModelClassName) } - val documentation = documentationProvider.featureDocumentation( - it.ecosystemPluginId, - it.featureName - ) - if (documentation != null) { - put("documentation", documentation) - } + documentationProvider.functionDocumentation(function) + ?.let { put("documentation", it) } } } } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt index 71cb99c..4a4a934 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefaultsCommand.kt @@ -58,7 +58,7 @@ class SettingsDefaultsCommand : DclCommand("settings-defaults") { defaultsBlocks.forEach { defaultsBlock -> defaultsBlock.content.forEach { node -> if (node is ElementNode && (projectTypeNames == null || node.name in projectTypeNames)) { - visitNode(null, node) + visitNode(node) } } } diff --git a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt index 42d5ad1..a9679bc 100644 --- a/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt +++ b/agentic-tool/src/main/kotlin/org/gradle/client/agentic/SettingsDefinitionCommand.kt @@ -37,7 +37,7 @@ class SettingsDefinitionCommand : DclCommand("settings-definition") { put("settingsFile", prerequisites.settingsFile.path) put("content", buildJsonArray { with(DomJsonRenderer(withUnusedMembers, dom, documentationProvider)) { - dom.document.content.forEach { visitNode(null, it) } + dom.document.content.forEach { visitNode(it) } } }) } From fc98cb550abe04d77a3aa29c0e36241d51022c0d Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 11 May 2026 21:07:47 +0300 Subject: [PATCH 18/19] Add a stub doc catalog resource to `build-logic` --- .../META-INF/declarative-dsl/documentation.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 build-logic/plugins/src/main/resources/META-INF/declarative-dsl/documentation.json diff --git a/build-logic/plugins/src/main/resources/META-INF/declarative-dsl/documentation.json b/build-logic/plugins/src/main/resources/META-INF/declarative-dsl/documentation.json new file mode 100644 index 0000000..42a731c --- /dev/null +++ b/build-logic/plugins/src/main/resources/META-INF/declarative-dsl/documentation.json @@ -0,0 +1,10 @@ +{ + "types": { + "org.gradle.client.projectfeatures.compose.Compose": { + "documentation": "The top-level Compose definition.", + "properties": { + "mainClass": "Main class to execute in the packaged Compose app." + } + } + } +} From 9553c34126e676c216f26640ee0f1173f7ca01c2 Mon Sep 17 00:00:00 2001 From: Paul Merlin Date: Tue, 12 May 2026 16:15:53 +0200 Subject: [PATCH 19/19] Fix warnings emited by agentic-tool JNA when doing --help SLF4J when running any other command --- agentic-tool/build.gradle.dcl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agentic-tool/build.gradle.dcl b/agentic-tool/build.gradle.dcl index a05b6e7..3577627 100644 --- a/agentic-tool/build.gradle.dcl +++ b/agentic-tool/build.gradle.dcl @@ -1,5 +1,6 @@ kotlinJvmApplication { mainClass = "org.gradle.client.agentic.AgenticToolKt" + jvmArguments += listOf("--enable-native-access=ALL-UNNAMED") dependencies { implementation(project(":build-action")) @@ -12,5 +13,7 @@ kotlinJvmApplication { implementation("com.github.ajalt.clikt:clikt:5.0.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + + runtimeOnly("org.slf4j:slf4j-simple:2.0.17") } }