Skip to content

Commit

Permalink
Remove OciImagesInput, OciImageInput directly references OciVariantIn…
Browse files Browse the repository at this point in the history
…puts instead of indices

Change OciImage/VariantInput to data classes
Change OciImagesInputTask.imageInputs from list to set property
  • Loading branch information
SgtSilvio committed Jul 11, 2024
1 parent 00b6b5a commit 72a3811
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 68 deletions.
91 changes: 40 additions & 51 deletions src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<OciVariantInput>,
@get:Nested val imageInputs: List<OciImageInput>,
)

class OciVariantInput(
data class OciVariantInput(
@get:InputFile @get:PathSensitive(PathSensitivity.NONE) val metadataFile: File,
@get:InputFiles @get:PathSensitive(PathSensitivity.NONE) val layerFiles: List<File>,
)

class OciImageInput(
data class OciImageInput(
@get:Input val platform: Platform,
@get:Input val variantIndices: List<Int>, // TODO document must not be empty
@get:Nested val variants: List<OciVariantInput>, // TODO document must not be empty
@get:Input val referenceSpecs: Set<OciImageReferenceSpec>,
)

Expand Down Expand Up @@ -68,26 +63,28 @@ internal class OciMultiArchImage(
abstract class OciImagesInputTask : DefaultTask() {

@get:Nested
val imagesInputs = project.objects.listProperty<OciImagesInput>()
val imageInputs = project.objects.setProperty<OciImageInput>()

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<OciImagesInput> = imagesInputs.get()
val imageInputs: Set<OciImageInput> = imageInputs.get()
// digestToLayerFile map is linked because it will be iterated
val digestToLayerFile = LinkedHashMap<OciDigest, File>()
val duplicateLayerFiles = HashSet<File>()
val images = ArrayList<OciImage>()
// referenceToPlatformToImage map is linked because it will be iterated
// platformToImage map is linked to preserve the platform order
val referenceToPlatformToImage = LinkedHashMap<OciImageReference, LinkedHashMap<Platform, OciImage>>()
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<OciLayer>(layerFiles.size) // TODO fun associateLayerMetadataAndFiles
Expand All @@ -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'")
}
}
}
Expand All @@ -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)")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -9,7 +9,7 @@ import org.gradle.api.provider.Provider
*/
interface ResolvableOciImageDependencies : OciImageDependencies<ResolvableOciImageDependencies.Nameable>, Named {

fun asInput(): Provider<OciImagesInput>
fun asInput(): Provider<List<OciImageInput>>

interface Taggable {
fun tag(vararg tags: String): Taggable
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<OciImagesInput> {
internal fun ResolvableDependencies.resolveOciImagesInput(): Provider<List<OciImageInput>> {
val rootComponentProvider = resolutionResult.rootComponent
val imageSpecsProvider = rootComponentProvider.map(::resolveOciImageSpecs)
val artifactsResultsProvider = artifactView {
val artifactResultsProvider = artifactView {
componentFilter(
ArtifactViewVariantFilter(
rootComponentProvider,
Expand All @@ -26,26 +25,79 @@ internal fun ResolvableDependencies.resolveOciImagesInput(): Provider<OciImagesI
// this mapper function does not read the file contents, so can already be called once the value is available
// this allows this mapper function to be run before storing the configuration cache
// apart from performance benefits this also avoids a bug where the artifactsResultsProvider value is different when using the configuration cache
return artifactsResultsProvider.flatMap { artifactsResults ->
return artifactResultsProvider.flatMap { artifactResults ->
val imageSpecs = imageSpecsProvider.get()
val variantDescriptorToArtifacts = artifactsResults.groupBy({ it.variant.toDescriptor() }) { it.file }
val variantInputs = ArrayList<OciVariantInput>(variantDescriptorToArtifacts.size)
val variantDescriptorToIndex = HashMap<VariantDescriptor, Int>()
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<VariantDescriptor, OciVariantInput>()
// 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<File>()
// 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<VariantDescriptor, OciVariantInput>()
// val artifactResultsIterator = artifactResults.iterator()
// if (artifactResultsIterator.hasNext()) {
// val metadataArtifactResult = artifactResultsIterator.next()
// var variantResult = metadataArtifactResult.variant
// var metadataFile = metadataArtifactResult.file
// var layerFiles = ArrayList<File>()
// 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<VariantDescriptor, OciVariantInput>()
// var metadataArtifactResult: ResolvedArtifactResult? = null
// val layerFiles = ArrayList<File>()
// 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 }
}
}

Expand Down

0 comments on commit 72a3811

Please sign in to comment.