Skip to content

Commit

Permalink
feat(abg): Support different binding versions on different server routes
Browse files Browse the repository at this point in the history
  • Loading branch information
Vampire committed Sep 5, 2024
1 parent 1290877 commit 029b490
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 29 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/bindings-server.main.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
15 changes: 13 additions & 2 deletions action-binding-generator/api/action-binding-generator.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}

Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -54,6 +56,7 @@ private object Properties {
}

public fun ActionCoords.generateBinding(
bindingVersion: BindingVersion = V1,
metadataRevision: MetadataRevision,
metadata: Metadata? = null,
inputTypings: Pair<Map<String, Typing>, TypingActualSource?>? = null,
Expand All @@ -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) },
)
Expand All @@ -90,6 +94,7 @@ public fun ActionCoords.generateBinding(
generateActionBindingSourceCode(
metadata = metadataProcessed,
coords = this,
bindingVersion = bindingVersion,
inputTypings = inputTypingsResolved.first,
className = className,
)
Expand Down Expand Up @@ -120,6 +125,7 @@ private fun Metadata.removeDeprecatedInputsIfNameClash(): Metadata {
private fun generateActionBindingSourceCode(
metadata: Metadata,
coords: ActionCoords,
bindingVersion: BindingVersion,
inputTypings: Map<String, Typing>,
className: String,
untypedClass: Boolean = false,
Expand All @@ -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()
Expand Down Expand Up @@ -165,6 +171,7 @@ private fun FileSpec.Builder.addSuppressAnnotation(metadata: Metadata) =
private fun generateActionClass(
metadata: Metadata,
coords: ActionCoords,
bindingVersion: BindingVersion,
inputTypings: Map<String, Typing>,
className: String,
untypedClass: Boolean,
Expand All @@ -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)
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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<String, Typing>,
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
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,7 +31,7 @@ import kotlin.time.Duration.Companion.hours
fun main() {
val bindingsCache =
Cache
.Builder<ActionCoords, Result<Map<String, Artifact>>>()
.Builder<CacheKey, Result<Map<String, Artifact>>>()
.expireAfterWrite(1.hours)
.build()
val openTelemetry = buildOpenTelemetryConfig(serviceName = "github-actions-bindings")
Expand Down Expand Up @@ -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")
}
Expand All @@ -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
}
Expand All @@ -93,7 +115,7 @@ private fun Route.metadata(refresh: Boolean = false) {
}

private fun Route.artifact(
bindingsCache: Cache<ActionCoords, Result<Map<String, Artifact>>>,
bindingsCache: Cache<CacheKey, Result<Map<String, Artifact>>>,
refresh: Boolean = false,
) {
get {
Expand Down Expand Up @@ -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<ActionCoords, Result<Map<String, Artifact>>>,
bindingsCache: Cache<CacheKey, Result<Map<String, Artifact>>>,
refresh: Boolean,
): Map<String, Artifact>? {
val bindingVersion = bindingVersion ?: return null
val owner = parameters["owner"]!!
val name = parameters["name"]!!
val version = parameters["version"]!!
Expand All @@ -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
Expand All @@ -170,3 +206,8 @@ private fun Result.Companion.failure(): Result<Nothing> = failure(object : Throw
private fun <T> Result.Companion.of(value: T?): Result<T> = 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,
)
Loading

0 comments on commit 029b490

Please sign in to comment.