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 3188943..6f10bb5 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 @@ -12,6 +12,7 @@ import java.io.File; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; public class GetResolvedDomAction implements BuildAction { @@ -37,10 +38,10 @@ private static Pair> getDeclarativeBuildFiles(BuildController c if (declarativeBuildFiles.isEmpty()) { throw new RuntimeException("No declarative project file found"); } - return Pair.of(rootProjectDirectory, declarativeBuildFiles); + return Pair.of(rootProjectDirectory, declarativeBuildFiles); } - + private static final class ResolvedDomPrerequisitesImpl implements ResolvedDomPrerequisites { private final InterpretationSequence settingsSequence; @@ -51,7 +52,7 @@ private static final class ResolvedDomPrerequisitesImpl implements ResolvedDomPr public ResolvedDomPrerequisitesImpl( InterpretationSequence settingsSequence, InterpretationSequence projectSequence, - File rootDir, + File rootDir, List declarativeBuildFiles ) { this.settingsSequence = settingsSequence; @@ -91,8 +92,11 @@ public File getSettingsFile() { } @Override - public List getDeclarativeBuildFiles() { - return declarativeBuildFiles; + public List getDeclarativeFiles() { + return Stream.concat( + declarativeBuildFiles.stream(), + getSettingsFile().canRead() ? Stream.of(getSettingsFile()) : Stream.empty() + ).collect(Collectors.toList()); } } } \ 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 40863ab..2e9980c 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 @@ -19,5 +19,5 @@ public interface ResolvedDomPrerequisites extends Serializable { File getSettingsFile(); - List getDeclarativeBuildFiles(); + List getDeclarativeFiles(); } diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt index 891dded..9aeda49 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/DocumentUtils.kt @@ -2,8 +2,17 @@ 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 import org.gradle.internal.declarativedsl.dom.data.collectToMap +import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlay.overlayResolvedDocuments +import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult +import org.gradle.internal.declarativedsl.dom.resolution.DocumentResolutionContainer 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.stepResultOrPartialResult +import org.gradle.internal.declarativedsl.language.SourceData +import org.gradle.internal.declarativedsl.language.SyntheticallyProduced import java.util.* fun DeclarativeDocument.relevantRange(): IntRange { @@ -42,3 +51,109 @@ fun DeclarativeDocument.nodeAt(fileIdentifier: String, offset: Int): Declarative } return node } + +fun settingsWithNoOverlayOrigin(analysisSequenceResult: AnalysisSequenceResult): DocumentOverlayResult? { + val docs = analysisSequenceResult.stepResults.map { it.value.stepResultOrPartialResult.resolvedDocument() } + if (docs.isEmpty()) + return null + + return indexBasedOverlayResultFromDocuments(docs) +} + + +internal fun indexBasedOverlayResultFromDocuments(docs: List): DocumentOverlayResult { + val emptyDoc = DocumentWithResolution( + object : DeclarativeDocument { + override val content: List = emptyList() + override val sourceData: SourceData = SyntheticallyProduced + }, + indexBasedMultiResolutionContainer(emptyList()) + ) + + val lastDocWithAllResolutionResults = DocumentWithResolution( + docs.last().document, + indexBasedMultiResolutionContainer(docs) + ) + + /** + * NB: No real overlay origin data is going to be present, as we are overlaying the doc with all the resolution + * results collected over the empty document. + */ + return overlayResolvedDocuments(emptyDoc, lastDocWithAllResolutionResults) +} + +/** + * A resolution results container collected from multiple resolved instances of the same document (or multiple + * different instances of the same document, no referential equality required). + * + * The document parts are matched based on indices. + * + * If any of the [docs] is different from the others, the result is undefined (likely to be a broken container). + */ +internal fun indexBasedMultiResolutionContainer(docs: List): DocumentResolutionContainer { + val indicesMaps: Map> = docs.associateWith { + buildMap { + fun visitValue(valueNode: DeclarativeDocument.ValueNode) { + put(valueNode.sourceData.indexRange, valueNode) + when (valueNode) { + is DeclarativeDocument.ValueNode.ValueFactoryNode -> valueNode.values.forEach(::visitValue) + is DeclarativeDocument.ValueNode.LiteralValueNode, + is DeclarativeDocument.ValueNode.NamedReferenceNode -> Unit + } + } + + fun visitDocumentNode(documentNode: DeclarativeDocument.DocumentNode) { + put(documentNode.sourceData.indexRange, documentNode) + when (documentNode) { + is DeclarativeDocument.DocumentNode.ElementNode -> { + documentNode.elementValues.forEach(::visitValue) + documentNode.content.forEach(::visitDocumentNode) + } + + is DeclarativeDocument.DocumentNode.PropertyNode -> visitValue(documentNode.value) + is DeclarativeDocument.DocumentNode.ErrorNode -> Unit + } + } + + it.document.content.forEach(::visitDocumentNode) + } + } + + /** + * The resolution containers work with node identities. + * Querying resolution results using nodes from a different document is prohibited. + * Given that all documents are the same, we can map the node indices and use them to find matching nodes in + * the documents that we are merging. + */ + return object : DocumentResolutionContainer { + inline fun retryOverContainers( + node: N, + noinline get: DocumentResolutionContainer.(N) -> T + ) = docs.map { doc -> + val matchingNode = indicesMaps.getValue(doc)[node.sourceData.indexRange] + ?: error("index not found in index map") + get(doc.resolutionContainer, matchingNode as N) + }.let { results -> + results.firstOrNull { + it !is DocumentResolution.UnsuccessfulResolution || !it.reasons.contains(UnresolvedBase) + } ?: results.first() + } + + override fun data(node: DeclarativeDocument.DocumentNode.ElementNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.DocumentNode.ErrorNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.DocumentNode.PropertyNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.ValueNode.LiteralValueNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.ValueNode.NamedReferenceNode) = + retryOverContainers(node) { data(it) } + + override fun data(node: DeclarativeDocument.ValueNode.ValueFactoryNode) = retryOverContainers(node) { data(it) } + } +} + +internal fun DocumentResolutionContainer.isUnresolvedBase(node: DeclarativeDocument.Node): Boolean { + val resolution = when (node) { + is DeclarativeDocument.DocumentNode -> data(node) + is DeclarativeDocument.ValueNode -> data(node) + } + return resolution is DocumentResolution.UnsuccessfulResolution && resolution.reasons != listOf(UnresolvedBase) +} diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/MutationUtils.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/MutationUtils.kt index 900e538..479e06b 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/MutationUtils.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/core/gradle/dcl/MutationUtils.kt @@ -56,9 +56,9 @@ object MutationUtils { overlayResult: DocumentOverlayResult, ): NodeData> { val overlayDocument = overlayResult.inputOverlay - val compatibleMutationsCatalog = DefaultMutationDefinitionCatalog().apply { - mutationCatalog.mutationDefinitionsById.values.forEach { - if (it.isCompatibleWithSchema(modelSchema)) { + val compatibleMutationsCatalog = DefaultMutationDefinitionCatalog().apply { + mutationCatalog.mutationDefinitionsById.values.forEach { + if (it.isCompatibleWithSchema(modelSchema)) { registerMutationDefinition(it) } } @@ -124,3 +124,9 @@ internal class OverlayRoutedNodeDataContainer overlay.data(node) } } + +internal class NoApplicableMutationsNodeData : NodeData> { + override fun data(node: DeclarativeDocument.DocumentNode.ElementNode) = emptyList() + override fun data(node: DeclarativeDocument.DocumentNode.ErrorNode) = emptyList() + override fun data(node: DeclarativeDocument.DocumentNode.PropertyNode) = emptyList() +} diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeDomHelpers.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeDomHelpers.kt index 1597692..1e91ea9 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeDomHelpers.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeDomHelpers.kt @@ -3,14 +3,15 @@ package org.gradle.client.ui.connected.actions import org.gradle.internal.declarativedsl.dom.DeclarativeDocument import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.PropertyNode +import org.gradle.internal.declarativedsl.dom.DocumentNodeContainer val DeclarativeDocument.singleSoftwareTypeNode: ElementNode? get() = content.filterIsInstance().singleOrNull() -fun ElementNode.childElementNode( +fun DocumentNodeContainer.childElementNode( name: String ): ElementNode? = content.filterIsInstance().singleOrNull() { it.name == name } -fun ElementNode.property(name: String): PropertyNode? = +fun DocumentNodeContainer.property(name: String): PropertyNode? = content.filterIsInstance().singleOrNull { it.name == name } diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeSchemaHelpers.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeSchemaHelpers.kt index 3c02ea8..1b7bc45 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeSchemaHelpers.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/DeclarativeSchemaHelpers.kt @@ -25,14 +25,6 @@ val DataProperty.typeName: String is DataTypeRef.Name -> propType.toHumanReadable() } -val DataProperty.kotlinType: KClass<*> - get() = when (typeName) { - "String" -> String::class - "Int" -> Int::class - "Boolean" -> Boolean::class - else -> Any::class - } - fun DataTypeRef.toHumanReadable(): String = when (this) { is DataTypeRef.Name -> fqName.simpleName diff --git a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetDeclarativeDocuments.kt b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetDeclarativeDocuments.kt index c9880d0..cb52893 100644 --- a/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetDeclarativeDocuments.kt +++ b/gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetDeclarativeDocuments.kt @@ -30,10 +30,10 @@ import org.gradle.client.ui.connected.TwoPanes import org.gradle.client.ui.theme.spacing import org.gradle.client.ui.theme.transparency import org.gradle.declarative.dsl.evaluation.EvaluationSchema -import org.gradle.declarative.dsl.schema.DataClass -import org.gradle.declarative.dsl.schema.DataProperty -import org.gradle.declarative.dsl.schema.SchemaMemberFunction +import org.gradle.declarative.dsl.schema.* import org.gradle.internal.declarativedsl.dom.DeclarativeDocument +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode +import org.gradle.internal.declarativedsl.dom.DocumentNodeContainer import org.gradle.internal.declarativedsl.dom.DocumentResolution.ElementResolution.SuccessfulElementResolution.ContainerElementResolved import org.gradle.internal.declarativedsl.dom.DocumentResolution.PropertyResolution.PropertyAssignmentResolved import org.gradle.internal.declarativedsl.dom.data.NodeData @@ -43,7 +43,9 @@ import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlay import org.gradle.internal.declarativedsl.dom.operations.overlay.OverlayNodeOrigin.* 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 +import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils.documentWithModelDefaults +import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils.resolvedDocument +import org.gradle.internal.declarativedsl.evaluator.main.AnalysisSequenceResult import org.gradle.internal.declarativedsl.evaluator.runner.stepResultOrPartialResult import org.gradle.tooling.BuildAction import org.jetbrains.skiko.Cursor @@ -65,15 +67,16 @@ class GetDeclarativeDocuments : GetModelAction.GetCompositeModelAction(model.declarativeBuildFiles.first()) } + val selectedDocument = remember { mutableStateOf(model.declarativeFiles.first()) } + + fun readDocumentFile() = selectedDocument.value.takeIf { it.canRead() }?.readText().orEmpty() - fun readBuildFile() = selectedBuildFile.value.takeIf { it.canRead() }?.readText().orEmpty() fun readSettingsFile() = model.settingsFile.takeIf { it.canRead() }?.readText().orEmpty() - val buildFileContent = remember(selectedBuildFile.value) { - mutableStateOf(readBuildFile()) + val selectedFileContent = remember(selectedDocument.value) { + mutableStateOf(readDocumentFile()) } - val settingsFileContent = remember(selectedBuildFile.value, model.settingsFile) { + val settingsFileContent = remember(selectedDocument.value, model.settingsFile) { mutableStateOf(readSettingsFile()) } @@ -83,10 +86,10 @@ class GetDeclarativeDocuments : GetModelAction.GetCompositeModelAction, + highlightedSourceRangeByFileId: MutableState>, + updateFileContents: () -> Unit + ) { + val highlightingContext = HighlightingContext( + domWithDefaults.overlayNodeOriginContainer, + highlightedSourceRangeByFileId + ) + + val projectEvaluationSchemas = selectedDocumentResult.stepResults.values.last() + .stepResultOrPartialResult.evaluationSchema + + val projectAnalysisSchema = projectEvaluationSchemas.analysisSchema + + val mutationApplicability = + MutationUtils.checkApplicabilityForOverlay(projectAnalysisSchema, domWithDefaults) + + val softwareTypeSchema = projectAnalysisSchema.softwareTypeNamed(softwareTypeNode.name) + if (softwareTypeSchema == null) { + Text("No software type named '${softwareTypeNode.name}'") + } else { + + val softwareTypeType = + projectAnalysisSchema.configuredTypeOf(softwareTypeSchema.softwareTypeSemantics) + + ModelView( + selectedDocument, + highlightingContext, + projectEvaluationSchemas, + domWithDefaults, + softwareTypeNode, + softwareTypeType, + mutationApplicability, + updateFileContents, + ) + } + } + + @Composable + private fun SettingsModelContent( + highlightedSourceRangeByFileId: MutableState>, + selectedDocumentResult: AnalysisSequenceResult + ) { + selectedDocumentResult.stepResults.forEach { (step, result) -> + val document = result.stepResultOrPartialResult.resolvedDocument() + + /** + * For multistep settings, we want to avoid document content appearing as duplicates in + * multiple steps, so isolate each step's results within a separate document. + * This separate document does not have any resolution results for the parts resolved in the other steps, + * so functions visiting the schema+DOM will skip those subtrees. + */ + val overlayForJustThisDocument = indexBasedOverlayResultFromDocuments(listOf(document)) + + val highlightingContext = HighlightingContext( + overlayForJustThisDocument.overlayNodeOriginContainer, + highlightedSourceRangeByFileId + ) + + with( + ModelTreeRendering( + indexBasedMultiResolutionContainer(listOf(document)), + highlightingContext, + NoApplicableMutationsNodeData(), + { _, _ -> } + ) + ) { + val topLevelReceiverType = + result.stepResultOrPartialResult.evaluationSchema.analysisSchema.topLevelReceiverType + TitleLarge("Step: ${step.stepIdentifier.key}") + ElementInfoOrNothingDeclared(topLevelReceiverType, document.document, 0) + } + } + } + @Composable @Suppress("LongParameterList") private fun ModelView( @@ -194,7 +258,7 @@ class GetDeclarativeDocuments : GetModelAction.GetCompositeModelAction>, updateFileContents: () -> Unit @@ -243,16 +307,19 @@ class GetDeclarativeDocuments : GetModelAction.GetCompositeModelAction @@ -305,7 +372,7 @@ class GetDeclarativeDocuments : GetModelAction.GetCompositeModelAction() + .filterIsInstance() .filter { it.name in addAndConfigureByName } elementsByAddAndConfigure.forEach { element -> @@ -373,10 +443,12 @@ class ModelTreeRendering( @Composable private fun ModelTreeRendering.AddingFunctionInfo( - element: DeclarativeDocument.DocumentNode.ElementNode, + element: ElementNode, indentLevel: Int ) { - val arguments = element.elementValues.joinToString { valueNode -> + if (resolutionContainer.isUnresolvedBase(element)) return + + val arguments = element.elementValues.joinToString { valueNode -> when (valueNode) { is DeclarativeDocument.ValueNode.LiteralValueNode -> valueNode.value.toString() is DeclarativeDocument.ValueNode.NamedReferenceNode -> valueNode.referenceName @@ -388,7 +460,7 @@ class ModelTreeRendering( "${valueNode.factoryName}$argsString" } } - } + } val elementType = (resolutionContainer.data(element) as? ContainerElementResolved)?.elementType as? DataClass @@ -426,9 +498,12 @@ class ModelTreeRendering( @Composable private fun ModelTreeRendering.ConfiguringFunctionInfo( subFunction: SchemaMemberFunction, - parentNode: DeclarativeDocument.DocumentNode.ElementNode, + parentNode: DocumentNodeContainer, indentLevel: Int ) { + if (parentNode is ElementNode && resolutionContainer.isUnresolvedBase(parentNode)) + return + val functionNode = parentNode.childElementNode(subFunction.simpleName) val functionType = functionNode?.type(resolutionContainer) as? DataClass WithDecoration(functionNode) { @@ -453,6 +528,10 @@ class ModelTreeRendering( propertyNode: DeclarativeDocument.DocumentNode.PropertyNode?, property: DataProperty ) { + if (propertyNode != null && resolutionContainer.isUnresolvedBase(propertyNode)) { + return + } + WithDecoration(propertyNode) { val maybeInvalidDecoration = if (propertyNode != null && resolutionContainer.data(propertyNode) !is PropertyAssignmentResolved) @@ -463,7 +542,7 @@ class ModelTreeRendering( .withClickTextRangeSelection(propertyNode, highlightingContext) .semiTransparentIfNull(propertyNode), textStyle = TextStyle(textDecoration = maybeInvalidDecoration), - text = "${property.name}: ${property.kotlinType.simpleName} = ${ + text = "${property.name}: ${property.typeName} = ${ propertyNode?.value?.sourceData?.text() ?: NOTHING_DECLARED }" )