secondaryManifestFiles) {
+ this.mSecondaryManifestFiles = secondaryManifestFiles;
+ }
+
+ public File getOutputFile() {
+ return mOutputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.mOutputFile = outputFile;
}
}
diff --git a/source/src/main/java/com/kezong/fataar/RClassesTransform.java b/source/src/main/java/com/kezong/fataar/RClassesTransform.java
new file mode 100644
index 00000000..c21a72d0
--- /dev/null
+++ b/source/src/main/java/com/kezong/fataar/RClassesTransform.java
@@ -0,0 +1,222 @@
+package com.kezong.fataar;
+
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.google.common.collect.ImmutableSet;
+
+import org.gradle.api.Project;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.ConstPool;
+import kotlin.io.FilesKt;
+
+/**
+ * com.sdk.R
+ * |-- com.lib1.R
+ * |-- com.lib2.R
+ *
+ * rename com.lib1.R and com.lib2.R to com.sdk.R
+ */
+public class RClassesTransform extends Transform {
+
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+
+ private final ExecutorService executor = Executors.newFixedThreadPool(CPU_COUNT + 1);
+
+ private final List> futures = new ArrayList<>();
+
+ private final Project project;
+
+ private final Map targetPackageMap = new HashMap<>();
+
+ private final Map> libraryPackageMap = new HashMap<>();
+
+ public RClassesTransform(final Project project) {
+ this.project = project;
+ }
+
+ /**
+ * Different variants have different package names.
+ * So targetPackageName must set after evaluate
+ * @param variantName variant name
+ * @param targetPackage main module's package name
+ */
+ public void putTargetPackage(String variantName, String targetPackage) {
+ targetPackageMap.put(variantName, targetPackage);
+ }
+
+ /**
+ * library packages name must set after exploded task perform
+ * @param variantName variant name
+ * @param libraryPackages sub module's package name, read from AndroidManifest.xml
+ */
+ public void putLibraryPackages(String variantName, Collection libraryPackages) {
+ libraryPackageMap.put(variantName, libraryPackages);
+ }
+
+ @Override
+ public String getName() {
+ return "transformR";
+ }
+
+ @Override
+ public Set getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @Override
+ public Set super QualifiedContent.Scope> getScopes() {
+ return ImmutableSet.of(QualifiedContent.Scope.PROJECT);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(TransformInvocation transformInvocation) throws InterruptedException, IOException {
+ long startTime = System.currentTimeMillis();
+ Map transformTable = buildTransformTable(transformInvocation.getContext().getVariantName());
+ final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental();
+ final TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
+
+ if (!isIncremental) {
+ outputProvider.deleteAll();
+ }
+
+ final File outputDir = outputProvider.getContentLocation("classes", getOutputTypes(), getScopes(), Format.DIRECTORY);
+
+ try {
+ for (final TransformInput input : transformInvocation.getInputs()) {
+ for (final DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ final File directoryFile = directoryInput.getFile();
+ final ClassPool classPool = new ClassPool();
+ classPool.insertClassPath(directoryFile.getAbsolutePath());
+
+ for (final File originalClassFile : getChangedClassesList(directoryInput)) {
+ if (!originalClassFile.getPath().endsWith(".class")) {
+ continue; // ignore anything that is not class file
+ }
+
+ Future> submit = executor.submit(() -> {
+ try {
+ File relative = FilesKt.relativeTo(originalClassFile, directoryFile);
+ String className = filePathToClassname(relative);
+ final CtClass ctClass = classPool.get(className);
+ if (transformTable != null) {
+ ClassFile classFile = ctClass.getClassFile();
+ ConstPool constPool = classFile.getConstPool();
+ constPool.renameClass(transformTable);
+ }
+ ctClass.writeFile(outputDir.getAbsolutePath());
+ } catch (CannotCompileException | NotFoundException | IOException e) {
+ e.printStackTrace();
+ }
+ });
+
+ futures.add(submit);
+ }
+ }
+ }
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+
+ for (Future> future : futures) {
+ try {
+ future.get();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ futures.clear();
+
+ long endTime = System.currentTimeMillis();
+ project.getLogger().info("the task cost "
+ + (endTime - startTime)
+ + "ms");
+ }
+
+ private Map buildTransformTable(String variantName) {
+ String targetPackage = targetPackageMap.get(variantName);
+ Collection libraryPackages = libraryPackageMap.get(variantName);
+ if (targetPackage == null || libraryPackages == null) {
+ return null;
+ }
+
+ final List resourceTypes = Arrays.asList("anim", "animator", "array", "attr", "bool", "color", "dimen",
+ "drawable", "font", "fraction", "id", "integer", "interpolator", "layout", "menu", "mipmap", "navigation",
+ "plurals", "raw", "string", "style", "styleable", "transition", "xml");
+
+ HashMap map = new HashMap<>();
+ for (String resource : resourceTypes) {
+ String targetClass = targetPackageMap.get(variantName).replace(".", "/") + "/R$" + resource;
+ for (String libraryPackage : libraryPackageMap.get(variantName)) {
+ String fromClass = libraryPackage.replace(".", "/") + "/R$" + resource;
+ map.put(fromClass, targetClass);
+ }
+ }
+
+ return map;
+ }
+
+ private List getChangedClassesList(final DirectoryInput directoryInput) throws IOException {
+ final Map changedFiles = directoryInput.getChangedFiles();
+ if (changedFiles.isEmpty()) {
+ // we're in non incremental mode
+ return Files.walk(directoryInput.getFile().toPath())
+ .filter(Files::isRegularFile)
+ .map(Path::toFile)
+ .collect(Collectors.toList());
+ } else {
+ changedFiles.entrySet().stream()
+ .filter(it -> it.getValue() == Status.REMOVED)
+ .forEach(it -> it.getKey().delete());
+
+ return changedFiles.entrySet().stream()
+ .filter(it -> it.getValue() == Status.ADDED || it.getValue() == Status.CHANGED)
+ .map(Map.Entry::getKey)
+ .filter(File::isFile)
+ .collect(Collectors.toList());
+ }
+ }
+
+ private String filePathToClassname(File file) {
+ // com/classify/module/a.class -> com.classify.module.a.class -> comify.module.a is not expected
+ // so must be replace .class first
+ return file.getPath().replace(".class", "")
+ .replace("/", ".")
+ .replace("\\", ".");
+ }
+}
diff --git a/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties b/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties
index f1cfe9af..5131a2a4 100644
--- a/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties
+++ b/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties
@@ -1 +1 @@
-implementation-class=com.kezong.fataar.FatLibraryPlugin
\ No newline at end of file
+implementation-class=com.kezong.fataar.FatAarPlugin
\ No newline at end of file
diff --git a/source/upload.gradle b/source/upload.gradle
new file mode 100644
index 00000000..03cbfd67
--- /dev/null
+++ b/source/upload.gradle
@@ -0,0 +1,110 @@
+apply plugin: 'maven-publish'
+apply plugin: 'java'
+apply plugin: 'signing'
+
+task sourcesJar(type: Jar) {
+ from sourceSets.main.allJava
+ classifier = 'sources'
+}
+
+task javadocJar(type: Jar) {
+ from javadoc
+ classifier = 'javadoc'
+}
+
+
+File secretPropsFile = project.rootProject.file('local.properties')
+if (secretPropsFile.exists()) {
+ println "Found secret props file, loading props"
+ Properties p = new Properties()
+ p.load(new FileInputStream(secretPropsFile))
+ p.each { name, value ->
+ ext[name] = value
+ }
+} else {
+ println "No props file, loading env vars"
+}
+
+publishing {
+ publications {
+ release(MavenPublication) {
+ // The coordinates of the library, being set from variables that
+ // we'll set up in a moment
+ groupId PUBLISH_GROUP_ID
+ artifactId PUBLISH_ARTIFACT_ID
+ version PUBLISH_VERSION
+
+ artifact jar
+ artifact sourcesJar
+ artifact javadocJar
+
+ // Self-explanatory metadata for the most part
+ pom {
+ name = PUBLISH_ARTIFACT_ID
+ description = 'A gradle plugin that merge dependencies into the final aar file works with AGP 3.+'
+ // If your project has a dedicated site, use its URL here
+ url = 'https://github.com/kezong/fat-aar-android'
+ licenses {
+ license {
+ name = 'The MIT License'
+ url = 'https://opensource.org/licenses/MIT'
+ }
+ }
+ developers {
+ developer {
+ id = 'kezong'
+ name = 'kezong'
+ email = 'kezong1811@gmail.com'
+ }
+ }
+ // Version control info, if you're using GitHub, follow the format as seen here
+ scm {
+ connection = 'scm:git:github.com/kezong/fat-aar-android.git'
+ developerConnection = 'scm:git:ssh://github.com/kezong/fat-aar-android.git'
+ url = 'https://github.com/kezong/fat-aar-android/tree/master'
+ }
+ // A slightly hacky fix so that your POM will include any transitive dependencies
+ // that your library builds upon
+ withXml {
+ def dependenciesNode = asNode().appendNode('dependencies')
+
+ project.configurations.implementation.allDependencies.each {
+ if (it.group == "com.android.tools.build" && it.name == "gradle") {
+ return
+ }
+ if (it.group == null) {
+ return
+ }
+ def dependencyNode = dependenciesNode.appendNode('dependency')
+ dependencyNode.appendNode('groupId', it.group)
+ dependencyNode.appendNode('artifactId', it.name)
+ dependencyNode.appendNode('version', it.version)
+ }
+ }
+ }
+ }
+ }
+ repositories {
+ // The repository to publish to, Sonatype/MavenCentral
+ maven {
+ // This is an arbitrary name, you may also use "mavencentral" or
+ // any other name that's descriptive for you
+ name = "fat-aar"
+
+ def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+ def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
+ // You only need this if you want to publish snapshots, otherwise just set the URL
+ // to the release repo directly
+ url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
+
+ // The username and password we've fetched earlier
+ credentials {
+ username ossrhUsername
+ password ossrhPassword
+ }
+ }
+ }
+}
+signing {
+ sign publishing.publications
+}
\ No newline at end of file