Skip to content

Commit f883c69

Browse files
authored
feat: add PoC of just-in-time binding server (#1319)
Part of #1318.
1 parent a16982b commit f883c69

File tree

8 files changed

+350
-0
lines changed

8 files changed

+350
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
plugins {
2+
buildsrc.convention.`kotlin-jvm`
3+
application
4+
}
5+
6+
dependencies {
7+
implementation(platform("io.ktor:ktor-bom:2.3.9"))
8+
implementation("io.ktor:ktor-server-core")
9+
implementation("io.ktor:ktor-server-netty")
10+
implementation("ch.qos.logback:logback-classic:1.5.3")
11+
12+
implementation(projects.mavenBindingBuilder)
13+
}
14+
15+
application {
16+
mainClass.set("io.github.typesafegithub.workflows.jitbindingserver.MainKt")
17+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.github.typesafegithub.workflows.jitbindingserver
2+
3+
import io.github.typesafegithub.workflows.mavenbinding.buildJar
4+
import io.github.typesafegithub.workflows.mavenbinding.buildModuleFile
5+
import io.github.typesafegithub.workflows.mavenbinding.buildPomFile
6+
import io.ktor.http.ContentType
7+
import io.ktor.http.HttpStatusCode
8+
import io.ktor.server.application.call
9+
import io.ktor.server.engine.embeddedServer
10+
import io.ktor.server.netty.Netty
11+
import io.ktor.server.response.respondOutputStream
12+
import io.ktor.server.response.respondText
13+
import io.ktor.server.routing.get
14+
import io.ktor.server.routing.head
15+
import io.ktor.server.routing.routing
16+
17+
fun main() {
18+
embeddedServer(Netty, port = 8080) {
19+
routing {
20+
get("/binding/{owner}/{name}/{version}/{file}") {
21+
val owner = call.parameters["owner"]!!
22+
val name = call.parameters["name"]!!
23+
val version = call.parameters["version"]!!
24+
val file = call.parameters["file"]!!
25+
when (file) {
26+
"$name-$version.jar" ->
27+
call.respondOutputStream(
28+
contentType = ContentType.parse("application/java-archive"),
29+
status = HttpStatusCode.OK,
30+
producer = { this.buildJar(owner = owner, name = name, version = version) },
31+
)
32+
"$name-$version.pom" -> call.respondText(buildPomFile(owner = owner, name = name, version = version))
33+
"$name-$version.module" -> call.respondText(buildModuleFile(owner = owner, name = name, version = version))
34+
else -> call.respondText(text = "Not found", status = HttpStatusCode.NotFound)
35+
}
36+
}
37+
38+
head("/binding/{owner}/{name}/{version}/{file}") {
39+
val name = call.parameters["name"]!!
40+
val version = call.parameters["version"]!!
41+
val file = call.parameters["file"]!!
42+
when (file) {
43+
"$name-$version.jar" -> call.respondText("ok", status = HttpStatusCode.OK)
44+
"$name-$version.pom" -> call.respondText("ok", status = HttpStatusCode.OK)
45+
"$name-$version.module" -> call.respondText("ok", status = HttpStatusCode.OK)
46+
else -> call.respondText(text = "Not found", status = HttpStatusCode.NotFound)
47+
}
48+
}
49+
}
50+
}.start(wait = true)
51+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
buildsrc.convention.`kotlin-jvm`
3+
}
4+
5+
dependencies {
6+
implementation("org.jetbrains.kotlin:kotlin-compiler")
7+
implementation(projects.actionBindingGenerator)
8+
runtimeOnly(projects.githubWorkflowsKt)
9+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package io.github.typesafegithub.workflows.mavenbinding
2+
3+
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4+
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.NewestForVersion
5+
import io.github.typesafegithub.workflows.actionbindinggenerator.generation.ActionBinding
6+
import io.github.typesafegithub.workflows.actionbindinggenerator.generation.generateBinding
7+
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
8+
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
9+
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
10+
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
11+
import org.jetbrains.kotlin.config.Services
12+
import java.io.OutputStream
13+
import java.nio.file.Path
14+
import kotlin.io.path.Path
15+
import kotlin.io.path.createParentDirectories
16+
import kotlin.io.path.createTempDirectory
17+
import kotlin.io.path.div
18+
import kotlin.io.path.writeText
19+
20+
fun OutputStream.buildJar(
21+
owner: String,
22+
name: String,
23+
version: String,
24+
) {
25+
val binding = generateBinding(owner = owner, name = name, version = version)
26+
val pathWithJarContents = binding.compileBinding()
27+
return this.createZipFile(pathWithJarContents)
28+
}
29+
30+
private fun generateBinding(
31+
owner: String,
32+
name: String,
33+
version: String,
34+
): ActionBinding {
35+
val actionCoords =
36+
ActionCoords(
37+
owner = owner,
38+
name = name,
39+
version = version,
40+
)
41+
return actionCoords.generateBinding(
42+
metadataRevision = NewestForVersion,
43+
)
44+
}
45+
46+
private fun ActionBinding.compileBinding(): Path {
47+
val compilationInput = createTempDirectory()
48+
val compilationOutput = createTempDirectory()
49+
50+
val sourceFilePath = compilationInput / Path(filePath.substringAfter("kotlin/"))
51+
sourceFilePath.createParentDirectories()
52+
sourceFilePath.writeText(kotlinCode)
53+
54+
val args =
55+
K2JVMCompilerArguments().apply {
56+
destination = compilationOutput.toString()
57+
classpath = System.getProperty("java.class.path")
58+
freeArgs = listOf(sourceFilePath.toString())
59+
noStdlib = true
60+
noReflect = true
61+
includeRuntime = false
62+
}
63+
val compilerMessageCollector =
64+
PrintingMessageCollector(
65+
System.out,
66+
MessageRenderer.GRADLE_STYLE,
67+
false,
68+
)
69+
K2JVMCompiler().exec(
70+
messageCollector = compilerMessageCollector,
71+
services = Services.EMPTY,
72+
arguments = args,
73+
)
74+
return compilationOutput
75+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.github.typesafegithub.workflows.mavenbinding
2+
3+
fun buildModuleFile(
4+
owner: String,
5+
name: String,
6+
version: String,
7+
): String =
8+
"""
9+
{
10+
"formatVersion": "1.1",
11+
"component": {
12+
"group": "$owner",
13+
"module": "$name",
14+
"version": "$version",
15+
"attributes": {
16+
"org.gradle.status": "release"
17+
}
18+
},
19+
"createdBy": {
20+
"gradle": {
21+
"version": "8.7"
22+
}
23+
},
24+
"variants": [
25+
{
26+
"name": "apiElements",
27+
"attributes": {
28+
"org.gradle.category": "library",
29+
"org.gradle.dependency.bundling": "external",
30+
"org.gradle.jvm.environment": "standard-jvm",
31+
"org.gradle.jvm.version": 11,
32+
"org.gradle.libraryelements": "jar",
33+
"org.gradle.usage": "java-api",
34+
"org.jetbrains.kotlin.platform.type": "jvm"
35+
},
36+
"dependencies": [],
37+
"files": [
38+
{
39+
"name": "$name-$version.jar",
40+
"url": "$name-$version.jar",
41+
"size": 1
42+
}
43+
]
44+
},
45+
{
46+
"name": "runtimeElements",
47+
"attributes": {
48+
"org.gradle.category": "library",
49+
"org.gradle.dependency.bundling": "external",
50+
"org.gradle.jvm.environment": "standard-jvm",
51+
"org.gradle.jvm.version": 11,
52+
"org.gradle.libraryelements": "jar",
53+
"org.gradle.usage": "java-runtime",
54+
"org.jetbrains.kotlin.platform.type": "jvm"
55+
},
56+
"dependencies": [],
57+
"files": [
58+
{
59+
"name": "$name-$version.jar",
60+
"url": "$name-$version.jar",
61+
"size": 1
62+
}
63+
]
64+
}
65+
]
66+
}
67+
""".trimIndent()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.github.typesafegithub.workflows.mavenbinding
2+
3+
fun buildPomFile(
4+
owner: String,
5+
name: String,
6+
version: String,
7+
): String =
8+
"""
9+
<?xml version="1.0" encoding="UTF-8"?>
10+
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
11+
<modelVersion>3.0/0</modelVersion>
12+
<groupId>$owner</groupId>
13+
<artifactId>$name</artifactId>
14+
<version>$version</version>
15+
<name>$name</name>
16+
<description>Auto-generated binding for $owner/$name@$version.</description>
17+
<url>https://github.com/typesafegithub/github-workflows-kt</url>
18+
<licenses>
19+
<license>
20+
<name>Apache License, version 2.0</name>
21+
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
22+
</license>
23+
</licenses>
24+
<developers>
25+
<developer>
26+
<id>typesafegithub</id>
27+
<name>Piotr Krzemiński</name>
28+
<email>[email protected]</email>
29+
</developer>
30+
</developers>
31+
<scm>
32+
<connection>scm:git:git://github.com/typesafegithub/github-workflows-kt.git/</connection>
33+
<developerConnection>scm:git:ssh://github.com:typesafegithub/github-workflows-kt.git</developerConnection>
34+
<url>https://github.com/typesafegithub/github-workflows-kt.git</url>
35+
</scm>
36+
<dependencies></dependencies>
37+
</project>
38+
""".trimIndent()
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package io.github.typesafegithub.workflows.mavenbinding
2+
3+
import com.intellij.util.io.PagedFileStorage
4+
import java.io.BufferedInputStream
5+
import java.io.File
6+
import java.io.FileInputStream
7+
import java.io.FileNotFoundException
8+
import java.io.IOException
9+
import java.io.OutputStream
10+
import java.nio.file.Path
11+
import java.util.zip.ZipEntry
12+
import java.util.zip.ZipOutputStream
13+
import kotlin.io.path.isDirectory
14+
import kotlin.io.path.listDirectoryEntries
15+
import kotlin.io.path.name
16+
17+
internal fun OutputStream.createZipFile(contents: Path) =
18+
ZipOutputStream(this).use { zipOutputStream ->
19+
contents.listDirectoryEntries().forEach { file ->
20+
if (file.isDirectory()) {
21+
zipDirectory(file.toFile(), file.name, zipOutputStream)
22+
} else {
23+
zipFile(file.toFile(), zipOutputStream)
24+
}
25+
}
26+
zipOutputStream.flush()
27+
}
28+
29+
/**
30+
* Adds a directory to the current zip output stream
31+
* @param folder the directory to be added
32+
* @param parentFolder the path of parent directory
33+
* @param zos the current zip output stream
34+
* @throws FileNotFoundException
35+
* @throws IOException
36+
*/
37+
@Throws(FileNotFoundException::class, IOException::class)
38+
private fun zipDirectory(
39+
folder: File,
40+
parentFolder: String,
41+
zos: ZipOutputStream,
42+
) {
43+
for (file in folder.listFiles()) {
44+
if (file.isDirectory()) {
45+
zipDirectory(file, parentFolder + "/" + file.getName(), zos)
46+
continue
47+
}
48+
zos.putNextEntry(ZipEntry(parentFolder + "/" + file.getName()))
49+
val bis =
50+
BufferedInputStream(
51+
FileInputStream(file),
52+
)
53+
var bytesRead: Long = 0
54+
val bytesIn = ByteArray(PagedFileStorage.BUFFER_SIZE)
55+
var read: Int
56+
while ((bis.read(bytesIn).also { read = it }) != -1) {
57+
zos.write(bytesIn, 0, read)
58+
bytesRead += read.toLong()
59+
}
60+
zos.closeEntry()
61+
}
62+
}
63+
64+
/**
65+
* Adds a file to the current zip output stream
66+
* @param file the file to be added
67+
* @param zos the current zip output stream
68+
* @throws FileNotFoundException
69+
* @throws IOException
70+
*/
71+
@Throws(FileNotFoundException::class, IOException::class)
72+
private fun zipFile(
73+
file: File,
74+
zos: ZipOutputStream,
75+
) {
76+
zos.putNextEntry(ZipEntry(file.getName()))
77+
val bis =
78+
BufferedInputStream(
79+
FileInputStream(
80+
file,
81+
),
82+
)
83+
var bytesRead: Long = 0
84+
val bytesIn = ByteArray(PagedFileStorage.BUFFER_SIZE)
85+
var read: Int
86+
while ((bis.read(bytesIn).also { read = it }) != -1) {
87+
zos.write(bytesIn, 0, read)
88+
bytesRead += read.toLong()
89+
}
90+
zos.closeEntry()
91+
}

settings.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ apply(from = "./buildSrc/repositories.settings.gradle.kts")
55
include(
66
"github-workflows-kt",
77
"action-binding-generator",
8+
"maven-binding-builder",
9+
"jit-binding-server",
810
"shared-internal",
911
":automation:code-generator",
1012
)

0 commit comments

Comments
 (0)