Skip to content

Commit

Permalink
Add PlatformSelector
Browse files Browse the repository at this point in the history
Add OciExtension.platformSelector
Add OciImagesTask.platformSelector
Add OciImagesTask command line option --platform
Use PlatformSelector in OciImageInput/SpecResolution.kt
  • Loading branch information
SgtSilvio committed Jul 17, 2024
1 parent 7bc5ef0 commit d7ee630
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 21 deletions.
16 changes: 15 additions & 1 deletion src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import io.github.sgtsilvio.gradle.oci.dsl.ResolvableOciImageDependencies
import io.github.sgtsilvio.gradle.oci.internal.resolution.resolveOciImageInputs
import io.github.sgtsilvio.gradle.oci.metadata.*
import io.github.sgtsilvio.gradle.oci.platform.Platform
import io.github.sgtsilvio.gradle.oci.platform.PlatformSelector
import io.github.sgtsilvio.gradle.oci.platform.toPlatform
import org.apache.commons.io.FileUtils
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.*
import org.gradle.api.tasks.options.Option
import org.gradle.kotlin.dsl.property
import org.gradle.kotlin.dsl.setProperty
import java.io.File

Expand All @@ -33,13 +37,23 @@ abstract class OciImagesTask : DefaultTask() {
@get:InputFiles @get:PathSensitive(PathSensitivity.NONE) val layerFiles: List<File>,
)

@get:Internal
val platformSelector = project.objects.property<PlatformSelector>()

init {
@Suppress("LeakingThis")
dependsOn(images)
}

fun from(dependencies: ResolvableOciImageDependencies) =
images.addAll(dependencies.configuration.incoming.resolveOciImageInputs())
images.addAll(dependencies.configuration.incoming.resolveOciImageInputs(platformSelector))

@Option(
option = "platform",
description = "Selects the platform specified in the format: <os>,<arch>[,<variant>[,<osVersion>[,<osFeature>(,<osFeature>)*]]]. Option can be specified multiple times. If not specified, all supported platforms are selected.",
)
protected fun selectPlatforms(platforms: List<String>) =
platformSelector.set(platforms.map { PlatformSelector(it.toPlatform()) }.reduce(PlatformSelector::and))

@TaskAction
protected fun run() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.github.sgtsilvio.gradle.oci.OciRegistryDataTask
import io.github.sgtsilvio.gradle.oci.mapping.OciImageMapping
import io.github.sgtsilvio.gradle.oci.platform.Platform
import io.github.sgtsilvio.gradle.oci.platform.PlatformFilter
import io.github.sgtsilvio.gradle.oci.platform.PlatformSelector
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.provider.SetProperty
Expand Down Expand Up @@ -47,6 +48,8 @@ interface OciExtension {
val osVersions: SetProperty<String>
}

fun platformSelector(platform: Platform): PlatformSelector

fun copySpec(): OciCopySpec

fun copySpec(configuration: Action<in OciCopySpec>): OciCopySpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.github.sgtsilvio.gradle.oci.mapping.OciImageMapping
import io.github.sgtsilvio.gradle.oci.mapping.OciImageMappingImpl
import io.github.sgtsilvio.gradle.oci.platform.Platform
import io.github.sgtsilvio.gradle.oci.platform.PlatformFilter
import io.github.sgtsilvio.gradle.oci.platform.PlatformSelector
import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import org.gradle.kotlin.dsl.domainObjectContainer
Expand Down Expand Up @@ -68,6 +69,8 @@ internal abstract class OciExtensionImpl @Inject constructor(private val objectF
final override fun PlatformFilter.or(configuration: Action<in OciExtension.PlatformFilterBuilder>) =
or(platformFilter(configuration))

final override fun platformSelector(platform: Platform) = PlatformSelector(platform)

final override fun copySpec() = objectFactory.newOciCopySpec()

final override fun copySpec(configuration: Action<in OciCopySpec>): OciCopySpecImpl {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ package io.github.sgtsilvio.gradle.oci.internal.resolution

import io.github.sgtsilvio.gradle.oci.OciImagesTask
import io.github.sgtsilvio.gradle.oci.internal.gradle.ArtifactViewVariantFilter
import io.github.sgtsilvio.gradle.oci.internal.gradle.zipAbsentAsNull
import io.github.sgtsilvio.gradle.oci.platform.PlatformSelector
import org.gradle.api.artifacts.ResolvableDependencies
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedVariantResult
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.capabilities.Capability
import org.gradle.api.provider.Provider

internal fun ResolvableDependencies.resolveOciImageInputs(): Provider<List<OciImagesTask.ImageInput>> {
internal fun ResolvableDependencies.resolveOciImageInputs(
platformSelectorProvider: Provider<PlatformSelector>,
): Provider<List<OciImagesTask.ImageInput>> {
val rootComponentProvider = resolutionResult.rootComponent
val imageSpecsProvider = rootComponentProvider.map(::resolveOciImageSpecs)
val imageSpecsProvider =
rootComponentProvider.zipAbsentAsNull(platformSelectorProvider) { rootComponent, platformSelector ->
resolveOciImageSpecs(rootComponent, platformSelector)
}
val artifactResultsProvider = artifactView {
componentFilter(
ArtifactViewVariantFilter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.github.sgtsilvio.gradle.oci.metadata.DEFAULT_OCI_REFERENCE_SPEC
import io.github.sgtsilvio.gradle.oci.metadata.OciImageReferenceSpec
import io.github.sgtsilvio.gradle.oci.metadata.toOciImageReferenceSpec
import io.github.sgtsilvio.gradle.oci.platform.Platform
import io.github.sgtsilvio.gradle.oci.platform.PlatformSelector
import io.github.sgtsilvio.gradle.oci.platform.toPlatform
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
Expand All @@ -19,9 +20,12 @@ internal class OciImageSpec(
val referenceSpecs: Set<OciImageReferenceSpec>, // normalized setOf(OciImageReferenceSpec(null, null)) -> emptySet()
)

internal fun resolveOciImageSpecs(rootComponentResult: ResolvedComponentResult): List<OciImageSpec> {
internal fun resolveOciImageSpecs(
rootComponentResult: ResolvedComponentResult,
platformSelector: PlatformSelector?,
): List<OciImageSpec> {
val rootNodesToReferenceSpecs = resolveOciVariantGraph(rootComponentResult)
return resolveOciImageSpecs(rootNodesToReferenceSpecs)
return resolveOciImageSpecs(rootNodesToReferenceSpecs, platformSelector)
}

private fun resolveOciVariantGraph(
Expand Down Expand Up @@ -132,10 +136,15 @@ private val ResolvedVariantResult.platformOrUniversalOrMulti: String

private fun resolveOciImageSpecs(
rootNodesToReferenceSpecs: Map<OciVariantNode, Set<OciImageReferenceSpec>>,
platformSelector: PlatformSelector?,
): List<OciImageSpec> {
val imageSpecs = ArrayList<OciImageSpec>()
for ((rootNode, referenceSpecs) in rootNodesToReferenceSpecs) {
for (platform in rootNode.platformSet) {
val platforms = platformSelector?.select(rootNode.platformSet) ?: rootNode.platformSet.set
if (platforms.isEmpty()) {
throw IllegalStateException("no platforms can be selected for variant ${rootNode.variantResult} (supported platforms: ${rootNode.platformSet}, platform selector: $platformSelector)")
}
for (platform in platforms) {
val variantResults = rootNode.collectVariantResultsForPlatform(platform).toList()
imageSpecs += OciImageSpec(platform, variantResults, referenceSpecs.normalize())
}
Expand All @@ -157,12 +166,12 @@ private fun OciVariantNode.collectVariantResultsForPlatform(
when (this) {
is OciVariantNode.MultiPlatform -> {
platformToDependency[platform]?.collectVariantResultsForPlatform(platform, result)
?: throw IllegalArgumentException("variant $variantResult does not support platform $platform (supported platforms are ${platformToDependency.keys})")
?: throw IllegalArgumentException("variant $variantResult does not support platform $platform (supported platforms: ${platformToDependency.keys})")
}

is OciVariantNode.SinglePlatform -> {
if (platform != this.platform) {
throw IllegalArgumentException("variant $variantResult does not support platform $platform (supported platform is ${this.platform})")
throw IllegalArgumentException("variant $variantResult does not support platform $platform (supported platform: ${this.platform})")
}
for (dependency in dependencies) {
dependency.collectVariantResultsForPlatform(platform, result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ package io.github.sgtsilvio.gradle.oci.internal.resolution

import io.github.sgtsilvio.gradle.oci.platform.Platform

internal class PlatformSet : Iterable<Platform> {
internal class PlatformSet {
var isInfinite: Boolean private set
private val set = LinkedHashSet<Platform>() // linked to preserve the platform order
private val _set = LinkedHashSet<Platform>() // linked to preserve the platform order
val set: Set<Platform> get() = _set

constructor(isInfinite: Boolean) {
this.isInfinite = isInfinite
}

constructor(platform: Platform) {
isInfinite = false
set.add(platform)
_set.add(platform)
}

fun intersect(other: PlatformSet) {
Expand All @@ -21,9 +22,9 @@ internal class PlatformSet : Iterable<Platform> {
}
if (isInfinite) {
isInfinite = false
set.addAll(other.set)
_set.addAll(other._set)
} else {
set.retainAll((other.set))
_set.retainAll((other._set))
}
}

Expand All @@ -33,16 +34,11 @@ internal class PlatformSet : Iterable<Platform> {
}
if (other.isInfinite) {
isInfinite = true
set.clear()
_set.clear()
} else {
set.addAll(other.set)
_set.addAll(other._set)
}
}

override fun iterator(): Iterator<Platform> {
if (isInfinite) {
throw UnsupportedOperationException("iterating an infinite set is not possible")
}
return set.iterator()
}
override fun toString() = if (isInfinite) "[<any>]" else set.toString()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package io.github.sgtsilvio.gradle.oci.platform

import io.github.sgtsilvio.gradle.oci.internal.resolution.PlatformSet

/**
* @author Silvio Giebl
*/
sealed class PlatformSelector {

fun and(other: PlatformSelector): PlatformSelector = AndPlatformSelector(this, other)

fun or(other: PlatformSelector): PlatformSelector = OrPlatformSelector(this, other)

internal abstract fun select(platformSet: PlatformSet): Set<Platform>
// PlatformFilter as argument?
// return nullable? null -> not fulfilled, empty -> fulfilled with empty
}

fun PlatformSelector(platform: Platform): PlatformSelector = SinglePlatformSelector(platform)

fun PlatformSelector.and(platform: Platform) = and(PlatformSelector(platform))

fun PlatformSelector.or(platform: Platform) = or(PlatformSelector(platform))

private class SinglePlatformSelector(private val platform: Platform): PlatformSelector() {

override fun select(platformSet: PlatformSet) =
if (platformSet.isInfinite || (platform in platformSet.set)) setOf(platform) else emptySet()

override fun toString() = platform.toString()
}

private class AndPlatformSelector(
private val left: PlatformSelector,
private val right: PlatformSelector,
) : PlatformSelector() {

override fun select(platformSet: PlatformSet): Set<Platform> {
val leftResult = left.select(platformSet)
if (leftResult.isEmpty()) {
return emptySet()
}
val rightResult = right.select(platformSet)
if (rightResult.isEmpty()) {
return emptySet()
}
return leftResult + rightResult
}

override fun toString() = "($left and $right)"
}

private class OrPlatformSelector(
private val left: PlatformSelector,
private val right: PlatformSelector,
) : PlatformSelector() {

override fun select(platformSet: PlatformSet): Set<Platform> {
val leftResult = left.select(platformSet)
if (leftResult.isNotEmpty()) {
return leftResult
}
val rightResult = right.select(platformSet)
if (rightResult.isNotEmpty()) {
return rightResult
}
return emptySet()
}

override fun toString() = "($left or $right)"
}

//PlatformFilterSelector?
//MultiPlatformSelector instead of AndPlatformSelector and SinglePlatformSelector?

/*
fallback arch:
linux,arm64 or linux,amd64
multiple arch, fallback for specific:
linux,amd64 and optional(linux,arm64)
=> linux,amd64 and (linux,arm64 or linux,amd64)?
=> linux,amd64 and (linux,arm64 or empty)?
all linux
fallback arch, multiple os:
(linux,arm64 or linux,amd64) and (windows,arm64 or windows,amd64)
multiple arch, fallback for specific, multiple os:
(linux,amd64 and (linux,arm64 or linux,amd64)) and (windows,amd64 and (windows,arm64 or windows,amd64))
=> linux,amd64 and (linux,arm64 or linux,amd64) and windows,amd64 and (windows,arm64 or windows,amd64)
???
(linux,arm64 or (linux,amd64 and linux,arm)) and (windows,arm64 or (windows,amd64 and windows,arm))
(x or y) and z
(x and z) or (y and z)
*/

0 comments on commit d7ee630

Please sign in to comment.