1import static org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE;
2
3import com.android.build.gradle.internal.dependency.ExtractAarTransform;
4import com.google.common.base.Joiner;
5import java.io.File;
6import java.util.ArrayList;
7import java.util.List;
8import java.util.concurrent.atomic.AtomicReference;
9import javax.inject.Inject;
10import org.gradle.api.Action;
11import org.gradle.api.Plugin;
12import org.gradle.api.Project;
13import org.gradle.api.Task;
14import org.gradle.api.artifacts.transform.TransformOutputs;
15import org.gradle.api.file.FileCollection;
16import org.gradle.api.tasks.compile.JavaCompile;
17import org.jetbrains.annotations.NotNull;
18
19/**
20 * Resolve aar dependencies into jars for non-Android projects.
21 */
22public class AarDepsPlugin implements Plugin<Project> {
23  @Override
24  public void apply(Project project) {
25    project
26        .getDependencies()
27        .registerTransform(
28            ClassesJarExtractor.class,
29            reg -> {
30              reg.getParameters().getProjectName().set(project.getName());
31              reg.getFrom().attribute(ARTIFACT_TYPE_ATTRIBUTE, "aar");
32              reg.getTo().attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar");
33            });
34
35    project.afterEvaluate(
36        p ->
37            project
38                .getConfigurations()
39                .forEach(
40                    c -> {
41                      // I suspect we're meant to use the org.gradle.usage attribute, but this
42                      // works.
43                      if (c.getName().endsWith("Classpath")) {
44                        c.attributes(
45                            cfgAttrs -> cfgAttrs.attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar"));
46                      }
47                    }));
48
49    // warn if any AARs do make it through somehow; there must be a gradle configuration
50    // that isn't matched above.
51    //noinspection Convert2Lambda
52    project
53        .getTasks()
54        .withType(JavaCompile.class)
55        .all(
56            // the following Action<Task needs to remain an anonymous subclass or gradle's
57            // incremental compile breaks (run `gradlew -i classes` twice to see impact):
58            t -> t.doFirst(new Action<Task>() {
59              @Override
60              public void execute(Task task) {
61                List<File> aarFiles = AarDepsPlugin.this.findAarFiles(t.getClasspath());
62                if (!aarFiles.isEmpty()) {
63                  throw new IllegalStateException(
64                      "AARs on classpath: " + Joiner.on("\n  ").join(aarFiles));
65                }
66              }
67            }));
68  }
69
70  private List<File> findAarFiles(FileCollection files) {
71    List<File> bad = new ArrayList<>();
72    for (File file : files.getFiles()) {
73      if (file.getName().toLowerCase().endsWith(".aar")) {
74        bad.add(file);
75      }
76    }
77    return bad;
78  }
79
80  public static abstract class ClassesJarExtractor extends ExtractAarTransform {
81    @Inject
82    public ClassesJarExtractor() {
83    }
84
85    @Override
86    public void transform(@NotNull TransformOutputs outputs) {
87      AtomicReference<File> classesJarFile = new AtomicReference<>();
88      AtomicReference<File> outJarFile = new AtomicReference<>();
89      super.transform(new TransformOutputs() {
90        // This is the one that ExtractAarTransform calls.
91        @Override
92        public File dir(Object o) {
93          // ExtractAarTransform needs a place to extract the AAR. We don't really need to
94          // register this as an output, but it'd be tricky to avoid it.
95          File dir = outputs.dir(o);
96
97          // Also, register our jar file. Its name needs to be quasi-unique or
98          // IntelliJ Gradle/Android plugins get confused.
99          classesJarFile.set(new File(new File(dir, "jars"), "classes.jar"));
100          outJarFile.set(new File(new File(dir, "jars"), o + ".jar"));
101          outputs.file(o + "/jars/" + o + ".jar");
102          return outputs.dir(o);
103        }
104
105        @Override
106        public File file(Object o) {
107          throw new IllegalStateException("shouldn't be called");
108        }
109      });
110
111      classesJarFile.get().renameTo(outJarFile.get());
112    }
113  }
114}
115