From 375ff5ed2c9f05fc1af856991de3bcc06922f8f4 Mon Sep 17 00:00:00 2001 From: Dmitrii Krasnov Date: Tue, 9 May 2023 07:17:14 +0000 Subject: [PATCH] migrated AppleFrameworkIT to junit5 and gradle TestKit #KT-51553 In Progress Merge-request: KT-MR-9856 Merged-by: Dmitrii Krasnov --- .../gradle/testbase/GradleAndroidTest.java | 1 + .../gradle/testbase/GradleWithJdkTest.java | 1 + .../kotlin/gradle/native/AppleFrameworkIT.kt | 697 +++++++++++------- .../kotlin/gradle/testbase/fileAssertions.kt | 30 +- .../kotlin/gradle/testbase/tasksAssertions.kt | 7 +- .../kotlin/gradle/testbase/testDsl.kt | 28 +- 6 files changed, 483 insertions(+), 281 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleAndroidTest.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleAndroidTest.java index a24f86e6929..8766a7f0ea8 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleAndroidTest.java +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleAndroidTest.java @@ -18,6 +18,7 @@ import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @GradleTestVersions +@OsCondition @AndroidTestVersions @ParameterizedTest(name = "AGP {1} with {0}: {displayName}") @ArgumentsSource(GradleAndAgpArgumentsProvider.class) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleWithJdkTest.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleWithJdkTest.java index b0ea2e877d9..f50a60c70f6 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleWithJdkTest.java +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/testbase/GradleWithJdkTest.java @@ -18,6 +18,7 @@ import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @GradleTestVersions +@OsCondition @JdkVersions @ParameterizedTest(name = "{1} with {0}: {displayName}") @ArgumentsSource(GradleAndJdkArgumentsProvider.class) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt index d816cea400e..e9f90c9576e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt @@ -1,234 +1,336 @@ /* - * Copyright 2010-2021 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.gradle.api.logging.LogLevel +import org.gradle.api.JavaVersion +import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.gradle.* -import org.jetbrains.kotlin.gradle.testbase.TestVersions -import org.jetbrains.kotlin.gradle.util.AGPVersion -import org.jetbrains.kotlin.gradle.util.modify -import org.jetbrains.kotlin.konan.target.HostManager -import org.jetbrains.kotlin.test.util.KtTestUtil -import org.junit.Assume -import org.junit.BeforeClass -import kotlin.test.Test +import org.jetbrains.kotlin.gradle.testbase.* +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.condition.OS +import kotlin.io.path.absolutePathString +import kotlin.io.path.appendText -class AppleFrameworkIT : BaseGradleIT() { - companion object { - @BeforeClass - @JvmStatic - fun assumeItsMac() { - Assume.assumeTrue(HostManager.hostIsMac) - } - } +@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) +@DisplayName("Tests for K/N with Apple Framework") +@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_70) +@NativeGradlePluginTests +class AppleFrameworkIT : KGPBaseTest() { - override val defaultGradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT - override fun defaultBuildOptions() = super.defaultBuildOptions().copy( - androidHome = KtTestUtil.findAndroidSdk(), - androidGradlePluginVersion = AGPVersion.v7_4_0, - javaHome = KtTestUtil.getJdk17Home() - ) + // TODO(Dmitrii Krasnov): remove it, when KT-58104 will be fixed + override val defaultBuildOptions = super.defaultBuildOptions.copy(statisticsForceValidation = false) - @Test - fun `assemble AppleFrameworkForXcode tasks for IosArm64`() { - with(Project("sharedAppleFramework")) { - val options: BuildOptions = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( + @DisplayName("Assembling AppleFrameworkForXcode tasks for IosArm64") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) + fun shouldAssembleAppleFrameworkForXcodeForIosArm64( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildJdk = jdkProvider.location, + buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ), + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( "CONFIGURATION" to "debug", "SDK_NAME" to "iphoneos123", "ARCHS" to "arm64", "TARGET_BUILD_DIR" to "no use", "FRAMEWORKS_FOLDER_PATH" to "no use" - ), + ) ) - build("assembleDebugAppleFrameworkForXcodeIosArm64", options = options) { - assertSuccessful() + + build( + "assembleDebugAppleFrameworkForXcodeIosArm64", + environmentVariables = environmentVariables, + ) { assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeIosArm64") - assertFileExists("/shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework") - assertFileExists("/shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework.dSYM") + assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework") + assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework.dSYM") } - build("assembleCustomDebugAppleFrameworkForXcodeIosArm64", options = options) { - assertSuccessful() + build( + "assembleCustomDebugAppleFrameworkForXcodeIosArm64", + environmentVariables = environmentVariables + ) { assertTasksExecuted(":shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64") - assertFileExists("/shared/build/xcode-frameworks/debug/iphoneos123/lib.framework") - assertFileExists("/shared/build/xcode-frameworks/debug/iphoneos123/lib.framework.dSYM") + assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/lib.framework") + assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/lib.framework.dSYM") } } } - @Test - fun `assemble fat AppleFrameworkForXcode tasks for Arm64 and X64 simulators`() { - with(Project("sharedAppleFramework")) { - val options: BuildOptions = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "Release", - "SDK_NAME" to "iphonesimulator", - "ARCHS" to "arm64 x86_64", - "TARGET_BUILD_DIR" to "no use", - "FRAMEWORKS_FOLDER_PATH" to "no use" - ), + @DisplayName("Assembling fat AppleFrameworkForXcode tasks for Arm64 and X64 simulators") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) + fun shouldAssembleAppleFrameworkForXcodeForArm64AndX64Simulators( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildJdk = jdkProvider.location, + buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion ) - build("assembleReleaseAppleFrameworkForXcode", options = options) { - assertSuccessful() + ) { + val environmentVariables = mapOf( + "CONFIGURATION" to "Release", + "SDK_NAME" to "iphonesimulator", + "ARCHS" to "arm64 x86_64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + build("assembleReleaseAppleFrameworkForXcode", environmentVariables = EnvironmentalVariables(environmentVariables)) { assertTasksExecuted(":shared:linkReleaseFrameworkIosSimulatorArm64") assertTasksExecuted(":shared:linkReleaseFrameworkIosX64") assertTasksExecuted(":shared:assembleReleaseAppleFrameworkForXcode") - assertFileExists("/shared/build/xcode-frameworks/Release/iphonesimulator/sdk.framework") - assertFileExists("/shared/build/xcode-frameworks/Release/iphonesimulator/sdk.framework.dSYM") + assertDirectoryInProjectExists("shared/build/xcode-frameworks/Release/iphonesimulator/sdk.framework") + assertDirectoryInProjectExists("shared/build/xcode-frameworks/Release/iphonesimulator/sdk.framework.dSYM") } } } - @Test - fun `check that macOS framework has symlinks`() { - with(Project("sharedAppleFramework")) { - val options: BuildOptions = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "debug", - "SDK_NAME" to "macosx", - "ARCHS" to "x86_64", - "EXPANDED_CODE_SIGN_IDENTITY" to "-", - "TARGET_BUILD_DIR" to workingDir.absolutePath, - "FRAMEWORKS_FOLDER_PATH" to "${projectName}/build/xcode-derived" - ) + @DisplayName("MacOS framework has symlinks") + @OptIn(EnvironmentalVariablesOverride::class) + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + @GradleAndroidTest + fun shouldCheckThatMacOSFrameworkHasSymlinks( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildJdk = jdkProvider.location, + buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion ) - build(":shared:embedAndSignAppleFrameworkForXcode", options = options) { - assertSuccessful() + ) { + val environmentVariables = mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "macosx", + "ARCHS" to "x86_64", + "EXPANDED_CODE_SIGN_IDENTITY" to "-", + "TARGET_BUILD_DIR" to projectPath.absolutePathString(), + "FRAMEWORKS_FOLDER_PATH" to "build/xcode-derived" + ) + build(":shared:embedAndSignAppleFrameworkForXcode", environmentVariables = EnvironmentalVariables(environmentVariables)) { assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeMacosX64") - // Verify symlinks in gradle build folder - assertFileExists("/shared/build/xcode-frameworks/debug/macosx/sdk.framework/Headers") - assertFileIsSymlink("/shared/build/xcode-frameworks/debug/macosx/sdk.framework/Headers") - // Verify symlinks in xcode derived folder - assertFileExists("/build/xcode-derived/sdk.framework/Headers") - assertFileIsSymlink("/build/xcode-derived/sdk.framework/Headers") + assertSymlinkInProjectExists("shared/build/xcode-frameworks/debug/macosx/sdk.framework/Headers") + assertSymlinkInProjectExists("build/xcode-derived/sdk.framework/Headers") } } } - @Test - fun `check embedAndSignAppleFrameworkForXcode fail`() { - with(Project("sharedAppleFramework")) { - build(":shared:embedAndSignAppleFrameworkForXcode") { - assertTasksFailed(":shared:embedAndSignAppleFrameworkForXcode") - assertContains("Please run the embedAndSignAppleFrameworkForXcode task from Xcode") + @DisplayName("embedAndSignAppleFrameworkForXcode fail") + @GradleAndroidTest + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + fun shouldFailWithExecutingEmbedAndSignAppleFrameworkForXcode( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + nativeProject("sharedAppleFramework", gradleVersion, buildJdk = jdkProvider.location) { + buildAndFail( + ":shared:embedAndSignAppleFrameworkForXcode", + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion) + ) { + assertOutputContains("Please run the embedAndSignAppleFrameworkForXcode task from Xcode") } } } - @Test - fun `check all registered tasks with Xcode environment for Debug IosArm64 configuration`() { - with(Project("sharedAppleFramework")) { - val currentGradleVersion = chooseWrapperVersionOrFinishTest() - val options: BuildOptions = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "Debug", - "SDK_NAME" to "iphoneos", - "ARCHS" to "arm64", - "EXPANDED_CODE_SIGN_IDENTITY" to "-", - "TARGET_BUILD_DIR" to "testBuildDir", - "FRAMEWORKS_FOLDER_PATH" to "testFrameworksDir" - ) - ).suppressDeprecationWarningsOn( - "AGP uses deprecated IncrementalTaskInputs (Gradle 7.5)" - ) { options -> - GradleVersion.version(currentGradleVersion) >= GradleVersion.version(TestVersions.Gradle.G_7_5) && options.safeAndroidGradlePluginVersion < AGPVersion.v7_3_0 - } - build("tasks", options = options) { - assertSuccessful() - assertTasksRegistered( - ":shared:assembleDebugAppleFrameworkForXcodeIosArm64", - ":shared:embedAndSignAppleFrameworkForXcode", - ":shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64", - ":shared:embedAndSignCustomAppleFrameworkForXcode", - ":shared:assembleDebugAppleFrameworkForXcodeIosX64", - ":shared:assembleReleaseAppleFrameworkForXcodeIosX64", - ":shared:assembleReleaseAppleFrameworkForXcodeIosArm64", - ":shared:assembleCustomDebugAppleFrameworkForXcodeIosX64", - ":shared:assembleCustomReleaseAppleFrameworkForXcodeIosX64", - ":shared:assembleCustomReleaseAppleFrameworkForXcodeIosArm64" - ) - } + @DisplayName("Registered tasks with Xcode environment for Debug IosArm64 configuration") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) + fun shouldCheckAllRegisteredTasksWithXcodeEnvironmentForDebugIosArm64( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + val buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildJdk = jdkProvider.location, + buildOptions = buildOptions + ) { + val environmentVariables = mapOf( + "CONFIGURATION" to "Debug", + "SDK_NAME" to "iphoneos", + "ARCHS" to "arm64", + "EXPANDED_CODE_SIGN_IDENTITY" to "-", + "TARGET_BUILD_DIR" to "testBuildDir", + "FRAMEWORKS_FOLDER_PATH" to "testFrameworksDir" + ) + buildAndAssertAllTasks( + registeredTasks = listOf( + "shared:assembleDebugAppleFrameworkForXcodeIosArm64", + "shared:embedAndSignAppleFrameworkForXcode", + "shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64", + "shared:embedAndSignCustomAppleFrameworkForXcode", + "shared:assembleDebugAppleFrameworkForXcodeIosX64", + "shared:assembleReleaseAppleFrameworkForXcodeIosX64", + "shared:assembleReleaseAppleFrameworkForXcodeIosArm64", + "shared:assembleCustomDebugAppleFrameworkForXcodeIosX64", + "shared:assembleCustomReleaseAppleFrameworkForXcodeIosX64", + "shared:assembleCustomReleaseAppleFrameworkForXcodeIosArm64" + ), + environmentVariables = EnvironmentalVariables(environmentVariables) + ) } } - @Test - fun `check embedAndSignAppleFrameworkForXcode was registered without required Xcode environments`() { - with(Project("sharedAppleFramework")) { - val currentGradleVersion = chooseWrapperVersionOrFinishTest() - val options: BuildOptions = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( + @DisplayName("embedAndSignAppleFrameworkForXcode was registered without required Xcode environments") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + fun shouldCheckEmbedAndSignAppleFrameworkForXcodeDoesNotRequireXcodeEnv( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + val buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildJdk = jdkProvider.location, + buildOptions = buildOptions + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( "CONFIGURATION" to "Debug", "SDK_NAME" to "iphoneos", "ARCHS" to "arm64" ) ) - build("tasks", options = options.suppressDeprecationWarningsOn("AGP uses deprecated IncrementalTaskInputs (Gradle 7.5)") { - GradleVersion.version(currentGradleVersion) >= GradleVersion.version(TestVersions.Gradle.G_7_5) && options.safeAndroidGradlePluginVersion < AGPVersion.v7_3_0 - }) { - assertSuccessful() - assertTasksRegistered( - ":shared:embedAndSignAppleFrameworkForXcode", - ":shared:embedAndSignCustomAppleFrameworkForXcode" - ) - assertTasksNotRegistered( - ":shared:assembleReleaseAppleFrameworkForXcodeIosX64", - ":shared:assembleDebugAppleFrameworkForXcodeIosX64", - ":shared:assembleReleaseAppleFrameworkForXcodeIosArm64", - ":shared:assembleDebugAppleFrameworkForXcodeIosArm64", - ":shared:assembleCustomDebugAppleFrameworkForXcodeIosX64", - ":shared:assembleCustomReleaseAppleFrameworkForXcodeIosX64", - ":shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64", - ":shared:assembleCustomReleaseAppleFrameworkForXcodeIosArm64" - ) - } - build(":shared:embedAndSignCustomAppleFrameworkForXcode", options = options) { + buildAndAssertAllTasks( + registeredTasks = listOf( + "shared:embedAndSignAppleFrameworkForXcode", + "shared:embedAndSignCustomAppleFrameworkForXcode" + ), + notRegisteredTasks = listOf( + "shared:assembleReleaseAppleFrameworkForXcodeIosX64", + "shared:assembleDebugAppleFrameworkForXcodeIosX64", + "shared:assembleReleaseAppleFrameworkForXcodeIosArm64", + "shared:assembleDebugAppleFrameworkForXcodeIosArm64", + "shared:assembleCustomDebugAppleFrameworkForXcodeIosX64", + "shared:assembleCustomReleaseAppleFrameworkForXcodeIosX64", + "shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64", + "shared:assembleCustomReleaseAppleFrameworkForXcodeIosArm64" + ), + environmentVariables = environmentVariables + ) + buildAndFail( + ":shared:embedAndSignCustomAppleFrameworkForXcode", + environmentVariables = environmentVariables + ) { assertTasksFailed(":shared:embedAndSignCustomAppleFrameworkForXcode") - assertContains("Please run the embedAndSignCustomAppleFrameworkForXcode task from Xcode") + assertOutputContains("Please run the embedAndSignCustomAppleFrameworkForXcode task from Xcode") } } } - @Test - fun `check that static framework for Arm64 is built but is not embedded`() { - with(Project("sharedAppleFramework")) { - val options: BuildOptions = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( + @DisplayName("Static framework for Arm64 is built but is not embedded") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) + fun shouldCheckThatStaticFrameworkForArm64IsBuildAndNotEmbedded( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildJdk = jdkProvider.location, + buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( "CONFIGURATION" to "debug", "SDK_NAME" to "iphoneos123", "ARCHS" to "arm64", "TARGET_BUILD_DIR" to "no use", "FRAMEWORKS_FOLDER_PATH" to "no use" - ), + ) ) - setupWorkingDir() - projectDir.resolve("shared/build.gradle.kts").modify { + + subProject("shared").buildGradleKts.modify { it.replace( "baseName = \"sdk\"", "baseName = \"sdk\"\nisStatic = true" ) } - build("embedAndSignAppleFrameworkForXcode", options = options) { - assertSuccessful() + build("embedAndSignAppleFrameworkForXcode", environmentVariables = environmentVariables) { assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeIosArm64") - assertTasksNotExecuted(":shared:embedAndSignAppleFrameworkForXcode") - assertFileExists("/shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework") - assertNoSuchFile("/shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework.dSYM") + assertTasksSkipped(":shared:embedAndSignAppleFrameworkForXcode") + assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework") + assertFileInProjectNotExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework.dSYM") } } } + @DisplayName("Configuration errors reported to Xcode when embedAndSign task requested") + @OptIn(EnvironmentalVariablesOverride::class) + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + @GradleAndroidTest + fun shouldReportConfErrorsToXcodeWhenRequestedByEmbedAndSign( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + val buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildJdk = jdkProvider.location, + buildOptions = buildOptions + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) - @Test - fun `configuration errors reported to Xcode when embedAndSign task requested`() { - with(Project("sharedAppleFramework")) { - setupWorkingDir() - projectDir.resolve("shared/build.gradle.kts").appendText( + subProject("shared").buildGradleKts.appendText( """ kotlin { sourceSets["commonMain"].dependencies { @@ -238,174 +340,227 @@ class AppleFrameworkIT : BaseGradleIT() { """.trimIndent() ) - build( - ":shared:embedAndSignAppleFrameworkForXcode", - options = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "debug", - "SDK_NAME" to "iphoneos123", - "ARCHS" to "arm64", - "TARGET_BUILD_DIR" to "no use", - "FRAMEWORKS_FOLDER_PATH" to "no use" - ) - ) - ) { - assertFailed() - assertContains("error: Could not find com.example.unknown:dependency:0.0.1.") + buildAndFail(":shared:embedAndSignAppleFrameworkForXcode", environmentVariables = environmentVariables) { + assertOutputContains("error: Could not find com.example.unknown:dependency:0.0.1.") } } } - @Test - fun `compilation errors reported to Xcode when embedAndSign task requested`() { - with(Project("sharedAppleFramework")) { - setupWorkingDir() - projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + @DisplayName("Compilation errors reported to Xcode when embedAndSign task requested") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + fun shouldReportCompilationErrorsToXcodeWhenRequestedByEmbedAndSign( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + val buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildOptions = buildOptions, + buildJdk = jdkProvider.location + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) + + projectPath.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") .appendText("this can't be compiled") - build( - ":shared:embedAndSignAppleFrameworkForXcode", - options = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "debug", - "SDK_NAME" to "iphoneos123", - "ARCHS" to "arm64", - "TARGET_BUILD_DIR" to "no use", - "FRAMEWORKS_FOLDER_PATH" to "no use" - ) - ) - ) { - assertFailed() - assertContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") - assertContains("error: Compilation finished with errors") + buildAndFail(":shared:embedAndSignAppleFrameworkForXcode", environmentVariables = environmentVariables) { + assertOutputContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") + assertOutputContains("error: Compilation finished with errors") } } } + @DisplayName("Compilation errors printed with Gradle-style when any other task requested") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + fun shouldPrintCompilationErrorsWithGradleStyle( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + val buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildOptions = buildOptions, + buildJdk = jdkProvider.location + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) - @Test - fun `compilation errors printed with Gradle-style when any other task requested`() { - with(Project("sharedAppleFramework")) { - setupWorkingDir() - projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + projectPath.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") .appendText("this can't be compiled") - build( + buildAndFail(":shared:assembleDebugAppleFrameworkForXcodeIosArm64", environmentVariables = environmentVariables) { + assertOutputContains("e: file:///") + assertOutputDoesNotContain("error: Compilation finished with errors") + } + } + } + + @DisplayName("Compilation errors printed with Xcode-style with explicit option") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + fun shouldPrintCompilationErrorsWithXcodeStyle( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + val buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildOptions = buildOptions, + buildJdk = jdkProvider.location + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) + + projectPath.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + .appendText("this can't be compiled") + + buildAndFail( ":shared:assembleDebugAppleFrameworkForXcodeIosArm64", - options = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "debug", - "SDK_NAME" to "iphoneos123", - "ARCHS" to "arm64", - "TARGET_BUILD_DIR" to "no use", - "FRAMEWORKS_FOLDER_PATH" to "no use" - ) - ) + "-Pkotlin.native.useXcodeMessageStyle=true", + environmentVariables = environmentVariables ) { - assertFailed() - assertContains("e: file:///") - assertNotContains("error: Compilation finished with errors") + assertOutputContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") + assertOutputContains("error: Compilation finished with errors") } } } + @DisplayName("Compilation errors reported to Xcode when embedAndSign task requested and compiler runs in a separate process") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleAndroidTest + // We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2) + fun shouldReportErrorsToXcodeWhenEmbedAndSignRequestedAndDisableCompilerDaemon( + gradleVersion: GradleVersion, + agpVersion: String, + jdkProvider: JdkVersions.ProvidedJdk + ) { + val buildOptions = defaultBuildOptions.copy( + androidVersion = agpVersion + ) + nativeProject( + "sharedAppleFramework", + gradleVersion, + buildOptions = buildOptions, + buildJdk = jdkProvider.location + ) { + val environmentVariables = EnvironmentalVariables( + mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) - @Test - fun `compilation errors printed with Xcode-style with explicit option`() { - with(Project("sharedAppleFramework")) { - setupWorkingDir() - projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + projectPath.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") .appendText("this can't be compiled") - build( - ":shared:assembleDebugAppleFrameworkForXcodeIosArm64", "-Pkotlin.native.useXcodeMessageStyle=true", - options = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "debug", - "SDK_NAME" to "iphoneos123", - "ARCHS" to "arm64", - "TARGET_BUILD_DIR" to "no use", - "FRAMEWORKS_FOLDER_PATH" to "no use" - ) - ) + buildAndFail( + ":shared:embedAndSignAppleFrameworkForXcode", + "-Pkotlin.native.disableCompilerDaemon=true", + environmentVariables = environmentVariables ) { - assertFailed() - assertContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") - assertContains("error: Compilation finished with errors") + assertOutputContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") } } } - @Test - fun `compilation errors reported to Xcode when embedAndSign task requested and compiler runs in a separate process`() { - with(Project("sharedAppleFramework")) { - setupWorkingDir() - projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") - .appendText("this can't be compiled") - - build( - ":shared:embedAndSignAppleFrameworkForXcode", "-Pkotlin.native.disableCompilerDaemon=true", - options = defaultBuildOptions().copy( - customEnvironmentVariables = mapOf( - "CONFIGURATION" to "debug", - "SDK_NAME" to "iphoneos123", - "ARCHS" to "arm64", - "TARGET_BUILD_DIR" to "no use", - "FRAMEWORKS_FOLDER_PATH" to "no use" - ) - ) - ) { - assertFailed() - assertContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") - } - } - } - - @Test - fun `frameworks can be consumed from other gradle project`() { - with(Project("consumableAppleFrameworks", minLogLevel = LogLevel.INFO)) { - setupWorkingDir() + @DisplayName("Frameworks can be consumed from other gradle project") + @GradleTest + fun shouldCheckFrameworksCanBeConsumedFromOtherGradleProjects(gradleVersion: GradleVersion) { + nativeProject("consumableAppleFrameworks", gradleVersion) { build(":consumer:help") { - assertContains("RESOLUTION_SUCCESS") - assertNotContains("RESOLUTION_FAILURE") + assertOutputContains("RESOLUTION_SUCCESS") + assertOutputDoesNotContain("RESOLUTION_FAILURE") } } } - @Test - fun `smoke test with apple gradle plugin`() { - with(Project("appleGradlePluginConsumesAppleFrameworks", minLogLevel = LogLevel.INFO)) { - setupWorkingDir() + @DisplayName("Smoke test with apple gradle plugin") + @GradleWithJdkTest + @JdkVersions(versions = [JavaVersion.VERSION_11]) + fun smokeTestWithAppleGradlePlugin( + gradleVersion: GradleVersion, + providedJdk: JdkVersions.ProvidedJdk + ) { + nativeProject( + "appleGradlePluginConsumesAppleFrameworks", + gradleVersion, + buildJdk = providedJdk.location + ) { fun dependencyInsight(configuration: String) = arrayOf( ":iosApp:dependencyInsight", "--configuration", configuration, "--dependency", "iosLib" ) - fun CompiledProject.assertContainsVariant(variantName: String) { - assertContains("Variant $variantName") + fun BuildResult.assertContainsVariant(variantName: String) { + assertOutputContains( + if (gradleVersion >= GradleVersion.version(TestVersions.Gradle.G_7_5)) + "Variant $variantName" + else "variant \"$variantName\"" + ) } build(*dependencyInsight("iosAppIosX64DebugImplementation")) { - assertSuccessful() assertContainsVariant("mainDynamicDebugFrameworkIos") } build(*dependencyInsight("iosAppIosX64ReleaseImplementation")) { - assertSuccessful() assertContainsVariant("mainDynamicReleaseFrameworkIos") } // NB: '0' is required at the end since dependency is added with custom attribute and it creates new configuration build(*dependencyInsight("iosAppIosX64DebugImplementation0"), "-PmultipleFrameworks") { - assertSuccessful() assertContainsVariant("mainStaticDebugFrameworkIos") } build(*dependencyInsight("iosAppIosX64ReleaseImplementation0"), "-PmultipleFrameworks") { - assertSuccessful() assertContainsVariant("mainStaticReleaseFrameworkIos") } } } - } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt index dc6083ff614..54b154ca816 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt @@ -97,6 +97,30 @@ fun GradleProject.assertFileNotExistsInTree( assertFileNotExistsInTree(projectPath.resolve(pathToTreeRoot), fileName) } +/** + * Asserts symlink under [path] exists and is a symlink + */ +fun assertSymlinkExists( + path: Path +) { + assert(Files.exists(path)) { + "Symlink '${path}' does not exist!" + } + + assert(Files.isSymbolicLink(path)) { + "'${path}' is not a symlink!" + } +} + +/** + * Asserts symlink under [pathToFile] relative to the test project exists and is a symlink. + */ +fun TestProject.assertSymlinkInProjectExists( + pathToFile: String +) { + assertSymlinkExists(projectPath.resolve(pathToFile)) +} + /** * Asserts directory under [pathToDir] relative to the test project exists and is a directory. */ @@ -105,9 +129,9 @@ fun GradleProject.assertDirectoryInProjectExists( ) = assertDirectoryExists(projectPath.resolve(pathToDir)) /** - * Asserts directory under [file] exists and is a directory. + * Asserts directory under [dirPath] exists and is a directory. */ -fun GradleProject.assertDirectoryExists( +fun assertDirectoryExists( dirPath: Path ) = assertDirectoriesExist(dirPath) @@ -132,7 +156,7 @@ fun assertDirectoriesExist( } /** - * Asserts file under [pathToFile] relative to the test project exists and contains all the lines from [expectedLines] + * Asserts file under [pathToFile] relative to the test project exists and contains all the lines from [expectedText] */ fun GradleProject.assertFileInProjectContains( pathToFile: String, diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt index 0170e4f06e9..c27eeab5be1 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt @@ -160,14 +160,17 @@ fun BuildResult.assertNativeTasksClasspath( * it could contain task paths as well, but without the first semicolon. * @param notRegisteredTasks An optional list of task names that should not have been registered, * it could contain task paths as well, but without the first semicolon. + * @param environmentVariables environmental variables for build process * @throws AssertionError if any of the registered tasks do not match the expected task names, * or if any of the not-registered tasks were actually registered. */ +@OptIn(EnvironmentalVariablesOverride::class) fun TestProject.buildAndAssertAllTasks( registeredTasks: List = emptyList(), - notRegisteredTasks: List = emptyList() + notRegisteredTasks: List = emptyList(), + environmentVariables: EnvironmentalVariables = EnvironmentalVariables() ) { - build("tasks", "--all") { + build("tasks", "--all", environmentVariables = environmentVariables) { assertTasksInBuildOutput(registeredTasks, notRegisteredTasks) } } 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 5b94067fb6d..9650b8d3126 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 @@ -33,6 +33,7 @@ import kotlin.test.assertTrue * @param [buildOptions] common Gradle build options * @param [buildJdk] path to JDK build should run with. *Note* Only append to 'gradle.properties'! */ +@OptIn(EnvironmentalVariablesOverride::class) fun KGPBaseTest.project( projectName: String, gradleVersion: GradleVersion, @@ -44,7 +45,8 @@ fun KGPBaseTest.project( projectPathAdditionalSuffix: String = "", buildJdk: File? = null, localRepoDir: Path? = null, - test: TestProject.() -> Unit = {}, + environmentVariables: EnvironmentalVariables = EnvironmentalVariables(), + test: TestProject.() -> Unit = {} ): TestProject { val projectPath = setupProjectFromTestResources( projectName, @@ -56,7 +58,6 @@ fun KGPBaseTest.project( projectPath.enableCacheRedirector() projectPath.enableAndroidSdk() - val gradleRunner = GradleRunner .create() .withProjectDir(projectPath.toFile()) @@ -72,6 +73,7 @@ fun KGPBaseTest.project( forceOutput = forceOutput, enableBuildScan = enableBuildScan, enableGradleDebug = enableGradleDebug, + environmentVariables = environmentVariables ) addHeapDumpOptions.ifTrue { testProject.addHeapDumpOptions() } localRepoDir?.let { testProject.configureLocalRepository(localRepoDir) } @@ -93,6 +95,7 @@ fun KGPBaseTest.project( * @param [buildOptions] common Gradle build options * @param [buildJdk] path to JDK build should run with. *Note* Only append to 'gradle.properties'! */ +@OptIn(EnvironmentalVariablesOverride::class) fun KGPBaseTest.nativeProject( projectName: String, gradleVersion: GradleVersion, @@ -104,7 +107,8 @@ fun KGPBaseTest.nativeProject( projectPathAdditionalSuffix: String = "", buildJdk: File? = null, localRepoDir: Path? = null, - test: TestProject.() -> Unit = {}, + environmentVariables: EnvironmentalVariables = EnvironmentalVariables(), + test: TestProject.() -> Unit = {} ): TestProject { val project = project( projectName = projectName, @@ -117,6 +121,7 @@ fun KGPBaseTest.nativeProject( projectPathAdditionalSuffix = projectPathAdditionalSuffix, buildJdk = buildJdk, localRepoDir = localRepoDir, + environmentVariables = environmentVariables, ) project.configureSingleNativeTarget() project.test() @@ -126,6 +131,7 @@ fun KGPBaseTest.nativeProject( /** * Trigger test project build with given [buildArguments] and assert build is successful. */ +@OptIn(EnvironmentalVariablesOverride::class) fun TestProject.build( vararg buildArguments: String, forceOutput: Boolean = this.forceOutput, @@ -134,7 +140,8 @@ fun TestProject.build( enableBuildCacheDebug: Boolean = false, enableBuildScan: Boolean = this.enableBuildScan, buildOptions: BuildOptions = this.buildOptions, - assertions: BuildResult.() -> Unit = {}, + environmentVariables: EnvironmentalVariables = this.environmentVariables, + assertions: BuildResult.() -> Unit = {} ) { if (enableBuildScan) agreeToBuildScanService() @@ -148,6 +155,7 @@ fun TestProject.build( ) val gradleRunnerForBuild = gradleRunner .also { if (forceOutput) it.forwardOutput() } + .also { if (environmentVariables.environmentalVariables.isNotEmpty()) it.withEnvironment(System.getenv() + environmentVariables.environmentalVariables) } .withDebug(enableGradleDebug) .withArguments(allBuildArguments) withBuildSummary(allBuildArguments) { @@ -169,7 +177,8 @@ fun TestProject.buildAndFail( enableBuildCacheDebug: Boolean = false, enableBuildScan: Boolean = this.enableBuildScan, buildOptions: BuildOptions = this.buildOptions, - assertions: BuildResult.() -> Unit = {}, + environmentVariables: EnvironmentalVariables = this.environmentVariables, + assertions: BuildResult.() -> Unit = {} ) { if (enableBuildScan) agreeToBuildScanService() @@ -183,6 +192,7 @@ fun TestProject.buildAndFail( ) val gradleRunnerForBuild = gradleRunner .also { if (forceOutput) it.forwardOutput() } + .also { if (environmentVariables.environmentalVariables.isNotEmpty()) it.withEnvironment(System.getenv() + environmentVariables.environmentalVariables) } .withDebug(enableGradleDebug) .withArguments(allBuildArguments) withBuildSummary(allBuildArguments) { @@ -295,6 +305,13 @@ open class GradleProject( ): List = files.map { projectPath.relativize(it) } } +@JvmInline +value class EnvironmentalVariables @EnvironmentalVariablesOverride constructor(val environmentalVariables: Map = emptyMap()) + +@RequiresOptIn("Environmental variables override may lead to interference of parallel builds and breaks Gradle tests debugging") +annotation class EnvironmentalVariablesOverride + +@OptIn(EnvironmentalVariablesOverride::class) class TestProject( val gradleRunner: GradleRunner, projectName: String, @@ -313,6 +330,7 @@ class TestProject( * Note that we'll need to let the debugger start listening at this port first *before* the Kotlin daemon is launched. */ val kotlinDaemonDebugPort: Int? = null, + val environmentVariables: EnvironmentalVariables = EnvironmentalVariables(), ) : GradleProject(projectName, projectPath) { fun subProject(name: String) = GradleProject(name, projectPath.resolve(name))