diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesTask.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesTask.kt index 2c4c4345..fd5683b6 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesTask.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciImagesTask.kt @@ -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 @@ -33,13 +37,23 @@ abstract class OciImagesTask : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.NONE) val layerFiles: List, ) + @get:Internal + val platformSelector = project.objects.property() + 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: ,[,[,[,(,)*]]]. Option can be specified multiple times. If not specified, all supported platforms are selected.", + ) + protected fun selectPlatforms(platforms: List) = + platformSelector.set(platforms.map { PlatformSelector(it.toPlatform()) }.reduce(PlatformSelector::and)) @TaskAction protected fun run() { diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/OciExtension.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/OciExtension.kt index 37fef947..e064c653 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/OciExtension.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/dsl/OciExtension.kt @@ -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 @@ -47,6 +48,8 @@ interface OciExtension { val osVersions: SetProperty } + fun platformSelector(platform: Platform): PlatformSelector + fun copySpec(): OciCopySpec fun copySpec(configuration: Action): OciCopySpec diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/dsl/OciExtensionImpl.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/dsl/OciExtensionImpl.kt index 03cd61e2..0b474242 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/dsl/OciExtensionImpl.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/dsl/OciExtensionImpl.kt @@ -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 @@ -68,6 +69,8 @@ internal abstract class OciExtensionImpl @Inject constructor(private val objectF final override fun PlatformFilter.or(configuration: Action) = or(platformFilter(configuration)) + final override fun platformSelector(platform: Platform) = PlatformSelector(platform) + final override fun copySpec() = objectFactory.newOciCopySpec() final override fun copySpec(configuration: Action): OciCopySpecImpl { diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageInputResolution.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageInputResolution.kt index 8e66df9d..fb6c7bd7 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageInputResolution.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageInputResolution.kt @@ -2,6 +2,8 @@ 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 @@ -9,9 +11,14 @@ import org.gradle.api.attributes.AttributeContainer import org.gradle.api.capabilities.Capability import org.gradle.api.provider.Provider -internal fun ResolvableDependencies.resolveOciImageInputs(): Provider> { +internal fun ResolvableDependencies.resolveOciImageInputs( + platformSelectorProvider: Provider, +): Provider> { val rootComponentProvider = resolutionResult.rootComponent - val imageSpecsProvider = rootComponentProvider.map(::resolveOciImageSpecs) + val imageSpecsProvider = + rootComponentProvider.zipAbsentAsNull(platformSelectorProvider) { rootComponent, platformSelector -> + resolveOciImageSpecs(rootComponent, platformSelector) + } val artifactResultsProvider = artifactView { componentFilter( ArtifactViewVariantFilter( diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageSpecResolution.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageSpecResolution.kt index fd2c29d0..44d24b16 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageSpecResolution.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/OciImageSpecResolution.kt @@ -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 @@ -19,9 +20,12 @@ internal class OciImageSpec( val referenceSpecs: Set, // normalized setOf(OciImageReferenceSpec(null, null)) -> emptySet() ) -internal fun resolveOciImageSpecs(rootComponentResult: ResolvedComponentResult): List { +internal fun resolveOciImageSpecs( + rootComponentResult: ResolvedComponentResult, + platformSelector: PlatformSelector?, +): List { val rootNodesToReferenceSpecs = resolveOciVariantGraph(rootComponentResult) - return resolveOciImageSpecs(rootNodesToReferenceSpecs) + return resolveOciImageSpecs(rootNodesToReferenceSpecs, platformSelector) } private fun resolveOciVariantGraph( @@ -132,10 +136,15 @@ private val ResolvedVariantResult.platformOrUniversalOrMulti: String private fun resolveOciImageSpecs( rootNodesToReferenceSpecs: Map>, + platformSelector: PlatformSelector?, ): List { val imageSpecs = ArrayList() 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()) } @@ -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) diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/PlatformSet.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/PlatformSet.kt index 7a1d661f..8b8d14c7 100644 --- a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/PlatformSet.kt +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/resolution/PlatformSet.kt @@ -2,9 +2,10 @@ package io.github.sgtsilvio.gradle.oci.internal.resolution import io.github.sgtsilvio.gradle.oci.platform.Platform -internal class PlatformSet : Iterable { +internal class PlatformSet { var isInfinite: Boolean private set - private val set = LinkedHashSet() // linked to preserve the platform order + private val _set = LinkedHashSet() // linked to preserve the platform order + val set: Set get() = _set constructor(isInfinite: Boolean) { this.isInfinite = isInfinite @@ -12,7 +13,7 @@ internal class PlatformSet : Iterable { constructor(platform: Platform) { isInfinite = false - set.add(platform) + _set.add(platform) } fun intersect(other: PlatformSet) { @@ -21,9 +22,9 @@ internal class PlatformSet : Iterable { } if (isInfinite) { isInfinite = false - set.addAll(other.set) + _set.addAll(other._set) } else { - set.retainAll((other.set)) + _set.retainAll((other._set)) } } @@ -33,16 +34,11 @@ internal class PlatformSet : Iterable { } if (other.isInfinite) { isInfinite = true - set.clear() + _set.clear() } else { - set.addAll(other.set) + _set.addAll(other._set) } } - override fun iterator(): Iterator { - if (isInfinite) { - throw UnsupportedOperationException("iterating an infinite set is not possible") - } - return set.iterator() - } + override fun toString() = if (isInfinite) "[]" else set.toString() } diff --git a/src/main/kotlin/io/github/sgtsilvio/gradle/oci/platform/PlatformSelector.kt b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/platform/PlatformSelector.kt new file mode 100644 index 00000000..b6dfee22 --- /dev/null +++ b/src/main/kotlin/io/github/sgtsilvio/gradle/oci/platform/PlatformSelector.kt @@ -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 + // 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 { + 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 { + 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) + */