diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt index e24ffa39a5a..5bb0c9fe4e4 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt @@ -1,42 +1,38 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.gradle.native -import org.jetbrains.kotlin.gradle.* -import org.jetbrains.kotlin.gradle.embedProject +import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.tasks.FrameworkLayout -import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl +import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.* -import org.jetbrains.kotlin.konan.target.HostManager -import org.junit.Assume -import org.junit.BeforeClass -import org.junit.Test -import java.io.File +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.condition.OS import java.util.* +import kotlin.io.path.absolutePathString +import kotlin.io.path.appendText import kotlin.test.assertTrue -class FatFrameworkIT : BaseGradleIT() { +@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) +@DisplayName("Tests for K/N builds with Fat Framework") +@NativeGradlePluginTests +class FatFrameworkIT : KGPBaseTest() { - override val defaultGradleVersion: GradleVersionRequired - get() = GradleVersionRequired.FOR_MPP_SUPPORT - - @Test - fun smokeIos() { - transformProjectWithPluginsDsl( - "smoke", - directoryPrefix = "native-fat-framework" - ).build("fat") { + @DisplayName("Fat framework for iOS smoke test") + @GradleTest + fun smokeIos(gradleVersion: GradleVersion) { + nativeProject("native-fat-framework/smoke", gradleVersion) { checkSmokeBuild( archs = listOf("x64", "arm64", "arm32"), targetPrefix = "ios", expectedPlistPlatform = "iPhoneOS" ) - val binary = fileInWorkingDir("build/fat-framework/smoke.framework/smoke") - with(runProcess(listOf("file", binary.absolutePath), project.projectDir)) { + val binary = projectPath.resolve("build/fat-framework/smoke.framework/smoke").absolutePathString() + assertProcessRunResult(runProcess(listOf("file", binary), projectPath.toFile())) { assertTrue(isSuccessful) assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex())) assertTrue(output.contains("\\(for architecture armv7\\):\\s+Mach-O dynamically linked shared library arm_v7".toRegex())) @@ -45,216 +41,198 @@ class FatFrameworkIT : BaseGradleIT() { } } - @Test - fun smokeWatchos() { - with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - - gradleBuildScript().modify { + @DisplayName("Fat framework for watchOS smoke test") + @GradleTest + fun smokeWatchos(gradleVersion: GradleVersion) { + nativeProject( + "native-fat-framework/smoke", + gradleVersion, + // TODO(d.krasnov): return default statisticsForceValidation, when KT-58104 will be fixed + buildOptions = defaultBuildOptions.copy(statisticsForceValidation = false) + ) { + buildGradleKts.modify { it.checkedReplace("iosArm32()", "watchosArm32()") .checkedReplace("iosArm64()", "watchosArm64(); watchosDeviceArm64()") .checkedReplace("iosX64()", "watchosX64()") } - - build("fat") { - checkSmokeBuild( - archs = listOf("x64", "arm64", "arm32", "deviceArm64"), - targetPrefix = "watchos", - expectedPlistPlatform = "WatchOS" - ) - - val binary = fileInWorkingDir("build/fat-framework/smoke.framework/smoke") - with(runProcess(listOf("file", binary.absolutePath), projectDir)) { - assertTrue(isSuccessful) - assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex())) - assertTrue(output.contains("\\(for architecture armv7k\\):\\s+Mach-O dynamically linked shared library arm_v7k".toRegex())) - assertTrue(output.contains("\\(for architecture arm64_32\\):\\s+Mach-O dynamically linked shared library arm64_32_v8".toRegex())) - assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex())) - } + checkSmokeBuild( + archs = listOf("x64", "arm64", "arm32", "deviceArm64"), + targetPrefix = "watchos", + expectedPlistPlatform = "WatchOS" + ) + val binary = projectPath.resolve("build/fat-framework/smoke.framework/smoke").absolutePathString() + assertProcessRunResult(runProcess(listOf("file", binary), projectPath.toFile())) { + assertTrue(isSuccessful) + assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex())) + assertTrue(output.contains("\\(for architecture armv7k\\):\\s+Mach-O dynamically linked shared library arm_v7k".toRegex())) + assertTrue(output.contains("\\(for architecture arm64_32\\):\\s+Mach-O dynamically linked shared library arm64_32_v8".toRegex())) + assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex())) } } } - @Test - fun smokeMacos() { - with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - - gradleBuildScript().modify { + @DisplayName("Fat framework with MacOS smoke test") + @GradleTest + fun smokeMacos(gradleVersion: GradleVersion) { + nativeProject( + "native-fat-framework/smoke", + gradleVersion + ) { + buildGradleKts.modify { it.checkedReplace("iosArm32()", "") .checkedReplace("iosArm64()", "macosArm64()") .checkedReplace("iosX64()", "macosX64()") } - - build("fat") { - checkSmokeBuild( - archs = listOf("x64", "arm64"), - targetPrefix = "macos", - expectedPlistPlatform = "MacOSX", - true - ) - - val binary = fileInWorkingDir("build/fat-framework/smoke.framework/Versions/A/smoke") - with(runProcess(listOf("file", binary.absolutePath), projectDir)) { - assertTrue(isSuccessful) - assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex())) - assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex())) - } + checkSmokeBuild( + archs = listOf("x64", "arm64"), + targetPrefix = "macos", + expectedPlistPlatform = "MacOSX", + true + ) + val binary = projectPath.resolve("build/fat-framework/smoke.framework/Versions/A/smoke").absolutePathString() + assertProcessRunResult(runProcess(listOf("file", binary), projectPath.toFile())) { + assertTrue(isSuccessful) + assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex())) + assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex())) } } } - private fun CompiledProject.checkSmokeBuild( + private fun TestProject.checkSmokeBuild( archs: List, targetPrefix: String, expectedPlistPlatform: String, isMacosFramework: Boolean = false ) { - assertSuccessful() - val linkTasks = archs.map { - ":linkDebugFramework${targetPrefix.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}${ - it.replaceFirstChar { - if (it.isLowerCase()) it.titlecase( - Locale.getDefault() - ) else it.toString() - } - }" } + build("fat") { + val linkTasks = archs.map { + ":linkDebugFramework${targetPrefix.capitalize()}${it.capitalize()}" + } - assertTasksExecuted(linkTasks) - assertTasksExecuted(":fat") + assertTasksExecuted(linkTasks) + assertTasksExecuted(":fat") - val frameworkLayout = FrameworkLayout(fileInWorkingDir("build/fat-framework/smoke.framework"), isMacosFramework) + val frameworkLayout = FrameworkLayout(projectPath.resolve("build/fat-framework/smoke.framework").toFile(), isMacosFramework) - fun File.projectRelative() = relativeTo(File(workingDir, project.projectName)).path + assertFileExists(frameworkLayout.binary.toPath()) + assertFileExists(frameworkLayout.header.toPath()) + assertFileExists(frameworkLayout.dSYM.binary.toPath()) - assertFileExists(frameworkLayout.binary.projectRelative()) - assertFileExists(frameworkLayout.header.projectRelative()) - assertFileExists(frameworkLayout.dSYM.binary.projectRelative()) + assertFileContains( + frameworkLayout.header.toPath(), + "+ (int32_t)foo __attribute__((swift_name(\"foo()\")));" + ) - val headerContent = frameworkLayout.header.readText() - assertTrue( - headerContent.contains("+ (int32_t)foo __attribute__((swift_name(\"foo()\")));"), - "Unexpected header content:\n$headerContent" - ) - - val plistContent = frameworkLayout.infoPlist - .readLines() - .joinToString(separator = "\n") { it.trim() } - - assertTrue( - plistContent.contains( + assertFileContains( + frameworkLayout.infoPlist.toPath(), """ - CFBundleSupportedPlatforms - - $expectedPlistPlatform - - """.trimIndent() - ), - "Unexpected Info.plist content:\n$plistContent" - ) + | CFBundleSupportedPlatforms + | + | $expectedPlistPlatform + | + """.trimMargin() + .replace(Regex(" {4}"), "\t") + ) + } } - @Test - fun testDuplicatedArchitecture() { - with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - gradleBuildScript().modify { - it + """ - val anotherDeviceTarget = kotlin.iosArm64("another") { - binaries.framework("DEBUG") - } - fat.from(anotherDeviceTarget.binaries.getFramework("DEBUG")) - """.trimIndent() + @DisplayName("Fat framework with duplicated iOS Arm 64") + @GradleTest + fun testDuplicatedArchitecture(gradleVersion: GradleVersion) { + nativeProject("native-fat-framework/smoke", gradleVersion) { + buildGradleKts.modify { + it + + //language=kotlin + """ + val anotherDeviceTarget = kotlin.iosArm64("another") { + binaries.framework("DEBUG") + } + fat.from(anotherDeviceTarget.binaries.getFramework("DEBUG")) + """.trimIndent() } - build("fat") { - assertFailed() - assertContains("This fat framework already has a binary for architecture `arm64`") + buildAndFail("fat") { + assertOutputContains("This fat framework already has a binary for architecture `arm64`") } } } - @Test - fun testIncorrectFamily() { - with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - gradleBuildScript().modify { - it + """ - val macos = kotlin.macosX64 { - binaries.framework("DEBUG") - } - fat.from(macos.binaries.getFramework("DEBUG")) - """.trimIndent() + @DisplayName("Fat framework with incorrect 'osx' family") + @GradleTest + fun testIncorrectFamily(gradleVersion: GradleVersion) { + nativeProject("native-fat-framework/smoke", gradleVersion) { + buildGradleKts.modify { + it + + //language=kotlin + """ + val macos = kotlin.macosX64 { + binaries.framework("DEBUG") + } + fat.from(macos.binaries.getFramework("DEBUG")) + """.trimIndent() } - build("fat") { - assertFailed() - assertContains("Cannot add a binary with platform family 'osx' to the fat framework") + buildAndFail("fat") { + assertOutputContains("Cannot add a binary with platform family 'osx' to the fat framework") } } } - @Test - fun testCustomName() { - with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - gradleBuildScript().modify { - it.addBeforeSubstring("baseName = \"custom\"\n","from(frameworksToMerge)") + @DisplayName("Fat framework with custom base name") + @GradleTest + fun testCustomName(gradleVersion: GradleVersion) { + nativeProject("native-fat-framework/smoke", gradleVersion) { + buildGradleKts.modify { + it.addBeforeSubstring("baseName = \"custom\"\n", "from(frameworksToMerge)") } build("fat") { - val binary = fileInWorkingDir("build/fat-framework/custom.framework/custom") - with(runProcess(listOf("otool", "-D", binary.absolutePath), project.projectDir)) { - assertSuccessful() + val binary = projectPath.resolve("build/fat-framework/custom.framework/custom").absolutePathString() + assertProcessRunResult(runProcess(listOf("otool", "-D", binary), projectPath.toFile())) { + assertTrue(isSuccessful) assertTrue { output.lines().any { it.contains("@rpath/custom.framework/custom") } } } } } } - @Test - fun testDifferentTypes() { - with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - gradleBuildScript().modify { + @DisplayName("Fat framework with different input types") + @GradleTest + fun testDifferentTypes(gradleVersion: GradleVersion) { + nativeProject("native-fat-framework/smoke", gradleVersion) { + buildGradleKts.modify { it.checkedReplace("iosArm32()", "iosArm32{binaries.framework {isStatic = true}}") .checkedReplace("iosArm64()", "iosArm64{binaries.framework {isStatic = false}}") .checkedReplace("iosX64()", "iosX64{binaries.framework {isStatic = false}}") .addBeforeSubstring("//", "binaries.framework(listOf(DEBUG))") } - build("fat") { - assertFailed() - assertContains("All input frameworks must be either static or dynamic") + buildAndFail("fat") { + assertOutputContains("All input frameworks must be either static or dynamic") } } } - @Test - fun testAllStatic() { - with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - gradleBuildScript().modify { + @DisplayName("Fat framework with all static inputs") + @GradleTest + fun testAllStatic(gradleVersion: GradleVersion) { + nativeProject("native-fat-framework/smoke", gradleVersion) { + buildGradleKts.modify { it.checkedReplace("iosArm32()", "iosArm32{binaries.framework {isStatic = true}}") .checkedReplace("iosArm64()", "iosArm64{binaries.framework {isStatic = true}}") .checkedReplace("iosX64()", "iosX64{binaries.framework {isStatic = true}}") .addBeforeSubstring("//", "binaries.framework(listOf(DEBUG))") } - build("fat") { - assertSuccessful() - } + build("fat") } } - /** - * Test that the configurations exposing the frameworks don't interfere with variant-aware dependency resolution - */ - @Test - fun testDependencyResolution() = with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { - setupWorkingDir() - val nestedProjectName = "nested" - embedProject(this, nestedProjectName) - gradleBuildScript(nestedProjectName).modify { it.replace(".version(\"$KOTLIN_VERSION\")", "") } - gradleBuildScript().appendText("dependencies { \"commonMainImplementation\"(project(\":$nestedProjectName\")) }") - testResolveAllConfigurations() - } - - companion object { - @BeforeClass - @JvmStatic - fun assumeItsMac() { - Assume.assumeTrue(HostManager.hostIsMac) + @DisplayName("Test that the configurations exposing the frameworks don't interfere with variant-aware dependency resolution") + @GradleTest + fun testDependencyResolution(gradleVersion: GradleVersion) { + nativeProject("native-fat-framework/smoke", gradleVersion) { + val nestedProjectName = "nested" + includeOtherProjectAsSubmodule("smoke", "native-fat-framework", nestedProjectName, true) + buildGradleKts.appendText("dependencies { \"commonMainImplementation\"(project(\":$nestedProjectName\")) }") + testResolveAllConfigurations() } } -} +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt index 86a0a9c6236..6229bad1b2b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt @@ -14,13 +14,15 @@ import org.jetbrains.kotlin.gradle.BaseGradleIT.Companion.acceptAndroidSdkLicens import org.jetbrains.kotlin.gradle.model.ModelContainer import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction import org.jetbrains.kotlin.gradle.native.disableKotlinNativeCaches -import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.report.BuildReportType +import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.presetName import org.jetbrains.kotlin.test.util.KtTestUtil import java.io.File -import java.nio.file.* +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths import kotlin.io.path.* import kotlin.test.assertTrue @@ -316,7 +318,7 @@ class TestProject( fun subProject(name: String) = GradleProject(name, projectPath.resolve(name)) fun addKotlinCompilerArgumentsPlugin() { - if (buildOptions.languageVersion != null || buildOptions.languageApiVersion != null ) { + if (buildOptions.languageVersion != null || buildOptions.languageApiVersion != null) { projectPath.toFile().walkTopDown().forEach { file -> when { file.name.equals("build.gradle") -> file.modify { @@ -336,17 +338,28 @@ class TestProject( } } + /** + * Includes another project as a submodule in the current project. + * @param otherProjectName The name of the other project to include as a submodule. + * @param pathPrefix An optional prefix to prepend to the submodule's path. Defaults to an empty string. + * @param newSubmoduleName An optional new name for the submodule. Defaults to the otherProjectName. + * @param isKts Whether to update a .kts settings file instead of a .gradle settings file. Defaults to false. + */ fun includeOtherProjectAsSubmodule( otherProjectName: String, - pathPrefix: String + pathPrefix: String, + newSubmoduleName: String = otherProjectName, + isKts: Boolean = false ) { val otherProjectPath = "$pathPrefix/$otherProjectName".testProjectPath - otherProjectPath.copyRecursively(projectPath.resolve(otherProjectName)) + otherProjectPath.copyRecursively(projectPath.resolve(newSubmoduleName)) - settingsGradle.append( + val gradleSettingToUpdate = if (isKts) settingsGradleKts else settingsGradle + + gradleSettingToUpdate.append( """ - - include ':$otherProjectName' + + include(":$newSubmoduleName") """.trimIndent() ) } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt index 7429166762b..0dc43d7871d 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt @@ -60,3 +60,34 @@ fun createGradleCommand(wrapperDir: File, tailParameters: List): List Unit) { + try { + result.assertions() + } catch (e: AssertionError) { + println( + """ + |Process info: + |####################### + |$result + |####################### + | + |Process output: + |####################### + |${result.output} + |####################### + | + """.trimMargin() + ) + throw e + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-fat-framework/smoke/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-fat-framework/smoke/build.gradle.kts index 3409808e8a9..2397b2b8347 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-fat-framework/smoke/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-fat-framework/smoke/build.gradle.kts @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask plugins { - id("org.jetbrains.kotlin.multiplatform").version("") + id("org.jetbrains.kotlin.multiplatform") } repositories {