Unverified Commit cb5f4865 authored by Ivan Matkov's avatar Ivan Matkov Committed by GitHub

AGP 9 migration: upgrade wrappers to Gradle 9.5 and fix sample runtime flows (#1196)

- Upgrade Gradle wrappers to `9.5.0` across root and sample projects.
- Migrate Android build logic to AGP 9-compatible APIs (including KMP
Android integration updates).
- Fix `SkiaMultiplatformSample` iOS simulator run flow:
  - remove `applePlugin` usage
- replace invalid simctl spawn `--standalone <kexe>` with app bundle
install + launch
  - add `-Pskiko.iosSimulatorDevice=<device>` for simulator targeting
  - fix `IosClocks` renderApi reference typo
- Disable Kotlin JS Yarn lock generation in JS-enabled projects
parent d914f913
......@@ -12,3 +12,4 @@ deploy.bat
skiko/src/jvmMain/java
.cache
.clangd
**/kotlin-js-store/
......@@ -5,7 +5,7 @@ kotlinxBrowser = "0.5.0"
coroutines = "1.8.0"
jetbrainsRuntime-api = "1.5.0"
androidGradlePlugin = "8.2.2"
androidGradlePlugin = "9.0.0"
dokka = "1.9.10"
buildHelpers-publishing = "0.1.28"
......
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
......
import com.android.build.gradle.tasks.MergeSourceSetFolders
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
buildscript {
repositories {
google()
mavenCentral {
url = uri("https://cache-redirector.jetbrains.com/maven-central")
}
maven("https://redirector.kotlinlang.org/maven/compose-dev")
}
dependencies {
classpath("com.android.tools.build:gradle:8.9.0")
}
}
repositories {
mavenLocal()
......@@ -26,8 +11,7 @@ repositories {
}
plugins {
id("com.android.application") version "8.9.0"
kotlin("android") version "2.3.20"
id("com.android.application") version "9.0.0"
}
val skikoNativeX64 by configurations.creating
......@@ -38,12 +22,12 @@ val jniDir = "${projectDir.absolutePath}/src/main/jniLibs"
// TODO: filter .so files only.
val unzipTaskX64 = tasks.register("unzipNativeX64", Copy::class) {
destinationDir = file("$jniDir/x86_64")
from(skikoNativeX64.map { zipTree(it) })
from({ skikoNativeX64.files.map { zipTree(it) } })
}
val unzipTaskArm64 = tasks.register("unzipNativeArm64", Copy::class) {
destinationDir = file("$jniDir/arm64-v8a")
from(skikoNativeArm64.map { zipTree(it) })
from({ skikoNativeArm64.files.map { zipTree(it) } })
}
android {
......@@ -88,22 +72,19 @@ dependencies {
skikoNativeArm64("org.jetbrains.skiko:skiko-android-runtime-arm64:$version")
}
tasks.withType<KotlinJvmCompile>().configureEach {
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
dependsOn(unzipTaskX64)
dependsOn(unzipTaskArm64)
}
// SKIKO-934: we need to unpack these libraries before these are collected from android
// TODO the tasks we're actually targetting are mergeDebugJniLibFolders and mergeReleaseJniLibFolders,
// this adds unncessary dependencies
tasks.withType<MergeSourceSetFolders>()
.configureEach {
// SKIKO-934: we need to unpack these libraries before Android collects JNI libs.
tasks.withType<MergeSourceSetFolders>().configureEach {
if (name.endsWith("JniLibFolders")) {
dependsOn(unzipTaskX64)
dependsOn(unzipTaskArm64)
}
}
tasks.withType<Copy> {
// This line needs to properly merge MANIFEST files from jars into dex
......
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
......
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
import org.jetbrains.kotlin.gradle.plugin.mpp.*
buildscript {
......@@ -17,7 +19,6 @@ buildscript {
plugins {
kotlin("multiplatform")
id("org.jetbrains.gradle.apple.applePlugin") version "222.3345.143-0.16"
}
repositories {
......@@ -206,31 +207,74 @@ kotlin {
}
if (hostOs == "macos") {
project.tasks.register<Exec>("runIosSim") {
val device = "iPhone 11"
workingDir = project.buildDir
val linkExecutableTaskName = when (host) {
"macos-x64" -> "linkReleaseExecutableIosX64"
val iosSimDevice = providers.gradleProperty("skiko.iosSimulatorDevice").orElse("booted")
val iosSimAppName = "SkiaMultiplatformSample"
val iosSimBundleId = "org.jetbrains.skiko.sample"
val iosSimLinkExecutableTaskName = when (host) {
"macos-x64" -> throw GradleException("runIosSim is supported only on Apple Silicon hosts (iosSimulatorArm64 target)")
"macos-arm64" -> "linkReleaseExecutableIosSimulatorArm64"
else -> throw GradleException("Host OS is not supported")
}
val binTask = project.tasks.named(linkExecutableTaskName)
val packageIosSimApp = project.tasks.register("packageIosSimApp") {
val binTask = project.tasks.named(iosSimLinkExecutableTaskName)
dependsOn(binTask)
commandLine = listOf(
"xcrun",
"simctl",
"spawn",
"--standalone",
device
doLast {
val executable = fileTree(binTask.get().outputs.files.files.single()) { include("*.kexe") }
.single { it.name.endsWith(".kexe") }
val appDir = project.layout.buildDirectory.dir("iosSimulator/${iosSimAppName}.app").get().asFile
appDir.mkdirs()
val targetExecutable = appDir.resolve(iosSimAppName)
executable.copyTo(targetExecutable, overwrite = true)
targetExecutable.setExecutable(true)
appDir.resolve("PkgInfo").writeText("APPL????")
val plistTemplate = project.file("plists/Ios/Info.plist").readText()
appDir.resolve("Info.plist").writeText(
plistTemplate
.replace("$(DEVELOPMENT_LANGUAGE)", "en")
.replace("$(EXECUTABLE_NAME)", iosSimAppName)
.replace("$(PRODUCT_BUNDLE_IDENTIFIER)", iosSimBundleId)
.replace("$(PRODUCT_NAME)", iosSimAppName)
)
argumentProviders.add {
val out = fileTree(binTask.get().outputs.files.files.single()) { include("*.kexe") }
listOf(out.single { it.name.endsWith(".kexe") }.absolutePath)
}
}
project.tasks.register("runIosSim") {
dependsOn(packageIosSimApp)
doLast {
fun runCommand(command: List<String>, ignoreFailure: Boolean = false) {
val process = ProcessBuilder(command)
.directory(project.projectDir)
.inheritIO()
.start()
val exitCode = process.waitFor()
if (exitCode != 0 && !ignoreFailure) {
throw GradleException("Command failed ($exitCode): ${command.joinToString(" ")}")
}
}
val appDir = project.layout.buildDirectory.dir("iosSimulator/${iosSimAppName}.app").get().asFile.absolutePath
val device = iosSimDevice.get()
val launchTarget = if (device == "booted") "booted" else device
if (device != "booted") {
runCommand(listOf("xcrun", "simctl", "boot", device), ignoreFailure = true)
runCommand(listOf("xcrun", "simctl", "bootstatus", device, "-b"))
}
runCommand(listOf("xcrun", "simctl", "install", launchTarget, appDir))
runCommand(listOf("xcrun", "simctl", "launch", "--terminate-running-process", launchTarget, iosSimBundleId))
}
}
project.tasks.register<Exec>("runNative") {
workingDir = project.buildDir
val binTask = project.tasks.named("linkDebugExecutable${hostOs.capitalize()}${hostArch.capitalize()}")
val hostOsCap = hostOs.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
val hostArchCap = hostArch.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
val binTask = project.tasks.named("linkDebugExecutable${hostOsCap}${hostArchCap}")
dependsOn(binTask)
// Hacky approach.
commandLine = listOf("bash", "-c")
......@@ -302,7 +346,7 @@ if (hostOs == "macos") {
val targetBuildDir: String? = System.getenv("TARGET_BUILD_DIR")
val executablePath: String? = System.getenv("EXECUTABLE_PATH")
val buildType = System.getenv("CONFIGURATION")?.let {
org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.valueOf(it.toUpperCase())
org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.valueOf(it.uppercase())
} ?: org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG
val currentTarget = kotlin.targets[target.key] as org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
......@@ -341,16 +385,6 @@ if (hostOs == "macos") {
}
}
apple {
iosApp {
productName = "SkikoAppCode"
sceneDelegateClass = "SceneDelegate"
dependencies {
implementation(project(":"))
}
}
}
fun KotlinNativeTarget.configureToLaunchFromAppCode() {
binaries {
framework {
......
kotlin.code.style=official
org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=512m
kotlin.version=2.3.20
kotlin.js.yarn.lock.file=false
skiko.version=0.144.0
#skiko.composite.build=1
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
......
......@@ -5,7 +5,7 @@ import org.jetbrains.skiko.SkikoUIView
import platform.UIKit.NSLayoutConstraint
import platform.UIKit.UIViewController
class IosClocks(skiaLayer: SkiaLayer) : Clocks(layer::renderApi) {
class IosClocks(skiaLayer: SkiaLayer) : Clocks(skiaLayer::renderApi) {
val viewController: UIViewController
init {
val view = SkikoUIView(skiaLayer)
......
kotlin.code.style=official
kotlin.version=2.3.20
kotlin.js.yarn.lock.file=false
skiko.version=0.144.0
#skiko.composite.build=1
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
......
@file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalWasmDsl::class)
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.crypto.checksum.Checksum
import org.jetbrains.compose.internal.publishing.MavenCentralProperties
......@@ -11,12 +11,14 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.gradle.kotlin.dsl.withType
import tasks.configuration.*
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import dsl.SkikoDependencyScope
plugins {
kotlin("multiplatform")
id("com.android.kotlin.multiplatform.library") apply false
org.jetbrains.dokka
`maven-publish`
signing
......@@ -25,7 +27,7 @@ plugins {
}
if (supportAndroid) {
apply<LibraryPlugin>()
apply(plugin = "com.android.kotlin.multiplatform.library")
}
apply<WasmImportsGeneratorCompilerPluginSupportPlugin>()
......@@ -229,21 +231,16 @@ kotlin {
}
if (supportAndroid) {
androidTarget("android") {
publishLibraryVariants("release")
compilations.all {
compileTaskProvider.configure {
compilerOptions.jvmTarget.set(JvmTarget.JVM_11)
}
}
targets.withType<KotlinMultiplatformAndroidLibraryTarget>().configureEach {
namespace = "org.jetbrains.skiko"
compileSdk = 35
minSdk = 24
withJava()
withHostTest {}
// Keep the previously defined attribute that was used to distinguish JVM and android variant
attributes {
attributes.attribute(Attribute.of("ui", String::class.java), "android")
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
// TODO: seems incorrect.
generateVersion(OS.Android, Arch.Arm64, skiko, "release")
}
}
......@@ -356,6 +353,12 @@ kotlin {
implementation(libs.coroutines.android)
}
if (supportAndroid && supportAwt) {
sourceSets.named("androidMain") {
dependsOn(sourceSets.getByName("jvmMain"))
}
}
skikoProjectContext.jvmTestSourceSet?.dependencies {
implementation(libs.coroutines.test)
implementation(kotlin("test-junit"))
......@@ -398,39 +401,21 @@ if (supportAwt) {
if (supportAndroid) {
// Android configuration, when available
configure<LibraryExtension> {
compileSdk = 33
namespace = "org.jetbrains.skiko"
defaultConfig.minSdk = 24
defaultConfig.targetSdk = 24
defaultConfig.javaCompileOptions
compileOptions.sourceCompatibility = JavaVersion.VERSION_11
compileOptions.targetCompatibility = JavaVersion.VERSION_11
sourceSets.named("main") {
java.srcDirs("src/androidMain/java")
res.srcDirs("src/androidMain/res")
}
}
val os = OS.Android
val skikoAndroidJar by project.tasks.registering(Jar::class) {
kotlin.targets.getByName("android").generateVersion(os, Arch.Arm64, skiko)
val skikoAndroidArtifact by project.tasks.registering(Jar::class) {
archiveBaseName.set("skiko-android")
from(kotlin.androidTarget("android").compilations["release"].output.allOutputs)
from(kotlin.targets.getByName("android").compilations.getByName("main").output.allOutputs)
}
for (arch in arrayOf(Arch.X64, Arch.Arm64)) {
skikoProjectContext.createSkikoJvmJarTask(os, arch, skikoAndroidJar)
skikoProjectContext.createSkikoJvmJarTask(os, arch, skikoAndroidArtifact)
}
tasks.matching { name == "publishAndroidReleasePublicationToMavenLocal" }.configureEach {
// It needs to be compatible with Gradle 8.1
dependsOn(skikoAndroidJar)
tasks.withType<JavaCompile>().configureEach {
if (name.startsWith("compileAndroid") && name.endsWith("JavaWithJavac")) {
sourceCompatibility = JavaVersion.VERSION_11.toString()
targetCompatibility = JavaVersion.VERSION_11.toString()
}
tasks.matching { name == "generateMetadataFileForAndroidReleasePublication" }.configureEach {
// It needs to be compatible with Gradle 8.1
dependsOn(skikoAndroidJar)
}
}
......@@ -499,12 +484,6 @@ tasks.withType<AbstractTestTask> {
}
}
tasks.withType<JavaCompile> {
// Workaround to configure Java sources on Android (src/androidMain/java)
targetCompatibility = JavaVersion.VERSION_11.toString()
sourceCompatibility = JavaVersion.VERSION_11.toString()
}
project.tasks.withType<KotlinJsCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(listOf(
"-Xwasm-enable-array-range-checks", "-Xir-dce=true", "-Xskip-prerelease-check",
......
......@@ -4,11 +4,16 @@ import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import java.io.ByteArrayOutputStream
import java.io.File
import javax.inject.Inject
abstract class BuildLocalSkiaTask : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations
@get:Input
abstract val skiaVersion: Property<String>
......@@ -111,9 +116,10 @@ abstract class BuildLocalSkiaTask : DefaultTask() {
logger.lifecycle("Running: ${fullCommand.joinToString(" ")}")
val output = ByteArrayOutputStream()
val result = project.exec {
val result = execOperations.exec {
workingDir = skiaRepoRoot
commandLine = fullCommand
executable = fullCommand.first()
args(fullCommand.drop(1))
standardOutput = output
errorOutput = output
isIgnoreExitValue = true
......
......@@ -11,7 +11,7 @@ import java.io.Writer
internal fun Provider<out FileSystemLocation>.resolveToIoFile(relative: Provider<String>): File =
get().asFile.resolve(relative.get())
internal inline fun <reified T> Task.provider(noinline fn: () -> T): Provider<T> =
internal inline fun <reified T : Any> Task.provider(noinline fn: () -> T): Provider<T> =
project.provider(fn)
internal fun File.writeLines(lines: Collection<String>) {
......
......@@ -330,7 +330,7 @@ private fun SkikoPublishingContext.configureWebPublication() = publications {
private fun SkikoPublishingContext.configureAndroidPublication() = publications {
if (!project.supportAndroid) return@publications
pomNameForPublication["androidRelease"] = "${skikoArtifacts.displayName} Android Runtime"
pomNameForPublication["android"] = "${skikoArtifacts.displayName} Android Runtime"
}
private fun SkikoPublishingContext.configurePomNames() = publications {
......
......@@ -152,18 +152,18 @@ fun Project.configureSignAndPublishDependencies() {
}
if (supportAndroid) {
tasks.configureEach {
val signAndroid = "signAndroidReleasePublication"
val generateMetadata = "generateMetadataFileForAndroidReleasePublication"
val publishAndroid = "publishAndroidReleasePublicationTo"
val signAndroid = "signAndroidPublication"
val generateMetadata = "generateMetadataFileForAndroidPublication"
val publishAndroid = "publishAndroidPublicationTo"
val publishX64 = "publishSkikoJvmRuntimeAndroidX64PublicationTo"
val publishArm64 = "publishSkikoJvmRuntimeAndroidArm64PublicationTo"
val signX64 = "signSkikoJvmRuntimeAndroidX64Publication"
val signArm64 = "signSkikoJvmRuntimeAndroidArm64Publication"
val skikoAndroidJar = "skikoAndroidJar"
val skikoAndroidArtifact = "skikoAndroidArtifact"
when {
name.startsWith(signAndroid) || name.startsWith(generateMetadata) -> {
dependsOn(skikoAndroidJar)
dependsOn(skikoAndroidArtifact)
}
name.startsWith(publishAndroid) -> {
dependsOn(signX64, signArm64)
......
......@@ -3,6 +3,7 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=512m
kotlin.code.style=official
kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.mpp.enableCInteropCommonization=true
kotlin.js.yarn.lock.file=false
deploy.version=0.0.0
......
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
......
@file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalWasmDsl::class)
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
......@@ -11,10 +10,11 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
kotlin("multiplatform")
id("com.android.kotlin.multiplatform.library") apply false
}
if (supportAndroid) {
apply<LibraryPlugin>()
apply(plugin = "com.android.kotlin.multiplatform.library")
}
repositories {
......@@ -41,11 +41,13 @@ kotlin {
}
if (supportAndroid) {
androidTarget("android") {
compilations.all {
compileTaskProvider.configure {
compilerOptions.jvmTarget.set(JvmTarget.JVM_11)
}
targets.withType<KotlinMultiplatformAndroidLibraryTarget>().configureEach {
namespace = "org.jetbrains.skiko.testutils"
compileSdk = 35
minSdk = 24
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
}
......@@ -101,17 +103,6 @@ kotlin {
}
}
if (supportAndroid) {
configure<LibraryExtension> {
compileSdk = 33
namespace = "org.jetbrains.skiko.testutils"
defaultConfig.minSdk = 24
defaultConfig.targetSdk = 24
compileOptions.sourceCompatibility = JavaVersion.VERSION_11
compileOptions.targetCompatibility = JavaVersion.VERSION_11
}
}
tasks.withType<KotlinNativeCompile>().configureEach {
compilerOptions.freeCompilerArgs.add("-opt-in=kotlinx.cinterop.ExperimentalForeignApi")
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment