diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml new file mode 100644 index 00000000..4aafd51d --- /dev/null +++ b/.github/workflows/build_mac.yml @@ -0,0 +1,35 @@ +name: Build and Test +on: + pull_request: + push: + workflow_dispatch: + +jobs: + build: + runs-on: macos-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - uses: actions/setup-java@v2 + with: + distribution: "adopt" + java-version: "17" + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Write Faktory Server Code + run: echo ${{ secrets.TOUCHLAB_TEST_ARTIFACT_CODE }} > kmmbridge/TOUCHLAB_TEST_ARTIFACT_CODE + + - name: Read Faktory Server Code + run: cat kmmbridge/TOUCHLAB_TEST_ARTIFACT_CODE + + - name: Local Publish For Tests + run: ./gradlew publishToMavenLocal --no-daemon --stacktrace --build-cache -PRELEASE_SIGNING_ENABLED=false -PVERSION_NAME=9.9.9 + + - name: Build + run: ./gradlew build --no-daemon --stacktrace --build-cache + +env: + GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m" \ No newline at end of file diff --git a/.gitignore b/.gitignore index b33bfc0a..9ab56bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ build *.xcbkptlist !/.idea/codeStyles/* !/.idea/inspectionProfiles/* +.kotlin +TOUCHLAB_TEST_ARTIFACT_CODE \ No newline at end of file diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000..8e6544c0 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,36 @@ +# KMMBridge Project Tests + +Gradle guidance suggests using include builds to test Gradle plugins. However, after a significant effort to avoid JVM classpath and other related issues, we decided to run tests by starting external Gradle builds. + +To do that, we need to locally publish KMMBridge, then point tests projects at that new version. For each test: + +* A temp folder is constructed +* A sample app project is copied to that folder +* A command line process is run, generally running Gradle to perform whatever task we intend to test + +## Publishing KMMBridge locally + +We want to publish KMMBridge as version `9.9.9`. This is a fake local version, just for testing. To do that, run the following on the root folder of KMMBridge: + +```shell +./gradlew publishToMavenLocal -PVERSION_NAME=9.9.9 +``` + +## Editing the test project + +Our simple test project lives at `test-projects/basic`. It points at KMMBridge version `9.9.9`. You should be able to open it directly with IJ or AS. + +## Tests + +See class `co.touchlab.kmmbridge.SimplePluginTest`. `fun setup()` copies the test project an initializes the test project. + +Here is a sample test: + +```kotlin +@Test +fun runSpmDevBuild() { + val result = ProcessHelper.runSh("./gradlew spmDevBuild --stacktrace", workingDir = testProjectDir) + logExecResult(result) + assertEquals(0, result.status) +} +``` \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f4b4d15d..67ca2b35 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,4 +14,37 @@ plugins { alias(libs.plugins.kotlin) apply false id("org.jetbrains.kotlin.plugin.allopen") version "1.9.0" apply false alias(libs.plugins.maven.publish) apply false +} + +subprojects { + repositories { + gradlePluginPortal() + mavenCentral() + } + + extensions.findByType()?.apply { + jvmToolchain(17) + } + + val GROUP: String by project + val VERSION_NAME: String by project + + group = GROUP + version = VERSION_NAME + + extensions.findByType()?.apply { + publishToMavenCentral() + val releaseSigningEnabled = + project.properties["RELEASE_SIGNING_ENABLED"]?.toString()?.equals("false", ignoreCase = true) != true + if (releaseSigningEnabled) signAllPublications() + @Suppress("UnstableApiUsage") + pomFromGradleProperties() + configureBasedOnAppliedPlugins() + } + + afterEvaluate { + tasks.getByName("test") { + useJUnitPlatform() + } + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e1082050..8da893e5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ org.gradle.jvmargs=-Xmx4g RELEASE_SIGNING_ENABLED=true GROUP=co.touchlab.kmmbridge -VERSION_NAME=1.1.0-a1 +VERSION_NAME=1.1.0-a2 VERSION_NAME_3x=0.3.7 POM_URL=https://github.com/touchlab/KMMBridge diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586a..81aa1c04 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kmmbridge-github/build.gradle.kts b/kmmbridge-github/build.gradle.kts index cb709856..2293dc68 100644 --- a/kmmbridge-github/build.gradle.kts +++ b/kmmbridge-github/build.gradle.kts @@ -15,18 +15,13 @@ plugins { `kotlin-dsl` - kotlin("jvm") + alias(libs.plugins.kotlin) id("org.jetbrains.kotlin.plugin.allopen") id("java-gradle-plugin") - id("com.vanniktech.maven.publish.base") + alias(libs.plugins.maven.publish) id("com.gradle.plugin-publish") version "1.0.0" } -repositories { - gradlePluginPortal() - mavenCentral() -} - @Suppress("UnstableApiUsage") gradlePlugin { website = "https://github.com/touchlab/KMMBridge" @@ -36,8 +31,8 @@ gradlePlugin { plugins { register("kmmbridge-github-plugin") { id = "co.touchlab.kmmbridge.github" - implementationClass = "co.touchlab.kmmbridge.KMMBridgePlugin" - displayName = "KMMBridge/GitHub for Teams" + implementationClass = "co.touchlab.kmmbridge.github.KMMBridgeGitHubPlugin" + displayName = "KMMBridge/GitHub" tags = listOf( "kmm", "kotlin", @@ -62,23 +57,3 @@ dependencies { testImplementation(kotlin("test")) } - -kotlin { - jvmToolchain(11) -} - -val GROUP: String by project -val VERSION_NAME: String by project - -group = GROUP -version = VERSION_NAME - -mavenPublishing { - publishToMavenCentral() - val releaseSigningEnabled = - project.properties["RELEASE_SIGNING_ENABLED"]?.toString()?.equals("false", ignoreCase = true) != true - if (releaseSigningEnabled) signAllPublications() - @Suppress("UnstableApiUsage") - pomFromGradleProperties() - configureBasedOnAppliedPlugins() -} diff --git a/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/GithubReleaseArtifactManager.kt b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/GithubReleaseArtifactManager.kt index eb09a8b4..f879f2bb 100644 --- a/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/GithubReleaseArtifactManager.kt +++ b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/GithubReleaseArtifactManager.kt @@ -13,7 +13,7 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.getByType import java.io.File -class GithubReleaseArtifactManager( +internal class GithubReleaseArtifactManager( private val repository: String?, private val releaseString: String?, private val useExistingRelease: Boolean ) : ArtifactManager { diff --git a/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/KMMBridgeGitHubPlugin.kt b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/KMMBridgeGitHubPlugin.kt new file mode 100644 index 00000000..cc71d9d7 --- /dev/null +++ b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/KMMBridgeGitHubPlugin.kt @@ -0,0 +1,84 @@ +package co.touchlab.kmmbridge.github + +import co.touchlab.kmmbridge.BaseKMMBridgePlugin +import co.touchlab.kmmbridge.TASK_GROUP_NAME +import co.touchlab.kmmbridge.findStringProperty +import co.touchlab.kmmbridge.github.internal.procRunFailLog +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.Task +import java.io.File +import java.nio.file.Files + +@Suppress("unused") +class KMMBridgeGitHubPlugin : BaseKMMBridgePlugin() { + override fun apply(project: Project) { + super.apply(project) + val githubDeploySourceRepo = project.findStringProperty("githubDeploySourceRepo") + val githubDeployTargetRepo = project.findStringProperty("githubDeployTargetRepo") + if (githubDeploySourceRepo != null && githubDeployTargetRepo != null) { + project.tasks.register("setupDeployKeys") { + group = TASK_GROUP_NAME + description = "Helper task to setup GitHub deploy keys" + outputs.upToDateWhen { false } // This should always run + + @Suppress("ObjectLiteralToLambda") + doLast(object : Action { + override fun execute(t: Task) { + val githubDeployKeyPrefix = project.findStringProperty("githubDeployKeyPrefix") ?: "KMMBridge" + + val keyname = "$githubDeployKeyPrefix Key" + + val tempDir = Files.createTempDirectory("kmmbridge") + println("Generated temp dir: $tempDir") + val deployKeyName = "deploykey" + val deployKeyPrivateFilePath = File(tempDir.toFile(), deployKeyName) + val deployKeyPublicFilePath = File(tempDir.toFile(), "${deployKeyName}.pub") + + try { + project.procRunFailLog( + "ssh-keygen", + "-t", + "ed25519", + "-f", + deployKeyPrivateFilePath.toString(), + "-C", + "git@github.com:$githubDeployTargetRepo", + "-P", + "" + ) + + project.procRunFailLog( + "gh", + "repo", + "deploy-key", + "add", + deployKeyPublicFilePath.toString(), + "-w", + "-R", + githubDeployTargetRepo, + "-t", + keyname + ) + + project.procRunFailLog( + "gh", + "secret", + "set", + "${githubDeployKeyPrefix}_SSH_KEY", + "--body", + deployKeyPrivateFilePath.readText(), + "-R", + githubDeploySourceRepo + ) + } finally { + deployKeyPrivateFilePath.delete() + deployKeyPublicFilePath.delete() + Files.deleteIfExists(tempDir) + } + } + }) + } + } + } +} \ No newline at end of file diff --git a/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/internal/GithubApi.kt b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/internal/GithubApi.kt index c321674c..537ce007 100644 --- a/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/internal/GithubApi.kt +++ b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/internal/GithubApi.kt @@ -22,9 +22,6 @@ internal val Project.githubPublishTokenOrNull: String? internal val Project.githubPublishUser: String? get() = project.findStringProperty("GITHUB_PUBLISH_USER") -internal val Project.skipGitHumReleaseSpmChecks: Boolean - get() = project.findStringProperty("SKIP_GITHUB_RELEASE_SPM_CHECKS") == "true" - internal val Project.githubRepoOrNull: String? get() { val repo = project.findStringProperty("GITHUB_REPO") ?: return null diff --git a/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/internal/ProcessHelper.kt b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/internal/ProcessHelper.kt new file mode 100644 index 00000000..2434a771 --- /dev/null +++ b/kmmbridge-github/src/main/kotlin/co/touchlab/kmmbridge/github/internal/ProcessHelper.kt @@ -0,0 +1,45 @@ +package co.touchlab.kmmbridge.github.internal + +import org.gradle.api.GradleException +import org.gradle.api.Project +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader + +internal fun procRun(vararg params: String, dir: File?, processLines: (String, Int) -> Unit) { + val processBuilder = ProcessBuilder(*params) + .redirectErrorStream(true) + if (dir != null) { + println("*** Running proc in ${dir.path}") + processBuilder.directory(dir) + } + + val process = processBuilder + .start() + + val streamReader = InputStreamReader(process.inputStream) + val bufferedReader = BufferedReader(streamReader) + var lineCount = 1 + + bufferedReader.forEachLine { line -> + processLines(line, lineCount) + lineCount++ + } + + bufferedReader.close() + val returnValue = process.waitFor() + if (returnValue != 0) + throw GradleException("Process failed: ${params.joinToString(" ")}") +} + +internal fun Project.procRunFailLog(vararg params: String, dir: File? = null): List { + val output = mutableListOf() + try { + logger.info("Project.procRunFailLog: ${params.joinToString(" ")}") + procRun(*params, dir = dir) { line, _ -> output.add(line) } + } catch (e: Exception) { + output.forEach { logger.error("error: $it") } + throw e + } + return output +} \ No newline at end of file diff --git a/kmmbridge-gitlab/build.gradle.kts b/kmmbridge-gitlab/build.gradle.kts index bf6271e6..f82f30e8 100644 --- a/kmmbridge-gitlab/build.gradle.kts +++ b/kmmbridge-gitlab/build.gradle.kts @@ -15,18 +15,13 @@ plugins { `kotlin-dsl` - kotlin("jvm") + alias(libs.plugins.kotlin) id("org.jetbrains.kotlin.plugin.allopen") id("java-gradle-plugin") - id("com.vanniktech.maven.publish.base") + alias(libs.plugins.maven.publish) id("com.gradle.plugin-publish") version "1.0.0" } -repositories { - gradlePluginPortal() - mavenCentral() -} - @Suppress("UnstableApiUsage") gradlePlugin { website = "https://github.com/touchlab/KMMBridge" @@ -36,8 +31,8 @@ gradlePlugin { plugins { register("kmmbridge-gitlab-plugin") { id = "co.touchlab.kmmbridge.gitlab" - implementationClass = "co.touchlab.kmmbridge.KMMBridgePlugin" - displayName = "KMMBridge/GitLab for Teams" + implementationClass = "co.touchlab.kmmbridge.gitlab.KMMBridgeGitLabPlugin" + displayName = "KMMBridge/GitLab" tags = listOf( "kmm", "kotlin", @@ -61,24 +56,4 @@ dependencies { api(project(":kmmbridge")) testImplementation(kotlin("test")) -} - -kotlin { - jvmToolchain(11) -} - -val GROUP: String by project -val VERSION_NAME: String by project - -group = GROUP -version = VERSION_NAME - -mavenPublishing { - publishToMavenCentral() - val releaseSigningEnabled = - project.properties["RELEASE_SIGNING_ENABLED"]?.toString()?.equals("false", ignoreCase = true) != true - if (releaseSigningEnabled) signAllPublications() - @Suppress("UnstableApiUsage") - pomFromGradleProperties() - configureBasedOnAppliedPlugins() -} +} \ No newline at end of file diff --git a/kmmbridge-gitlab/src/main/kotlin/co/touchlab/kmmbridge/gitlab/KMMBridgeGitLabPlugin.kt b/kmmbridge-gitlab/src/main/kotlin/co/touchlab/kmmbridge/gitlab/KMMBridgeGitLabPlugin.kt new file mode 100644 index 00000000..cefb0b2d --- /dev/null +++ b/kmmbridge-gitlab/src/main/kotlin/co/touchlab/kmmbridge/gitlab/KMMBridgeGitLabPlugin.kt @@ -0,0 +1,7 @@ +package co.touchlab.kmmbridge.gitlab + +import co.touchlab.kmmbridge.BaseKMMBridgePlugin + +@Suppress("unused") +class KMMBridgeGitLabPlugin : BaseKMMBridgePlugin() { +} \ No newline at end of file diff --git a/kmmbridge-test/build.gradle.kts b/kmmbridge-test/build.gradle.kts new file mode 100644 index 00000000..64ee87b2 --- /dev/null +++ b/kmmbridge-test/build.gradle.kts @@ -0,0 +1,58 @@ +@file:Suppress("PropertyName") + +/* +* Copyright (c) 2024 Touchlab. +* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software distributed under the License +* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +* or implied. See the License for the specific language governing permissions and limitations under +* the License. +*/ +plugins { + `kotlin-dsl` + alias(libs.plugins.kotlin) + id("org.jetbrains.kotlin.plugin.allopen") + id("java-gradle-plugin") + alias(libs.plugins.maven.publish) + id("com.gradle.plugin-publish") version "1.0.0" +} + +@Suppress("UnstableApiUsage") +gradlePlugin { + website = "https://github.com/touchlab/KMMBridge" + vcsUrl = "https://github.com/touchlab/KMMBridge.git" + description = + "KMMBridge is a set of Gradle tooling that facilitates publishing and consuming pre-built KMM (Kotlin Multiplatform Mobile) Xcode Framework binaries." + plugins { + register("kmmbridge-test-plugin") { + id = "co.touchlab.kmmbridge.test" + implementationClass = "co.touchlab.kmmbridge.test.KMMBridgeTestPlugin" + displayName = "KMMBridge/Test" + tags = listOf( + "kmm", + "kotlin", + "multiplatform", + "mobile", + "ios", + "xcode", + "framework", + "binary", + "publish", + "consume" + ) + } + } +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(libs.okhttp) + implementation(libs.gson) + api(project(":kmmbridge")) + + testImplementation(kotlin("test")) +} \ No newline at end of file diff --git a/kmmbridge-test/src/main/kotlin/Extensions.kt b/kmmbridge-test/src/main/kotlin/Extensions.kt new file mode 100644 index 00000000..3a0c89bc --- /dev/null +++ b/kmmbridge-test/src/main/kotlin/Extensions.kt @@ -0,0 +1,32 @@ +import co.touchlab.kmmbridge.findStringProperty +import co.touchlab.kmmbridge.test.TestArtifactManager +import co.touchlab.kmmbridge.test.TestUploadArtifactManager +import co.touchlab.kmmbridge.test.kmmBridgeExtension +import org.gradle.api.GradleException +import org.gradle.api.Project + +@Suppress("unused") +fun Project.testArtifacts() { + val artifactManager = kmmBridgeExtension.artifactManager + artifactManager.set(TestArtifactManager()) + artifactManager.finalizeValue() +} + +/** + * This is for Touchlab use. See the code for more details. + */ +@Suppress("unused") +fun Project.testUploadArtifacts() { + val server = findStringProperty("TOUCHLAB_TEST_ARTIFACT_SERVER") + val code = findStringProperty("TOUCHLAB_TEST_ARTIFACT_CODE") + + if (server == null || code == null) { + throw GradleException("TODO: Figure out a way for forks to not fail builds. But not today...") + } + + val am = TestUploadArtifactManager(server, code) + + val artifactManager = kmmBridgeExtension.artifactManager + artifactManager.set(am) + artifactManager.finalizeValue() +} \ No newline at end of file diff --git a/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/KMMBridgeTestPlugin.kt b/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/KMMBridgeTestPlugin.kt new file mode 100644 index 00000000..0283acad --- /dev/null +++ b/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/KMMBridgeTestPlugin.kt @@ -0,0 +1,7 @@ +package co.touchlab.kmmbridge.test + +import co.touchlab.kmmbridge.BaseKMMBridgePlugin + +@Suppress("unused") +class KMMBridgeTestPlugin : BaseKMMBridgePlugin() { +} \ No newline at end of file diff --git a/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/TestArtifactManager.kt b/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/TestArtifactManager.kt new file mode 100644 index 00000000..a1d782d6 --- /dev/null +++ b/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/TestArtifactManager.kt @@ -0,0 +1,26 @@ +package co.touchlab.kmmbridge.test + +import co.touchlab.kmmbridge.KmmBridgeExtension +import co.touchlab.kmmbridge.artifactmanager.ArtifactManager +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.getByType +import java.io.File + +class TestArtifactManager : ArtifactManager { + private lateinit var url: String + + override fun configure( + project: Project, + version: String, + uploadTask: TaskProvider, + kmmPublishTask: TaskProvider + ) { + url = "test://$version" + } + + override fun deployArtifact(task: Task, zipFilePath: File, version: String): String = url +} + +internal val Project.kmmBridgeExtension get() = extensions.getByType() \ No newline at end of file diff --git a/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/TestUploadArtifactManager.kt b/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/TestUploadArtifactManager.kt new file mode 100644 index 00000000..b24c329a --- /dev/null +++ b/kmmbridge-test/src/main/kotlin/co/touchlab/kmmbridge/test/TestUploadArtifactManager.kt @@ -0,0 +1,43 @@ +package co.touchlab.kmmbridge.test + +import co.touchlab.kmmbridge.artifactmanager.ArtifactManager +import com.google.gson.Gson +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import org.gradle.api.GradleException +import org.gradle.api.Task +import java.io.File +import java.time.Duration + +/** + * Simple artifact manager that posts files to our server (Touchlab). This isn't designed for general usage. Just for + * our tests. If there is more general demand for this functionality, reach out and we'll discuss ways of making it work. + */ +internal class TestUploadArtifactManager(private val server: String, private val code: String) : ArtifactManager { + override fun deployArtifact(task: Task, zipFilePath: File, version: String): String { + val body: RequestBody = zipFilePath.asRequestBody("application/octet-stream".toMediaTypeOrNull()) + val uploadRequest = Request.Builder().url( + "https://$server/infoadmin/storeTestZip" + ).post(body).addHeader("code", code) + .addHeader("Content-Type", "application/octet-stream").build() + + val okHttpClient = + OkHttpClient.Builder().callTimeout(Duration.ofMinutes(5)).connectTimeout(Duration.ofMinutes(2)) + .writeTimeout(Duration.ofMinutes(5)).readTimeout(Duration.ofMinutes(2)).build() + + val response = okHttpClient.newCall(uploadRequest).execute() + if (response.code >= 400) { + throw GradleException("Test zip upload failed. Code: ${response.code}, message: ${response.message}") + } + + val uploadResponseString = response.body!!.string() + val url = Gson().fromJson(uploadResponseString, UploadReply::class.java).url + + return "https://$server$url" + } + + data class UploadReply(var url: String) +} \ No newline at end of file diff --git a/kmmbridge/build.gradle.kts b/kmmbridge/build.gradle.kts index 2b30605e..0698aea9 100644 --- a/kmmbridge/build.gradle.kts +++ b/kmmbridge/build.gradle.kts @@ -15,18 +15,13 @@ plugins { `kotlin-dsl` - kotlin("jvm") + alias(libs.plugins.kotlin) id("org.jetbrains.kotlin.plugin.allopen") id("java-gradle-plugin") - id("com.vanniktech.maven.publish.base") + alias(libs.plugins.maven.publish) id("com.gradle.plugin-publish") version "1.0.0" } -repositories { - gradlePluginPortal() - mavenCentral() -} - @Suppress("UnstableApiUsage") gradlePlugin { website = "https://github.com/touchlab/KMMBridge" @@ -62,24 +57,6 @@ dependencies { implementation(libs.gson) testImplementation(kotlin("test")) -} - -kotlin { - jvmToolchain(11) -} - -val GROUP: String by project -val VERSION_NAME: String by project - -group = GROUP -version = VERSION_NAME - -mavenPublishing { - publishToMavenCentral() - val releaseSigningEnabled = - project.properties["RELEASE_SIGNING_ENABLED"]?.toString()?.equals("false", ignoreCase = true) != true - if (releaseSigningEnabled) signAllPublications() - @Suppress("UnstableApiUsage") - pomFromGradleProperties() - configureBasedOnAppliedPlugins() -} + testImplementation(gradleTestKit()) + testImplementation("commons-io:commons-io:2.18.0") +} \ No newline at end of file diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/Constants.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/Constants.kt new file mode 100644 index 00000000..27d54694 --- /dev/null +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/Constants.kt @@ -0,0 +1,4 @@ +package co.touchlab.kmmbridge + +public const val TASK_GROUP_NAME = "kmmbridge" +public const val EXTENSION_NAME = "kmmbridge" \ No newline at end of file diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KMMBridge.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KMMBridge.kt index eddb58de..6135b8f5 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KMMBridge.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KMMBridge.kt @@ -15,6 +15,13 @@ package co.touchlab.kmmbridge import co.touchlab.kmmbridge.artifactmanager.ArtifactManager import co.touchlab.kmmbridge.dependencymanager.SpmDependencyManager +import co.touchlab.kmmbridge.internal.enablePublishing +import co.touchlab.kmmbridge.internal.findXCFrameworkAssembleTask +import co.touchlab.kmmbridge.internal.kotlin +import co.touchlab.kmmbridge.internal.layoutBuildDir +import co.touchlab.kmmbridge.internal.spmBuildTargets +import co.touchlab.kmmbridge.internal.urlFile +import co.touchlab.kmmbridge.internal.zipFilePath import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project @@ -36,7 +43,10 @@ import kotlin.collections.flatMap import kotlin.collections.forEach @Suppress("unused") -class KMMBridgePlugin : Plugin { +open class KMMBridgePlugin : BaseKMMBridgePlugin() { +} + +abstract class BaseKMMBridgePlugin : Plugin { override fun apply(project: Project): Unit = with(project) { val extension = extensions.create(EXTENSION_NAME) diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KmmBridgeExtension.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KmmBridgeExtension.kt index 346e98a2..0e810474 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KmmBridgeExtension.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KmmBridgeExtension.kt @@ -21,7 +21,9 @@ import co.touchlab.kmmbridge.dependencymanager.DependencyManager import co.touchlab.kmmbridge.dependencymanager.SpecRepo import co.touchlab.kmmbridge.dependencymanager.SpmDependencyManager import co.touchlab.kmmbridge.dsl.TargetPlatformDsl +import co.touchlab.kmmbridge.internal.cocoapods import co.touchlab.kmmbridge.internal.domain.SwiftToolVersion +import co.touchlab.kmmbridge.internal.kotlin import org.gradle.api.Project import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/ProjectExtensions.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/ProjectExtensions.kt index ed7c6cbf..35039102 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/ProjectExtensions.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/ProjectExtensions.kt @@ -14,71 +14,17 @@ package co.touchlab.kmmbridge import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.UnknownTaskException -import org.gradle.api.plugins.ExtensionAware import org.gradle.api.plugins.ExtraPropertiesExtension import org.gradle.api.publish.PublishingExtension -import org.gradle.api.tasks.TaskProvider -import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.getByType -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension -import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType -import java.io.File -internal val Project.layoutBuildDir get() = layout.buildDirectory.get().asFile -internal val Project.kotlin: KotlinMultiplatformExtension get() = extensions.getByType() -internal val Project.kmmBridgeExtension get() = extensions.getByType() val Project.publishingExtension get() = extensions.getByType() -internal val Project.urlFile get() = file("$layoutBuildDir/faktory/url") - -// Cocoapods is an extension of KMP extension, so you can't just do project.extensions.getByType() -internal val KotlinMultiplatformExtension.cocoapodsOrNull get() = (this as ExtensionAware).extensions.findByType() -internal val KotlinMultiplatformExtension.cocoapods - get() = cocoapodsOrNull - ?: error("You must apply the org.jetbrains.kotlin.native.cocoapods plugin to use cocoapods() configuration") - -// This previously defaulted to 'false', but now you can disable it if needed, but otherwise ignore -internal val Project.enablePublishing: Boolean - get() = project.findStringProperty("ENABLE_PUBLISHING")?.toBoolean() ?: false - -internal val Project.spmBuildTargets: String? - get() = project.findStringProperty("spmBuildTargets") - -@Suppress("SpellCheckingInspection") -internal fun Project.zipFilePath(): File { - val tempDir = file("$layoutBuildDir/faktory/zip") - val artifactName = "frameworkarchive.zip" - return file("$tempDir/$artifactName") -} - -public fun Project.findStringProperty(name: String): String? { +fun Project.findStringProperty(name: String): String? { rootProject.extensions.getByType(ExtraPropertiesExtension::class.java).run { if (has(name)) return get(name).toString() } return null } - -internal const val TASK_GROUP_NAME = "kmmbridge" -public const val EXTENSION_NAME = "kmmbridge" - -internal fun Project.findXCFrameworkAssembleTask(buildType: NativeBuildType? = null): TaskProvider { - val extension = extensions.getByType() - val name = extension.frameworkName.get() - val buildTypeString = (buildType ?: extension.buildType.get()).getName().capitalized() - val taskWithoutName = "assemble${buildTypeString}XCFramework" - val taskWithName = "assemble${name.capitalized()}${buildTypeString}XCFramework" - return runCatching { - tasks.named(taskWithName) - }.recoverCatching { - tasks.named(taskWithoutName) - }.getOrElse { - throw UnknownTaskException( - "Cannot find XCFramework assemble task. Tried $taskWithName and ${taskWithoutName}." - ) - } -} diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/AwsS3PublicArtifactManager.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/AwsS3PublicArtifactManager.kt index d244aec5..80af4627 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/AwsS3PublicArtifactManager.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/AwsS3PublicArtifactManager.kt @@ -13,7 +13,7 @@ package co.touchlab.kmmbridge.artifactmanager -import co.touchlab.kmmbridge.kmmBridgeExtension +import co.touchlab.kmmbridge.internal.kmmBridgeExtension import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.tasks.TaskProvider @@ -27,7 +27,7 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest import java.io.File import java.util.* -class AwsS3PublicArtifactManager( +internal class AwsS3PublicArtifactManager( private val s3Region: String, private val s3Bucket: String, private val s3AccessKeyId: String, diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/MavenPublishArtifactManager.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/MavenPublishArtifactManager.kt index c65a51e4..34954dbf 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/MavenPublishArtifactManager.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/artifactmanager/MavenPublishArtifactManager.kt @@ -1,6 +1,6 @@ package co.touchlab.kmmbridge.artifactmanager -import co.touchlab.kmmbridge.capitalized +import co.touchlab.kmmbridge.internal.capitalized import co.touchlab.kmmbridge.publishingExtension import org.gradle.api.GradleException import org.gradle.api.Project @@ -17,7 +17,7 @@ import java.io.File private const val FRAMEWORK_PUBLICATION_NAME = "KMMBridgeFramework" private const val KMMBRIDGE_ARTIFACT_SUFFIX = "kmmbridge" -class MavenPublishArtifactManager( +internal class MavenPublishArtifactManager( private val publicationName: String?, private val artifactSuffix: String?, private val repositoryName: String?, diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/CocoapodsDependencyManager.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/CocoapodsDependencyManager.kt index 0dacf9f0..2842a637 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/CocoapodsDependencyManager.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/CocoapodsDependencyManager.kt @@ -14,11 +14,11 @@ package co.touchlab.kmmbridge.dependencymanager import co.touchlab.kmmbridge.TASK_GROUP_NAME -import co.touchlab.kmmbridge.cocoapods -import co.touchlab.kmmbridge.kmmBridgeExtension -import co.touchlab.kmmbridge.kotlin -import co.touchlab.kmmbridge.layoutBuildDir -import co.touchlab.kmmbridge.urlFile +import co.touchlab.kmmbridge.internal.cocoapods +import co.touchlab.kmmbridge.internal.kmmBridgeExtension +import co.touchlab.kmmbridge.internal.kotlin +import co.touchlab.kmmbridge.internal.layoutBuildDir +import co.touchlab.kmmbridge.internal.urlFile import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.Task @@ -31,12 +31,12 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.Framework import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import java.io.File -sealed class SpecRepo { +internal sealed class SpecRepo { object Trunk : SpecRepo() class Private(val url: String) : SpecRepo() } -class CocoapodsDependencyManager( +internal class CocoapodsDependencyManager( private val specRepoDeferred: () -> SpecRepo, private val allowWarnings: Boolean, private val verboseErrors: Boolean @@ -49,7 +49,7 @@ class CocoapodsDependencyManager( ) { val podSpecFile = - project.file("${project.layoutBuildDir}/faktory/podspec/${project.kmmBridgeExtension.buildType.get().name.lowercase()}/${project.kotlin.cocoapods.name}.podspec") + project.file("${project.layoutBuildDir}/kmmbridge/podspec/${project.kmmBridgeExtension.buildType.get().name.lowercase()}/${project.kotlin.cocoapods.name}.podspec") val generatePodspecTask = project.tasks.register("generateReleasePodspec") { inputs.files(project.urlFile) diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/SpmDependencyManager.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/SpmDependencyManager.kt index 9ff0dcea..e311e810 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/SpmDependencyManager.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/SpmDependencyManager.kt @@ -15,28 +15,35 @@ package co.touchlab.kmmbridge.dependencymanager import co.touchlab.kmmbridge.TASK_GROUP_NAME import co.touchlab.kmmbridge.dsl.TargetPlatformDsl -import co.touchlab.kmmbridge.findXCFrameworkAssembleTask import co.touchlab.kmmbridge.internal.domain.SwiftToolVersion import co.touchlab.kmmbridge.internal.domain.TargetPlatform import co.touchlab.kmmbridge.internal.domain.konanTarget import co.touchlab.kmmbridge.internal.domain.swiftPackagePlatformName -import co.touchlab.kmmbridge.kmmBridgeExtension -import co.touchlab.kmmbridge.kotlin -import co.touchlab.kmmbridge.layoutBuildDir -import co.touchlab.kmmbridge.urlFile -import co.touchlab.kmmbridge.zipFilePath +import co.touchlab.kmmbridge.internal.findXCFrameworkAssembleTask +import co.touchlab.kmmbridge.internal.kmmBridgeExtension +import co.touchlab.kmmbridge.internal.kotlin +import co.touchlab.kmmbridge.internal.layoutBuildDir +import co.touchlab.kmmbridge.internal.urlFile +import co.touchlab.kmmbridge.internal.zipFilePath import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.provider.ProviderFactory +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.withType +import org.gradle.process.ExecOperations +import org.gradle.process.internal.ExecException import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType +import java.io.ByteArrayOutputStream import java.io.File +import java.nio.charset.Charset import java.util.* +import javax.inject.Inject -class SpmDependencyManager( +internal class SpmDependencyManager( /** * Folder where the Package.swift file lives */ @@ -46,25 +53,18 @@ class SpmDependencyManager( private val _swiftToolVersion: String, private val _targetPlatforms: TargetPlatformDsl.() -> Unit, ) : DependencyManager { - -// private fun ProviderFactory.swiftPackageFolder(projectDir: File): String = -// _swiftPackageFolder ?: this.findRepoRoot(projectDir) -// /** * For new projects that aren't in git repos, it's *probably* OK to just return the current folder * until this is resolved, or let the user enter it manually. */ private fun Project.findRepoRoot(projectDir: File): String { - val results = providers.exec { - commandLine("git", "rev-parse", "--show-toplevel") - }.standardOutput.asText.get().lines() - - return if (results.isEmpty()) { - "." + val result = providers.of(GitRevParseValue::class.java) {}.get() + val repoRootFile = if (result == "") { + projectDir } else { - val repoFile = File(results.first()) - projectDir.toPath().relativize(repoFile.toPath()).toString() + File(result) } + return repoRootFile.toString() } private fun Project.swiftPackageFile(projectDir: File): File { @@ -84,7 +84,7 @@ class SpmDependencyManager( ?: throw IllegalArgumentException("Parameter swiftToolVersion should be not blank!") val platforms = swiftTargetPlatforms(project) - val swiftPackageFile = project.swiftPackageFile(project.projectDir) + val swiftPackageFile = project.swiftPackageFile(project.rootDir) val packageName = extension.frameworkName.get() if (useCustomPackageFile && !hasKmmbridgeVariablesSection(swiftPackageFile, packageName)) { project.logger.error(buildPackageFileErrorMessage(packageName, perModuleVariablesBlock)) @@ -201,7 +201,7 @@ class SpmDependencyManager( group = TASK_GROUP_NAME dependsOn(project.findXCFrameworkAssembleTask(NativeBuildType.DEBUG)) - val swiftPackageFile = project.swiftPackageFile(project.projectDir) + val swiftPackageFile = project.swiftPackageFile(project.rootDir) val layoutBuildDir = project.layoutBuildDir @Suppress("ObjectLiteralToLambda") @@ -246,6 +246,32 @@ class SpmDependencyManager( }.joinToString(separator = ",\n") } +/** + * Runs a git command to grab the root of the git repo. If there is no git repo, return an empty string. + */ +abstract class GitRevParseValue : ValueSource { + @get:Inject + abstract val execOperations: ExecOperations + + override fun obtain(): String { + val output = ByteArrayOutputStream() + val error = ByteArrayOutputStream() + return try { + execOperations.exec { + try { + commandLine("git", "rev-parse", "--show-toplevel") + standardOutput = output + errorOutput = error + } catch (e: Exception) { + } + } + String(output.toByteArray(), Charset.defaultCharset()).lines().first() + } catch (e: ExecException) { + "" + } + } +} + internal fun stripEndSlash(path: String): String { return if (path.endsWith("/")) { path.substring(0, path.length - 1) diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/internal/ProjectExtensionsInternal.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/internal/ProjectExtensionsInternal.kt new file mode 100644 index 00000000..7b158395 --- /dev/null +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/internal/ProjectExtensionsInternal.kt @@ -0,0 +1,59 @@ +package co.touchlab.kmmbridge.internal + +import co.touchlab.kmmbridge.KmmBridgeExtension +import co.touchlab.kmmbridge.findStringProperty +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.UnknownTaskException +import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.findByType +import org.gradle.kotlin.dsl.getByType +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType +import java.io.File + +internal val Project.layoutBuildDir get() = layout.buildDirectory.get().asFile + +internal val Project.kotlin: KotlinMultiplatformExtension get() = extensions.getByType() +internal val Project.kmmBridgeExtension get() = extensions.getByType() + +internal val Project.urlFile get() = file("$layoutBuildDir/kmmbridge/url") + +// Cocoapods is an extension of KMP extension, so you can't just do project.extensions.getByType() +internal val KotlinMultiplatformExtension.cocoapodsOrNull get() = (this as ExtensionAware).extensions.findByType() +internal val KotlinMultiplatformExtension.cocoapods + get() = cocoapodsOrNull + ?: error("You must apply the org.jetbrains.kotlin.native.cocoapods plugin to use cocoapods() configuration") + +// This previously defaulted to 'false', but now you can disable it if needed, but otherwise ignore +internal val Project.enablePublishing: Boolean + get() = project.findStringProperty("ENABLE_PUBLISHING")?.toBoolean() ?: false + +internal val Project.spmBuildTargets: String? + get() = project.findStringProperty("spmBuildTargets") + +@Suppress("SpellCheckingInspection") +internal fun Project.zipFilePath(): File { + val tempDir = file("$layoutBuildDir/kmmbridge/zip") + val artifactName = "frameworkarchive.zip" + return file("$tempDir/$artifactName") +} + +internal fun Project.findXCFrameworkAssembleTask(buildType: NativeBuildType? = null): TaskProvider { + val extension = extensions.getByType() + val name = extension.frameworkName.get() + val buildTypeString = (buildType ?: extension.buildType.get()).getName().capitalized() + val taskWithoutName = "assemble${buildTypeString}XCFramework" + val taskWithName = "assemble${name.capitalized()}${buildTypeString}XCFramework" + return runCatching { + tasks.named(taskWithName) + }.recoverCatching { + tasks.named(taskWithoutName) + }.getOrElse { + throw UnknownTaskException( + "Cannot find XCFramework assemble task. Tried $taskWithName and ${taskWithoutName}." + ) + } +} \ No newline at end of file diff --git a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/StringExtensions.kt b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/internal/StringExtensions.kt similarity index 63% rename from kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/StringExtensions.kt rename to kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/internal/StringExtensions.kt index ee00a352..4c5c8830 100644 --- a/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/StringExtensions.kt +++ b/kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/internal/StringExtensions.kt @@ -1,8 +1,8 @@ -package co.touchlab.kmmbridge +package co.touchlab.kmmbridge.internal import java.util.* -fun String.capitalized(): String { +internal fun String.capitalized(): String { return this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } diff --git a/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/ArtifactManagerTest.kt b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/ArtifactManagerTest.kt new file mode 100644 index 00000000..a6bf4c5f --- /dev/null +++ b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/ArtifactManagerTest.kt @@ -0,0 +1,43 @@ +package co.touchlab.kmmbridge + +import org.junit.jupiter.api.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ArtifactManagerTest : BasePluginTest() { + override fun testProjectPath(): String = "test-projects/basic" + + @Test + fun runKmmBridgePublishNoPublishingEnabled() { + val result = ProcessHelper.runSh( + "./gradlew kmmBridgePublish " + + "-PTOUCHLAB_TEST_ARTIFACT_SERVER=api.touchlab.dev " + + "-PTOUCHLAB_TEST_ARTIFACT_CODE=${TOUCHLAB_TEST_ARTIFACT_CODE} " + + "--stacktrace", workingDir = testProjectDir + ) + logExecResult(result) + assertEquals(1, result.status) + } + + @Test + fun runKmmBridgePublish() { + val urlFile = File(testProjectDir, "allshared/build/kmmbridge/url") + assertFalse(urlFile.exists()) + val result = ProcessHelper.runSh( + "./gradlew clean kmmBridgePublish " + + "-PENABLE_PUBLISHING=true " + + "-PTOUCHLAB_TEST_ARTIFACT_SERVER=api.touchlab.dev " + + "-PTOUCHLAB_TEST_ARTIFACT_CODE=${TOUCHLAB_TEST_ARTIFACT_CODE} " + + "--stacktrace", + workingDir = testProjectDir + ) + logExecResult(result) + + assertTrue(urlFile.exists()) + val urlValue = urlFile.readText() + assertTrue(urlValue.startsWith("https://api.touchlab.dev/infoadmin/streamTestZip")) + assertEquals(0, result.status) + } +} \ No newline at end of file diff --git a/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/BasePluginTest.kt b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/BasePluginTest.kt new file mode 100644 index 00000000..fa529eef --- /dev/null +++ b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/BasePluginTest.kt @@ -0,0 +1,50 @@ +package co.touchlab.kmmbridge + +import org.apache.commons.io.FileUtils +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.io.TempDir +import java.io.File +import java.io.FileInputStream +import java.util.* + +abstract class BasePluginTest { + @TempDir + lateinit var testProjectDir: File + internal val assumedRootProjectDir = File(File("..").absolutePath) + private val testProjectSource = File(assumedRootProjectDir, testProjectPath()) + + internal lateinit var settingsFile: File + internal lateinit var buildFile: File + internal lateinit var TOUCHLAB_TEST_ARTIFACT_CODE: String + + abstract fun testProjectPath(): String + + @BeforeEach + fun setup() { + TOUCHLAB_TEST_ARTIFACT_CODE = File("TOUCHLAB_TEST_ARTIFACT_CODE").readText().lines().first() + FileUtils.copyDirectory(testProjectSource, testProjectDir) + ProcessHelper.runSh("git init;git add .;git commit -m 'arst'", workingDir = testProjectDir) + settingsFile = File(testProjectDir, "settings.gradle.kts") + buildFile = File(testProjectDir, "build.gradle.kts") + } + + internal fun loadTestGradleProperties(): Properties { + val properties = Properties() + FileInputStream(File(testProjectDir, "gradle.properties")).use { stream -> + properties.load(stream) + } + return properties + } + + internal fun logExecResult(result: ExecutionResult) { + println("***********START***********") + println("Params: ${result.params.joinToString(" ")}") + println("Working dir: ${result.workingDir.absolutePath}") + + if (result.output.isNotEmpty()) + println(result.output) + if (result.error.isNotEmpty()) + System.err.println(result.error) + println("***********END***********") + } +} \ No newline at end of file diff --git a/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/NonKmmBridgeTasksTest.kt b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/NonKmmBridgeTasksTest.kt new file mode 100644 index 00000000..2a4710c1 --- /dev/null +++ b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/NonKmmBridgeTasksTest.kt @@ -0,0 +1,22 @@ +package co.touchlab.kmmbridge + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +/** + * Tests to ensure KMMBridge doesn't impact non-KMMBridge Gradle operation in any significant way. + */ +class NonKmmBridgeTasksTest : BasePluginTest() { + override fun testProjectPath(): String = "test-projects/basic" + + @Test + fun runBasicBuild() { + val result = ProcessHelper.runSh( + "./gradlew linkDebugFrameworkIosSimulatorArm64 " + + "-PTOUCHLAB_TEST_ARTIFACT_SERVER=api.touchlab.dev " + + "-PTOUCHLAB_TEST_ARTIFACT_CODE=${TOUCHLAB_TEST_ARTIFACT_CODE}", workingDir = testProjectDir + ) + logExecResult(result) + assertEquals(0, result.status) + } +} \ No newline at end of file diff --git a/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/ProcessHelper.kt b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/ProcessHelper.kt new file mode 100644 index 00000000..e05b8d9a --- /dev/null +++ b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/ProcessHelper.kt @@ -0,0 +1,76 @@ +package co.touchlab.kmmbridge + +import java.io.BufferedReader +import java.io.File +import java.io.InputStream +import java.io.InputStreamReader +import java.util.concurrent.atomic.AtomicReference + +object ProcessHelper { + fun runSh( + command: String, + envVars: Map = emptyMap(), + workingDir: File = File(".") + ): ExecutionResult { + return runParams("/bin/sh", "-c", command, envVars = envVars, workingDir = workingDir) + } + + fun runParams( + vararg params: String, + envVars: Map = emptyMap(), + workingDir: File = File(".") + ): ExecutionResult { + val processBuilder = ProcessBuilder(*params) + processBuilder.environment().putAll(envVars) + processBuilder.directory(workingDir) + val process = processBuilder + .start() + + val stdOut = readProcStream(process.inputStream) + val errOut = readProcStream(process.errorStream) + + val returnValue = process.waitFor() + + while (!stdOut.isDone && !errOut.isDone) { + Thread.sleep(1000) + } + + return ExecutionResult( + params = params.toList(), + workingDir = workingDir, + status = returnValue, + output = stdOut.result, + error = errOut.result + ) + } + + private fun readProcStream(iStream: InputStream): StreamCatcher { + val atom = AtomicReference("") + val t = Thread { + val bufferedReader = BufferedReader(InputStreamReader(iStream)) + val allOut = bufferedReader.readText() + + bufferedReader.close() + atom.set(allOut) + } + + t.start() + + return StreamCatcher(t, atom) + } + + private class StreamCatcher(val t: Thread, val atom: AtomicReference) { + val isDone: Boolean + get() = !t.isAlive + val result: String + get() = atom.get() + } +} + +data class ExecutionResult( + val params: List, + val workingDir: File, + val status: Int, + val output: String, + val error: String +) \ No newline at end of file diff --git a/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/SpmLocalDevTest.kt b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/SpmLocalDevTest.kt new file mode 100644 index 00000000..59a6d584 --- /dev/null +++ b/kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/SpmLocalDevTest.kt @@ -0,0 +1,34 @@ +package co.touchlab.kmmbridge + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class SpmLocalDevTest : BasePluginTest() { + override fun testProjectPath(): String = "test-projects/basic" + + @Test + fun runSpmDevBuild() { + val result = ProcessHelper.runSh( + "./gradlew spmDevBuild --stacktrace " + + "-PTOUCHLAB_TEST_ARTIFACT_SERVER=api.touchlab.dev " + + "-PTOUCHLAB_TEST_ARTIFACT_CODE=${TOUCHLAB_TEST_ARTIFACT_CODE}", workingDir = testProjectDir + ) + logExecResult(result) + assertEquals(0, result.status) + } + + /** + * Ensure that SPM local dev can load and run when there is no git repo set up. + */ + @Test + fun runSpmDevBuildNoGit() { + ProcessHelper.runSh("rm -rdf .git", workingDir = testProjectDir) + val result = ProcessHelper.runSh( + "./gradlew spmDevBuild --stacktrace " + + "-PTOUCHLAB_TEST_ARTIFACT_SERVER=api.touchlab.dev " + + "-PTOUCHLAB_TEST_ARTIFACT_CODE=${TOUCHLAB_TEST_ARTIFACT_CODE}", workingDir = testProjectDir + ) + logExecResult(result) + assertEquals(0, result.status) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index c747f009..37335306 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,3 +35,4 @@ rootProject.name = "KMMBridge" include(":kmmbridge") include(":kmmbridge-github") include(":kmmbridge-gitlab") +include(":kmmbridge-test") diff --git a/test-projects/basic/.gitignore b/test-projects/basic/.gitignore new file mode 100644 index 00000000..c0448de0 --- /dev/null +++ b/test-projects/basic/.gitignore @@ -0,0 +1,19 @@ + + +*.iml +.gradle +/local.properties +/.idea +.DS_Store +build +.build +/captures +.externalNativeBuild +.cxx +*.xcuserstate +*.xcbkptlist +!/.idea/codeStyles/* +!/.idea/inspectionProfiles/* +.kotlin + +.swiftpm \ No newline at end of file diff --git a/test-projects/basic/LICENSE.txt b/test-projects/basic/LICENSE.txt new file mode 100644 index 00000000..97d4aa3c --- /dev/null +++ b/test-projects/basic/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Touchlab + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/test-projects/basic/Package.swift b/test-projects/basic/Package.swift new file mode 100644 index 00000000..c3ef24ca --- /dev/null +++ b/test-projects/basic/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version:5.8 +import PackageDescription + +let packageName = "allshared" + +let package = Package( + name: packageName, + platforms: [ + .iOS(.v14) + ], + products: [ + .library( + name: packageName, + targets: [packageName] + ), + ], + targets: [ + .binaryTarget( + name: packageName, + path: "./allshared/build/XCFrameworks/debug/\(packageName).xcframework" + ) + , + ] +) \ No newline at end of file diff --git a/test-projects/basic/README.md b/test-projects/basic/README.md new file mode 100644 index 00000000..a82a04a5 --- /dev/null +++ b/test-projects/basic/README.md @@ -0,0 +1,9 @@ +# KMMBridge v1 SPM Template + +This is a template project for Kotlin Multiplatform using KMMBridge to publish Xcode Framework binaries. + +## Links + +[KMMBridge SPM Quick Start](https://touchlab.co/kmmbridge/spmquickstart) (For this template) +[KMMBridge v1 Blog Post](https://touchlab.co/kmmbridge-v1) +[KMMBridge Docs](https://touchlab.co/kmmbridge/) diff --git a/test-projects/basic/allshared/build.gradle.kts b/test-projects/basic/allshared/build.gradle.kts new file mode 100644 index 00000000..726ab837 --- /dev/null +++ b/test-projects/basic/allshared/build.gradle.kts @@ -0,0 +1,25 @@ +import co.touchlab.kmmbridge.test.TestArtifactManager + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kmmbridge) +} + +kotlin { + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { + isStatic = true + } + } +} + +kmmbridge { + testUploadArtifacts() + spm(swiftToolVersion = "5.8") { + iOS { v("14") } + } +} diff --git a/test-projects/basic/allshared/src/iosMain/kotlin/co.touchlab/kmmbridgetest/StartSDK.kt b/test-projects/basic/allshared/src/iosMain/kotlin/co.touchlab/kmmbridgetest/StartSDK.kt new file mode 100644 index 00000000..227a4e6a --- /dev/null +++ b/test-projects/basic/allshared/src/iosMain/kotlin/co.touchlab/kmmbridgetest/StartSDK.kt @@ -0,0 +1,3 @@ +package co.touchlab.kmmbridgetest + +fun sayHello() = "Hello from Kotlin!" \ No newline at end of file diff --git a/test-projects/basic/build.gradle.kts b/test-projects/basic/build.gradle.kts new file mode 100644 index 00000000..97a1309e --- /dev/null +++ b/test-projects/basic/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) apply false + alias(libs.plugins.kmmbridge) apply false +} + +subprojects { + val GROUP: String by project + val LIBRARY_VERSION: String by project + group = GROUP + version = LIBRARY_VERSION +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/test-projects/basic/gradle.properties b/test-projects/basic/gradle.properties new file mode 100644 index 00000000..a2228b4d --- /dev/null +++ b/test-projects/basic/gradle.properties @@ -0,0 +1,7 @@ +kotlin.code.style=official +android.useAndroidX=true +org.gradle.jvmargs=-Xmx4g + +LIBRARY_VERSION=0.1.8 +GROUP=co.touchlab.kmmbridgespmquickstart +org.gradle.configuration-cache=true \ No newline at end of file diff --git a/test-projects/basic/gradle/libs.versions.toml b/test-projects/basic/gradle/libs.versions.toml new file mode 100644 index 00000000..23cf0f13 --- /dev/null +++ b/test-projects/basic/gradle/libs.versions.toml @@ -0,0 +1,7 @@ +[versions] +kotlin = "2.0.10" +kmmBridge = "9.9.9" + +[plugins] +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kmmbridge = { id = "co.touchlab.kmmbridge.test", version.ref = "kmmBridge" } \ No newline at end of file diff --git a/test-projects/basic/gradle/wrapper/gradle-wrapper.jar b/test-projects/basic/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..033e24c4 Binary files /dev/null and b/test-projects/basic/gradle/wrapper/gradle-wrapper.jar differ diff --git a/test-projects/basic/gradle/wrapper/gradle-wrapper.properties b/test-projects/basic/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..3fa8f862 --- /dev/null +++ b/test-projects/basic/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/test-projects/basic/gradlew b/test-projects/basic/gradlew new file mode 100755 index 00000000..fcb6fca1 --- /dev/null +++ b/test-projects/basic/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/test-projects/basic/gradlew.bat b/test-projects/basic/gradlew.bat new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/test-projects/basic/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/test-projects/basic/settings.gradle.kts b/test-projects/basic/settings.gradle.kts new file mode 100644 index 00000000..dc38c30e --- /dev/null +++ b/test-projects/basic/settings.gradle.kts @@ -0,0 +1,20 @@ +pluginManagement { + // REPLACE_WITH_INCLUDE_BUILD + repositories { + mavenLocal() + google() + gradlePluginPortal() + mavenCentral() + } +} + +dependencyResolutionManagement { + @Suppress("UnstableApiUsage") + repositories { + mavenLocal() + google() + mavenCentral() + } +} + +include("allshared")