From 72a3811426e121c65c47742e0d0d9c712b299924 Mon Sep 17 00:00:00 2001 From: Silvio Giebl Date: Thu, 11 Jul 2024 17:32:27 +0200 Subject: [PATCH] Remove OciImagesInput, OciImageInput directly references OciVariantInputs instead of indices Change OciImage/VariantInput to data classes Change OciImagesInputTask.imageInputs from list to set property --- .../sgtsilvio/gradle/oci/OciImagesInput.kt | 91 ++++++++----------- .../oci/dsl/ResolvableOciImageDependencies.kt | 4 +- .../resolution/OciImagesInputResolution.kt | 82 ++++++++++++++--- 3 files changed, 109 insertions(+), 68 deletions(-) diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesInput.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesInput.kt index 0665208c..63d6081f 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesInput.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesInput.kt @@ -6,26 +6,21 @@ import io.github.sgtsilvio.gradle.oci.platform.Platform import org.apache.commons.io.FileUtils import org.gradle.api.DefaultTask import org.gradle.api.tasks.* -import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.setProperty import java.io.File import java.io.Serializable /** * @author Silvio Giebl */ -class OciImagesInput( - @get:Nested val variantInputs: List, - @get:Nested val imageInputs: List, -) - -class OciVariantInput( +data class OciVariantInput( @get:InputFile @get:PathSensitive(PathSensitivity.NONE) val metadataFile: File, @get:InputFiles @get:PathSensitive(PathSensitivity.NONE) val layerFiles: List, ) -class OciImageInput( +data class OciImageInput( @get:Input val platform: Platform, - @get:Input val variantIndices: List, // TODO document must not be empty + @get:Nested val variants: List, // TODO document must not be empty @get:Input val referenceSpecs: Set, ) @@ -68,26 +63,28 @@ internal class OciMultiArchImage( abstract class OciImagesInputTask : DefaultTask() { @get:Nested - val imagesInputs = project.objects.listProperty() + val imageInputs = project.objects.setProperty() init { @Suppress("LeakingThis") - dependsOn(imagesInputs) // TODO is it intended that nested does not track dependencies? + dependsOn(imageInputs) } - fun from(dependencies: ResolvableOciImageDependencies) = imagesInputs.add(dependencies.asInput()) + fun from(dependencies: ResolvableOciImageDependencies) = imageInputs.addAll(dependencies.asInput()) @TaskAction protected fun run() { - val imagesInputs: List = imagesInputs.get() + val imageInputs: Set = imageInputs.get() // digestToLayerFile map is linked because it will be iterated val digestToLayerFile = LinkedHashMap() + val duplicateLayerFiles = HashSet() val images = ArrayList() // referenceToPlatformToImage map is linked because it will be iterated // platformToImage map is linked to preserve the platform order val referenceToPlatformToImage = LinkedHashMap>() - for (imagesInput in imagesInputs) { - val variants = imagesInput.variantInputs.map { variantInput -> + for (imageInput in imageInputs) { + val variants = imageInput.variants.map { variantInput -> + // TODO deduplicate variant processing val metadata = variantInput.metadataFile.readText().decodeAsJsonToOciMetadata() val layerFiles = variantInput.layerFiles val layers = ArrayList(layerFiles.size) // TODO fun associateLayerMetadataAndFiles @@ -99,41 +96,35 @@ abstract class OciImagesInputTask : DefaultTask() { } val layerFile = layerFiles[layerFileIndex++] layers += OciLayer(layerDescriptor, layerFile) - val prevLayerFile = digestToLayerFile.putIfAbsent(layerDescriptor.digest, layerFile) - if (prevLayerFile != null) { - checkDuplicateLayer(layerDescriptor, prevLayerFile, layerFile) - } } if (layerFileIndex < layerFiles.size) { throw IllegalStateException("count of layer descriptors ($layerFileIndex) and layer files (${layerFiles.size}) do not match") } - OciVariant(metadata, layers) - } - for (imageInput in imagesInput.imageInputs) { - val imageVariants = imageInput.variantIndices.map { index -> - if (index !in variants.indices) { - throw IllegalStateException("imageInput.variantIndices contains wrong index $index") + for (layer in layers) { + val prevLayerFile = digestToLayerFile.putIfAbsent(layer.descriptor.digest, layer.file) + if ((prevLayerFile != null) && (prevLayerFile != layer.file) && duplicateLayerFiles.add(layer.file)) { + checkDuplicateLayer(layer.descriptor, prevLayerFile, layer.file) } - variants[index] } - val config = createConfig(imageInput.platform, imageVariants) - val manifest = createManifest(config, imageVariants) - val image = OciImage(manifest, config, imageVariants) - images += image - - val defaultImageReference = imageVariants.last().metadata.imageReference - // imageReferences set is linked because it will be iterated - val imageReferences = imageInput.referenceSpecs.mapTo(LinkedHashSet()) { - OciImageReference(it.name ?: defaultImageReference.name, it.tag ?: defaultImageReference.tag) - }.ifEmpty { setOf(defaultImageReference) } - val platform = imageInput.platform - for (imageReference in imageReferences) { - // platformToImage map is linked to preserve the platform order - val platformToImage = referenceToPlatformToImage.getOrPut(imageReference) { LinkedHashMap() } - val prevImage = platformToImage.putIfAbsent(platform, image) - if (prevImage != null) { - throw IllegalStateException("only one image with platform $platform can be referenced by the same image reference '$imageReference'") - } + OciVariant(metadata, layers) + } + val config = createConfig(imageInput.platform, variants) + val manifest = createManifest(config, variants) + val image = OciImage(manifest, config, variants) + images += image + + val defaultImageReference = variants.last().metadata.imageReference + // imageReferences set is linked because it will be iterated + val imageReferences = imageInput.referenceSpecs.mapTo(LinkedHashSet()) { + OciImageReference(it.name ?: defaultImageReference.name, it.tag ?: defaultImageReference.tag) + }.ifEmpty { setOf(defaultImageReference) } + val platform = imageInput.platform + for (imageReference in imageReferences) { + // platformToImage map is linked to preserve the platform order + val platformToImage = referenceToPlatformToImage.getOrPut(imageReference) { LinkedHashMap() } + val prevImage = platformToImage.putIfAbsent(platform, image) + if (prevImage != null) { + throw IllegalStateException("only one image with platform $platform can be referenced by the same image reference '$imageReference'") } } } @@ -159,13 +150,11 @@ abstract class OciImagesInputTask : DefaultTask() { ) private fun checkDuplicateLayer(layerDescriptor: OciMetadata.Layer.Descriptor, file1: File, file2: File) { - if (file1 != file2) { - if (!FileUtils.contentEquals(file1, file2)) { - throw IllegalStateException("hash collision for digest ${layerDescriptor.digest}: expected file contents of $file1 and $file2 to be the same") - } - if (layerDescriptor.diffId !in EMPTY_LAYER_DIFF_IDS) { - logger.warn("the same layer (${layerDescriptor.digest}) should not be provided by multiple artifacts ($file1, $file2)") - } + if (!FileUtils.contentEquals(file1, file2)) { + throw IllegalStateException("hash collision for digest ${layerDescriptor.digest}: expected file contents of $file1 and $file2 to be the same") + } + if (layerDescriptor.diffId !in EMPTY_LAYER_DIFF_IDS) { + logger.warn("the same layer (${layerDescriptor.digest}) should not be provided by multiple artifacts ($file1, $file2)") } } } diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/ResolvableOciImageDependencies.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/ResolvableOciImageDependencies.kt index a9ce9a81..120a7f60 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/ResolvableOciImageDependencies.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/ResolvableOciImageDependencies.kt @@ -1,6 +1,6 @@ package io.github.sgtsilvio.gradle.oci.dsl -import io.github.sgtsilvio.gradle.oci.OciImagesInput +import io.github.sgtsilvio.gradle.oci.OciImageInput import org.gradle.api.Named import org.gradle.api.provider.Provider @@ -9,7 +9,7 @@ import org.gradle.api.provider.Provider */ interface ResolvableOciImageDependencies : OciImageDependencies, Named { - fun asInput(): Provider + fun asInput(): Provider> interface Taggable { fun tag(vararg tags: String): Taggable diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImagesInputResolution.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImagesInputResolution.kt index 13061cc3..d65c5d84 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImagesInputResolution.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImagesInputResolution.kt @@ -1,7 +1,6 @@ package io.github.sgtsilvio.gradle.oci.internal.resolution import io.github.sgtsilvio.gradle.oci.OciImageInput -import io.github.sgtsilvio.gradle.oci.OciImagesInput import io.github.sgtsilvio.gradle.oci.OciVariantInput import io.github.sgtsilvio.gradle.oci.internal.gradle.ArtifactViewVariantFilter import org.gradle.api.artifacts.ResolvableDependencies @@ -11,10 +10,10 @@ import org.gradle.api.attributes.AttributeContainer import org.gradle.api.capabilities.Capability import org.gradle.api.provider.Provider -internal fun ResolvableDependencies.resolveOciImagesInput(): Provider { +internal fun ResolvableDependencies.resolveOciImagesInput(): Provider> { val rootComponentProvider = resolutionResult.rootComponent val imageSpecsProvider = rootComponentProvider.map(::resolveOciImageSpecs) - val artifactsResultsProvider = artifactView { + val artifactResultsProvider = artifactView { componentFilter( ArtifactViewVariantFilter( rootComponentProvider, @@ -26,26 +25,79 @@ internal fun ResolvableDependencies.resolveOciImagesInput(): Provider + return artifactResultsProvider.flatMap { artifactResults -> val imageSpecs = imageSpecsProvider.get() - val variantDescriptorToArtifacts = artifactsResults.groupBy({ it.variant.toDescriptor() }) { it.file } - val variantInputs = ArrayList(variantDescriptorToArtifacts.size) - val variantDescriptorToIndex = HashMap() - var variantIndex = 0 - for ((variantDescriptor, artifacts) in variantDescriptorToArtifacts) { - variantInputs += OciVariantInput(artifacts.first(), artifacts.drop(1)) - variantDescriptorToIndex[variantDescriptor] = variantIndex++ - } + val variantDescriptorToInput = artifactResults.groupBy({ it.variant.toDescriptor() }) { it.file } + .mapValues { (_, files) -> OciVariantInput(files.first(), files.drop(1)) } + +// val variantDescriptorToInput = HashMap() +// val artifactResultsIterator = artifactResults.iterator() +// if (artifactResultsIterator.hasNext()) { +// var artifactResult = artifactResultsIterator.next() +// outer@ while (true) { +// val variantResult = artifactResult.variant +// val metadataFile = artifactResult.file +// val layerFiles = ArrayList() +// while (true) { +// if (!artifactResultsIterator.hasNext()) { +// break@outer +// } +// artifactResult = artifactResultsIterator.next() +// if (artifactResult.variant != variantResult) { +// break +// } +// layerFiles += artifactResult.file +// } +// variantDescriptorToInput[variantResult.toDescriptor()] = OciVariantInput(metadataFile, layerFiles) +// } +// } + +// val variantDescriptorToInput = HashMap() +// val artifactResultsIterator = artifactResults.iterator() +// if (artifactResultsIterator.hasNext()) { +// val metadataArtifactResult = artifactResultsIterator.next() +// var variantResult = metadataArtifactResult.variant +// var metadataFile = metadataArtifactResult.file +// var layerFiles = ArrayList() +// while (artifactResultsIterator.hasNext()) { +// val artifactResult = artifactResultsIterator.next() +// if (artifactResult.variant == variantResult) { +// layerFiles += artifactResult.file +// } else { +// variantDescriptorToInput[variantResult.toDescriptor()] = OciVariantInput(metadataFile, layerFiles) +// variantResult = artifactResult.variant +// metadataFile = artifactResult.file +// layerFiles = ArrayList() +// } +// } +// variantDescriptorToInput[variantResult.toDescriptor()] = OciVariantInput(metadataFile, layerFiles) +// } + +// val variantDescriptorToInput = HashMap() +// var metadataArtifactResult: ResolvedArtifactResult? = null +// val layerFiles = ArrayList() +// for (artifactResult in artifactResults) { +// if (artifactResult.variant == metadataArtifactResult?.variant) { +// layerFiles += artifactResult.file +// } else { +// if (metadataArtifactResult != null) { +// variantDescriptorToInput[metadataArtifactResult.variant.toDescriptor()] = +// OciVariantInput(metadataArtifactResult.file, layerFiles.toList()) +// } +// metadataArtifactResult = artifactResult +// layerFiles.clear() +// } +// } + val imageInputs = imageSpecs.map { imageSpec -> OciImageInput( imageSpec.platform, - imageSpec.variants.mapNotNull { variant -> variantDescriptorToIndex[variant.toDescriptor()] }, + imageSpec.variants.mapNotNull { variant -> variantDescriptorToInput[variant.toDescriptor()] }, imageSpec.referenceSpecs, ) } - val imagesInputs = OciImagesInput(variantInputs, imageInputs) // using map to attach the task dependencies from the artifactsResultsProvider - artifactsResultsProvider.map { imagesInputs } + artifactResultsProvider.map { imageInputs } } }