diff --git a/.github/actions/checkout-head/action.yml b/.github/actions/checkout-head/action.yml index f3632d42..1eec81aa 100644 --- a/.github/actions/checkout-head/action.yml +++ b/.github/actions/checkout-head/action.yml @@ -23,6 +23,11 @@ inputs: commands. The post-job step removes the PAT. default: ${{ github.token }} required: false + submodules: + description: > + Whether to checkout submodules. `true` to checkout submodules or `recursive` to recursively checkout submodules + default: 'false' + required: 'false' runs: using: composite @@ -44,7 +49,9 @@ runs: echo "ref=$ref" >> "$GITHUB_OUTPUT" else baseref="main" - if [ -n "$GITHUB_BASE_REF" ]; then + if [[ "$ref" == kn-* ]] && git ls-remote --exit-code --heads "https://github.com/$REPOSITORY.git" "kn-main"; then + baseref="kn-main" + elif [ -n "$GITHUB_BASE_REF" ]; then echo "attempting GH base ref: $GITHUB_BASE_REF" if git ls-remote --exit-code --heads "https://github.com/$REPOSITORY.git" "$GITHUB_BASE_REF"; then baseref="$GITHUB_BASE_REF" @@ -60,4 +67,5 @@ runs: path: ${{ inputs.path }} repository: ${{ inputs.repository }} ref: ${{ steps.repo.outputs.ref }} - token: ${{ inputs.token }} \ No newline at end of file + token: ${{ inputs.token }} + submodules: ${{ inputs.submodules }} diff --git a/build-plugins/build-support/build.gradle.kts b/build-plugins/build-support/build.gradle.kts index 1c9e3dd2..38ae0da9 100644 --- a/build-plugins/build-support/build.gradle.kts +++ b/build-plugins/build-support/build.gradle.kts @@ -29,7 +29,8 @@ dependencies { compileOnly(gradleApi()) implementation(libs.aws.sdk.s3) implementation(libs.aws.sdk.cloudwatch) - testImplementation(libs.junit.jupiter) + testImplementation(kotlin("test")) + testImplementation(libs.kotlinx.coroutines.test) } gradlePlugin { diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt index b3d617ac..853240d1 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt @@ -38,14 +38,13 @@ private object EnvironmentVariables { const val GPG_SECRET_KEY = "JRELEASER_GPG_SECRET_KEY" } -private val ALLOWED_PUBLICATION_NAMES = setOf( +internal val ALLOWED_PUBLICATION_NAMES = setOf( "common", "jvm", - "metadata", "kotlinMultiplatform", + "metadata", "bom", "versionCatalog", - "android", // aws-crt-kotlin "codegen", "codegen-testutils", @@ -57,6 +56,22 @@ private val ALLOWED_PUBLICATION_NAMES = setOf( "dynamodb-mapper-schema-generatorPluginMarkerMaven", ) +internal val KOTLIN_NATIVE_PUBLICATION_NAMES = setOf( + "iosArm64", + "iosX64", + "linuxArm64", + "linuxX64", + "macosArm64", + "macosX64", + "mingwX64", +) + +// TODO Refactor to support project names _or_ publication group names. +// aws-crt-kotlin is not published with a group name, so we need to check project names instead. +private val KOTLIN_NATIVE_PROJECT_NAMES = setOf( + "aws-crt-kotlin", +) + /** * Mark this project as excluded from publishing */ @@ -360,7 +375,7 @@ fun Project.configureJReleaser() { } } -private fun isAvailableForPublication(project: Project, publication: MavenPublication): Boolean { +internal fun isAvailableForPublication(project: Project, publication: MavenPublication): Boolean { var shouldPublish = true // Check SKIP_PUBLISH_PROP @@ -371,7 +386,12 @@ private fun isAvailableForPublication(project: Project, publication: MavenPublic shouldPublish = shouldPublish && (publishGroupName == null || publication.groupId.startsWith(publishGroupName)) // Validate publication name is allowed to be published - shouldPublish = shouldPublish && ALLOWED_PUBLICATION_NAMES.any { publication.name.equals(it, ignoreCase = true) } + shouldPublish = shouldPublish && + ( + ALLOWED_PUBLICATION_NAMES.any { publication.name.equals(it, ignoreCase = true) } || + // standard publication + (KOTLIN_NATIVE_PUBLICATION_NAMES.any { publication.name.equals(it, ignoreCase = true) } && KOTLIN_NATIVE_PROJECT_NAMES.any { project.name.equals(it, ignoreCase = true) }) // Kotlin/Native publication + ) return shouldPublish } diff --git a/build-plugins/build-support/src/test/kotlin/aws/sdk/kotlin/gradle/dsl/PublishTest.kt b/build-plugins/build-support/src/test/kotlin/aws/sdk/kotlin/gradle/dsl/PublishTest.kt new file mode 100644 index 00000000..45d7c7d4 --- /dev/null +++ b/build-plugins/build-support/src/test/kotlin/aws/sdk/kotlin/gradle/dsl/PublishTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.gradle.dsl + +import kotlinx.coroutines.test.runTest +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.testfixtures.ProjectBuilder +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class PublishTest { + @Test + fun `aws-crt-kotlin can publish Kotlin Native artifacts`() = runTest { + val project = ProjectBuilder.builder().withName("aws-crt-kotlin").build() + project.group = "aws.sdk.kotlin.crt" + project.version = "1.2.3" + + project.configurePublishing("aws-crt-kotlin") + + val publishing = project.extensions.getByType(PublishingExtension::class.java) + publishing.publications { + ALLOWED_PUBLICATION_NAMES.forEach { + val jvmRuntimePublication = create(it, MavenPublication::class.java).apply { + groupId = "aws.sdk.kotlin.crt" + version = "1.2.3" + artifactId = "aws-crt-kotlin" + } + assertTrue(isAvailableForPublication(project, jvmRuntimePublication)) + } + + KOTLIN_NATIVE_PUBLICATION_NAMES.forEach { + val nativeRuntimePublication = create(it, MavenPublication::class.java).apply { + groupId = "aws.sdk.kotlin.crt" + version = "1.2.3" + artifactId = "aws-crt-kotlin" + } + assertTrue(isAvailableForPublication(project, nativeRuntimePublication)) + } + } + } + + @Test + fun `aws-sdk-kotlin cannot publish Kotlin Native artifacts`() = runTest { + val project = ProjectBuilder.builder().withName("aws-sdk-kotlin").build() + project.group = "aws.sdk.kotlin" + project.version = "1.2.3" + + project.configurePublishing("aws-sdk-kotlin") + + val publishing = project.extensions.getByType(PublishingExtension::class.java) + publishing.publications { + ALLOWED_PUBLICATION_NAMES.forEach { + val jvmRuntimePublication = create(it, MavenPublication::class.java).apply { + groupId = "aws.sdk.kotlin" + version = "1.2.3" + artifactId = "aws-runtime" + } + assertTrue(isAvailableForPublication(project, jvmRuntimePublication)) + } + + KOTLIN_NATIVE_PUBLICATION_NAMES.forEach { + val nativeRuntimePublication = create(it, MavenPublication::class.java).apply { + groupId = "aws.sdk.kotlin" + version = "1.2.3" + artifactId = "aws-runtime" + } + assertFalse(isAvailableForPublication(project, nativeRuntimePublication)) + } + } + } + + @Test + fun `smithy-kotlin cannot publish Kotlin Native artifacts`() = runTest { + val project = ProjectBuilder.builder().withName("aws-smithy-kotlin").build() + project.group = "aws.smithy.kotlin" + project.version = "1.2.3" + + project.configurePublishing("smithy-kotlin", "smithy-lang") + + val publishing = project.extensions.getByType(PublishingExtension::class.java) + publishing.publications { + ALLOWED_PUBLICATION_NAMES.forEach { + val jvmRuntimePublication = create(it, MavenPublication::class.java).apply { + groupId = "aws.smithy.kotlin" + version = "1.2.3" + artifactId = "runtime" + } + assertTrue(isAvailableForPublication(project, jvmRuntimePublication)) + } + + KOTLIN_NATIVE_PUBLICATION_NAMES.forEach { + val nativeRuntimePublication = create(it, MavenPublication::class.java).apply { + groupId = "aws.smithy.kotlin" + version = "1.2.3" + artifactId = "runtime" + } + assertFalse(isAvailableForPublication(project, nativeRuntimePublication)) + } + } + } +} diff --git a/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/ConfigureIosSimulator.kt b/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/ConfigureIosSimulator.kt new file mode 100644 index 00000000..0b2582dc --- /dev/null +++ b/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/ConfigureIosSimulator.kt @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.gradle.kmp + +import org.gradle.api.Project +import org.gradle.api.tasks.Exec +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest +import org.jetbrains.kotlin.konan.target.HostManager + +private val DEFAULT_SIMULATOR_DEVICE_NAME = "iPhone 16" + +/** + * Disables standalone mode in simulator tests since it causes issues with TLS. + * This means we need to manage the simulator state ourselves (booting, shutting down). + * https://youtrack.jetbrains.com/issue/KT-38317 + */ +public fun Project.configureIosSimulatorTasks() { + if (!HostManager.hostIsMac) return + + val simulatorDeviceName = project.findProperty("iosSimulatorDevice") as? String ?: DEFAULT_SIMULATOR_DEVICE_NAME + val xcrun = "/usr/bin/xcrun" + + val bootTask = rootProject.tasks.maybeCreate("bootIosSimulatorDevice", Exec::class.java).apply { + isIgnoreExitValue = true + commandLine(xcrun, "simctl", "boot", simulatorDeviceName) + + doLast { + val result = executionResult.get() + val code = result.exitValue + if (code != 148 && code != 149) { // ignore "simulator already running" errors + result.assertNormalExitValue() + } + } + } + + val shutdownTask = rootProject.tasks.maybeCreate("shutdownIosSimulatorDevice", Exec::class.java).apply { + isIgnoreExitValue = true + commandLine(xcrun, "simctl", "shutdown", simulatorDeviceName) + + doLast { + val result = executionResult.get() + val code = result.exitValue + if (code != 148 && code != 149) { // ignore "simulator already shutdown" errors + result.assertNormalExitValue() + } + } + } + + allprojects { + val simulatorTasks = tasks.withType() + simulatorTasks.configureEach { + dependsOn(bootTask) + standalone.set(false) + device.set(simulatorDeviceName) + } + shutdownTask.mustRunAfter(simulatorTasks) + } +} diff --git a/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/ConfigureTargets.kt b/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/ConfigureTargets.kt index e450c4cd..785c3d45 100644 --- a/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/ConfigureTargets.kt +++ b/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/ConfigureTargets.kt @@ -4,12 +4,14 @@ */ package aws.sdk.kotlin.gradle.kmp +import aws.sdk.kotlin.gradle.util.prop import org.gradle.api.Project import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.named import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.konan.target.HostManager import java.io.File internal fun Project.tryGetClass(className: String): Class? { @@ -45,7 +47,7 @@ val Project.hasWindows: Boolean get() = hasNative || files.any { it.name == "win * Test if a project follows the convention and needs configured for KMP (used in handful of spots where we have a * subproject that is just a container for other projects but isn't a KMP project itself). */ -public val Project.needsKmpConfigured: Boolean get() = hasCommon || hasJvm || hasNative || hasJs +val Project.needsKmpConfigured: Boolean get() = hasCommon || hasJvm || hasNative || hasJs || hasJvmAndNative || hasDesktop || hasLinux || hasApple || hasWindows @OptIn(ExperimentalKotlinGradlePluginApi::class) fun Project.configureKmpTargets() { @@ -64,7 +66,8 @@ fun Project.configureKmpTargets() { return@withPlugin } - // configure the target hierarchy, this does not actually enable the targets, just their relationships + // extend the default KMP target hierarchy + // this does not actually enable the targets, just their relationships // see https://kotlinlang.org/docs/multiplatform-hierarchy.html#see-the-full-hierarchy-template kmpExt.applyDefaultHierarchyTemplate { if (hasJvmAndNative) { @@ -95,7 +98,7 @@ fun Project.configureKmpTargets() { } } - // enable targets + // enable the targets configureCommon() if (hasJvm && JVM_ENABLED) { @@ -103,16 +106,21 @@ fun Project.configureKmpTargets() { } // FIXME Configure JS - // FIXME Configure Apple - // FIXME Configure Windows - withIf(hasLinux && NATIVE_ENABLED, kmpExt) { - configureLinux() - } - - withIf(hasDesktop && NATIVE_ENABLED, kmpExt) { - configureLinux() - // FIXME Configure desktop + if (NATIVE_ENABLED) { + if ((hasApple || hasDesktop) && HostManager.hostIsMac) { + kmpExt.apply { configureApple() } + } + if ((hasWindows || hasDesktop) && HostManager.hostIsMingw) { + kmpExt.apply { configureWindows() } + } + if ((hasLinux || hasDesktop) && HostManager.hostIsLinux) { + if (group == "aws.sdk.kotlin.crt") { // TODO Remove special-casing once K/N is released across the entire project + kmpExt.apply { configureLinux() } + } else { + kmpExt.apply { configureDummyLinux() } + } + } } kmpExt.configureSourceSetsConvention() @@ -154,6 +162,17 @@ fun Project.configureJvm() { } fun Project.configureLinux() { + kotlin { + linuxX64() + linuxArm64() // FIXME - Okio missing arm64 target support. Added as experimental in https://square.github.io/okio/changelog/#version-360 + } +} + +/** + * Dummy configuration for projects which need to declare Linux but not actually use them. + * This is useful for projects that do not have any native targets but still need to be a KMP build (Dokka in particular). + */ +fun Project.configureDummyLinux() { kotlin { linuxX64 { // FIXME enable tests once the target is fully implemented @@ -161,8 +180,28 @@ fun Project.configureLinux() { enabled = false } } - // FIXME - Okio missing arm64 target support - // linuxArm64() + } +} + +fun Project.configureApple() { + kotlin { + configureMacos() + iosSimulatorArm64() + iosArm64() + iosX64() + } +} + +fun Project.configureMacos() { + kotlin { + macosX64() + macosArm64() + } +} + +fun Project.configureWindows() { + kotlin { + mingwX64() } } @@ -179,8 +218,5 @@ fun KotlinMultiplatformExtension.configureSourceSetsConvention() { } } -internal inline fun withIf(condition: Boolean, receiver: T, block: T.() -> Unit) { - if (condition) { - receiver.block() - } -} +val Project.JVM_ENABLED get() = prop("aws.kotlin.jvm")?.let { it == "true" } ?: true +val Project.NATIVE_ENABLED get() = prop("aws.kotlin.native")?.let { it == "true" } ?: true diff --git a/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/IdeUtils.kt b/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/IdeUtils.kt deleted file mode 100644 index 8ab7cf86..00000000 --- a/build-plugins/kmp-conventions/src/main/kotlin/aws/sdk/kotlin/gradle/kmp/IdeUtils.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.gradle.kmp - -import aws.sdk.kotlin.gradle.util.prop -import org.gradle.api.Project -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import org.jetbrains.kotlin.konan.target.HostManager -import org.jetbrains.kotlin.konan.target.KonanTarget -import java.util.* - -/** - * Whether Intellij is active or not - */ -val IDEA_ACTIVE = System.getProperty("idea.active") == "true" - -val OS_NAME = System.getProperty("os.name").lowercase() - -val HOST_NAME = when { - OS_NAME.startsWith("linux") -> "linux" - OS_NAME.startsWith("windows") -> "windows" - OS_NAME.startsWith("mac") -> "macos" - else -> error("Unknown os name `$OS_NAME`") -} - -val Project.JVM_ENABLED get() = prop("aws.kotlin.jvm")?.let { it == "true" } ?: true -val Project.NATIVE_ENABLED get() = prop("aws.kotlin.native")?.let { it == "true" } ?: true - -/** - * Scope down the native target enabled when working in intellij - */ -val KotlinMultiplatformExtension.ideaTarget: KotlinNativeTarget - get() = when (HostManager.host) { - is KonanTarget.LINUX_X64 -> linuxX64() - is KonanTarget.LINUX_ARM64 -> linuxArm64() - is KonanTarget.MACOS_X64 -> macosX64() - is KonanTarget.MACOS_ARM64 -> macosArm64() - is KonanTarget.MINGW_X64 -> mingwX64() - else -> error("Unsupported target ${HostManager.host}") - } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6fdad8df..fb03bcf6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ publish-plugin-version = "1.3.1" smithy-version = "1.60.2" smithy-gradle-plugin-version = "1.3.0" junit-version = "5.10.1" +coroutines-version = "1.10.2" [libraries] aws-sdk-cloudwatch = { module = "aws.sdk.kotlin:cloudwatch", version.ref = "aws-sdk-version" } @@ -19,8 +20,8 @@ nexus-publish-plugin = { module = "io.github.gradle-nexus:publish-plugin", versi jreleaser-plugin = { module = "org.jreleaser:jreleaser-gradle-plugin", version.ref = "jreleaser-plugin-version" } smithy-model = { module = "software.amazon.smithy:smithy-model", version.ref = "smithy-version" } smithy-gradle-base-plugin = { module = "software.amazon.smithy.gradle:smithy-base", version.ref = "smithy-gradle-plugin-version" } - junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-version" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines-version" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" }