diff --git a/.github/workflows/bindings-server.main.kts b/.github/workflows/bindings-server.main.kts index 10e82ffcd5..ee74cf5037 100755 --- a/.github/workflows/bindings-server.main.kts +++ b/.github/workflows/bindings-server.main.kts @@ -91,6 +91,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/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 ab354073ab..2a44cd72a5 100644 --- a/action-binding-generator/api/action-binding-generator.api +++ b/action-binding-generator/api/action-binding-generator.api @@ -69,8 +69,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 { @@ -179,3 +179,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 c7c452cf0d..9824a7cf82 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 @@ -32,6 +32,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, @@ -54,6 +56,7 @@ private object Properties { } public fun ActionCoords.generateBinding( + bindingVersion: BindingVersion = V1, metadataRevision: MetadataRevision, metadata: Metadata? = null, inputTypings: Pair, TypingActualSource?>? = null, @@ -69,10 +72,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) }, ) @@ -90,6 +94,7 @@ public fun ActionCoords.generateBinding( generateActionBindingSourceCode( metadata = metadataProcessed, coords = this, + bindingVersion = bindingVersion, inputTypings = inputTypingsResolved.first, className = className, ) @@ -120,6 +125,7 @@ private fun Metadata.removeDeprecatedInputsIfNameClash(): Metadata { private fun generateActionBindingSourceCode( metadata: Metadata, coords: ActionCoords, + bindingVersion: BindingVersion, inputTypings: Map, className: String, untypedClass: Boolean = false, @@ -136,7 +142,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() @@ -165,6 +171,7 @@ private fun FileSpec.Builder.addSuppressAnnotation(metadata: Metadata) = private fun generateActionClass( metadata: Metadata, coords: ActionCoords, + bindingVersion: BindingVersion, inputTypings: Map, className: String, untypedClass: Boolean, @@ -174,12 +181,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, inputTypings, untypedClass) .addFunction(metadata.secondaryConstructor(inputTypings, coords, className, untypedClass)) .addFunction(metadata.buildToYamlArgumentsFunction(inputTypings, untypedClass)) .addCustomTypes(inputTypings, coords, className) @@ -374,8 +382,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()) @@ -533,15 +556,61 @@ private fun ParameterSpec.Builder.defaultValueIfNullable( return this } -private fun TypeSpec.Builder.addInitializerBlockIfNecessary( +private fun TypeSpec.Builder.addInitializerBlock( metadata: Metadata, + bindingVersion: BindingVersion, 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)) + + val initializerBlock = CodeBlock.builder() + if (bindingVersion.isDeprecated) { + val firstStableVersion = BindingVersion.entries.find { !it.isDeprecated && !it.isExperimental } + initializerBlock.addStatement( + "println(%S)", + """ + WARNING: The used binding version $bindingVersion is deprecated! First stable version is $firstStableVersion. + """.trimIndent(), + ) + initializerBlock.beginControlFlow("""if (System.getenv("GITHUB_ACTIONS").toBoolean())""") + initializerBlock.addStatement( + "println(%S)", + """ + + ::warning title=Deprecated Binding Version Used::The used binding version $bindingVersion is deprecated! First stable version is $firstStableVersion. + """.trimIndent(), + ) + initializerBlock.endControlFlow() + initializerBlock.add("\n") + } + if (bindingVersion.isExperimental) { + val lastStableVersion = BindingVersion.entries.findLast { !it.isDeprecated && !it.isExperimental } + initializerBlock.addStatement( + "println(%S)", + """ + WARNING: The used binding version $bindingVersion is experimental! Last stable version is $lastStableVersion. + """.trimIndent(), + ) + initializerBlock.beginControlFlow("""if (System.getenv("GITHUB_ACTIONS").toBoolean())""") + initializerBlock.addStatement( + "println(%S)", + """ + + ::warning title=Experimental Binding Version Used::The used binding version $bindingVersion is experimental! Last stable version is $lastStableVersion. + """.trimIndent(), + ) + initializerBlock.endControlFlow() + initializerBlock.add("\n") + } + initializerBlock.add(metadata.initializerBlock(inputTypings)) + + addInitializerBlock(initializerBlock.build()) 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..e109473b94 --- /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.0.0"), + ; + + override fun toString(): String = super.toString().lowercase() +} 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 654cf6c761..8ea3316232 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 @@ -3,6 +3,8 @@ package io.github.typesafegithub.workflows.jitbindingserver import io.github.reactivecircus.cache4k.Cache import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords 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.Artifact import io.github.typesafegithub.workflows.mavenbinding.JarArtifact import io.github.typesafegithub.workflows.mavenbinding.TextArtifact @@ -29,7 +31,7 @@ import kotlin.time.Duration.Companion.hours fun main() { val bindingsCache = Cache - .Builder>>() + .Builder>>() .expireAfterWrite(1.hours) .build() val openTelemetry = buildOpenTelemetryConfig(serviceName = "github-actions-bindings") @@ -57,6 +59,26 @@ fun main() { } } + route("{bindingVersion}") { + route("{owner}/{name}/{version}/{file}") { + artifact(bindingsCache) + } + + route("{owner}/{name}/{file}") { + metadata() + } + + route("/refresh") { + route("{owner}/{name}/{version}/{file}") { + artifact(bindingsCache, refresh = true) + } + + route("{owner}/{name}/{file}") { + metadata(refresh = true) + } + } + } + get("/status") { call.respondText("OK") } @@ -66,7 +88,7 @@ fun main() { private fun Route.metadata(refresh: Boolean = false) { get { - if (refresh && !deliverOnRefreshRoute) { + if ((call.bindingVersion == null) || (refresh && !deliverOnRefreshRoute)) { call.respondText(text = "Not found", status = HttpStatusCode.NotFound) return@get } @@ -93,7 +115,7 @@ private fun Route.metadata(refresh: Boolean = false) { } private fun Route.artifact( - bindingsCache: Cache>>, + bindingsCache: Cache>>, refresh: Boolean = false, ) { get { @@ -138,10 +160,23 @@ private fun Route.artifact( } } +private val ApplicationCall.bindingVersion: BindingVersion? + get() { + val bindingVersion = parameters["bindingVersion"] + return if (bindingVersion == null) { + V1 + } else { + BindingVersion + .entries + .find { it.name.equals(bindingVersion.uppercase(), ignoreCase = true) } + } + } + private suspend fun ApplicationCall.toBindingArtifacts( - bindingsCache: Cache>>, + bindingsCache: Cache>>, refresh: Boolean, ): Map? { + val bindingVersion = bindingVersion ?: return null val owner = parameters["owner"]!! val name = parameters["name"]!! val version = parameters["version"]!! @@ -151,15 +186,16 @@ private suspend fun ApplicationCall.toBindingArtifacts( name = name, version = version, ) - println("➡️ Requesting ${actionCoords.prettyPrint}") + println("➡️ Requesting ${actionCoords.prettyPrint} binding version $bindingVersion") + val cacheKey = CacheKey(actionCoords, bindingVersion) val bindingArtifacts = if (refresh) { - actionCoords.buildVersionArtifacts().also { - bindingsCache.put(actionCoords, Result.of(it)) + actionCoords.buildVersionArtifacts(bindingVersion).also { + bindingsCache.put(cacheKey, Result.of(it)) } } else { bindingsCache - .get(actionCoords) { Result.of(actionCoords.buildVersionArtifacts()) } + .get(cacheKey) { Result.of(actionCoords.buildVersionArtifacts(bindingVersion)) } .getOrNull() } return bindingArtifacts @@ -170,3 +206,8 @@ private fun Result.Companion.failure(): Result = failure(object : Throw private fun Result.Companion.of(value: T?): Result = value?.let { success(it) } ?: failure() private val deliverOnRefreshRoute = System.getenv("GWKT_DELIVER_ON_REFRESH").toBoolean() + +private data class CacheKey( + val actionCoords: ActionCoords, + val bindingVersion: BindingVersion, +) 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 78c1619546..9ae2c9f1dd 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 @@ -6,6 +6,8 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCo import io.github.typesafegithub.workflows.actionbindinggenerator.domain.NewestForVersion 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 @@ -30,9 +32,10 @@ internal fun buildJars( owner: String, name: String, version: String, + bindingVersion: BindingVersion = V1, ): Jars? { val binding = - generateBinding(owner = owner, name = name, version = version).also { + generateBinding(owner = owner, name = name, version = version, bindingVersion = bindingVersion).also { if (it.isEmpty()) return null } val (sourceFilePaths, compilationInputDir) = binding.prepareDirectoryWithSources() @@ -54,6 +57,7 @@ private fun generateBinding( owner: String, name: String, version: String, + bindingVersion: BindingVersion, ): List { val actionCoords = ActionCoords( @@ -62,6 +66,7 @@ private fun generateBinding( version = version, ) return actionCoords.generateBinding( + bindingVersion = bindingVersion, metadataRevision = NewestForVersion, ) } 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 2abf694830..91c2e9a2c9 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 @@ -1,11 +1,10 @@ package io.github.typesafegithub.workflows.mavenbinding -internal const val LATEST_RELASED_LIBRARY_VERSION = "3.0.0" - internal fun buildPomFile( owner: String, name: String, version: String, + libraryVersion: String, ): String { val nameForRepo = name.substringBefore("/") return """ @@ -27,7 +26,7 @@ internal fun 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 eb9800c4a4..d20f43645a 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 @@ -1,6 +1,8 @@ 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 import java.security.MessageDigest sealed interface Artifact @@ -13,9 +15,9 @@ data class JarArtifact( val data: ByteArray, ) : Artifact -fun ActionCoords.buildVersionArtifacts(): Map? { - val jars = buildJars(owner = owner, name = name.replace("__", "/"), version = version) ?: return null - val pom = buildPomFile(owner = owner, name = name.replace("__", "/"), version = version) +fun ActionCoords.buildVersionArtifacts(bindingVersion: BindingVersion = V1): Map? { + val jars = buildJars(owner = owner, name = name.replace("__", "/"), version = version, bindingVersion = bindingVersion) ?: return null + val pom = buildPomFile(owner = owner, name = name.replace("__", "/"), version = version, libraryVersion = bindingVersion.libraryVersion) val module = buildModuleFile(owner = owner, name = name.replace("__", "/"), version = version) return mapOf( "$name-$version.jar" to JarArtifact(jars.mainJar),