From de5fc2edd642b1902ce40154bda201f7ae33d67c Mon Sep 17 00:00:00 2001 From: Tom Tresansky Date: Thu, 15 Aug 2024 15:10:34 -0400 Subject: [PATCH] Update project specs to use samples from GH --- .../AbstractProjectInitSpecification.groovy | 63 ++++++++++++++ .../plugin-android/build.gradle.kts | 3 +- .../AndroidApplicationInitProjectSpec.groovy | 11 +++ .../buildinit/AndroidProjectSource.java | 25 ++++++ .../experimental/buildinit/package-info.java | 5 ++ ...e.buildinit.projectspecs.InitProjectSource | 1 + .../templates/android-application/README.md | 21 +++++ .../android-application/app/build.gradle.dcl | 8 ++ .../app/src/main/AndroidManifest.xml | 11 +++ .../kotlin/org/example/app/MainActivity.kt | 28 ++++++ .../kotlin/org/example/app/MessageUtils.kt | 5 ++ .../app/src/main/res/layout/activity_main.xml | 12 +++ .../app/src/main/res/values/strings.xml | 3 + .../org/example/app/MessageUtilsTest.kt | 12 +++ .../android-application/gradle.properties | 6 ++ .../android-application/list/build.gradle.dcl | 3 + .../kotlin/org/example/list/LinkedList.kt | 86 +++++++++++++++++++ .../android-application/local.properties | 8 ++ .../android-application/settings.gradle.dcl | 48 +++++++++++ .../utilities/build.gradle.dcl | 7 ++ .../kotlin/org/example/utilities/JoinUtils.kt | 17 ++++ .../org/example/utilities/SplitUtils.kt | 36 ++++++++ .../org/example/utilities/StringUtils.kt | 13 +++ .../plugin-common/build.gradle.kts | 10 +-- .../util/ResourceLoaderIntegrationTest.groovy | 2 +- .../buildinit/StaticProjectGenerator.java | 2 +- .../java/org/gradle/util/ResourceLoader.java | 40 +++++++-- .../org/gradle/util/ResourceLoaderTest.groovy | 7 +- .../plugin-jvm/build.gradle.kts | 20 ++++- .../jvm/JavaApplicationInitProjectSpec.groovy | 11 +++ .../buildinit/JVMProjectSource.java | 4 +- .../experimental/buildinit/package-info.java | 3 + .../templates/java-library/build.gradle.dcl | 7 -- .../java-library/settings.gradle.dcl | 10 --- .../main/java/com/example/lib/Library.java | 13 --- .../plugin-kmp/build.gradle.kts | 19 ++++ .../KotlinApplicationInitProjectSpec.groovy | 10 +++ .../buildinit/KMPProjectSource.java | 25 ++++++ ...e.buildinit.projectspecs.InitProjectSource | 1 + .../templates/kotlin-application/README.md | 19 ++++ .../kotlin-application/app/build.gradle.dcl | 7 ++ .../src/main/kotlin/org/example/app/App.kt | 14 +++ .../kotlin/org/example/app/MessageUtils.kt | 10 +++ .../org/example/app/MessageUtilsTest.kt | 14 +++ .../kotlin-application/list/build.gradle.dcl | 1 + .../kotlin/org/example/list/LinkedList.kt | 86 +++++++++++++++++++ .../kotlin/org/example/list/LinkedListTest.kt | 50 +++++++++++ .../kotlin-application/settings.gradle.dcl | 53 ++++++++++++ .../utilities/build.gradle.dcl | 5 ++ .../kotlin/org/example/utilities/JoinUtils.kt | 22 +++++ .../org/example/utilities/SplitUtils.kt | 42 +++++++++ .../org/example/utilities/StringUtils.kt | 18 ++++ 52 files changed, 903 insertions(+), 54 deletions(-) create mode 100644 unified-prototype/unified-plugin/internal-testing-utils/src/main/groovy/org/gradle/integtests/fixtures/AbstractProjectInitSpecification.groovy create mode 100644 unified-prototype/unified-plugin/plugin-android/src/integTest/groovy/org/gradle/api/experimental/android/AndroidApplicationInitProjectSpec.groovy create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/AndroidProjectSource.java create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/package-info.java create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/README.md create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/build.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/AndroidManifest.xml create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MainActivity.kt create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MessageUtils.kt create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/layout/activity_main.xml create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/values/strings.xml create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/gradle.properties create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/build.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/src/main/kotlin/org/example/list/LinkedList.kt create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/local.properties create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/settings.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/build.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt create mode 100644 unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt create mode 100644 unified-prototype/unified-plugin/plugin-jvm/src/integTest/groovy/org/gradle/api/experimental/jvm/JavaApplicationInitProjectSpec.groovy delete mode 100644 unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/build.gradle.dcl delete mode 100644 unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/settings.gradle.dcl delete mode 100644 unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/src/main/java/com/example/lib/Library.java create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/integTest/groovy/org/gradle/api/experimental/kmp/KotlinApplicationInitProjectSpec.groovy create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/java/org/gradle/api/experimental/buildinit/KMPProjectSource.java create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/README.md create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/build.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/App.kt create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/MessageUtils.kt create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/build.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/main/kotlin/org/example/list/LinkedList.kt create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/test/kotlin/org/example/list/LinkedListTest.kt create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/settings.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/build.gradle.dcl create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt create mode 100644 unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt diff --git a/unified-prototype/unified-plugin/internal-testing-utils/src/main/groovy/org/gradle/integtests/fixtures/AbstractProjectInitSpecification.groovy b/unified-prototype/unified-plugin/internal-testing-utils/src/main/groovy/org/gradle/integtests/fixtures/AbstractProjectInitSpecification.groovy new file mode 100644 index 00000000..f45a4676 --- /dev/null +++ b/unified-prototype/unified-plugin/internal-testing-utils/src/main/groovy/org/gradle/integtests/fixtures/AbstractProjectInitSpecification.groovy @@ -0,0 +1,63 @@ +package org.gradle.integtests.fixtures + +import org.gradle.internal.nativeintegration.console.TestOverrideConsoleDetector +import org.gradle.plugin.management.internal.autoapply.AutoAppliedPluginHandler +import org.gradle.test.fixtures.AbstractSpecification +import org.gradle.testkit.runner.GradleRunner + +/** + * Base class for tests that generate, build, and validate (via run, or something else) included project init specifications. + */ +abstract class AbstractProjectInitSpecification extends AbstractSpecification { + private static final String DECLARATIVE_PROTOTYPE_VERSION = "0.1.11" + + protected File projectDir = file("new-project").tap { mkdirs() } + + abstract String getPluginId() + + // TODO: add project type method here, specify type of project to be generated (need to update Gradle wrapper to new nightly containing getType() first) + + def "can generate project from init project spec"() { + when: + runInitWithPluginAsInitProjectSpecSupplier() + + then: + canBuildGeneratedProject() + + and: + validateGeneratedProjectRuns() + } + + protected void runInitWithPluginAsInitProjectSpecSupplier() { + def args = ["init", + "-D${AutoAppliedPluginHandler.INIT_PROJECT_SPEC_SUPPLIERS_PROP}=$pluginId:$DECLARATIVE_PROTOTYPE_VERSION", + "-D${TestOverrideConsoleDetector.INTERACTIVE_TOGGLE}=true"] as String[] + + result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(args) + .withPluginClasspath() + .withDebug(true) + .forwardOutput() + .build() + } + + protected void canBuildGeneratedProject() { + result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments("build") + .withDebug(true) + .forwardOutput() + .build() + } + + protected void validateGeneratedProjectRuns() { + result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(":app:run") + .forwardOutput() + .build() + + assert result.output.contains("Hello World!") + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/build.gradle.kts b/unified-prototype/unified-plugin/plugin-android/build.gradle.kts index eb186da6..de886a21 100644 --- a/unified-prototype/unified-plugin/plugin-android/build.gradle.kts +++ b/unified-prototype/unified-plugin/plugin-android/build.gradle.kts @@ -40,7 +40,6 @@ testing { dependencies { implementation(project(":internal-testing-utils")) - implementation(project()) } } @@ -49,6 +48,8 @@ testing { } gradlePlugin { + testSourceSets(project.sourceSets.getByName("integTest")) + plugins { create("android-library") { id = "org.gradle.experimental.android-library" diff --git a/unified-prototype/unified-plugin/plugin-android/src/integTest/groovy/org/gradle/api/experimental/android/AndroidApplicationInitProjectSpec.groovy b/unified-prototype/unified-plugin/plugin-android/src/integTest/groovy/org/gradle/api/experimental/android/AndroidApplicationInitProjectSpec.groovy new file mode 100644 index 00000000..6cf6fc6e --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/integTest/groovy/org/gradle/api/experimental/android/AndroidApplicationInitProjectSpec.groovy @@ -0,0 +1,11 @@ +package org.gradle.api.experimental.android + +import org.gradle.integtests.fixtures.AbstractProjectInitSpecification +import org.gradle.testkit.runner.GradleRunner + +class AndroidApplicationInitProjectSpec extends AbstractProjectInitSpecification { + @Override + String getPluginId() { + return "org.gradle.experimental.android-ecosystem" + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/AndroidProjectSource.java b/unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/AndroidProjectSource.java new file mode 100644 index 00000000..0f0c5f9d --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/AndroidProjectSource.java @@ -0,0 +1,25 @@ +package org.gradle.api.experimental.buildinit; + +import org.gradle.buildinit.projectspecs.InitProjectGenerator; +import org.gradle.buildinit.projectspecs.InitProjectSource; +import org.gradle.buildinit.projectspecs.InitProjectSpec; + +import java.util.List; + +/** + * A {@link InitProjectSource} of project specifications for Android projects. + */ +@SuppressWarnings("UnstableApiUsage") +public final class AndroidProjectSource implements InitProjectSource { + @Override + public List getProjectSpecs() { + return List.of( + new StaticProjectSpec("android-application", "Declarative Android Application Project") + ); + } + + @Override + public Class getProjectGenerator() { + return StaticProjectGenerator.class; + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/package-info.java b/unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/package-info.java new file mode 100644 index 00000000..ba6b56d5 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/java/org/gradle/api/experimental/buildinit/package-info.java @@ -0,0 +1,5 @@ +/** + * This package uses the experimental API for dynamically providing project specifications for Gradle's {@code init} build task. + */ +@org.gradle.api.NonNullApi +package org.gradle.api.experimental.buildinit; diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource b/unified-prototype/unified-plugin/plugin-android/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource new file mode 100644 index 00000000..27193ca1 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource @@ -0,0 +1 @@ +org.gradle.api.experimental.buildinit.AndroidProjectSource diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/README.md b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/README.md new file mode 100644 index 00000000..1402de36 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/README.md @@ -0,0 +1,21 @@ +# declarative-samples-android-app +A sample Android application written in the Declarative Gradle DSL, using the prototype Declarative Gradle `androidApplication` Software Type defined in the `org.gradle.experimental.android-ecosystem` ecosystem plugin. + +## Building and Running + +This sample shows the definition of a multiproject Android application implemented using Kotlin 1.9.23 source code. +The project is the result of reproducing the project produced by the `gradle init` command in Gradle 8.9 as an Android project. + +To build the project without running, use: + +```shell + ./gradlew build +``` + +To run the application, first install it on a connected Android device using: + +```shell + :app:installDebug +``` + +Then search for "Sample Declarative Gradle Android App" and launch app to see a hello world message. \ No newline at end of file diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/build.gradle.dcl b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/build.gradle.dcl new file mode 100644 index 00000000..0801a263 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/build.gradle.dcl @@ -0,0 +1,8 @@ +androidApplication { + namespace = "org.example.app" + + dependencies { + implementation("org.apache.commons:commons-text:1.11.0") + implementation(project(":utilities")) + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/AndroidManifest.xml b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..68131cf8 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MainActivity.kt b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MainActivity.kt new file mode 100644 index 00000000..1d8d7548 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MainActivity.kt @@ -0,0 +1,28 @@ +package org.example.app + +import org.apache.commons.text.WordUtils + +import org.example.list.LinkedList +import org.example.utilities.SplitUtils +import org.example.utilities.StringUtils + +import android.widget.TextView +import android.os.Bundle +import android.app.Activity + +class MainActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val textView = findViewById(R.id.textView) as TextView + textView.text = buildMessage() + } + + private fun buildMessage(): String { + val tokens: LinkedList + tokens = SplitUtils.split(MessageUtils.message()) + val result: String = StringUtils.join(tokens) + return WordUtils.capitalize(result) + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MessageUtils.kt b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MessageUtils.kt new file mode 100644 index 00000000..4d1fc1a9 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/kotlin/org/example/app/MessageUtils.kt @@ -0,0 +1,5 @@ +package org.example.app + +internal object MessageUtils { + fun message() = "Hello World!" +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/layout/activity_main.xml b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..60ae7dc8 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/values/strings.xml b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..a4e5ed21 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Sample Declarative Gradle Android App + diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt new file mode 100644 index 00000000..9d5ff979 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt @@ -0,0 +1,12 @@ +package org.example.app + +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.assertEquals + +class MessageUtilsTest { + @Test + fun testGetMessage() { + assertEquals("Hello World!", MessageUtils.message()) + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/gradle.properties b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/gradle.properties new file mode 100644 index 00000000..4b260430 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/gradle.properties @@ -0,0 +1,6 @@ +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/build.gradle.dcl b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/build.gradle.dcl new file mode 100644 index 00000000..168d0a1e --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/build.gradle.dcl @@ -0,0 +1,3 @@ +androidLibrary { + namespace = "org.gradle.experimental.android.list" +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/src/main/kotlin/org/example/list/LinkedList.kt b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/src/main/kotlin/org/example/list/LinkedList.kt new file mode 100644 index 00000000..388c15e6 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/list/src/main/kotlin/org/example/list/LinkedList.kt @@ -0,0 +1,86 @@ +package org.example.list + +class LinkedList { + private var head: Node? = null + + fun add(element: String?) { + val newNode = Node(element) + + val it = tail(head) + if (it == null) { + head = newNode + } else { + it.next = newNode + } + } + + fun remove(element: String): Boolean { + var result = false + var previousIt: Node? = null + var it: Node? + it = head + while (!result && it != null) { + if (0 == element.compareTo(it.data!!)) { + result = true + unlink(previousIt, it) + break + } + previousIt = it + it = it.next + } + + return result + } + + private fun unlink(previousIt: Node?, currentIt: Node) { + if (currentIt === head) { + head = currentIt.next + } else { + previousIt!!.next = currentIt.next + } + } + + fun size(): Int { + var size = 0 + + var it = head + while (it != null) { + ++size + it = it.next + } + + return size + } + + fun get(index: Int): String? { + var currIdx = index + var it = head + while (currIdx > 0 && it != null) { + it = it.next + currIdx-- + } + + if (it == null) { + throw java.lang.IndexOutOfBoundsException("Index is out of range") + } + + return it.data + } + + private class Node(val data: String?) { + var next: Node? = null + } + + companion object { + private fun tail(head: Node?): Node? { + var it: Node? + + it = head + while (it?.next != null) { + it = it.next + } + + return it + } + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/local.properties b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/local.properties new file mode 100644 index 00000000..f93e0191 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Tue Jul 30 10:37:16 EDT 2024 +sdk.dir=/Users/ttresansky/Library/Android/sdk diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/settings.gradle.dcl b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/settings.gradle.dcl new file mode 100644 index 00000000..6bc0bd78 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/settings.gradle.dcl @@ -0,0 +1,48 @@ +pluginManagement { + repositories { + google() // Needed for the Android plugin, applied by the unified plugin + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.experimental.android-ecosystem") version "0.1.7" +} + +rootProject.name = "example-android-app" + +include("app") +include("list") +include("utilities") + +defaults { + androidApplication { + jdkVersion = 11 + compileSdk = 34 + minSdk = 30 + + versionCode = 1 + versionName = "0.1" + applicationId = "org.gradle.experimental.android.app" + + testing { + dependencies { + implementation("org.junit.jupiter:junit-jupiter:5.10.2") + runtimeOnly("org.junit.platform:junit-platform-launcher") + } + } + } + + androidLibrary { + jdkVersion = 11 + compileSdk = 34 + minSdk = 30 + + testing { + dependencies { + implementation("org.junit.jupiter:junit-jupiter:5.10.2") + runtimeOnly("org.junit.platform:junit-platform-launcher") + } + } + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/build.gradle.dcl b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/build.gradle.dcl new file mode 100644 index 00000000..43495184 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/build.gradle.dcl @@ -0,0 +1,7 @@ +androidLibrary { + namespace = "org.gradle.experimental.android.utilities" + + dependencies { + api(project(":list")) + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt new file mode 100644 index 00000000..f65b98e7 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt @@ -0,0 +1,17 @@ +package org.example.utilities + +import org.example.list.LinkedList + +internal object JoinUtils { + fun join(source: LinkedList): String { + val result: java.lang.StringBuilder = java.lang.StringBuilder() + for (i in 0 until source.size()) { + if (result.length > 0) { + result.append(" ") + } + result.append(source.get(i)) + } + + return result.toString() + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt new file mode 100644 index 00000000..0b17d67b --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt @@ -0,0 +1,36 @@ +package org.example.utilities + +import org.example.list.LinkedList + +object SplitUtils { + fun split(source: String): LinkedList { + var lastFind = 0 + var currentFind: Int + val result: LinkedList = LinkedList() + + while ((source.indexOf(" ", lastFind).also { currentFind = it }) != -1) { + var token: String = source.substring(lastFind) + if (currentFind != -1) { + token = token.substring(0, currentFind - lastFind) + } + + addIfValid(token, result) + lastFind = currentFind + 1 + } + + val token: String = source.substring(lastFind) + addIfValid(token, result) + + return result + } + + private fun addIfValid(token: String, list: LinkedList) { + if (isTokenValid(token)) { + list.add(token) + } + } + + private fun isTokenValid(token: String): Boolean { + return !token.isEmpty() + } +} diff --git a/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt new file mode 100644 index 00000000..23dc2075 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-android/src/main/resources/templates/android-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt @@ -0,0 +1,13 @@ +package org.example.utilities + +import org.example.list.LinkedList + +object StringUtils { + fun join(source: LinkedList): String { + return JoinUtils.join(source) + } + + fun split(source: String): LinkedList { + return SplitUtils.split(source) + } +} diff --git a/unified-prototype/unified-plugin/plugin-common/build.gradle.kts b/unified-prototype/unified-plugin/plugin-common/build.gradle.kts index 321f2528..9321609b 100644 --- a/unified-prototype/unified-plugin/plugin-common/build.gradle.kts +++ b/unified-prototype/unified-plugin/plugin-common/build.gradle.kts @@ -1,3 +1,5 @@ +@file:Suppress("UnstableApiUsage") + plugins { `kotlin-dsl` id("build-logic.publishing") @@ -9,27 +11,25 @@ description = "Common APIs and implementation classes shared by the ecosystem sp dependencies { implementation(libs.android.agp.application) - implementation("commons-io:commons-io:2.8.0") + implementation("commons-io:commons-io:2.15.1") implementation(gradleApi()) } testing { suites { - @Suppress("UnstableApiUsage") val test by getting(JvmTestSuite::class) { useSpock("2.2-groovy-3.0") dependencies { - implementation("commons-io:commons-io:2.8.0") + implementation("commons-io:commons-io:2.15.1") } } - @Suppress("UnstableApiUsage") val integTest by registering(JvmTestSuite::class) { useSpock("2.2-groovy-3.0") dependencies { - implementation("commons-io:commons-io:2.8.0") + implementation("commons-io:commons-io:2.15.1") implementation(project(":plugin-jvm")) implementation(project()) } diff --git a/unified-prototype/unified-plugin/plugin-common/src/integTest/groovy/org/gradle/util/ResourceLoaderIntegrationTest.groovy b/unified-prototype/unified-plugin/plugin-common/src/integTest/groovy/org/gradle/util/ResourceLoaderIntegrationTest.groovy index b73aae1e..656725ca 100644 --- a/unified-prototype/unified-plugin/plugin-common/src/integTest/groovy/org/gradle/util/ResourceLoaderIntegrationTest.groovy +++ b/unified-prototype/unified-plugin/plugin-common/src/integTest/groovy/org/gradle/util/ResourceLoaderIntegrationTest.groovy @@ -15,7 +15,7 @@ class ResourceLoaderIntegrationTest extends Specification { ResourceLoader resourceLoader = new ResourceLoader() when: - resourceLoader.extractResourcesFromJar("templates/java-library", outputDir) + resourceLoader.extractDirectoryFromResources("templates/java-library", outputDir) then: assertOutputIs(['build.gradle.dcl', 'src/main/java/com/example/lib/Library.java']) diff --git a/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/api/experimental/buildinit/StaticProjectGenerator.java b/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/api/experimental/buildinit/StaticProjectGenerator.java index 5f221a04..8c95a482 100644 --- a/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/api/experimental/buildinit/StaticProjectGenerator.java +++ b/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/api/experimental/buildinit/StaticProjectGenerator.java @@ -23,7 +23,7 @@ public void generate(InitProjectConfig config, Directory projectDir) { ResourceLoader resourceLoader = new ResourceLoader(); try { - resourceLoader.extractResourcesFromJar(templatePath, projectDir.getAsFile()); + resourceLoader.extractDirectoryFromResources(templatePath, projectDir.getAsFile()); } catch (Exception e) { throw new RuntimeException("Error extracting resources for: '" + projectSpec.getDisplayName() + "' from: '" + templatePath + "'!", e); } diff --git a/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/util/ResourceLoader.java b/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/util/ResourceLoader.java index beabedd4..c90b0b44 100644 --- a/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/util/ResourceLoader.java +++ b/unified-prototype/unified-plugin/plugin-common/src/main/java/org/gradle/util/ResourceLoader.java @@ -9,27 +9,49 @@ import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; +import java.net.URLConnection; import java.util.Iterator; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** - * Static util class containing methods for loading resources from the classpath. + * Static util class containing methods for extracting resource directories from the classpath. */ public final class ResourceLoader { /** - * Recursively extracts the contents of a directory in a jar file on the classpath to a specified directory. + * Recursively copies the contents of a directory from the classpath (or a jar file on the classpath) + * to a specified directory. * - * @param relativePath path to the source directory within the jar file + * @param relativePath relative path to the source directory (or relative path within a jar file to the directory) * @param destDir target directory to extract the contents to * @throws IOException if an I/O error occurs */ - public void extractResourcesFromJar(String relativePath, File destDir) throws IOException { - URL jarDirURL = ResourceLoader.class.getClassLoader().getResource(relativePath); - if (jarDirURL == null) { - throw new IllegalArgumentException("Directory: '" + relativePath + "' not found on classpath."); + public void extractDirectoryFromResources(String relativePath, File destDir) throws IOException { + ClassLoader classLoader = ResourceLoader.class.getClassLoader(); + URL url = classLoader.getResource(relativePath); + if (url == null) { + throw new IllegalArgumentException("Directory: '" + relativePath + "' not found (on the classpath loaded by: '" + classLoader + "')!"); } - JarFile jarFile = ((JarURLConnection) jarDirURL.openConnection()).getJarFile(); + + URLConnection connection = url.openConnection(); + if (connection instanceof JarURLConnection) { + copyDirectoryFromJar(relativePath, destDir, (JarURLConnection) connection, classLoader); + } else { + copyDirectory(relativePath, destDir, connection, classLoader); + } + } + + private static void copyDirectory(String relativePath, File destDir, URLConnection connection, ClassLoader classLoader) throws IOException { + try { + File file = new File(connection.getURL().toURI()); + FileUtils.copyDirectory(file, destDir); + } catch (Exception e) { + throw new IOException("Error extracting: '" + relativePath + "' (from the root of the classpath loaded by: '" + classLoader + "')!", e); + } + } + + private static void copyDirectoryFromJar(String relativePath, File destDir, JarURLConnection connection, ClassLoader classLoader) throws IOException { + JarFile jarFile = connection.getJarFile(); Iterator iterator = jarFile.entries().asIterator(); while (iterator.hasNext()) { @@ -46,6 +68,8 @@ public void extractResourcesFromJar(String relativePath, File destDir) throws IO try (InputStream is = jarFile.getInputStream(entry); FileOutputStream fos = new FileOutputStream(destFile)) { IOUtils.copy(is, fos); + } catch (Exception e) { + throw new IOException("Error extracting: '" + entryName + "' from: '" + connection.getURL() + "' (from the root of the classpath loaded by: '" + classLoader + "')!", e); } } } diff --git a/unified-prototype/unified-plugin/plugin-common/src/test/groovy/org/gradle/util/ResourceLoaderTest.groovy b/unified-prototype/unified-plugin/plugin-common/src/test/groovy/org/gradle/util/ResourceLoaderTest.groovy index 482f957a..1160cbfc 100644 --- a/unified-prototype/unified-plugin/plugin-common/src/test/groovy/org/gradle/util/ResourceLoaderTest.groovy +++ b/unified-prototype/unified-plugin/plugin-common/src/test/groovy/org/gradle/util/ResourceLoaderTest.groovy @@ -5,14 +5,13 @@ import org.apache.commons.io.FileUtils import spock.lang.Specification class ResourceLoaderTest extends Specification { - def "can load resource from jar file"() { + def "can extract resource directory"() { given: File output = new File("output").tap { mkdirs() } - ResourceLoader resourceLoader = new ResourceLoader(this.getClass().getClassLoader()) + ResourceLoader resourceLoader = new ResourceLoader() when: - File templatesDir = resourceLoader.getResource("templates/java-library") - FileUtils.copyDirectory(templatesDir, output) + resourceLoader.extractDirectoryFromResources("templates/java-library", output) then: FileUtils.listFiles(output, null, true)*.path == ['output/build.gradle.dcl', 'output/src/main/java/com/example/lib/Library.java'] diff --git a/unified-prototype/unified-plugin/plugin-jvm/build.gradle.kts b/unified-prototype/unified-plugin/plugin-jvm/build.gradle.kts index 40506b4c..21e8e932 100644 --- a/unified-prototype/unified-plugin/plugin-jvm/build.gradle.kts +++ b/unified-prototype/unified-plugin/plugin-jvm/build.gradle.kts @@ -1,18 +1,36 @@ +@file:Suppress("UnstableApiUsage") + plugins { `kotlin-dsl` id("build-logic.publishing") + groovy // For spock testing } description = "Implements the declarative JVM DSL prototype" dependencies { implementation(project(":plugin-common")) - implementation("commons-io:commons-io:2.8.0") implementation("org.gradle.toolchains:foojay-resolver:0.8.0") implementation(gradleApi()) } +testing { + suites { + val integTest by registering(JvmTestSuite::class) { + useSpock("2.2-groovy-3.0") + + dependencies { + implementation(project(":internal-testing-utils")) + } + } + + tasks.getByPath("check").dependsOn(integTest) + } +} + gradlePlugin { + testSourceSets(project.sourceSets.getByName("integTest")) + plugins { create("jvm-library") { id = "org.gradle.experimental.jvm-library" diff --git a/unified-prototype/unified-plugin/plugin-jvm/src/integTest/groovy/org/gradle/api/experimental/jvm/JavaApplicationInitProjectSpec.groovy b/unified-prototype/unified-plugin/plugin-jvm/src/integTest/groovy/org/gradle/api/experimental/jvm/JavaApplicationInitProjectSpec.groovy new file mode 100644 index 00000000..2fe66f27 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-jvm/src/integTest/groovy/org/gradle/api/experimental/jvm/JavaApplicationInitProjectSpec.groovy @@ -0,0 +1,11 @@ +//file:noinspection GroovyAssignabilityCheck +package org.gradle.api.experimental.jvm + +import org.gradle.integtests.fixtures.AbstractProjectInitSpecification + +class JavaApplicationInitProjectSpec extends AbstractProjectInitSpecification { + @Override + String getPluginId() { + return "org.gradle.experimental.jvm-ecosystem" + } +} diff --git a/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/JVMProjectSource.java b/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/JVMProjectSource.java index 1ddea38c..934909da 100644 --- a/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/JVMProjectSource.java +++ b/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/JVMProjectSource.java @@ -1,6 +1,5 @@ package org.gradle.api.experimental.buildinit; -import java.util.Arrays; import java.util.List; import org.gradle.buildinit.projectspecs.InitProjectGenerator; @@ -14,8 +13,7 @@ public final class JVMProjectSource implements InitProjectSource { @Override public List getProjectSpecs() { - return Arrays.asList( - new StaticProjectSpec("java-library", "Declarative Java Library Project"), + return List.of( new StaticProjectSpec("java-application", "Declarative Java Application Project") ); } diff --git a/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/package-info.java b/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/package-info.java index 01fe064b..ba6b56d5 100644 --- a/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/package-info.java +++ b/unified-prototype/unified-plugin/plugin-jvm/src/main/java/org/gradle/api/experimental/buildinit/package-info.java @@ -1,2 +1,5 @@ +/** + * This package uses the experimental API for dynamically providing project specifications for Gradle's {@code init} build task. + */ @org.gradle.api.NonNullApi package org.gradle.api.experimental.buildinit; diff --git a/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/build.gradle.dcl b/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/build.gradle.dcl deleted file mode 100644 index 22cc9c76..00000000 --- a/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/build.gradle.dcl +++ /dev/null @@ -1,7 +0,0 @@ -javaLibrary { - javaVersion = 21 - - dependencies { - implementation("com.google.guava:guava:32.1.3-jre") - } -} diff --git a/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/settings.gradle.dcl b/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/settings.gradle.dcl deleted file mode 100644 index b9e24938..00000000 --- a/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/settings.gradle.dcl +++ /dev/null @@ -1,10 +0,0 @@ -pluginManagement { - repositories { - google() // for Android plugin - gradlePluginPortal() - } -} - -plugins { - id("org.gradle.experimental.jvm-ecosystem") version "0.1.10" -} diff --git a/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/src/main/java/com/example/lib/Library.java b/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/src/main/java/com/example/lib/Library.java deleted file mode 100644 index b6d22b4f..00000000 --- a/unified-prototype/unified-plugin/plugin-jvm/src/main/resources/templates/java-library/src/main/java/com/example/lib/Library.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.lib; - -import com.google.common.collect.ImmutableList; - -public class Library { - public Iterable getMessages() { - // Verify that Guava is available - ImmutableList.Builder builder = ImmutableList.builder(); - builder.add("Hello from Java " + System.getProperty("java.version")); - - return builder.build(); - } -} diff --git a/unified-prototype/unified-plugin/plugin-kmp/build.gradle.kts b/unified-prototype/unified-plugin/plugin-kmp/build.gradle.kts index d3a9cefa..40baf3ed 100644 --- a/unified-prototype/unified-plugin/plugin-kmp/build.gradle.kts +++ b/unified-prototype/unified-plugin/plugin-kmp/build.gradle.kts @@ -1,6 +1,9 @@ +@file:Suppress("UnstableApiUsage") + plugins { `kotlin-dsl` id("build-logic.publishing") + groovy // For spock testing } description = "Implements the declarative KMP DSL prototype" @@ -12,7 +15,23 @@ dependencies { implementation(libs.kotlin.jvm) } +testing { + suites { + val integTest by registering(JvmTestSuite::class) { + useSpock("2.2-groovy-3.0") + + dependencies { + implementation(project(":internal-testing-utils")) + } + } + + tasks.getByPath("check").dependsOn(integTest) + } +} + gradlePlugin { + testSourceSets(project.sourceSets.getByName("integTest")) + plugins { create("kmp-library") { id = "org.gradle.experimental.kmp-library" diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/integTest/groovy/org/gradle/api/experimental/kmp/KotlinApplicationInitProjectSpec.groovy b/unified-prototype/unified-plugin/plugin-kmp/src/integTest/groovy/org/gradle/api/experimental/kmp/KotlinApplicationInitProjectSpec.groovy new file mode 100644 index 00000000..36ab19a5 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/integTest/groovy/org/gradle/api/experimental/kmp/KotlinApplicationInitProjectSpec.groovy @@ -0,0 +1,10 @@ +package org.gradle.api.experimental.kmp + +import org.gradle.integtests.fixtures.AbstractProjectInitSpecification + +class KotlinApplicationInitProjectSpec extends AbstractProjectInitSpecification { + @Override + String getPluginId() { + return "org.gradle.experimental.kmp-ecosystem" + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/java/org/gradle/api/experimental/buildinit/KMPProjectSource.java b/unified-prototype/unified-plugin/plugin-kmp/src/main/java/org/gradle/api/experimental/buildinit/KMPProjectSource.java new file mode 100644 index 00000000..1f57b093 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/java/org/gradle/api/experimental/buildinit/KMPProjectSource.java @@ -0,0 +1,25 @@ +package org.gradle.api.experimental.buildinit; + +import org.gradle.buildinit.projectspecs.InitProjectGenerator; +import org.gradle.buildinit.projectspecs.InitProjectSource; +import org.gradle.buildinit.projectspecs.InitProjectSpec; + +import java.util.List; + +/** + * A {@link InitProjectSource} of project specifications for Kotlin (JVM) projects. + */ +@SuppressWarnings("UnstableApiUsage") +public final class KMPProjectSource implements InitProjectSource{ + @Override + public List getProjectSpecs() { + return List.of( + new StaticProjectSpec("kotlin-application", "Declarative Kotlin (JVM) Application Project") + ); + } + + @Override + public Class getProjectGenerator() { + return StaticProjectGenerator.class; + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource new file mode 100644 index 00000000..8dd7c661 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/META-INF/services/org.gradle.buildinit.projectspecs.InitProjectSource @@ -0,0 +1 @@ +org.gradle.api.experimental.buildinit.KMPProjectSource diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/README.md b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/README.md new file mode 100644 index 00000000..8b482ff9 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/README.md @@ -0,0 +1,19 @@ +# declarative-samples-kotlin-app +A sample Kotlin application written in the Declarative Gradle DSL, using the prototype Declarative Gradle `kotlinJvmApplication` Software Type defined in the `org.gradle.experimental.kmp-ecosystem` ecosystem plugin. + +## Building and Running + +This sample shows the definition of a Kotlin JVM application implemented using Kotlin 1.9.23 source code. +The project is the result of converting the project produced by the `gradle init` command in Gradle 8.9. + +To build and test the application without running, use: + +```shell +> ./gradlew build +``` + +To run the application, use: + +```shell +> ./gradlew run +``` diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/build.gradle.dcl b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/build.gradle.dcl new file mode 100644 index 00000000..6cfd5f47 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/build.gradle.dcl @@ -0,0 +1,7 @@ +kotlinJvmApplication { + mainClass = "org.example.app.AppKt" + + dependencies { + implementation(project(":utilities")) + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/App.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/App.kt new file mode 100644 index 00000000..d42f21cc --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/App.kt @@ -0,0 +1,14 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.app + +import org.example.utilities.StringUtils + +import org.apache.commons.text.WordUtils + +fun main() { + val tokens = StringUtils.split(MessageUtils.getMessage()) + val result = StringUtils.join(tokens) + println(WordUtils.capitalize(result)) +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/MessageUtils.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/MessageUtils.kt new file mode 100644 index 00000000..7c20c773 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/main/kotlin/org/example/app/MessageUtils.kt @@ -0,0 +1,10 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.app + +class MessageUtils { + companion object { + fun getMessage(): String = "Hello World!" + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt new file mode 100644 index 00000000..a1a67c85 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/app/src/test/kotlin/org/example/app/MessageUtilsTest.kt @@ -0,0 +1,14 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.app + +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.assertEquals + +class MessageUtilsTest { + @Test fun testGetMessage() { + assertEquals("Hello World!", MessageUtils.getMessage()) + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/build.gradle.dcl b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/build.gradle.dcl new file mode 100644 index 00000000..99988967 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/build.gradle.dcl @@ -0,0 +1 @@ +kotlinJvmLibrary {} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/main/kotlin/org/example/list/LinkedList.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/main/kotlin/org/example/list/LinkedList.kt new file mode 100644 index 00000000..dbe12220 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/main/kotlin/org/example/list/LinkedList.kt @@ -0,0 +1,86 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.list + +class LinkedList { + private var head: Node? = null + + fun add(element: String) { + val newNode = Node(element) + + val it = tail(head) + if (it == null) { + head = newNode + } else { + it.next = newNode + } + } + + private fun tail(head: Node?): Node? { + var it: Node? + + it = head + while (it?.next != null) { + it = it.next + } + + return it + } + + fun remove(element: String): Boolean { + var result = false + var previousIt: Node? = null + var it: Node? = head + while (!result && it != null) { + if (0 == element.compareTo(it.data)) { + result = true + unlink(previousIt, it) + break + } + previousIt = it + it = it.next + } + + return result + } + + private fun unlink(previousIt: Node?, currentIt: Node) { + if (currentIt == head) { + head = currentIt.next + } else { + previousIt?.next = currentIt.next + } + } + + fun size(): Int { + var size = 0 + + var it = head + while (it != null) { + ++size + it = it.next + } + + return size + } + + fun get(idx: Int): String { + var index = idx + var it = head + while (index > 0 && it != null) { + it = it.next + index-- + } + + if (it == null) { + throw IndexOutOfBoundsException("Index is out of range") + } + + return it.data + } + + private data class Node(val data: String) { + var next: Node? = null + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/test/kotlin/org/example/list/LinkedListTest.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/test/kotlin/org/example/list/LinkedListTest.kt new file mode 100644 index 00000000..63a9738d --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/list/src/test/kotlin/org/example/list/LinkedListTest.kt @@ -0,0 +1,50 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.list + +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +class LinkedListTest { + @Test fun testConstructor() { + val list = LinkedList() + assertEquals(0, list.size()) + } + + @Test fun testAdd() { + val list = LinkedList() + + list.add("one") + assertEquals(1, list.size()) + assertEquals("one", list.get(0)) + + list.add("two") + assertEquals(2, list.size()) + assertEquals("two", list.get(1)) + } + + @Test fun testRemove() { + val list = LinkedList() + + list.add("one") + list.add("two") + assertTrue(list.remove("one")) + + assertEquals(1, list.size()) + assertEquals("two", list.get(0)) + + assertTrue(list.remove("two")) + assertEquals(0, list.size()) + } + + @Test fun testRemoveMissing() { + val list = LinkedList() + + list.add("one") + list.add("two") + assertFalse(list.remove("three")) + assertEquals(2, list.size()) + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/settings.gradle.dcl b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/settings.gradle.dcl new file mode 100644 index 00000000..f3c725b8 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/settings.gradle.dcl @@ -0,0 +1,53 @@ +pluginManagement { + repositories { + google() // Needed for the Android plugin, applied by the unified plugin + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.experimental.kmp-ecosystem") version "0.1.7" +} + +dependencyResolutionManagement { + repositories { + google() // Needed for the linter plugin, used by the unified plugin + } +} + +rootProject.name = "example-kotlin-jvm-app" + +include("app") +include("list") +include("utilities") + +defaults { + kotlinJvmApplication { + javaVersion = 21 + + dependencies { + implementation("org.apache.commons:commons-text:1.11.0") + } + + testing { + dependencies { + implementation("org.junit.jupiter:junit-jupiter:5.10.2") + runtimeOnly("org.junit.platform:junit-platform-launcher") + } + } + } + + kotlinJvmLibrary { + javaVersion = 21 + + dependencies { + implementation("org.apache.commons:commons-text:1.11.0") + } + testing { + dependencies { + implementation("org.junit.jupiter:junit-jupiter:5.10.2") + runtimeOnly("org.junit.platform:junit-platform-launcher") + } + } + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/build.gradle.dcl b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/build.gradle.dcl new file mode 100644 index 00000000..f5def0b7 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/build.gradle.dcl @@ -0,0 +1,5 @@ +kotlinJvmLibrary { + dependencies { + api(project(":list")) + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt new file mode 100644 index 00000000..0173d35a --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/JoinUtils.kt @@ -0,0 +1,22 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.utilities + +import org.example.list.LinkedList + +class JoinUtils { + companion object { + fun join(source: LinkedList): String { + val result = StringBuilder() + for (i in 0 until source.size()) { + if (result.isNotEmpty()) { + result.append(" ") + } + result.append(source.get(i)) + } + + return result.toString() + } + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt new file mode 100644 index 00000000..170ac5d6 --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/SplitUtils.kt @@ -0,0 +1,42 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.utilities + +import org.example.list.LinkedList + +class SplitUtils { + companion object { + fun split(source: String): LinkedList { + var lastFind = 0 + val result = LinkedList() + + var currentFind = source.indexOf(" ", lastFind) + while (currentFind != -1) { + var token = source.substring(lastFind) + if (currentFind != -1) { + token = token.substring(0, currentFind - lastFind) + } + + addIfValid(token, result) + lastFind = currentFind + 1 + currentFind = source.indexOf(" ", lastFind) + } + + val token = source.substring(lastFind) + addIfValid(token, result) + + return result + } + + private fun addIfValid(token: String, list: LinkedList) { + if (isTokenValid(token)) { + list.add(token) + } + } + + private fun isTokenValid(token: String): Boolean { + return token.isNotEmpty() + } + } +} diff --git a/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt new file mode 100644 index 00000000..6e8d819a --- /dev/null +++ b/unified-prototype/unified-plugin/plugin-kmp/src/main/resources/templates/kotlin-application/utilities/src/main/kotlin/org/example/utilities/StringUtils.kt @@ -0,0 +1,18 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example.utilities + +import org.example.list.LinkedList + +class StringUtils { + companion object { + fun join(source: LinkedList): String { + return JoinUtils.join(source) + } + + fun split(source: String): LinkedList { + return SplitUtils.split(source) + } + } +}