diff --git a/.github/workflows/bindings-server.main.kts b/.github/workflows/bindings-server.main.kts index 99f9ca00f1..a7803201bc 100755 --- a/.github/workflows/bindings-server.main.kts +++ b/.github/workflows/bindings-server.main.kts @@ -98,6 +98,16 @@ workflow( cleanMavenLocal() + run( + name = "Execute the script using the bindings from the server with v1 route", + command = """ + mv .github/workflows/test-script-consuming-jit-bindings-v1.main.do-not-compile.kts .github/workflows/test-script-consuming-jit-bindings-v1.main.kts + .github/workflows/test-script-consuming-jit-bindings-v1.main.kts + """.trimIndent(), + ) + + cleanMavenLocal() + run( name = "Execute the script using bindings but without dependency on library", command = """ diff --git a/.github/workflows/bindings-server.yaml b/.github/workflows/bindings-server.yaml index ece2a1b151..c23d570a9a 100644 --- a/.github/workflows/bindings-server.yaml +++ b/.github/workflows/bindings-server.yaml @@ -76,49 +76,57 @@ jobs: name: 'Clean Maven Local to fetch required POMs again' run: 'rm -rf ~/.m2/repository/' - id: 'step-7' + name: 'Execute the script using the bindings from the server with v1 route' + run: |- + mv .github/workflows/test-script-consuming-jit-bindings-v1.main.do-not-compile.kts .github/workflows/test-script-consuming-jit-bindings-v1.main.kts + .github/workflows/test-script-consuming-jit-bindings-v1.main.kts + - id: 'step-8' + name: 'Clean Maven Local to fetch required POMs again' + run: 'rm -rf ~/.m2/repository/' + - id: 'step-9' name: 'Execute the script using bindings but without dependency on library' run: |- mv .github/workflows/test-served-bindings-depend-on-library.main.do-not-compile.kts .github/workflows/test-served-bindings-depend-on-library.main.kts .github/workflows/test-served-bindings-depend-on-library.main.kts - - id: 'step-8' + - id: 'step-10' name: 'Install Kotlin 1.9.0' uses: 'fwilhe2/setup-kotlin@v1' with: version: '1.9.0' - - id: 'step-9' + - id: 'step-11' name: 'Clean Maven Local to fetch required POMs again' run: 'rm -rf ~/.m2/repository/' - - id: 'step-10' + - id: 'step-12' name: 'Execute the script using the bindings from the server, using older Kotlin (1.9.0) as consumer' run: |2- cp .github/workflows/test-script-consuming-jit-bindings.main.kts .github/workflows/test-script-consuming-jit-bindings-too-old-kotlin.main.kts (.github/workflows/test-script-consuming-jit-bindings-too-old-kotlin.main.kts || true) >> output.txt 2>&1 grep "was compiled with an incompatible version of Kotlin" output.txt - - id: 'step-11' + - id: 'step-13' name: 'Install Kotlin 2.0.0' uses: 'fwilhe2/setup-kotlin@v1' with: version: '2.0.0' - - id: 'step-12' + - id: 'step-14' name: 'Clean Maven Local to fetch required POMs again' run: 'rm -rf ~/.m2/repository/' - - id: 'step-13' + - id: 'step-15' name: 'Execute the script using the bindings from the server, using older Kotlin (2.0.0) as consumer' run: |- cp .github/workflows/test-script-consuming-jit-bindings.main.kts .github/workflows/test-script-consuming-jit-bindings-older-kotlin.main.kts .github/workflows/test-script-consuming-jit-bindings-older-kotlin.main.kts - - id: 'step-14' + - id: 'step-16' name: 'Compile a Gradle project using the bindings from the server' run: |- cd .github/workflows/test-gradle-project-using-bindings-server ./gradlew build - - id: 'step-15' + - id: 'step-17' name: 'Fetch maven-metadata.xml for top-level action' run: 'curl --fail http://localhost:8080/actions/checkout/maven-metadata.xml | grep ''v4''' - - id: 'step-16' + - id: 'step-18' name: 'Fetch maven-metadata.xml for nested action' run: 'curl --fail http://localhost:8080/actions/cache__save/maven-metadata.xml | grep ''v4''' - - id: 'step-17' + - id: 'step-19' name: 'Print server logs' run: 'cat jit-binding-server/logs/server.log' if: '${{ always() }}' diff --git a/.github/workflows/test-script-consuming-jit-bindings-v1.main.do-not-compile.kts b/.github/workflows/test-script-consuming-jit-bindings-v1.main.do-not-compile.kts new file mode 100755 index 0000000000..0104863de3 --- /dev/null +++ b/.github/workflows/test-script-consuming-jit-bindings-v1.main.do-not-compile.kts @@ -0,0 +1,33 @@ +#!/usr/bin/env kotlin +@file:Repository("https://repo.maven.apache.org/maven2/") +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:1.13.0") + +@file:Repository("http://localhost:8080/v1") + +// Regular, top-level action. +@file:DependsOn("actions:checkout:v4") + +// Nested action. +@file:DependsOn("gradle:actions__setup-gradle:v3") + +// Using specific version. +@file:DependsOn("actions:cache:v3.3.3") + +// Always untyped action. +@file:DependsOn("typesafegithub:always-untyped-action-for-tests:v1") + +import io.github.typesafegithub.workflows.actions.actions.Cache +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.actions.Checkout_Untyped +import io.github.typesafegithub.workflows.actions.gradle.ActionsSetupGradle +import io.github.typesafegithub.workflows.actions.typesafegithub.AlwaysUntypedActionForTests_Untyped + +println(Checkout_Untyped(fetchTags_Untyped = "false")) +println(Checkout(fetchTags = false)) +println(Checkout(fetchTags_Untyped = "false")) +println(AlwaysUntypedActionForTests_Untyped(foobar_Untyped = "baz")) +println(ActionsSetupGradle()) +println(Cache(path = listOf("some-path"), key = "some-key")) + +// Ensure that 'copy(...)' method is exposed. +Checkout(fetchTags = false).copy(fetchTags = true) diff --git a/action-binding-generator/api/action-binding-generator.api b/action-binding-generator/api/action-binding-generator.api index ca56118d20..6c3e36f7ef 100644 --- a/action-binding-generator/api/action-binding-generator.api +++ b/action-binding-generator/api/action-binding-generator.api @@ -85,8 +85,8 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/gen } public final class io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationKt { - public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;)Ljava/util/List; - public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;ILjava/lang/Object;)Ljava/util/List; + public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;)Ljava/util/List; + public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;ILjava/lang/Object;)Ljava/util/List; } public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input { @@ -192,3 +192,14 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/met public abstract interface class io/github/typesafegithub/workflows/actionbindinggenerator/typing/Typing { } +public final class io/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion : java/lang/Enum { + public static final field V1 Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getLibraryVersion ()Ljava/lang/String; + public final fun isDeprecated ()Z + public final fun isExperimental ()Z + public fun toString ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion; + public static fun values ()[Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion; +} + diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt index 6af3e5c84d..594d1ebe8f 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt @@ -23,6 +23,7 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.Signific import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource import io.github.typesafegithub.workflows.actionbindinggenerator.domain.fullName import io.github.typesafegithub.workflows.actionbindinggenerator.domain.isTopLevel +import io.github.typesafegithub.workflows.actionbindinggenerator.domain.prettyPrint import io.github.typesafegithub.workflows.actionbindinggenerator.domain.subName import io.github.typesafegithub.workflows.actionbindinggenerator.generation.Properties.CUSTOM_INPUTS import io.github.typesafegithub.workflows.actionbindinggenerator.generation.Properties.CUSTOM_VERSION @@ -39,6 +40,8 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.typing.provideT import io.github.typesafegithub.workflows.actionbindinggenerator.utils.removeTrailingWhitespacesForEachLine import io.github.typesafegithub.workflows.actionbindinggenerator.utils.toCamelCase import io.github.typesafegithub.workflows.actionbindinggenerator.utils.toKotlinPackageName +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1 public data class ActionBinding( val kotlinCode: String, @@ -61,6 +64,7 @@ private object Properties { } public fun ActionCoords.generateBinding( + bindingVersion: BindingVersion = V1, metadataRevision: MetadataRevision, metadata: Metadata? = null, inputTypings: Pair, TypingActualSource?>? = null, @@ -76,10 +80,11 @@ public fun ActionCoords.generateBinding( val actionBindingSourceCodeUntyped = generateActionBindingSourceCode( - metadataProcessed, - this, - emptyMap(), - classNameUntyped, + metadata = metadataProcessed, + coords = this, + bindingVersion = bindingVersion, + inputTypings = emptyMap(), + className = classNameUntyped, untypedClass = true, replaceWith = inputTypingsResolved.second?.let { CodeBlock.of("ReplaceWith(%S)", className) }, ) @@ -97,6 +102,7 @@ public fun ActionCoords.generateBinding( generateActionBindingSourceCode( metadata = metadataProcessed, coords = this, + bindingVersion = bindingVersion, inputTypings = inputTypingsResolved.first, className = className, ) @@ -127,6 +133,7 @@ private fun Metadata.removeDeprecatedInputsIfNameClash(): Metadata { private fun generateActionBindingSourceCode( metadata: Metadata, coords: ActionCoords, + bindingVersion: BindingVersion, inputTypings: Map, className: String, untypedClass: Boolean = false, @@ -143,7 +150,7 @@ private fun generateActionBindingSourceCode( changes will be overwritten with the next binding code regeneration. See https://github.com/typesafegithub/github-workflows-kt for more info. """.trimIndent(), - ).addType(generateActionClass(metadata, coords, inputTypings, className, untypedClass, replaceWith)) + ).addType(generateActionClass(metadata, coords, bindingVersion, inputTypings, className, untypedClass, replaceWith)) .addSuppressAnnotation(metadata) .indent(" ") .build() @@ -172,6 +179,7 @@ private fun FileSpec.Builder.addSuppressAnnotation(metadata: Metadata) = private fun generateActionClass( metadata: Metadata, coords: ActionCoords, + bindingVersion: BindingVersion, inputTypings: Map, className: String, untypedClass: Boolean, @@ -181,12 +189,13 @@ private fun generateActionClass( .classBuilder(className) .addModifiers(KModifier.DATA) .addKdocIfNotEmpty(actionKdoc(metadata, coords, untypedClass)) - .replaceWith(replaceWith) + .deprecateBindingVersion(bindingVersion) + .replaceWith(bindingVersion, replaceWith) .addClassConstructorAnnotation() .inheritsFromRegularAction(coords, metadata, className) .primaryConstructor(metadata.primaryConstructor(inputTypings, coords, className, untypedClass)) .properties(metadata, coords, inputTypings, className, untypedClass) - .addInitializerBlockIfNecessary(metadata, inputTypings, untypedClass) + .addInitializerBlock(metadata, bindingVersion, coords, inputTypings, untypedClass) .addFunction(metadata.secondaryConstructor(inputTypings, coords, className, untypedClass)) .addFunction(metadata.buildToYamlArgumentsFunction(inputTypings, untypedClass)) .addCustomTypes(inputTypings, coords, className) @@ -374,8 +383,23 @@ private fun Metadata.linkedMapOfInputs( } } -private fun TypeSpec.Builder.replaceWith(replaceWith: CodeBlock?): TypeSpec.Builder { - if (replaceWith != null) { +private fun TypeSpec.Builder.deprecateBindingVersion(bindingVersion: BindingVersion): TypeSpec.Builder { + if (bindingVersion.isDeprecated) { + addAnnotation( + AnnotationSpec + .builder(Deprecated::class.asClassName()) + .addMember("%S", "Use a non-deprecated binding version in the repository URL") + .build(), + ) + } + return this +} + +private fun TypeSpec.Builder.replaceWith( + bindingVersion: BindingVersion, + replaceWith: CodeBlock?, +): TypeSpec.Builder { + if (!bindingVersion.isDeprecated && replaceWith != null) { addAnnotation( AnnotationSpec .builder(Deprecated::class.asClassName()) @@ -544,15 +568,63 @@ private fun ParameterSpec.Builder.defaultValueIfNullable( return this } -private fun TypeSpec.Builder.addInitializerBlockIfNecessary( +private fun TypeSpec.Builder.addInitializerBlock( metadata: Metadata, + bindingVersion: BindingVersion, + coords: ActionCoords, inputTypings: Map, untypedClass: Boolean, ): TypeSpec.Builder { - if (untypedClass || metadata.inputs.isEmpty() || metadata.inputs.none { inputTypings.containsKey(it.key) }) { + if (!bindingVersion.isDeprecated && + !bindingVersion.isExperimental && + (untypedClass || metadata.inputs.isEmpty() || metadata.inputs.none { inputTypings.containsKey(it.key) }) + ) { return this } - addInitializerBlock(metadata.initializerBlock(inputTypings)) + + addInitializerBlock( + buildCodeBlock { + if (bindingVersion.isDeprecated) { + val firstStableVersion = BindingVersion.entries.find { !it.isDeprecated && !it.isExperimental } + addStatement( + "println(%S)", + """ + WARNING: The used binding version $bindingVersion for ${coords.prettyPrint} is deprecated! First stable version is $firstStableVersion. + """.trimIndent(), + ) + beginControlFlow("""if (System.getenv("GITHUB_ACTIONS").toBoolean())""") + addStatement( + "println(%S)", + """ + + ::warning title=Deprecated Binding Version Used::The used binding version $bindingVersion for ${coords.prettyPrint} is deprecated! First stable version is $firstStableVersion. + """.trimIndent(), + ) + endControlFlow() + add("\n") + } + if (bindingVersion.isExperimental) { + val lastStableVersion = BindingVersion.entries.findLast { !it.isDeprecated && !it.isExperimental } + addStatement( + "println(%S)", + """ + WARNING: The used binding version $bindingVersion for ${coords.prettyPrint} is experimental! Last stable version is $lastStableVersion. + """.trimIndent(), + ) + beginControlFlow("""if (System.getenv("GITHUB_ACTIONS").toBoolean())""") + addStatement( + "println(%S)", + """ + + ::warning title=Experimental Binding Version Used::The used binding version $bindingVersion for ${coords.prettyPrint} is experimental! Last stable version is $lastStableVersion. + """.trimIndent(), + ) + endControlFlow() + add("\n") + } + add(metadata.initializerBlock(inputTypings)) + }, + ) return this } diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion.kt new file mode 100644 index 0000000000..85f32e73c4 --- /dev/null +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion.kt @@ -0,0 +1,12 @@ +package io.github.typesafegithub.workflows.actionbindinggenerator.versioning + +public enum class BindingVersion( + public val isDeprecated: Boolean = false, + public val isExperimental: Boolean = true, + public val libraryVersion: String, +) { + V1(isExperimental = false, libraryVersion = "3.4.0"), + ; + + override fun toString(): String = super.toString().lowercase() +} diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt index 553e969ee9..6eed7b56cd 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt @@ -1,10 +1,11 @@ package io.github.typesafegithub.workflows.jitbindingserver -import com.sksamuel.aedile.core.LoadingCache import io.github.oshai.kotlinlogging.KotlinLogging.logger import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource import io.github.typesafegithub.workflows.actionbindinggenerator.domain.prettyPrint +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1 import io.github.typesafegithub.workflows.mavenbinding.JarArtifact import io.github.typesafegithub.workflows.mavenbinding.TextArtifact import io.github.typesafegithub.workflows.mavenbinding.VersionArtifacts @@ -33,7 +34,7 @@ typealias CachedVersionArtifact = VersionArtifacts? private val prefetchScope = CoroutineScope(Dispatchers.IO) fun Routing.artifactRoutes( - bindingsCache: LoadingCache, + bindingsCache: BindingsCache, prometheusRegistry: PrometheusMeterRegistry? = null, ) { prometheusRegistry?.let { @@ -41,17 +42,27 @@ fun Routing.artifactRoutes( } route("{owner}/{name}/{version}/{file}") { - artifact(prometheusRegistry, bindingsCache, refresh = false) + artifact(prometheusRegistry, bindingsCache) } route("/refresh/{owner}/{name}/{version}/{file}") { artifact(prometheusRegistry, bindingsCache, refresh = true) } + + route("""(?v\d+)""".toRegex()) { + route("{owner}/{name}/{version}/{file}") { + artifact(prometheusRegistry, bindingsCache) + } + + route("/refresh/{owner}/{name}/{version}/{file}") { + artifact(prometheusRegistry, bindingsCache, refresh = true) + } + } } private fun Route.artifact( prometheusRegistry: PrometheusMeterRegistry?, - bindingsCache: LoadingCache, + bindingsCache: BindingsCache, refresh: Boolean = false, ) { headArtifact(bindingsCache, prometheusRegistry, refresh) @@ -59,7 +70,7 @@ private fun Route.artifact( } private fun Route.headArtifact( - bindingsCache: LoadingCache, + bindingsCache: BindingsCache, prometheusRegistry: PrometheusMeterRegistry?, refresh: Boolean, ) { @@ -79,7 +90,7 @@ private fun Route.headArtifact( } private fun Route.getArtifact( - bindingsCache: LoadingCache, + bindingsCache: BindingsCache, prometheusRegistry: PrometheusMeterRegistry?, refresh: Boolean, ) { @@ -103,30 +114,46 @@ private fun Route.getArtifact( internal fun prefetchBindingArtifacts( coords: Collection, - bindingsCache: LoadingCache, + bindingVersion: BindingVersion = V1, + bindingsCache: BindingsCache, ) { prefetchScope.launch { - bindingsCache.getAll(coords) + bindingsCache.getAll(coords.map { CacheKey(it, bindingVersion) }) } } +val ApplicationCall.bindingVersion: BindingVersion? + get() { + val bindingVersion = parameters["bindingVersion"] + return if (bindingVersion == null) { + V1 + } else { + BindingVersion + .entries + .find { it.name.lowercase() == bindingVersion } + } + } + private suspend fun ApplicationCall.toBindingArtifacts( refresh: Boolean, - bindingsCache: LoadingCache, + bindingsCache: BindingsCache, ): VersionArtifacts? { + val bindingVersion = bindingVersion ?: return null val actionCoords = parameters.extractActionCoords(extractVersion = true) - logger.info { "➡️ Requesting ${actionCoords.prettyPrint}" } + logger.info { "➡️ Requesting ${actionCoords.prettyPrint} binding version $bindingVersion" } + val cacheKey = CacheKey(actionCoords, bindingVersion) if (refresh) { - bindingsCache.invalidate(actionCoords) + bindingsCache.invalidate(cacheKey) } - return bindingsCache.get(actionCoords) + return bindingsCache.get(cacheKey) } private fun PrometheusMeterRegistry.incrementArtifactCounter( call: ApplicationCall, typingActualSource: TypingActualSource?, ) { + val bindingVersion = call.parameters["bindingVersion"] ?: "v1" val owner = call.parameters["owner"] ?: "unknown" val name = call.parameters["name"] ?: "unknown" val version = call.parameters["version"] ?: "unknown" @@ -148,6 +175,7 @@ private fun PrometheusMeterRegistry.incrementArtifactCounter( this.counter( "artifact_requests_total", listOf( + Tag.of("bindingVersion", bindingVersion), Tag.of("owner", owner), Tag.of("name", name), Tag.of("version", version), diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt index a77f43e0c4..ee27b25f0f 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt @@ -6,6 +6,7 @@ import com.sksamuel.aedile.core.asLoadingCache import com.sksamuel.aedile.core.refreshAfterWrite import io.github.oshai.kotlinlogging.KotlinLogging.logger import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion import io.github.typesafegithub.workflows.mavenbinding.VersionArtifacts import io.github.typesafegithub.workflows.mavenbinding.buildPackageArtifacts import io.github.typesafegithub.workflows.mavenbinding.buildVersionArtifacts @@ -52,16 +53,24 @@ fun main() { } embeddedServer(Netty, port = 8080) { appModule( - buildVersionArtifacts = ::buildVersionArtifacts, + buildVersionArtifacts = { buildVersionArtifacts(it.actionCoords, it.bindingVersion) }, buildPackageArtifacts = ::buildPackageArtifacts, getGithubAuthToken = ::getGithubAuthToken, ) }.start(wait = true) } +private typealias VersionArtifactsBuilder = (CacheKey) -> VersionArtifacts? + fun Application.appModule( - buildVersionArtifacts: (ActionCoords) -> VersionArtifacts?, - buildPackageArtifacts: suspend (ActionCoords, String, (Collection) -> Unit) -> Map, + buildVersionArtifacts: VersionArtifactsBuilder, + buildPackageArtifacts: + suspend ( + ActionCoords, + String, + (Collection, BindingVersion) -> Unit, + bindingVersion: BindingVersion, + ) -> Map, getGithubAuthToken: () -> String, ) { val bindingsCache = buildBindingsCache(buildVersionArtifacts) @@ -76,33 +85,48 @@ fun Application.appModule( } } -private fun buildBindingsCache( - buildVersionArtifacts: (ActionCoords) -> VersionArtifacts?, -): LoadingCache = +typealias BindingsCache = LoadingCache + +private fun buildBindingsCache(buildVersionArtifacts: VersionArtifactsBuilder): BindingsCache = Caffeine .newBuilder() .refreshAfterWrite(1.hours) .recordStats() - .asLoadingCache { buildVersionArtifacts(it) } + .asLoadingCache { buildVersionArtifacts(it) } + +typealias MetadataCache = LoadingCache -@Suppress("ktlint:standard:function-signature") // Conflict with detekt. private fun buildMetadataCache( - bindingsCache: LoadingCache, - buildPackageArtifacts: suspend (ActionCoords, String, (Collection) -> Unit) -> Map, + bindingsCache: BindingsCache, + buildPackageArtifacts: + suspend ( + ActionCoords, + String, + (Collection, BindingVersion) -> Unit, + bindingVersion: BindingVersion, + ) -> Map, getGithubAuthToken: () -> String, -): LoadingCache = +): MetadataCache = Caffeine .newBuilder() .refreshAfterWrite(1.hours) .recordStats() - .asLoadingCache { + .asLoadingCache { buildPackageArtifacts( - it, + it.actionCoords, getGithubAuthToken(), - { coords -> prefetchBindingArtifacts(coords, bindingsCache) }, + { coords, bindingsVersion -> + prefetchBindingArtifacts(coords, bindingsVersion, bindingsCache) + }, + it.bindingVersion, ) } val deliverOnRefreshRoute = System.getenv("GWKT_DELIVER_ON_REFRESH").toBoolean() suspend fun ApplicationCall.respondNotFound() = respondText(text = "Not found", status = HttpStatusCode.NotFound) + +data class CacheKey( + val actionCoords: ActionCoords, + val bindingVersion: BindingVersion, +) diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt index 09886a3875..c0e1e14d6c 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt @@ -1,8 +1,6 @@ package io.github.typesafegithub.workflows.jitbindingserver -import com.sksamuel.aedile.core.LoadingCache import io.github.oshai.kotlinlogging.KotlinLogging.logger -import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords import io.github.typesafegithub.workflows.actionbindinggenerator.domain.prettyPrintWithoutVersion import io.ktor.server.response.respondText import io.ktor.server.routing.Route @@ -17,7 +15,7 @@ private val logger = logger { } typealias CachedMetadataArtifact = Map fun Routing.metadataRoutes( - metadataCache: LoadingCache, + metadataCache: MetadataCache, prometheusRegistry: PrometheusMeterRegistry? = null, ) { prometheusRegistry?.let { @@ -31,21 +29,34 @@ fun Routing.metadataRoutes( route("/refresh/{owner}/{name}/{file}") { metadata(metadataCache, refresh = true) } + + route("""(?v\d+)""".toRegex()) { + route("{owner}/{name}/{file}") { + metadata(metadataCache) + } + + route("/refresh/{owner}/{name}/{file}") { + metadata(metadataCache, refresh = true) + } + } } private fun Route.metadata( - metadataCache: LoadingCache, + metadataCache: MetadataCache, refresh: Boolean = false, ) { get { + val bindingVersion = call.bindingVersion ?: return@get call.respondNotFound() val actionCoords = call.parameters.extractActionCoords(extractVersion = false) - logger.info { "➡️ Requesting metadata for ${actionCoords.prettyPrintWithoutVersion}" } - + logger.info { + "➡️ Requesting metadata for ${actionCoords.prettyPrintWithoutVersion} binding version $bindingVersion" + } + val cacheKey = CacheKey(actionCoords, bindingVersion) if (refresh) { - metadataCache.invalidate(actionCoords) + metadataCache.invalidate(cacheKey) } - val metadataArtifacts = metadataCache.get(actionCoords) + val metadataArtifacts = metadataCache.get(cacheKey) if (refresh && !deliverOnRefreshRoute) return@get call.respondText(text = "OK") diff --git a/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutesTest.kt b/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutesTest.kt index c7ac7545e8..4a3b7f500b 100644 --- a/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutesTest.kt +++ b/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutesTest.kt @@ -1,6 +1,5 @@ package io.github.typesafegithub.workflows.jitbindingserver -import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource import io.github.typesafegithub.workflows.mavenbinding.TextArtifact import io.github.typesafegithub.workflows.mavenbinding.VersionArtifacts @@ -29,7 +28,7 @@ class ArtifactRoutesTest : ) }, // Irrelevant for these tests. - buildPackageArtifacts = { _, _, _ -> emptyMap() }, + buildPackageArtifacts = { _, _, _, _ -> emptyMap() }, getGithubAuthToken = { "" }, ) } @@ -50,7 +49,7 @@ class ArtifactRoutesTest : appModule( buildVersionArtifacts = { null }, // Irrelevant for these tests. - buildPackageArtifacts = { _, _, _ -> emptyMap() }, + buildPackageArtifacts = { _, _, _, _ -> emptyMap() }, getGithubAuthToken = { "" }, ) } @@ -70,7 +69,7 @@ class ArtifactRoutesTest : appModule( buildVersionArtifacts = { error("An internal error occurred!") }, // Irrelevant for these tests. - buildPackageArtifacts = { _, _, _ -> emptyMap() }, + buildPackageArtifacts = { _, _, _, _ -> emptyMap() }, getGithubAuthToken = { "" }, ) } @@ -86,7 +85,7 @@ class ArtifactRoutesTest : test("when binding generation fails and then succeeds, and two requests are made") { testApplication { // Given - val mockBuildVersionArtifacts = mockk<(ActionCoords) -> VersionArtifacts?>() + val mockBuildVersionArtifacts = mockk<(CacheKey) -> VersionArtifacts?>() every { mockBuildVersionArtifacts(any()) } throws Exception("An internal error occurred!") andThen VersionArtifacts( @@ -97,7 +96,7 @@ class ArtifactRoutesTest : appModule( buildVersionArtifacts = mockBuildVersionArtifacts, // Irrelevant for these tests. - buildPackageArtifacts = { _, _, _ -> emptyMap() }, + buildPackageArtifacts = { _, _, _, _ -> emptyMap() }, getGithubAuthToken = { "" }, ) } diff --git a/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutesTest.kt b/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutesTest.kt index db18feb1e6..7554cf5b0e 100644 --- a/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutesTest.kt +++ b/jit-binding-server/src/test/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutesTest.kt @@ -1,6 +1,7 @@ package io.github.typesafegithub.workflows.jitbindingserver import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion import io.github.typesafegithub.workflows.mavenbinding.VersionArtifacts import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe @@ -20,7 +21,7 @@ class MetadataRoutesTest : // Given application { appModule( - buildPackageArtifacts = { _, _, _ -> + buildPackageArtifacts = { _, _, _, _ -> mapOf("maven-metadata.xml" to "Some XML contents") }, getGithubAuthToken = { "some-token" }, @@ -48,7 +49,7 @@ class MetadataRoutesTest : // Given application { appModule( - buildPackageArtifacts = { _, _, _ -> + buildPackageArtifacts = { _, _, _, _ -> emptyMap() }, getGithubAuthToken = { "some-token" }, @@ -75,7 +76,7 @@ class MetadataRoutesTest : // Given application { appModule( - buildPackageArtifacts = { _, _, _ -> + buildPackageArtifacts = { _, _, _, _ -> error("An internal error occurred!") }, getGithubAuthToken = { "some-token" }, @@ -105,10 +106,11 @@ class MetadataRoutesTest : ( ActionCoords, String, - (Collection) -> Unit, + (Collection, BindingVersion) -> Unit, + BindingVersion, ) -> Map, >() - every { mockBuildPackageArtifacts(any(), any(), any()) } throws + every { mockBuildPackageArtifacts(any(), any(), any(), any()) } throws Exception("An internal error occurred!") andThen mapOf("maven-metadata.xml" to "Some XML contents") application { @@ -135,7 +137,7 @@ class MetadataRoutesTest : // Then response2.status shouldBe HttpStatusCode.OK - verify(exactly = 2) { mockBuildPackageArtifacts(any(), any(), any()) } + verify(exactly = 2) { mockBuildPackageArtifacts(any(), any(), any(), any()) } } } } diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt index 603a3a6c3f..c693cc0dfa 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt @@ -7,6 +7,8 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.NewestFo import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource import io.github.typesafegithub.workflows.actionbindinggenerator.generation.ActionBinding import io.github.typesafegithub.workflows.actionbindinggenerator.generation.generateBinding +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1 import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.messages.MessageRenderer @@ -28,9 +30,12 @@ internal data class Jars( val typingActualSource: TypingActualSource?, ) -internal fun ActionCoords.buildJars(): Jars? { +internal fun ActionCoords.buildJars(bindingVersion: BindingVersion = V1): Jars? { val binding = - generateBinding(metadataRevision = NewestForVersion).also { + generateBinding( + bindingVersion = bindingVersion, + metadataRevision = NewestForVersion, + ).also { if (it.isEmpty()) return null } diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt index ef1fe14994..0b1ff75281 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt @@ -5,6 +5,8 @@ import arrow.core.getOrElse import io.github.oshai.kotlinlogging.KotlinLogging.logger import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1 import io.github.typesafegithub.workflows.shared.internal.fetchAvailableVersions import io.github.typesafegithub.workflows.shared.internal.model.Version import java.time.format.DateTimeFormatter @@ -18,7 +20,8 @@ internal suspend fun ActionCoords.buildMavenMetadataFile( name: String, githubAuthToken: String?, ) -> Either> = ::fetchAvailableVersions, - prefetchBindingArtifacts: (Collection) -> Unit = {}, + prefetchBindingArtifacts: (Collection, BindingVersion) -> Unit = { _, _ -> }, + bindingVersion: BindingVersion = V1, ): String? { val availableVersions = fetchAvailableVersions(owner, name, githubAuthToken) @@ -26,7 +29,7 @@ internal suspend fun ActionCoords.buildMavenMetadataFile( logger.error { it } emptyList() }.filter { it.isMajorVersion() || (significantVersion < FULL) } - prefetchBindingArtifacts(availableVersions.map { copy(version = "$it") }) + prefetchBindingArtifacts(availableVersions.map { copy(version = "$it") }, bindingVersion) val newest = availableVersions.maxOrNull() ?: return null val lastUpdated = DateTimeFormatter diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt index e2dda2ba8f..29fe78216f 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt @@ -3,6 +3,7 @@ package io.github.typesafegithub.workflows.mavenbinding import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords internal fun ActionCoords.buildModuleFile( + libraryVersion: String, mainJarSize: Int, mainJarMd5Checksum: String, mainJarSha1Checksum: String, @@ -69,7 +70,7 @@ internal fun ActionCoords.buildModuleFile( "group": "io.github.typesafegithub", "module": "github-workflows-kt", "version": { - "requires": "$LATEST_RELASED_LIBRARY_VERSION" + "requires": "$libraryVersion" } } ], diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PackageArtifactsBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PackageArtifactsBuilding.kt index 55e58817b1..9ee4472277 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PackageArtifactsBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PackageArtifactsBuilding.kt @@ -1,11 +1,14 @@ package io.github.typesafegithub.workflows.mavenbinding import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1 suspend fun buildPackageArtifacts( actionCoords: ActionCoords, githubAuthToken: String, - prefetchBindingArtifacts: (Collection) -> Unit, + prefetchBindingArtifacts: (Collection, BindingVersion) -> Unit, + bindingVersion: BindingVersion = V1, ): Map { val mavenMetadata = actionCoords.buildMavenMetadataFile( diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt index 071a899bf4..212212f594 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt @@ -4,9 +4,7 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCo import io.github.typesafegithub.workflows.actionbindinggenerator.domain.fullName import io.github.typesafegithub.workflows.actionbindinggenerator.domain.prettyPrint -internal const val LATEST_RELASED_LIBRARY_VERSION = "3.4.0" - -internal fun ActionCoords.buildPomFile() = +internal fun ActionCoords.buildPomFile(libraryVersion: String) = """ @@ -26,7 +24,7 @@ internal fun ActionCoords.buildPomFile() = io.github.typesafegithub github-workflows-kt - $LATEST_RELASED_LIBRARY_VERSION + $libraryVersion compile diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt index 803d871aa5..cb5f5fc3f0 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt @@ -2,6 +2,8 @@ package io.github.typesafegithub.workflows.mavenbinding import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion +import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1 sealed interface Artifact @@ -18,10 +20,13 @@ data class VersionArtifacts( val typingActualSource: TypingActualSource?, ) -fun buildVersionArtifacts(actionCoords: ActionCoords): VersionArtifacts? { +fun buildVersionArtifacts( + actionCoords: ActionCoords, + bindingVersion: BindingVersion = V1, +): VersionArtifacts? { with(actionCoords) { - val jars = buildJars() ?: return null - val pom = buildPomFile() + val jars = buildJars(bindingVersion = bindingVersion) ?: return null + val pom = buildPomFile(libraryVersion = bindingVersion.libraryVersion) val mainJarSize by lazy { jars.mainJar().size } val mainJarMd5Checksum by lazy { jars.mainJar().md5Checksum() } val mainJarSha1Checksum by lazy { jars.mainJar().sha1Checksum() } @@ -34,6 +39,7 @@ fun buildVersionArtifacts(actionCoords: ActionCoords): VersionArtifacts? { val sourcesJarSha512Checksum by lazy { jars.sourcesJar().sha512Checksum() } val module by lazy { buildModuleFile( + bindingVersion.libraryVersion, mainJarSize, mainJarMd5Checksum, mainJarSha1Checksum,