Skip to content

Commit 029b490

Browse files
committed
feat(abg): Support different binding versions on different server routes
1 parent 1290877 commit 029b490

File tree

9 files changed

+211
-29
lines changed

9 files changed

+211
-29
lines changed

.github/workflows/bindings-server.main.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ workflow(
9191

9292
cleanMavenLocal()
9393

94+
run(
95+
name = "Execute the script using the bindings from the server with v1 route",
96+
command = """
97+
mv .github/workflows/test-script-consuming-jit-bindings-v1.main.do-not-compile.kts .github/workflows/test-script-consuming-jit-bindings-v1.main.kts
98+
.github/workflows/test-script-consuming-jit-bindings-v1.main.kts
99+
""".trimIndent(),
100+
)
101+
102+
cleanMavenLocal()
103+
94104
run(
95105
name = "Execute the script using bindings but without dependency on library",
96106
command = """
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env kotlin
2+
@file:Repository("https://repo.maven.apache.org/maven2/")
3+
@file:DependsOn("io.github.typesafegithub:github-workflows-kt:1.13.0")
4+
5+
@file:Repository("http://localhost:8080/v1")
6+
7+
// Regular, top-level action.
8+
@file:DependsOn("actions:checkout:v4")
9+
10+
// Nested action.
11+
@file:DependsOn("gradle:actions__setup-gradle:v3")
12+
13+
// Using specific version.
14+
@file:DependsOn("actions:cache:v3.3.3")
15+
16+
// Always untyped action.
17+
@file:DependsOn("typesafegithub:always-untyped-action-for-tests:v1")
18+
19+
import io.github.typesafegithub.workflows.actions.actions.Cache
20+
import io.github.typesafegithub.workflows.actions.actions.Checkout
21+
import io.github.typesafegithub.workflows.actions.actions.Checkout_Untyped
22+
import io.github.typesafegithub.workflows.actions.gradle.ActionsSetupGradle
23+
import io.github.typesafegithub.workflows.actions.typesafegithub.AlwaysUntypedActionForTests_Untyped
24+
25+
println(Checkout_Untyped(fetchTags_Untyped = "false"))
26+
println(Checkout(fetchTags = false))
27+
println(Checkout(fetchTags_Untyped = "false"))
28+
println(AlwaysUntypedActionForTests_Untyped(foobar_Untyped = "baz"))
29+
println(ActionsSetupGradle())
30+
println(Cache(path = listOf("some-path"), key = "some-key"))
31+
32+
// Ensure that 'copy(...)' method is exposed.
33+
Checkout(fetchTags = false).copy(fetchTags = true)

action-binding-generator/api/action-binding-generator.api

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/gen
6969
}
7070

7171
public final class io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationKt {
72-
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;
73-
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;
72+
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;
73+
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;
7474
}
7575

7676
public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input {
@@ -179,3 +179,14 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/met
179179
public abstract interface class io/github/typesafegithub/workflows/actionbindinggenerator/typing/Typing {
180180
}
181181

182+
public final class io/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion : java/lang/Enum {
183+
public static final field V1 Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion;
184+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
185+
public final fun getLibraryVersion ()Ljava/lang/String;
186+
public final fun isDeprecated ()Z
187+
public final fun isExperimental ()Z
188+
public fun toString ()Ljava/lang/String;
189+
public static fun valueOf (Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion;
190+
public static fun values ()[Lio/github/typesafegithub/workflows/actionbindinggenerator/versioning/BindingVersion;
191+
}
192+

action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.typing.provideT
3232
import io.github.typesafegithub.workflows.actionbindinggenerator.utils.removeTrailingWhitespacesForEachLine
3333
import io.github.typesafegithub.workflows.actionbindinggenerator.utils.toCamelCase
3434
import io.github.typesafegithub.workflows.actionbindinggenerator.utils.toKotlinPackageName
35+
import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion
36+
import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1
3537

3638
public data class ActionBinding(
3739
val kotlinCode: String,
@@ -54,6 +56,7 @@ private object Properties {
5456
}
5557

5658
public fun ActionCoords.generateBinding(
59+
bindingVersion: BindingVersion = V1,
5760
metadataRevision: MetadataRevision,
5861
metadata: Metadata? = null,
5962
inputTypings: Pair<Map<String, Typing>, TypingActualSource?>? = null,
@@ -69,10 +72,11 @@ public fun ActionCoords.generateBinding(
6972

7073
val actionBindingSourceCodeUntyped =
7174
generateActionBindingSourceCode(
72-
metadataProcessed,
73-
this,
74-
emptyMap(),
75-
classNameUntyped,
75+
metadata = metadataProcessed,
76+
coords = this,
77+
bindingVersion = bindingVersion,
78+
inputTypings = emptyMap(),
79+
className = classNameUntyped,
7680
untypedClass = true,
7781
replaceWith = inputTypingsResolved.second?.let { CodeBlock.of("ReplaceWith(%S)", className) },
7882
)
@@ -90,6 +94,7 @@ public fun ActionCoords.generateBinding(
9094
generateActionBindingSourceCode(
9195
metadata = metadataProcessed,
9296
coords = this,
97+
bindingVersion = bindingVersion,
9398
inputTypings = inputTypingsResolved.first,
9499
className = className,
95100
)
@@ -120,6 +125,7 @@ private fun Metadata.removeDeprecatedInputsIfNameClash(): Metadata {
120125
private fun generateActionBindingSourceCode(
121126
metadata: Metadata,
122127
coords: ActionCoords,
128+
bindingVersion: BindingVersion,
123129
inputTypings: Map<String, Typing>,
124130
className: String,
125131
untypedClass: Boolean = false,
@@ -136,7 +142,7 @@ private fun generateActionBindingSourceCode(
136142
changes will be overwritten with the next binding code regeneration.
137143
See https://github.com/typesafegithub/github-workflows-kt for more info.
138144
""".trimIndent(),
139-
).addType(generateActionClass(metadata, coords, inputTypings, className, untypedClass, replaceWith))
145+
).addType(generateActionClass(metadata, coords, bindingVersion, inputTypings, className, untypedClass, replaceWith))
140146
.addSuppressAnnotation(metadata)
141147
.indent(" ")
142148
.build()
@@ -165,6 +171,7 @@ private fun FileSpec.Builder.addSuppressAnnotation(metadata: Metadata) =
165171
private fun generateActionClass(
166172
metadata: Metadata,
167173
coords: ActionCoords,
174+
bindingVersion: BindingVersion,
168175
inputTypings: Map<String, Typing>,
169176
className: String,
170177
untypedClass: Boolean,
@@ -174,12 +181,13 @@ private fun generateActionClass(
174181
.classBuilder(className)
175182
.addModifiers(KModifier.DATA)
176183
.addKdocIfNotEmpty(actionKdoc(metadata, coords, untypedClass))
177-
.replaceWith(replaceWith)
184+
.deprecateBindingVersion(bindingVersion)
185+
.replaceWith(bindingVersion, replaceWith)
178186
.addClassConstructorAnnotation()
179187
.inheritsFromRegularAction(coords, metadata, className)
180188
.primaryConstructor(metadata.primaryConstructor(inputTypings, coords, className, untypedClass))
181189
.properties(metadata, coords, inputTypings, className, untypedClass)
182-
.addInitializerBlockIfNecessary(metadata, inputTypings, untypedClass)
190+
.addInitializerBlock(metadata, bindingVersion, inputTypings, untypedClass)
183191
.addFunction(metadata.secondaryConstructor(inputTypings, coords, className, untypedClass))
184192
.addFunction(metadata.buildToYamlArgumentsFunction(inputTypings, untypedClass))
185193
.addCustomTypes(inputTypings, coords, className)
@@ -374,8 +382,23 @@ private fun Metadata.linkedMapOfInputs(
374382
}
375383
}
376384

377-
private fun TypeSpec.Builder.replaceWith(replaceWith: CodeBlock?): TypeSpec.Builder {
378-
if (replaceWith != null) {
385+
private fun TypeSpec.Builder.deprecateBindingVersion(bindingVersion: BindingVersion): TypeSpec.Builder {
386+
if (bindingVersion.isDeprecated) {
387+
addAnnotation(
388+
AnnotationSpec
389+
.builder(Deprecated::class.asClassName())
390+
.addMember("%S", "Use a non-deprecated binding version in the repository URL")
391+
.build(),
392+
)
393+
}
394+
return this
395+
}
396+
397+
private fun TypeSpec.Builder.replaceWith(
398+
bindingVersion: BindingVersion,
399+
replaceWith: CodeBlock?,
400+
): TypeSpec.Builder {
401+
if (!bindingVersion.isDeprecated && replaceWith != null) {
379402
addAnnotation(
380403
AnnotationSpec
381404
.builder(Deprecated::class.asClassName())
@@ -533,15 +556,61 @@ private fun ParameterSpec.Builder.defaultValueIfNullable(
533556
return this
534557
}
535558

536-
private fun TypeSpec.Builder.addInitializerBlockIfNecessary(
559+
private fun TypeSpec.Builder.addInitializerBlock(
537560
metadata: Metadata,
561+
bindingVersion: BindingVersion,
538562
inputTypings: Map<String, Typing>,
539563
untypedClass: Boolean,
540564
): TypeSpec.Builder {
541-
if (untypedClass || metadata.inputs.isEmpty() || metadata.inputs.none { inputTypings.containsKey(it.key) }) {
565+
if (!bindingVersion.isDeprecated &&
566+
!bindingVersion.isExperimental &&
567+
(untypedClass || metadata.inputs.isEmpty() || metadata.inputs.none { inputTypings.containsKey(it.key) })
568+
) {
542569
return this
543570
}
544-
addInitializerBlock(metadata.initializerBlock(inputTypings))
571+
572+
val initializerBlock = CodeBlock.builder()
573+
if (bindingVersion.isDeprecated) {
574+
val firstStableVersion = BindingVersion.entries.find { !it.isDeprecated && !it.isExperimental }
575+
initializerBlock.addStatement(
576+
"println(%S)",
577+
"""
578+
WARNING: The used binding version $bindingVersion is deprecated! First stable version is $firstStableVersion.
579+
""".trimIndent(),
580+
)
581+
initializerBlock.beginControlFlow("""if (System.getenv("GITHUB_ACTIONS").toBoolean())""")
582+
initializerBlock.addStatement(
583+
"println(%S)",
584+
"""
585+
586+
::warning title=Deprecated Binding Version Used::The used binding version $bindingVersion is deprecated! First stable version is $firstStableVersion.
587+
""".trimIndent(),
588+
)
589+
initializerBlock.endControlFlow()
590+
initializerBlock.add("\n")
591+
}
592+
if (bindingVersion.isExperimental) {
593+
val lastStableVersion = BindingVersion.entries.findLast { !it.isDeprecated && !it.isExperimental }
594+
initializerBlock.addStatement(
595+
"println(%S)",
596+
"""
597+
WARNING: The used binding version $bindingVersion is experimental! Last stable version is $lastStableVersion.
598+
""".trimIndent(),
599+
)
600+
initializerBlock.beginControlFlow("""if (System.getenv("GITHUB_ACTIONS").toBoolean())""")
601+
initializerBlock.addStatement(
602+
"println(%S)",
603+
"""
604+
605+
::warning title=Experimental Binding Version Used::The used binding version $bindingVersion is experimental! Last stable version is $lastStableVersion.
606+
""".trimIndent(),
607+
)
608+
initializerBlock.endControlFlow()
609+
initializerBlock.add("\n")
610+
}
611+
initializerBlock.add(metadata.initializerBlock(inputTypings))
612+
613+
addInitializerBlock(initializerBlock.build())
545614
return this
546615
}
547616

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.github.typesafegithub.workflows.actionbindinggenerator.versioning
2+
3+
public enum class BindingVersion(
4+
public val isDeprecated: Boolean = false,
5+
public val isExperimental: Boolean = true,
6+
public val libraryVersion: String,
7+
) {
8+
V1(isExperimental = false, libraryVersion = "3.0.0"),
9+
;
10+
11+
override fun toString(): String = super.toString().lowercase()
12+
}

jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package io.github.typesafegithub.workflows.jitbindingserver
33
import io.github.reactivecircus.cache4k.Cache
44
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
55
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.prettyPrint
6+
import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion
7+
import io.github.typesafegithub.workflows.actionbindinggenerator.versioning.BindingVersion.V1
68
import io.github.typesafegithub.workflows.mavenbinding.Artifact
79
import io.github.typesafegithub.workflows.mavenbinding.JarArtifact
810
import io.github.typesafegithub.workflows.mavenbinding.TextArtifact
@@ -29,7 +31,7 @@ import kotlin.time.Duration.Companion.hours
2931
fun main() {
3032
val bindingsCache =
3133
Cache
32-
.Builder<ActionCoords, Result<Map<String, Artifact>>>()
34+
.Builder<CacheKey, Result<Map<String, Artifact>>>()
3335
.expireAfterWrite(1.hours)
3436
.build()
3537
val openTelemetry = buildOpenTelemetryConfig(serviceName = "github-actions-bindings")
@@ -57,6 +59,26 @@ fun main() {
5759
}
5860
}
5961

62+
route("{bindingVersion}") {
63+
route("{owner}/{name}/{version}/{file}") {
64+
artifact(bindingsCache)
65+
}
66+
67+
route("{owner}/{name}/{file}") {
68+
metadata()
69+
}
70+
71+
route("/refresh") {
72+
route("{owner}/{name}/{version}/{file}") {
73+
artifact(bindingsCache, refresh = true)
74+
}
75+
76+
route("{owner}/{name}/{file}") {
77+
metadata(refresh = true)
78+
}
79+
}
80+
}
81+
6082
get("/status") {
6183
call.respondText("OK")
6284
}
@@ -66,7 +88,7 @@ fun main() {
6688

6789
private fun Route.metadata(refresh: Boolean = false) {
6890
get {
69-
if (refresh && !deliverOnRefreshRoute) {
91+
if ((call.bindingVersion == null) || (refresh && !deliverOnRefreshRoute)) {
7092
call.respondText(text = "Not found", status = HttpStatusCode.NotFound)
7193
return@get
7294
}
@@ -93,7 +115,7 @@ private fun Route.metadata(refresh: Boolean = false) {
93115
}
94116

95117
private fun Route.artifact(
96-
bindingsCache: Cache<ActionCoords, Result<Map<String, Artifact>>>,
118+
bindingsCache: Cache<CacheKey, Result<Map<String, Artifact>>>,
97119
refresh: Boolean = false,
98120
) {
99121
get {
@@ -138,10 +160,23 @@ private fun Route.artifact(
138160
}
139161
}
140162

163+
private val ApplicationCall.bindingVersion: BindingVersion?
164+
get() {
165+
val bindingVersion = parameters["bindingVersion"]
166+
return if (bindingVersion == null) {
167+
V1
168+
} else {
169+
BindingVersion
170+
.entries
171+
.find { it.name.equals(bindingVersion.uppercase(), ignoreCase = true) }
172+
}
173+
}
174+
141175
private suspend fun ApplicationCall.toBindingArtifacts(
142-
bindingsCache: Cache<ActionCoords, Result<Map<String, Artifact>>>,
176+
bindingsCache: Cache<CacheKey, Result<Map<String, Artifact>>>,
143177
refresh: Boolean,
144178
): Map<String, Artifact>? {
179+
val bindingVersion = bindingVersion ?: return null
145180
val owner = parameters["owner"]!!
146181
val name = parameters["name"]!!
147182
val version = parameters["version"]!!
@@ -151,15 +186,16 @@ private suspend fun ApplicationCall.toBindingArtifacts(
151186
name = name,
152187
version = version,
153188
)
154-
println("➡️ Requesting ${actionCoords.prettyPrint}")
189+
println("➡️ Requesting ${actionCoords.prettyPrint} binding version $bindingVersion")
190+
val cacheKey = CacheKey(actionCoords, bindingVersion)
155191
val bindingArtifacts =
156192
if (refresh) {
157-
actionCoords.buildVersionArtifacts().also {
158-
bindingsCache.put(actionCoords, Result.of(it))
193+
actionCoords.buildVersionArtifacts(bindingVersion).also {
194+
bindingsCache.put(cacheKey, Result.of(it))
159195
}
160196
} else {
161197
bindingsCache
162-
.get(actionCoords) { Result.of(actionCoords.buildVersionArtifacts()) }
198+
.get(cacheKey) { Result.of(actionCoords.buildVersionArtifacts(bindingVersion)) }
163199
.getOrNull()
164200
}
165201
return bindingArtifacts
@@ -170,3 +206,8 @@ private fun Result.Companion.failure(): Result<Nothing> = failure(object : Throw
170206
private fun <T> Result.Companion.of(value: T?): Result<T> = value?.let { success(it) } ?: failure()
171207

172208
private val deliverOnRefreshRoute = System.getenv("GWKT_DELIVER_ON_REFRESH").toBoolean()
209+
210+
private data class CacheKey(
211+
val actionCoords: ActionCoords,
212+
val bindingVersion: BindingVersion,
213+
)

0 commit comments

Comments
 (0)