Skip to content

Commit

Permalink
build: Add AarDepsPlugin and replace the project config to kotlin jvm…
Browse files Browse the repository at this point in the history
… from kotlin android

diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index e76e4f9..cdc4970 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,12 +4,24 @@
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
+        <compositeConfiguration>
+          <compositeBuild compositeDefinitionSource="SCRIPT">
+            <builds>
+              <build path="$PROJECT_DIR$/buildSrc" name="buildSrc">
+                <projects>
+                  <project path="$PROJECT_DIR$/buildSrc" />
+                </projects>
+              </build>
+            </builds>
+          </compositeBuild>
+        </compositeConfiguration>
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="gradleHome" value="" />
         <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
         <option name="modules">
           <set>
             <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/buildSrc" />
             <option value="$PROJECT_DIR$/robolectric-extension" />
           </set>
         </option>
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 8d81632..fe63bb6 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="KotlinJpsPluginSettings">
-    <option name="version" value="1.9.22" />
+    <option name="version" value="1.9.23" />
   </component>
 </project>
\ No newline at end of file
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 0000000..748e06b
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,45 @@
+plugins {
+    id 'java-library'
+    id 'groovy'
+}
+
+repositories {
+    google()
+    mavenCentral()
+    gradlePluginPortal()
+}
+
+dependencies {
+    compileOnly gradleApi()
+    compileOnly localGroovy()
+    implementation libs.guava
+    implementation libs.androidBuildTools
+}
+
+java {
+    toolchain {
+        languageVersion.set(JavaLanguageVersion.of(libs.versions.jvmToolchain.get()))
+    }
+}
+
+task downloadAarDepsPlugin {
+    final from = "https://raw.githubusercontent.com/robolectric/robolectric/robolectric-${libs.versions.robolectric.get()}/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java"
+    final groovySourceSet = new File(sourceSets.findByName('main').allSource.sourceDirectories.find { it.name == 'groovy' }.path)
+    final to = new File(groovySourceSet, '/org/robolectric/gradle/AarDepsPlugin.java')
+
+    inputs.property("from", from)
+    outputs.file(to)
+
+    doLast {
+        try {
+            new URL(from).withInputStream { i -> to.withOutputStream { it << i } }
+        } catch (IOException e) {
+            logger.debug("Error during downloading AarDepsPlugin. Keep the stored version.\n$e")
+        }
+    }
+}
+
+tasks {
+    compileJava.dependsOn(downloadAarDepsPlugin)
+    compileGroovy.dependsOn(downloadAarDepsPlugin)
+}
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
new file mode 100644
index 0000000..6f31e6e
--- /dev/null
+++ b/buildSrc/settings.gradle
@@ -0,0 +1,7 @@
+dependencyResolutionManagement {
+    versionCatalogs {
+        libs {
+            from(files("../gradle/libs.versions.toml"))
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java b/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java
new file mode 100644
index 0000000..0432cea
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java
@@ -0,0 +1,116 @@
+package org.robolectric.gradle;
+
+import static org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE;
+
+import com.android.build.gradle.internal.dependency.ExtractAarTransform;
+import com.google.common.base.Joiner;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.inject.Inject;
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.transform.TransformOutputs;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Resolve aar dependencies into jars for non-Android projects.
+ */
+public class AarDepsPlugin implements Plugin<Project> {
+  @OverRide
+  public void apply(Project project) {
+    project
+        .getDependencies()
+        .registerTransform(
+            ClassesJarExtractor.class,
+            reg -> {
+              reg.getParameters().getProjectName().set(project.getName());
+              reg.getFrom().attribute(ARTIFACT_TYPE_ATTRIBUTE, "aar");
+              reg.getTo().attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar");
+            });
+
+    project.afterEvaluate(
+        p ->
+            project
+                .getConfigurations()
+                .forEach(
+                    c -> {
+                      // I suspect we're meant to use the org.gradle.usage attribute, but this
+                      // works.
+                      if (c.getName().endsWith("Classpath")) {
+                        c.attributes(
+                            cfgAttrs -> cfgAttrs.attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar"));
+                      }
+                    }));
+
+    // warn if any AARs do make it through somehow; there must be a gradle configuration
+    // that isn't matched above.
+    //noinspection Convert2Lambda
+    project
+        .getTasks()
+        .withType(JavaCompile.class)
+        .all(
+            // the following Action<Task needs to remain an anonymous subclass or gradle's
+            // incremental compile breaks (run `gradlew -i classes` twice to see impact):
+            t -> t.doFirst(new Action<Task>() {
+              @OverRide
+              public void execute(Task task) {
+                List<File> aarFiles = AarDepsPlugin.this.findAarFiles(t.getClasspath());
+                if (!aarFiles.isEmpty()) {
+                  throw new IllegalStateException(
+                      "AARs on classpath: " + Joiner.on("\n  ").join(aarFiles));
+                }
+              }
+            }));
+  }
+
+  private List<File> findAarFiles(FileCollection files) {
+    List<File> bad = new ArrayList<>();
+    for (File file : files.getFiles()) {
+      if (file.getName().toLowerCase().endsWith(".aar")) {
+        bad.add(file);
+      }
+    }
+    return bad;
+  }
+
+  public static abstract class ClassesJarExtractor extends ExtractAarTransform {
+    @Inject
+    public ClassesJarExtractor() {
+    }
+
+    @OverRide
+    public void transform(@NotNull TransformOutputs outputs) {
+      AtomicReference<File> classesJarFile = new AtomicReference<>();
+      AtomicReference<File> outJarFile = new AtomicReference<>();
+      super.transform(new TransformOutputs() {
+        // This is the one that ExtractAarTransform calls.
+        @OverRide
+        public File dir(Object o) {
+          // ExtractAarTransform needs a place to extract the AAR. We don't really need to
+          // register this as an output, but it'd be tricky to avoid it.
+          File dir = outputs.dir(o);
+
+          // Also, register our jar file. Its name needs to be quasi-unique or
+          // IntelliJ Gradle/Android plugins get confused.
+          classesJarFile.set(new File(new File(dir, "jars"), "classes.jar"));
+          outJarFile.set(new File(new File(dir, "jars"), o + ".jar"));
+          outputs.file(o + "/jars/" + o + ".jar");
+          return outputs.dir(o);
+        }
+
+        @OverRide
+        public File file(Object o) {
+          throw new IllegalStateException("shouldn't be called");
+        }
+      });
+
+      classesJarFile.get().renameTo(outJarFile.get());
+    }
+  }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bca64aa..4676734 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,19 +1,21 @@
 [versions]
 androidBuildTools = "8.3.0"
-androidCompileSdk = "34"
 androidJUnit5 = "1.10.0.0"
 androidxTestExtJunit = "1.1.5"
+guava = "33.0.0-jre"
 junit4 = "4.13.2"
 junit5 = "5.10.2"
 jvmToolchain = "17"
-kotlin = "1.9.22"
+kotlin = "1.9.23"
 robolectric = "4.11.1"
 robolectricAndroidAll = "14-robolectric-10818077"
 # Use when bom also added to the dependencies
 sources = "sources"

 [libraries]
+androidBuildTools = { module = "com.android.tools.build:gradle", version.ref = "androidBuildTools" }
 androidxTestExtJunit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
+guava = { module = "com.google.guava:guava", version.ref = "guava" }
 guavaConstraint = { module = "com.google.guava:guava", version = { require = "[32.0.1-jre,]" } }
 junit4 = { module = "junit:junit", version.ref = "junit4" }
 junit5Bom = { module = "org.junit:junit-bom", version.ref = "junit5" }
@@ -29,6 +31,7 @@ robolectricAndroidAll = { module = "org.robolectric:android-all", version.ref =
 androidJUnit5 = { id = "de.mannodermaus.android-junit5", version.ref = "androidJUnit5" }
 androidLibrary = { id = "com.android.library", version.ref = "androidBuildTools" }
 kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

 [bundles]
 junit5 = [
diff --git a/robolectric-extension/build.gradle b/robolectric-extension/build.gradle
index 4376dbe..e594b51 100644
--- a/robolectric-extension/build.gradle
+++ b/robolectric-extension/build.gradle
@@ -1,21 +1,15 @@
+import org.robolectric.gradle.AarDepsPlugin
+
 plugins {
-    alias(libs.plugins.androidLibrary)
-    alias(libs.plugins.kotlinAndroid)
-    alias(libs.plugins.androidJUnit5)
+    alias(libs.plugins.kotlinJvm)
+
 }

-android {
-    defaultConfig {
-        namespace = "$group.$name"
-        compileOptions {
-            compileSdk libs.versions.androidCompileSdk.get().toInteger()
-        }
-    }
-    testOptions {
-        unitTests.all {
-            useJUnitPlatform()
-        }
-    }
+apply plugin: AarDepsPlugin
+
+configurations.configureEach { configuration ->
+    configuration.exclude(group: 'androidx.tracing', module: 'tracing')
+    configuration.exclude(group: 'androidx.annotation', module: 'annotation-experimental')
 }

 dependencies {
@@ -38,3 +32,7 @@ dependencies {
 kotlin {
     jvmToolchain(libs.versions.jvmToolchain.get().toInteger())
 }
+
+test {
+    useJUnitPlatform()
+}
  • Loading branch information
warnyul committed Mar 11, 2024
1 parent 8e1b646 commit 7f3ce50
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 18 deletions.
12 changes: 12 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
id 'java-library'
id 'groovy'
}

repositories {
google()
mavenCentral()
gradlePluginPortal()
}

dependencies {
compileOnly gradleApi()
compileOnly localGroovy()
implementation libs.guava
implementation libs.androidBuildTools
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(libs.versions.jvmToolchain.get()))
}
}

task downloadAarDepsPlugin {
final from = "https://raw.githubusercontent.com/robolectric/robolectric/robolectric-${libs.versions.robolectric.get()}/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java"
final groovySourceSet = new File(sourceSets.findByName('main').allSource.sourceDirectories.find { it.name == 'groovy' }.path)
final to = new File(groovySourceSet, '/org/robolectric/gradle/AarDepsPlugin.java')

inputs.property("from", from)
outputs.file(to)

doLast {
try {
new URL(from).withInputStream { i -> to.withOutputStream { it << i } }
} catch (IOException e) {
logger.debug("Error during downloading AarDepsPlugin. Keep the stored version.\n$e")
}
}
}

tasks {
compileJava.dependsOn(downloadAarDepsPlugin)
compileGroovy.dependsOn(downloadAarDepsPlugin)
}
7 changes: 7 additions & 0 deletions buildSrc/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
libs {
from(files("../gradle/libs.versions.toml"))
}
}
}
116 changes: 116 additions & 0 deletions buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.robolectric.gradle;

import static org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE;

import com.android.build.gradle.internal.dependency.ExtractAarTransform;
import com.google.common.base.Joiner;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.transform.TransformOutputs;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.compile.JavaCompile;
import org.jetbrains.annotations.NotNull;

/**
* Resolve aar dependencies into jars for non-Android projects.
*/
public class AarDepsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project
.getDependencies()
.registerTransform(
ClassesJarExtractor.class,
reg -> {
reg.getParameters().getProjectName().set(project.getName());
reg.getFrom().attribute(ARTIFACT_TYPE_ATTRIBUTE, "aar");
reg.getTo().attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar");
});

project.afterEvaluate(
p ->
project
.getConfigurations()
.forEach(
c -> {
// I suspect we're meant to use the org.gradle.usage attribute, but this
// works.
if (c.getName().endsWith("Classpath")) {
c.attributes(
cfgAttrs -> cfgAttrs.attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar"));
}
}));

// warn if any AARs do make it through somehow; there must be a gradle configuration
// that isn't matched above.
//noinspection Convert2Lambda
project
.getTasks()
.withType(JavaCompile.class)
.all(
// the following Action<Task needs to remain an anonymous subclass or gradle's
// incremental compile breaks (run `gradlew -i classes` twice to see impact):
t -> t.doFirst(new Action<Task>() {
@Override
public void execute(Task task) {
List<File> aarFiles = AarDepsPlugin.this.findAarFiles(t.getClasspath());
if (!aarFiles.isEmpty()) {
throw new IllegalStateException(
"AARs on classpath: " + Joiner.on("\n ").join(aarFiles));
}
}
}));
}

private List<File> findAarFiles(FileCollection files) {
List<File> bad = new ArrayList<>();
for (File file : files.getFiles()) {
if (file.getName().toLowerCase().endsWith(".aar")) {
bad.add(file);
}
}
return bad;
}

public static abstract class ClassesJarExtractor extends ExtractAarTransform {
@Inject
public ClassesJarExtractor() {
}

@Override
public void transform(@NotNull TransformOutputs outputs) {
AtomicReference<File> classesJarFile = new AtomicReference<>();
AtomicReference<File> outJarFile = new AtomicReference<>();
super.transform(new TransformOutputs() {
// This is the one that ExtractAarTransform calls.
@Override
public File dir(Object o) {
// ExtractAarTransform needs a place to extract the AAR. We don't really need to
// register this as an output, but it'd be tricky to avoid it.
File dir = outputs.dir(o);

// Also, register our jar file. Its name needs to be quasi-unique or
// IntelliJ Gradle/Android plugins get confused.
classesJarFile.set(new File(new File(dir, "jars"), "classes.jar"));
outJarFile.set(new File(new File(dir, "jars"), o + ".jar"));
outputs.file(o + "/jars/" + o + ".jar");
return outputs.dir(o);
}

@Override
public File file(Object o) {
throw new IllegalStateException("shouldn't be called");
}
});

classesJarFile.get().renameTo(outJarFile.get());
}
}
}
7 changes: 5 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
[versions]
androidBuildTools = "8.3.0"
androidCompileSdk = "34"
androidJUnit5 = "1.10.0.0"
androidxTestExtJunit = "1.1.5"
guava = "33.0.0-jre"
junit4 = "4.13.2"
junit5 = "5.10.2"
jvmToolchain = "17"
kotlin = "1.9.22"
kotlin = "1.9.23"
robolectric = "4.11.1"
robolectricAndroidAll = "14-robolectric-10818077"
# Use when bom also added to the dependencies
sources = "sources"

[libraries]
androidBuildTools = { module = "com.android.tools.build:gradle", version.ref = "androidBuildTools" }
androidxTestExtJunit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
guavaConstraint = { module = "com.google.guava:guava", version = { require = "[32.0.1-jre,]" } }
junit4 = { module = "junit:junit", version.ref = "junit4" }
junit5Bom = { module = "org.junit:junit-bom", version.ref = "junit5" }
Expand All @@ -29,6 +31,7 @@ robolectricAndroidAll = { module = "org.robolectric:android-all", version.ref =
androidJUnit5 = { id = "de.mannodermaus.android-junit5", version.ref = "androidJUnit5" }
androidLibrary = { id = "com.android.library", version.ref = "androidBuildTools" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

[bundles]
junit5 = [
Expand Down
28 changes: 13 additions & 15 deletions robolectric-extension/build.gradle
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import org.robolectric.gradle.AarDepsPlugin

plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinAndroid)
alias(libs.plugins.androidJUnit5)
alias(libs.plugins.kotlinJvm)

}

android {
defaultConfig {
namespace = "$group.$name"
compileOptions {
compileSdk libs.versions.androidCompileSdk.get().toInteger()
}
}
testOptions {
unitTests.all {
useJUnitPlatform()
}
}
apply plugin: AarDepsPlugin

configurations.configureEach { configuration ->
configuration.exclude(group: 'androidx.tracing', module: 'tracing')
configuration.exclude(group: 'androidx.annotation', module: 'annotation-experimental')
}

dependencies {
Expand All @@ -38,3 +32,7 @@ dependencies {
kotlin {
jvmToolchain(libs.versions.jvmToolchain.get().toInteger())
}

test {
useJUnitPlatform()
}

0 comments on commit 7f3ce50

Please sign in to comment.