Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a gradle plugin #30

Merged
merged 6 commits into from
Apr 30, 2024
Merged

feat: Add a gradle plugin #30

merged 6 commits into from
Apr 30, 2024

Conversation

warnyul
Copy link
Member

@warnyul warnyul commented Apr 29, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced GitHub Actions to include a step for publishing a Gradle plugin.
    • Introduced new Gradle plugin configurations and dependencies for improved project setup and management.
    • Added Maven Central publishing setup and GPG signing capabilities.
    • Updated documentation to reflect new configurations and plugin details.
  • Bug Fixes

    • Improved Android unit testing configuration by integrating Robolectric with JUnit Jupiter.
  • Documentation

    • Updated README.md with new repository additions and plugin usage instructions.
  • Chores

    • Added new scripts and configurations for building and publishing processes.

@warnyul warnyul self-assigned this Apr 29, 2024
Copy link
Contributor

coderabbitai bot commented Apr 29, 2024

Warning

Rate Limit Exceeded

@warnyul has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 17 minutes and 5 seconds before requesting another review.

How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.
Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
Please see our FAQ for further information.

Commits Files that changed from the base of the PR and between 22caea0 and a8bd713.

Walkthrough

This update enhances a project's Gradle configuration by integrating a new plugin for JUnit 5 and Robolectric, improving Maven Central publishing setups, and updating GitHub Actions to include a new publishing step. It also extends repository configurations and updates dependencies, ensuring a streamlined development and deployment process for Android testing frameworks.

Changes

Files Summary
.github/workflows/build.yml Added publishing step; modified build step to include multiple tasks.
.idea/gradle.xml, README.md, settings.gradle Updated Gradle setup, repositories, and new plugin configurations.
build.gradle, gradle/libs.versions.toml, gradle/*.gradle Updated dependencies; introduced Maven Central setup, GPG signing, and Dokka documentation configurations.
integration-tests/agp-groovy-dsl/build.gradle, integration-tests/agp-kotlin-dsl/build.gradle.kts Updated build scripts with new plugin imports and configurations.
publishGradlePlugin.sh, robolectric-extension-gradle-plugin/*, robolectric-extension/build.gradle Introduced scripts, Kotlin classes, and updated plugin configurations.

🐇✨
In the burrow of code, beneath the digital sky,
A rabbit hopped with a gleam in its eye.
"A new plugin!" it squeaked with joyous acclaim,
For tests that shine bright, and never the same!
With each commit and push, its heart did swell,
In the land of code, where the crafty rabbits dwell. 🌟🌿


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0a47460..8a04822 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -22,8 +22,15 @@ jobs:
           distribution: 'temurin'
       - name: Setup Gradle
         uses: gradle/actions@db19848 # v3.3.2
+      - name: Publish Gradle Plugin
+        run: |
+          ./publishGradlePlugin.sh
+        env:
+          JUNIT5_ROBOLECTRIC_EXTENSION_GRADLE_PLUGIN_PORTAL_KEY: '${{ secrets.JUNIT5_ROBOLECTRIC_EXTENSION_GRADLE_PLUGIN_PORTAL_KEY }}'
+          JUNIT5_ROBOLECTRIC_EXTENSION_GRADLE_PLUGIN_PORTAL_SECRET: '${{ secrets.JUNIT5_ROBOLECTRIC_EXTENSION_GRADLE_PLUGIN_PORTAL_SECRET }}'
       - name: Build with Gradle Wrapper
-        run: ./gradlew build koverXmlReport publish
+        run: |
+          ./gradlew build koverXmlReport publish
         env:
           JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_USERNAME: '${{ secrets.JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_USERNAME }}'
           JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_PASSWORD: '${{ secrets.JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_PASSWORD }}'
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 9efa103..b91666f 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -26,6 +26,7 @@
             <option value="$PROJECT_DIR$/integration-tests/agp-groovy-dsl" />
             <option value="$PROJECT_DIR$/integration-tests/agp-kotlin-dsl" />
             <option value="$PROJECT_DIR$/robolectric-extension" />
+            <option value="$PROJECT_DIR$/robolectric-extension-gradle-plugin" />
           </set>
         </option>
         <option name="resolveExternalAnnotations" value="false" />
diff --git a/README.md b/README.md
index 2caaf66..c72b713 100644
--- a/README.md
+++ b/README.md
@@ -21,14 +21,25 @@ in-memory environment.

 ## Installation

-1. Add the Maven Central repository to your project's `build.gradle`:
+1. Add the Gradle Plugin Portal and Maven Central and Google's Maven repository to your project's `settings.gradle` file:

 <details open>
 <summary>Kotlin</summary>

 ```kotlin
-repositories {
-    mavenCentral()
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        mavenCentral()
+        google()
+    }
+}
+
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        google()
+    }
 }
 ```

@@ -38,8 +49,19 @@ repositories {
 <summary>Groovy</summary>

 ```groovy
-repositories {
-    mavenCentral()
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        mavenCentral()
+        google()
+    }
+}
+
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        google()
+    }
 }
 ```

@@ -52,30 +74,8 @@ repositories {
 <summary>Kotlin</summary>

 ```kotlin
-android {
-    testOptions {
-        unitTests {
-            isIncludeAndroidResources = true
-            all { test ->
-                test.useJUnitPlatform()
-                test.jvmArgs(
-                    listOf(
-                        "-Djunit.platform.launcher.interceptors.enabled=true",
-                        "--add-exports", "java.base/jdk.internal.loader=ALL-UNNAMED",
-                        "--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED",
-                    )
-                )
-            }
-        }
-    }
-}
-
-dependencies {
-    testImplementation("tech.apter.junit.jupiter:robolectric-extension:<latest.release>")
-    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:<latest.release>") // JUnit 5 Jupiter Engine
-    // Optional dependencies if you want to use different version than used by the extension
-    testImplementation("org.junit.jupiter:junit-jupiter-api:<latest.release>") // Latest JUnit 5 Jupiter API
-    testImplementation("org.robolectric:robolectric:<latest.release>") // Latest Robolectric version
+plugins {
+    id("tech.apter.junit5.jupiter.robolectric-extension-gradle-plugin")
 }
 ```

@@ -85,29 +85,8 @@ dependencies {
 <summary>Groovy</summary>

 ```groovy
-android {
-    testOptions {
-        unitTests {
-            includeAndroidResources = true
-            all {
-                useJUnitPlatform()
-                jvmArgs(
-                    '-Djunit.platform.launcher.interceptors.enabled=true',
-                    '--add-exports', 'java.base/jdk.internal.loader=ALL-UNNAMED',
-                    '--add-opens', 'java.base/jdk.internal.loader=ALL-UNNAMED',
-                )
-            }
-        }
-    }
-}
-
-dependencies {
-    testImplementation 'tech.apter.junit.jupiter:robolectric-extension:<latest.release>'
-    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:<latest.release>'
-    // Latest JUnit 5 Jupiter Engine
-    // Optional dependencies if you want to use different versions than used by the extension
-    testImplementation 'org.junit.jupiter:junit-jupiter-api:<latest.release>' // Latest JUnit 5 Jupiter API
-    testImplementation 'org.robolectric:robolectric:<latest.release>' // Latest Robolectric version
+plugins {
+    id 'tech.apter.junit5.jupiter.robolectric-extension-gradle-plugin'
 }
 ```

@@ -270,10 +249,5 @@ public class RobolectricExtensionSelfTest {

 ## Important Notes

-* Ensure `isIncludeAndroidResources` is set to true in your testOptions configuration to access
-  Android resources in your tests.
-* JUnit Platform Launcher Interceptors must be
-  enabled (`junit.platform.launcher.interceptors.enabled=true`), otherwise
-  test instances will not be created by Robolectric's classloader.
 * Parallel execution is currently not supported. Run tests sequentially for now.

diff --git a/build.gradle b/build.gradle
index cdd3977..f9542bc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,8 +7,17 @@ allprojects {
     version = property('tech.apter.junit5.robolectric.extension.version')
 }

+subprojects {
+    configurations.configureEach {
+        resolutionStrategy.dependencySubstitution {
+            substitute module("tech.apter.junit5.jupiter:robolectric-extension:$version") using project(':robolectric-extension')
+        }
+    }
+}
+
 dependencies {
     kover(project(':integration-tests:agp-groovy-dsl'))
     kover(project(':integration-tests:agp-kotlin-dsl'))
     kover(project(':robolectric-extension'))
+    kover(project(':robolectric-extension-gradle-plugin'))
 }
diff --git a/gradle/dokka.gradle b/gradle/dokka.gradle
new file mode 100644
index 0000000..ebb32eb
--- /dev/null
+++ b/gradle/dokka.gradle
@@ -0,0 +1,5 @@
+tasks.register('dokkaJavadocJar', Jar) {
+    dependsOn dokkaJavadoc
+    archiveClassifier.set 'javadoc'
+    from javadoc.destinationDir
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 01785af..4b94201 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -4,8 +4,10 @@ androidCompileSdk = "34"
 androidGradle = "8.3.2"
 androidMinimumSdk = "14"
 androidxTestExtJunit = "1.1.5"
+buildConfig = "5.3.5"
 detekt = "1.23.6"
 dokka = "1.9.20"
+gradlePluginPublish = "1.2.1"
 guava = "33.1.0-jre"
 junit4 = "4.13.2"
 junit5 = "5.10.2"
@@ -37,8 +39,10 @@ robolectricAndroidAll = { module = "org.robolectric:android-all", version.ref =

 [plugins]
 androidLibrary = { id = "com.android.library", version.ref = "androidGradle" }
+buildConfig = { id = "com.github.gmazzo.buildconfig", version.ref = "buildConfig" }
 detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
 dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
+gradlePluginPublish = { id = "com.gradle.plugin-publish", version.ref = "gradlePluginPublish" }
 kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
 kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 kotlinxKover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
diff --git a/gradle/pom.gradle b/gradle/pom.gradle
new file mode 100644
index 0000000..46a8cb9
--- /dev/null
+++ b/gradle/pom.gradle
@@ -0,0 +1,37 @@
+final void setupPom(final MavenPublication publication, final String publicationName) {
+    publication.pom { pom ->
+        name = publicationName
+        description = """This repository aims to bridge the gap between JUnit 5 and Robolectric,
+                    |enabling developers to leverage the benefits of both frameworks
+                    |for unit testing Android applications. While Robolectric currently lacks
+                    |a dedicated JUnit 5 extension, this project proposes a community-driven solution to
+                    |achieve seamless integration.""".stripMargin()
+        url = 'https://github.com/apter-tech/junit5-robolectric-extension'
+        licenses {
+            license {
+                name = 'The Apache License, Version 2.0'
+                url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
+            }
+        }
+        organization {
+            name = 'Apter Technologies Ltd.'
+            url = 'https://apter.tech'
+        }
+        developers {
+            developer {
+                id = 'warnyul'
+                name = 'Balázs Varga'
+                email = '[email protected]'
+            }
+        }
+        scm {
+            connection = 'scm:git:git://github.com:apter-tech/junit5-robolectric-extension.git'
+            developerConnection = 'scm:git:ssh://github.com:apter-tech/junit5-robolectric-extension.git'
+            url = 'https://github.com/apter-tech/junit5-robolectric-extension'
+        }
+    }
+}
+
+ext {
+    setupPom = this.&setupPom
+}
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
deleted file mode 100644
index 6231c22..0000000
--- a/gradle/publishing.gradle
+++ /dev/null
@@ -1,79 +0,0 @@
-apply plugin: 'maven-publish'
-apply plugin: 'signing'
-
-tasks.register('dokkaJavadocJar', Jar) {
-    dependsOn dokkaJavadoc
-    archiveClassifier.set 'javadoc'
-    from javadoc.destinationDir
-}
-
-publishing {
-    publications {
-        mavenJava(MavenPublication) {
-            from components.java
-            artifact(kotlinSourcesJar)
-            artifact(dokkaJavadocJar)
-
-            pom {
-                name = 'JUnit5 Robolectric Extension'
-                description = """This repository aims to bridge the gap between JUnit 5 and Robolectric,
-                    |enabling developers to leverage the benefits of both frameworks
-                    |for unit testing Android applications. While Robolectric currently lacks
-                    |a dedicated JUnit 5 extension, this project proposes a community-driven solution to
-                    |achieve seamless integration.""".stripMargin()
-                url = 'https://github.com/apter-tech/junit5-robolectric-extension'
-                licenses {
-                    license {
-                        name = 'The Apache License, Version 2.0'
-                        url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
-                    }
-                }
-                organization {
-                    name = 'Apter Technologies Ltd.'
-                    url = 'https://apter.tech'
-                }
-                developers {
-                    developer {
-                        id = 'warnyul'
-                        name = 'Balázs Varga'
-                        email = '[email protected]'
-                    }
-                }
-                scm {
-                    connection = 'scm:git:git://github.com:apter-tech/junit5-robolectric-extension.git'
-                    developerConnection = 'scm:git:ssh://github.com:apter-tech/junit5-robolectric-extension.git'
-                    url = 'https://github.com/apter-tech/junit5-robolectric-extension'
-                }
-            }
-        }
-    }
-    repositories {
-        maven {
-            name = 'MavenCentral'
-            final releasesRepoUrl = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2'
-            final snapshotsRepoUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots'
-            url = version.toString().endsWith("SNAPSHOT") ? snapshotsRepoUrl : releasesRepoUrl
-            credentials {
-                username = System.getenv('JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_USERNAME')
-                password = System.getenv('JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_PASSWORD')
-            }
-        }
-    }
-}
-
-final JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY = System.getenv(
-    'JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY'
-) ?: ""
-final JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD = System.getenv(
-    'JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD'
-) ?: ""
-
-if (!JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY.isEmpty() && !JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD.isEmpty()) {
-    signing {
-        useInMemoryPgpKeys(
-            JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY,
-            JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD,
-        )
-        sign(publishing.publications["mavenJava"])
-    }
-}
diff --git a/gradle/setupMavenCentralForPublishing.gradle b/gradle/setupMavenCentralForPublishing.gradle
new file mode 100644
index 0000000..06d44ec
--- /dev/null
+++ b/gradle/setupMavenCentralForPublishing.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'maven-publish'
+
+publishing {
+    repositories {
+        maven {
+            name = 'MavenCentral'
+            final releasesRepoUrl = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2'
+            final snapshotsRepoUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots'
+            url = version.toString().endsWith("SNAPSHOT") ? snapshotsRepoUrl : releasesRepoUrl
+            credentials {
+                username = System.getenv('JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_USERNAME')
+                password = System.getenv('JUNIT5_ROBOLECTRIC_EXTENSION_MAVEN_PASSWORD')
+            }
+        }
+    }
+}
diff --git a/gradle/signing.gradle b/gradle/signing.gradle
new file mode 100644
index 0000000..2ac41e4
--- /dev/null
+++ b/gradle/signing.gradle
@@ -0,0 +1,18 @@
+final JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY = System.getenv(
+    'JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY'
+) ?: ""
+final JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD = System.getenv(
+    'JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD'
+) ?: ""
+
+if (!JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY.isEmpty() && !JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD.isEmpty()) {
+    apply plugin: 'signing'
+
+    signing {
+        useInMemoryPgpKeys(
+            JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_KEY,
+            JUNIT5_ROBOLECTRIC_EXTENSION_GPG_SIGNING_PASSWORD,
+        )
+        sign(publishing.publications)
+    }
+}
diff --git a/integration-tests/agp-groovy-dsl/build.gradle b/integration-tests/agp-groovy-dsl/build.gradle
index 642ce0d..f829f67 100644
--- a/integration-tests/agp-groovy-dsl/build.gradle
+++ b/integration-tests/agp-groovy-dsl/build.gradle
@@ -1,3 +1,11 @@
+import tech.apter.robolectric.junit.jupiter.gradle.plugin.RobolectricJUnitJupiterGradlePlugin
+
+buildscript {
+    dependencies {
+        classpath("tech.apter.junit5.jupiter:robolectric-extension-gradle-plugin:$version")
+    }
+}
+
 plugins {
     id('com.android.library')
     alias(libs.plugins.kotlinAndroid)
@@ -5,6 +13,8 @@ plugins {
     alias(libs.plugins.detekt)
 }

+apply plugin: RobolectricJUnitJupiterGradlePlugin
+
 android {
     namespace = 'tech.apter.junit.jupiter.robolectric.integration.tests.agp.groovy.dsl'
     compileSdk = libs.versions.androidCompileSdk.get().toInteger()
@@ -13,20 +23,6 @@ android {
     defaultConfig {
         minSdk = libs.versions.androidMinimumSdk.get().toInteger()
     }
-
-    testOptions {
-        unitTests {
-            includeAndroidResources = true
-            all {
-                useJUnitPlatform()
-                jvmArgs(
-                    '--add-exports', 'java.base/jdk.internal.loader=ALL-UNNAMED',
-                    '--add-opens', 'java.base/jdk.internal.loader=ALL-UNNAMED',
-                    '-Djunit.platform.launcher.interceptors.enabled=true'
-                )
-            }
-        }
-    }
 }

 detekt {
@@ -41,8 +37,4 @@ kotlin {
 dependencies {
     detektPlugins(libs.detektFormatting)
     detektPlugins(libs.detektRulesLibraries)
-    testImplementation(project(':robolectric-extension'))
-    testImplementation(libs.robolectric)
-    testImplementation(libs.junit5JupiterApi)
-    testRuntimeOnly(libs.junit5JupiterEngine)
 }
diff --git a/integration-tests/agp-kotlin-dsl/build.gradle.kts b/integration-tests/agp-kotlin-dsl/build.gradle.kts
index 6c2827d..c65ef95 100644
--- a/integration-tests/agp-kotlin-dsl/build.gradle.kts
+++ b/integration-tests/agp-kotlin-dsl/build.gradle.kts
@@ -1,3 +1,11 @@
+import tech.apter.robolectric.junit.jupiter.gradle.plugin.RobolectricJUnitJupiterGradlePlugin
+
+buildscript {
+    dependencies {
+        classpath("tech.apter.junit5.jupiter:robolectric-extension-gradle-plugin:$version")
+    }
+}
+
 plugins {
     id("com.android.library")
     alias(libs.plugins.kotlinAndroid)
@@ -5,6 +13,8 @@ plugins {
     alias(libs.plugins.detekt)
 }

+apply(mapOf("plugin" to RobolectricJUnitJupiterGradlePlugin::class.java))
+
 android {
     namespace = "tech.apter.junit.jupiter.robolectric.integration.tests.agp.kotlin.dsl"
     compileSdk = libs.versions.androidCompileSdk.get().toInt()
@@ -13,23 +23,6 @@ android {
     defaultConfig {
         minSdk = libs.versions.androidMinimumSdk.get().toInt()
     }
-
-    @Suppress("UnstableApiUsage")
-    testOptions {
-        unitTests {
-            isIncludeAndroidResources = true
-            all { test ->
-                test.useJUnitPlatform()
-                test.jvmArgs(
-                    listOf(
-                        "--add-exports", "java.base/jdk.internal.loader=ALL-UNNAMED",
-                        "--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED",
-                        "-Djunit.platform.launcher.interceptors.enabled=true"
-                    )
-                )
-            }
-        }
-    }
 }

 detekt {
@@ -44,8 +37,4 @@ kotlin {
 dependencies {
     detektPlugins(libs.detektFormatting)
     detektPlugins(libs.detektRulesLibraries)
-    testImplementation(project(":robolectric-extension"))
-    testImplementation(libs.robolectric)
-    testImplementation(libs.junit5JupiterApi)
-    testRuntimeOnly(libs.junit5JupiterEngine)
 }
diff --git a/publishGradlePlugin.sh b/publishGradlePlugin.sh
new file mode 100755
index 0000000..a1968e5
--- /dev/null
+++ b/publishGradlePlugin.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+function publishGradlePlugin() {
+  ./gradlew :robolectric-extension-gradle-plugin:publishToMavenLocal --configure-on-demand
+  local -r robolectricGradlePluginVersion=$(cat < gradle.properties | grep tech.apter.junit5.robolectric.extension.version | cut -d'=' -f2)
+
+  if [[ "$robolectricGradlePluginVersion" != *SNAPSHOT ]]; then
+    ./gradlew :robolectric-extension-gradle-plugin:publishPlugins \
+      --configure-on-demand \
+      -D"gradle.publish.key=${JUNIT5_ROBOLECTRIC_EXTENSION_GRADLE_PLUGIN_PORTAL_KEY}" \
+      -D"gradle.publish.secret=${JUNIT5_ROBOLECTRIC_EXTENSION_GRADLE_PLUGIN_PORTAL_SECRET}"
+  fi
+}
+
+publishGradlePlugin
diff --git a/robolectric-extension-gradle-plugin/build.gradle b/robolectric-extension-gradle-plugin/build.gradle
new file mode 100644
index 0000000..328e338
--- /dev/null
+++ b/robolectric-extension-gradle-plugin/build.gradle
@@ -0,0 +1,62 @@
+plugins {
+    id('java-gradle-plugin')
+    alias(libs.plugins.kotlinJvm)
+    alias(libs.plugins.buildConfig)
+    alias(libs.plugins.detekt)
+    alias(libs.plugins.dokka)
+    alias(libs.plugins.kotlinxKover)
+    id('maven-publish')
+    id('signing')
+    alias(libs.plugins.gradlePluginPublish)
+}
+
+buildConfig {
+    useKotlinOutput()
+    packageName('tech.apter.robolectric.junit.jupiter.gradle.plugin')
+    buildConfigField(String, 'ROBOLECTRIC_EXTENSION_VERSION', project.version)
+    buildConfigField(String, 'JUNIT5_BOM_VERSION', libs.junit5Bom.get().version)
+}
+
+kotlin {
+    jvmToolchain(libs.versions.jvmToolchain.get().toInteger())
+}
+
+detekt {
+    toolVersion = libs.versions.detekt.get()
+    autoCorrect true
+}
+
+dependencies {
+    detektPlugins(libs.detektFormatting)
+    detektPlugins(libs.detektRulesLibraries)
+    compileOnly(gradleApi())
+    compileOnly(libs.androidGradle)
+}
+
+gradlePlugin {
+    website = 'https://github.com/apter-tech/junit5-robolectric-extension'
+    vcsUrl = 'https://github.com/apter-tech/junit5-robolectric-extension.git'
+    plugins {
+        robolectricJUnitJupiterGradlePlugin {
+            id = 'tech.apter.junit5.jupiter.robolectric-extension-gradle-plugin'
+            displayName = 'JUnit5 Robolectric Extension Gradle Plugin'
+            description = """This repository aims to bridge the gap between JUnit 5 and Robolectric,
+                    |enabling developers to leverage the benefits of both frameworks
+                    |for unit testing Android applications. While Robolectric currently lacks
+                    |a dedicated JUnit 5 extension, this project proposes a community-driven solution to
+                    |achieve seamless integration.""".stripMargin()
+            tags.addAll('Robolectric', 'JUnit5', 'JUnit Jupiter', 'androidTesting', 'testing', 'android')
+            implementationClass = 'tech.apter.robolectric.junit.jupiter.gradle.plugin.RobolectricJUnitJupiterGradlePlugin'
+        }
+    }
+}
+
+apply from: "${rootProject.layout.projectDirectory.file('gradle/pom.gradle')}"
+publishing {
+    publications.withType(MavenPublication).configureEach {
+        setupPom(it, 'JUnit5 Robolectric Extension Gradle Plugin')
+    }
+}
+apply from: "${rootProject.layout.projectDirectory.file('gradle/setupMavenCentralForPublishing.gradle')}"
+apply from: "${rootProject.layout.projectDirectory.file('gradle/signing.gradle')}"
+
diff --git a/robolectric-extension-gradle-plugin/src/main/kotlin/tech/apter/robolectric/junit/jupiter/gradle/plugin/RobolectricJUnitJupiterGradlePlugin.kt b/robolectric-extension-gradle-plugin/src/main/kotlin/tech/apter/robolectric/junit/jupiter/gradle/plugin/RobolectricJUnitJupiterGradlePlugin.kt
new file mode 100644
index 0000000..cc6b3aa
--- /dev/null
+++ b/robolectric-extension-gradle-plugin/src/main/kotlin/tech/apter/robolectric/junit/jupiter/gradle/plugin/RobolectricJUnitJupiterGradlePlugin.kt
@@ -0,0 +1,70 @@
+package tech.apter.robolectric.junit.jupiter.gradle.plugin
+
+import com.android.build.api.dsl.CommonExtension
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.logging.Logging
+import org.gradle.api.tasks.testing.Test
+
+@Suppress("LibraryEntitiesShouldNotBePublic")
+class RobolectricJUnitJupiterGradlePlugin : Plugin<Project> {
+    private inline val logger get() = Logging.getLogger(javaClass)
+
+    override fun apply(target: Project) {
+        logger.trace("${javaClass.name} is applied.")
+
+        with(target) {
+            val extension = registerExtension()
+            setupTestTasks()
+            enableIncludeAndroidResources()
+            afterEvaluate {
+                if (!extension.doNotAddDependencies) {
+                    addDependencies()
+                }
+            }
+        }
+    }
+
+    private fun Project.registerExtension(): RobolectricJUnitJupiterGradlePluginExtension = extensions.create(
+        "robolectricJUnitJupiter",
+        RobolectricJUnitJupiterGradlePluginExtension::class.java,
+        /* doNotAddDependencies= */
+        false,
+    )
+
+    private fun Project.setupTestTasks() {
+        val jvmArgs = listOf(
+            "--add-exports",
+            "java.base/jdk.internal.loader=ALL-UNNAMED",
+            "--add-opens",
+            "java.base/jdk.internal.loader=ALL-UNNAMED",
+            "-Djunit.platform.launcher.interceptors.enabled=true",
+        )
+        tasks.withType(Test::class.java).configureEach { testTask ->
+            testTask.useJUnitPlatform()
+            testTask.jvmArgs(jvmArgs)
+        }
+        tasks.whenTaskAdded { task ->
+            if (task is Test) {
+                task.useJUnitPlatform()
+                task.jvmArgs(jvmArgs)
+            }
+        }
+    }
+
+    private fun Project.enableIncludeAndroidResources() {
+        val androidExtension = extensions.findByName("android") as? CommonExtension<*, *, *, *, *, *>
+        androidExtension?.testOptions?.unitTests?.isIncludeAndroidResources = true
+    }
+
+    private fun Project.addDependencies() {
+        with(dependencies) {
+            add(
+                "testImplementation",
+                "tech.apter.junit5.jupiter:robolectric-extension:${BuildConfig.ROBOLECTRIC_EXTENSION_VERSION}"
+            )
+            add("testImplementation", platform("org.junit:junit-bom:${BuildConfig.JUNIT5_BOM_VERSION}"))
+            add("testRuntimeOnly", "org.junit.jupiter:junit-jupiter-engine")
+        }
+    }
+}
diff --git a/robolectric-extension-gradle-plugin/src/main/kotlin/tech/apter/robolectric/junit/jupiter/gradle/plugin/RobolectricJUnitJupiterGradlePluginExtension.kt b/robolectric-extension-gradle-plugin/src/main/kotlin/tech/apter/robolectric/junit/jupiter/gradle/plugin/RobolectricJUnitJupiterGradlePluginExtension.kt
new file mode 100644
index 0000000..1de5bbf
--- /dev/null
+++ b/robolectric-extension-gradle-plugin/src/main/kotlin/tech/apter/robolectric/junit/jupiter/gradle/plugin/RobolectricJUnitJupiterGradlePluginExtension.kt
@@ -0,0 +1,4 @@
+package tech.apter.robolectric.junit.jupiter.gradle.plugin
+
+@Suppress("LibraryEntitiesShouldNotBePublic")
+open class RobolectricJUnitJupiterGradlePluginExtension(var doNotAddDependencies: Boolean)
diff --git a/robolectric-extension/build.gradle b/robolectric-extension/build.gradle
index 36abd66..dd9681f 100644
--- a/robolectric-extension/build.gradle
+++ b/robolectric-extension/build.gradle
@@ -1,13 +1,22 @@
 import org.robolectric.gradle.AarDepsPlugin
+import tech.apter.robolectric.junit.jupiter.gradle.plugin.RobolectricJUnitJupiterGradlePlugin
+
+buildscript {
+    dependencies {
+        classpath("tech.apter.junit5.jupiter:robolectric-extension-gradle-plugin:$version")
+    }
+}

 plugins {
     alias(libs.plugins.kotlinJvm)
     alias(libs.plugins.kotlinxKover)
     alias(libs.plugins.detekt)
     alias(libs.plugins.dokka)
+    id('maven-publish')
 }

 apply plugin: AarDepsPlugin
+apply plugin: RobolectricJUnitJupiterGradlePlugin

 configurations.configureEach { configuration ->
     configuration.exclude(group: 'androidx.tracing', module: 'tracing')
@@ -18,19 +27,17 @@ kotlin {
     jvmToolchain(libs.versions.jvmToolchain.get().toInteger())
 }

+robolectricJUnitJupiter {
+    doNotAddDependencies = true
+}
+
 test {
-    useJUnitPlatform()
     testLogging {
         showStandardStreams = true
         showExceptions = true
         showCauses = true
         showStackTraces = true
     }
-    jvmArgs(
-        '--add-exports', 'java.base/jdk.internal.loader=ALL-UNNAMED',
-        '--add-opens', 'java.base/jdk.internal.loader=ALL-UNNAMED',
-    )
-    systemProperty 'junit.platform.launcher.interceptors.enabled', 'true'
     systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
     systemProperty 'junit.jupiter.execution.parallel.mode.default ', 'same_thread'
     systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'same_thread'
@@ -103,4 +110,20 @@ tasks.register('generateAndroidR') {
     }
 }

-apply from: "${rootProject.layout.projectDirectory.file('gradle/publishing.gradle')}"
+apply from: "${rootProject.layout.projectDirectory.file('gradle/dokka.gradle')}"
+apply from: "${rootProject.layout.projectDirectory.file('gradle/pom.gradle')}"
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+            artifact(kotlinSourcesJar)
+            artifact(dokkaJavadocJar)
+
+            setupPom(it, 'JUnit5 Robolectric Extension')
+        }
+    }
+}
+
+apply from: "${rootProject.layout.projectDirectory.file('gradle/setupMavenCentralForPublishing.gradle')}"
+apply from: "${rootProject.layout.projectDirectory.file('gradle/signing.gradle')}"
diff --git a/settings.gradle b/settings.gradle
index 4d65571..656e9ce 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -3,6 +3,7 @@ pluginManagement {
         gradlePluginPortal()
         mavenCentral()
         google()
+        mavenLocal()
     }
 }

@@ -21,4 +22,5 @@ rootProject.name = 'junit5-robolectric-extension'
 include('integration-tests:agp-groovy-dsl')
 include('integration-tests:agp-kotlin-dsl')
 include('robolectric-extension')
+include('robolectric-extension-gradle-plugin')
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Out of diff range and nitpick comments (1)
README.md (1)

121-122: Consider a more formal alternative to "if you want to."

Instead of "if you want to," consider using "as needed" or "if required" to elevate the formality of the documentation.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@warnyul warnyul merged commit 602c195 into main Apr 30, 2024
2 checks passed
@warnyul warnyul deleted the plugin branch April 30, 2024 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant