Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IC for Compose Wasm #775

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/
!**/src/main/**
!**/src/test/**
src/main/resources/application.properties
resource-server/src/main/resources/application.properties

# compile artifacts
.kotlin/
Expand Down
56 changes: 43 additions & 13 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import org.gradle.kotlin.dsl.support.serviceOf
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.tasks.bundling.BootJar
import java.io.FileInputStream
import java.util.Properties

val policy: String by System.getProperties()

Expand Down Expand Up @@ -41,9 +42,17 @@ allprojects {
maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
afterEvaluate {
dependencies {
dependencies {
}

setOf(
rootProject,
project(":common"),
project(":executors"),
project(":indexation"),
).forEach { project ->
project.afterEvaluate {
project.dependencies {
project.dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
implementation(libs.kotlin.idea) {
isTransitive = false
Expand All @@ -53,9 +62,21 @@ allprojects {
}
}

val resourceDependency: Configuration by configurations.creating {

val kotlinComposeWasmStdlibTypeInfo: Configuration by configurations.creating {
isTransitive = false
isCanBeResolved = true
isCanBeConsumed = false
attributes {
attribute(
Category.CATEGORY_ATTRIBUTE,
objects.categoryComposeCache
)
attribute(
CacheAttribute.cacheAttribute,
CacheAttribute.TYPEINFO
)
}
}

dependencies {
Expand Down Expand Up @@ -84,7 +105,7 @@ dependencies {
}
testImplementation(libs.kotlinx.coroutines.test)

resourceDependency(libs.skiko.js.wasm.runtime)
kotlinComposeWasmStdlibTypeInfo(project(":cache-maker"))
}

fun buildPropertyFile() {
Expand All @@ -109,17 +130,27 @@ fun generateProperties(prefix: String = "") = """
libraries.folder.compose-wasm=${prefix + libComposeWasm}
libraries.folder.compose-wasm-compiler-plugins=${prefix + libComposeWasmCompilerPlugins}
libraries.folder.compiler-plugins=${prefix + compilerPluginsForJVM}
caches.folder.compose-wasm=${prefix + cachesComposeWasm}
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
server.compression.enabled=true
server.compression.mime-types=application/json,text/javascript,application/wasm
skiko.version=${libs.versions.skiko.get()}
""".trimIndent()

val composeWasmPropertiesUpdater by tasks.registering(ComposeWasmPropertiesUpdater::class) {
dependsOn(kotlinComposeWasmStdlibTypeInfo)
propertiesPath.set(rootDir.resolve("src/main/resources/${propertyFile}").absolutePath)
typeInfoFile.set(kotlinComposeWasmStdlibTypeInfo.singleFile)
}

tasks.withType<KotlinCompile> {
compilerOptions {
freeCompilerArgs.set(listOf("-Xjsr305=strict"))
}
dependsOn(":executors:jar")
dependsOn(":indexation:run")
dependsOn(kotlinComposeWasmStdlibTypeInfo)
dependsOn(composeWasmPropertiesUpdater)
buildPropertyFile()
}
println("Using Kotlin compiler ${libs.versions.kotlin.get()}")
Expand All @@ -131,13 +162,16 @@ tasks.withType<BootJar> {

val buildLambda by tasks.creating(Zip::class) {
val propertyFile = propertyFile
val propertyFileContent = generateProperties("/var/task/")
val propertyFileContent = generateProperties(lambdaPrefix)

from(tasks.compileKotlin)
from(tasks.processResources) {
eachFile {
if (name == propertyFile) {
val properties = Properties().apply { load(FileInputStream(file)) }
val composeWasmHash = properties.get("dependencies.compose.wasm")
file.writeText(propertyFileContent)
file.appendText("\ndependencies.compose.wasm=$composeWasmHash")
}
}
}
Expand All @@ -152,18 +186,14 @@ val buildLambda by tasks.creating(Zip::class) {
from(libJVMFolder) { into(libJVM) }
from(compilerPluginsForJVMFolder) {into(compilerPluginsForJVM)}
from(libComposeWasmCompilerPluginsFolder) { into(libComposeWasmCompilerPlugins) }
from(kotlinComposeWasmStdlibTypeInfo) { into(cachesComposeWasm) }
into("lib") {
from(configurations.compileClasspath) { exclude("tomcat-embed-*") }
}
}

tasks.named<Copy>("processResources") {
val archiveOperation = project.serviceOf<ArchiveOperations>()
from(resourceDependency.map {
archiveOperation.zipTree(it)
}) {
into("com/compiler/server")
}
dependsOn(composeWasmPropertiesUpdater)
}

tasks.withType<Test> {
Expand Down
10 changes: 10 additions & 0 deletions buildSrc/src/main/kotlin/CacheAttribute.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import org.gradle.api.attributes.Attribute

enum class CacheAttribute {
FULL,
TYPEINFO;

companion object {
val cacheAttribute = Attribute.of("org.jetbrains.kotlin-compiler-server.cache", CacheAttribute::class.java)
}
}
5 changes: 5 additions & 0 deletions buildSrc/src/main/kotlin/CategoryAttribute.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import org.gradle.api.attributes.Category
import org.gradle.api.model.ObjectFactory

val ObjectFactory.categoryComposeCache
get() = named(Category::class.java, "compose-cache")
61 changes: 61 additions & 0 deletions buildSrc/src/main/kotlin/PropertiesUpdater.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.io.FileInputStream
import java.security.MessageDigest

abstract class ComposeWasmPropertiesUpdater : DefaultTask() {

@get:Input
abstract val propertiesPath: Property<String>

@get:InputFile
abstract val typeInfoFile: RegularFileProperty

@get:Input
abstract val propertiesMap: MapProperty<String, String>

@get:OutputFile
val updatedPropertiesFile: RegularFileProperty = project.objects.fileProperty().fileProvider(propertiesPath.map { File(it) })

@TaskAction
fun updateProperties() {
val file = updatedPropertiesFile.get().asFile

propertiesMap.get().let {
if (it.isNotEmpty()) {
file.writeText("")
it.forEach { (key, value) ->
file.appendText("$key=$value\n")
}
}
}

file.appendText(
"\ndependencies.compose.wasm=${hashFileContent(typeInfoFile.get().asFile.absolutePath)}"
)
}
}

fun hashFileContent(filePath: String, hashAlgorithm: String = "SHA-256"): String {
val file = File(filePath)
val digest = MessageDigest.getInstance(hashAlgorithm)

// Read the file content in chunks and update the digest
FileInputStream(file).use { fileInputStream ->
val buffer = ByteArray(1024)
var bytesRead: Int
while (fileInputStream.read(buffer).also { bytesRead = it } != -1) {
digest.update(buffer, 0, bytesRead)
}
}

// Convert the resulting byte array to a readable hex string
return digest.digest().joinToString("") { "%02x".format(it) }
}
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/lambda.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
val lambdaPrefix = "/var/task/"
9 changes: 7 additions & 2 deletions buildSrc/src/main/kotlin/properties.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Project
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.kotlin.dsl.the

val indexes: String by System.getProperties()
Expand All @@ -25,6 +25,8 @@ val Project.libComposeWasm
get() = "$kotlinVersion-compose-wasm"
val Project.libComposeWasmCompilerPlugins
get() = "$kotlinVersion-compose-wasm-compiler-plugins"
val Project.cachesComposeWasm
get() = "$kotlinVersion-caches-compose-wasm"

val Project.libJVMFolder
get() = rootProject.layout.projectDirectory.dir(libJVM)
Expand All @@ -42,4 +44,7 @@ val Project.libComposeWasmFolder
get() = rootProject.layout.projectDirectory.dir(libComposeWasm)

val Project.libComposeWasmCompilerPluginsFolder
get() = rootProject.layout.projectDirectory.dir(libComposeWasmCompilerPlugins)
get() = rootProject.layout.projectDirectory.dir(libComposeWasmCompilerPlugins)

val Project.cachesComposeWasmFolder
get() = rootProject.layout.projectDirectory.dir(cachesComposeWasm)
20 changes: 20 additions & 0 deletions cache-maker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM amazoncorretto:17

ARG BASE_DIR
ARG KOTLIN_VERSION

RUN if [ -z "$KOTLIN_VERSION" ]; then \
echo "Error: KOTLIN_VERSION argument is not set. Use docker-build-incremental-cache.sh to build the image." >&2; \
exit 1; \
fi

ENV KOTLIN_VERSION=$KOTLIN_VERSION

RUN mkdir -p $BASE_DIR
WORKDIR $BASE_DIR
ADD . $BASE_DIR

RUN sed -i 's@kotlin = ".*"@kotlin = "'$KOTLIN_VERSION'"@g' gradle/libs.versions.toml
RUN ./gradlew clean

RUN ./gradlew :cache-maker:compileProductionExecutableKotlinWasmJs
93 changes: 93 additions & 0 deletions cache-maker/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
plugins {
kotlin("multiplatform")
}

kotlin {
wasmJs {
outputModuleName.set("stdlib")
binaries.executable().forEach {
it.linkTask.configure {
compilerOptions.freeCompilerArgs.add("-Xir-dce=false")
}
}
}

sourceSets {
wasmJsMain {
dependencies {
implementation(libs.bundles.compose)
implementation(libs.kotlinx.coroutines.core.compose.wasm)
}
}
}
}

val composeWasmStdlib: Provider<Directory> = layout.buildDirectory
.dir("compose-wasm-stdlib-output")
val composeWasmStdlibTypeInfo: Provider<RegularFile> = composeWasmStdlib
.map { it.file("stdlib.typeinfo.bin") }

val buildComposeWasmStdlibModule by tasks.registering(Exec::class) {

inputs.files(configurations.named("wasmJsRuntimeClasspath"))

workingDir = rootDir
executable = "${project.name}/docker-build-incremental-cache.sh"

val outputDir = composeWasmStdlib

inputs.file(layout.projectDirectory.file("Dockerfile"))
inputs.file(layout.projectDirectory.file("docker-build-incremental-cache.sh"))
outputs.dir(outputDir)

args(lambdaPrefix, outputDir.get().asFile.normalize().absolutePath)
}

val prepareTypeInfoIntoComposeWasmCache by tasks.registering(Sync::class) {
dependsOn(buildComposeWasmStdlibModule)
from(composeWasmStdlibTypeInfo)
into(cachesComposeWasmFolder)
}

val kotlinComposeWasmStdlibTypeInfo: Configuration by configurations.creating {
isTransitive = false
isCanBeResolved = false
isCanBeConsumed = true
attributes {
attribute(
Category.CATEGORY_ATTRIBUTE,
objects.categoryComposeCache
)
}
}

kotlinComposeWasmStdlibTypeInfo.outgoing.variants.create("stdlib") {
attributes {
attribute(
CacheAttribute.cacheAttribute,
CacheAttribute.FULL
)
}

artifact(composeWasmStdlib) {
builtBy(prepareTypeInfoIntoComposeWasmCache)
}
}

kotlinComposeWasmStdlibTypeInfo.outgoing.variants.create("typeinfo") {
attributes {
attribute(
CacheAttribute.cacheAttribute,
CacheAttribute.TYPEINFO
)
}

artifact(cachesComposeWasmFolder.file("stdlib.typeinfo.bin")) {
builtBy(prepareTypeInfoIntoComposeWasmCache)
}
}

// we don't need to build cache-maker
tasks.named("build") {
dependsOn.clear()
}
24 changes: 24 additions & 0 deletions cache-maker/docker-build-incremental-cache.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh

kotlinVersion=$(awk '{ if ($1 == "kotlinWasmStdlibCompiler") { gsub(/"/, "", $2); print $2; } }' FS=' = ' ./gradle/libs.versions.toml)

baseDir=$1
targetDir=$2

echo "Kotlin Version for the docker: $kotlinVersion"
echo "Base directory: $baseDir"
echo "Target directory: $targetDir"

image_tag=my-image-name:$(date +%s)

docker build . --file cache-maker/Dockerfile --tag $image_tag --build-arg BASE_DIR=$baseDir --build-arg KOTLIN_VERSION=$kotlinVersion

container=$(docker create $image_tag)

docker cp $container:$baseDir/cache-maker/build/compileSync/wasmJs/main/productionExecutable/kotlin/. $targetDir

docker start $container
docker stop $container
docker remove $container

docker rmi $image_tag
Loading
Loading