diff --git a/build.gradle b/build.gradle index cf6d2bc..cb59ae5 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { dependencies { classpath 'org.gradle.api.plugins:gradle-nexus-plugin:0.7' - classpath 'com.android.tools.build:gradle:1.3.1' + classpath 'com.android.tools.build:gradle:1.5.0' classpath 'org.aspectj:aspectjtools:1.8.6' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' } diff --git a/hugo-example/build.gradle b/hugo-example/build.gradle index aa7ac54..3a94420 100644 --- a/hugo-example/build.gradle +++ b/hugo-example/build.gradle @@ -39,3 +39,7 @@ android { } } } + +hugo { + enabled true +} \ No newline at end of file diff --git a/hugo-plugin/build.gradle b/hugo-plugin/build.gradle index 21cb5e5..7721d9b 100644 --- a/hugo-plugin/build.gradle +++ b/hugo-plugin/build.gradle @@ -8,7 +8,7 @@ sourceCompatibility = JavaVersion.VERSION_1_7 dependencies { compile gradleApi() compile localGroovy() - compile 'com.android.tools.build:gradle:1.3.1' + compile "com.android.tools.build:gradle:1.5.0" compile 'org.aspectj:aspectjtools:1.8.6' compile 'org.aspectj:aspectjrt:1.8.6' } diff --git a/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoExec.groovy b/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoExec.groovy new file mode 100644 index 0000000..c70e9f8 --- /dev/null +++ b/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoExec.groovy @@ -0,0 +1,59 @@ +package hugo.weaving.plugin + +import groovy.transform.CompileStatic +import org.aspectj.bridge.IMessage +import org.aspectj.bridge.MessageHandler +import org.aspectj.tools.ajc.Main +import org.gradle.api.Project + +@CompileStatic +class HugoExec { + + String inpath; + String aspectpath; + String destinationpath; + String classpath; + String bootclasspath; + + private final Project project; + + HugoExec(Project project) { + this.project = project; + } + + public void exec() { + final def log = project.logger + + String[] args = [ + "-showWeaveInfo", + "-1.5", + "-inpath", inpath, + "-aspectpath", aspectpath, + "-d", destinationpath, + "-classpath", classpath, + "-bootclasspath", bootclasspath + ] +// System.out.println "ajc args: " + Arrays.toString(args.join("\n")) + + MessageHandler handler = new MessageHandler(true); + new Main().run(args, handler); + for (IMessage message : handler.getMessages(null, true)) { + switch (message.getKind()) { + case IMessage.ABORT: + case IMessage.ERROR: + case IMessage.FAIL: + log.error message.message, message.thrown + break; + case IMessage.WARNING: + log.warn message.message, message.thrown + break; + case IMessage.INFO: + log.info message.message, message.thrown + break; + case IMessage.DEBUG: + log.debug message.message, message.thrown + break; + } + } + } +} \ No newline at end of file diff --git a/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoPlugin.groovy b/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoPlugin.groovy index 5e9fe8a..ed36894 100644 --- a/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoPlugin.groovy +++ b/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoPlugin.groovy @@ -1,28 +1,40 @@ package hugo.weaving.plugin +import com.android.build.gradle.AppExtension import com.android.build.gradle.AppPlugin +import com.android.build.gradle.LibraryExtension import com.android.build.gradle.LibraryPlugin -import org.aspectj.bridge.IMessage -import org.aspectj.bridge.MessageHandler -import org.aspectj.tools.ajc.Main +import com.android.build.gradle.api.BaseVariant import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.tasks.compile.JavaCompile class HugoPlugin implements Plugin { - @Override void apply(Project project) { + + @Override + void apply(Project project) { def hasApp = project.plugins.withType(AppPlugin) def hasLib = project.plugins.withType(LibraryPlugin) if (!hasApp && !hasLib) { throw new IllegalStateException("'android' or 'android-library' plugin required.") } - final def log = project.logger - final def variants - if (hasApp) { - variants = project.android.applicationVariants + def transform = new HugoTransform(project, isEnabled(project)) + + if (hasLib) { + def android = project.extensions.getByType(LibraryExtension) + android.registerTransform(transform) + + android.libraryVariants.all { BaseVariant variant -> + configureCompileJavaTask(variant, variant.javaCompile, transform) + } } else { - variants = project.android.libraryVariants + def android = project.extensions.getByType(AppExtension) + android.registerTransform(transform) + + android.applicationVariants.all { BaseVariant variant -> + configureCompileJavaTask(variant, variant.javaCompile, transform) + } } project.dependencies { @@ -31,52 +43,17 @@ class HugoPlugin implements Plugin { debugCompile 'org.aspectj:aspectjrt:1.8.6' compile 'com.jakewharton.hugo:hugo-annotations:1.2.2-SNAPSHOT' } - project.extensions.create('hugo', HugoExtension) + } - variants.all { variant -> - if (!variant.buildType.isDebuggable()) { - log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") - return; - } else if (!project.hugo.enabled) { - log.debug("Hugo is not disabled.") - return; - } - - JavaCompile javaCompile = variant.javaCompile - javaCompile.doLast { - String[] args = [ - "-showWeaveInfo", - "-1.5", - "-inpath", javaCompile.destinationDir.toString(), - "-aspectpath", javaCompile.classpath.asPath, - "-d", javaCompile.destinationDir.toString(), - "-classpath", javaCompile.classpath.asPath, - "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator) - ] - log.debug "ajc args: " + Arrays.toString(args) - - MessageHandler handler = new MessageHandler(true); - new Main().run(args, handler); - for (IMessage message : handler.getMessages(null, true)) { - switch (message.getKind()) { - case IMessage.ABORT: - case IMessage.ERROR: - case IMessage.FAIL: - log.error message.message, message.thrown - break; - case IMessage.WARNING: - log.warn message.message, message.thrown - break; - case IMessage.INFO: - log.info message.message, message.thrown - break; - case IMessage.DEBUG: - log.debug message.message, message.thrown - break; - } - } - } + private static boolean isEnabled(Project project) { + if(project.hasProperty("hugo") && project.hugo.hasProperty("enabled")) { + return project.hugo.enabled; } + return true; + } + + private static configureCompileJavaTask(BaseVariant variant, JavaCompile javaCompileTask, HugoTransform transform) { + transform.putJavaCompileTask(variant.flavorName, variant.buildType.name, javaCompileTask) } } diff --git a/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoTransform.groovy b/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoTransform.groovy new file mode 100644 index 0000000..a188c3f --- /dev/null +++ b/hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoTransform.groovy @@ -0,0 +1,156 @@ +package hugo.weaving.plugin + +import com.android.build.api.transform.* +import com.android.utils.Pair +import groovy.transform.CompileStatic +import org.apache.commons.io.FileUtils +import org.gradle.api.Project +import org.gradle.api.ProjectConfigurationException +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.file.collections.SimpleFileCollection +import org.gradle.api.tasks.compile.JavaCompile + +import static com.android.build.api.transform.Status.* + +/** + * Created by williamwebb on 2/16/16. + */ +@CompileStatic +class HugoTransform extends Transform { + + private final Project project + private final Map, JavaCompile> javaCompileTasks = new HashMap<>() + private final boolean enabled; + public HugoTransform(Project project, boolean enabled) { + this.project = project + this.enabled = enabled; + } + + /** + * We need to set this later because the classpath is not fully calculated until the last + * possible moment when the java compile task runs. While a Transform currently doesn't have any + * variant information, we can guess the variant based off the input path. + */ + public void putJavaCompileTask(String flavorName, String buildTypeName, JavaCompile javaCompileTask) { + javaCompileTasks.put(Pair.of(flavorName, buildTypeName), javaCompileTask) + } + + @Override + void transform(Context context, Collection inputs, Collection referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { + boolean debug = context.path.toLowerCase().endsWith("debug"); + + inputs.each { TransformInput input -> + def outputDir = outputProvider.getContentLocation("hugo", outputTypes, scopes, Format.DIRECTORY) + + input.directoryInputs.each { DirectoryInput directoryInput -> + File inputFile = directoryInput.file + + // All classes need to be copied regardless for some reason. So if we want to + // disable hugo simply copy files. + if(!enabled || !debug) { + FileUtils.copyDirectory(inputFile,outputDir) + return + } + + String inputDirs; + if (isIncremental) { + FileCollection changed = new SimpleFileCollection(project.files().asList()) + directoryInput.changedFiles.each { File file, Status status -> + if (status == ADDED || status == CHANGED) { + changed += project.files(file.parent); + } + } + inputDirs = changed.asPath + } else { + inputDirs = inputFile.path + } + + JavaCompile javaCompileTask = getJavaCompile(inputFile) + + String classpath = (getClasspath(javaCompileTask, referencedInputs) + project.files(inputFile)).asPath + String bootClasspath = getBootClassPath(javaCompileTask).asPath + + def exec = new HugoExec(project) + exec.inpath = inputDirs + exec.aspectpath = classpath + exec.destinationpath = outputDir + exec.classpath = classpath + exec.bootclasspath = bootClasspath + exec.exec() + } + } + } + + private FileCollection getBootClassPath(JavaCompile javaCompileTask) { + + def bootClasspath = javaCompileTask.options.bootClasspath + if (bootClasspath) { + return project.files(bootClasspath.tokenize(File.pathSeparator)) + } else { + // If this is null it means the javaCompile task didn't need to run, however, we still + // need to run but can't without the bootClasspath. Just fail and ask the user to rebuild. + throw new ProjectConfigurationException("Unable to obtain the bootClasspath. This may happen if your javaCompile tasks didn't run but hugo did. You must rebuild your project or otherwise force javaCompile to run.", null) + } + } + + private FileCollection getClasspath(JavaCompile javaCompileTask, Collection referencedInputs) { + + def classpathFiles = javaCompileTask.classpath + referencedInputs.each { TransformInput input -> classpathFiles += project.files(input.directoryInputs*.file) } + + // bootClasspath isn't set until the last possible moment because it's expensive to look + // up the android sdk path. + def bootClasspath = javaCompileTask.options.bootClasspath + if (bootClasspath) { + classpathFiles += project.files(bootClasspath.tokenize(File.pathSeparator)) + } else { + // If this is null it means the javaCompile task didn't need to run, however, we still + // need to run but can't without the bootClasspath. Just fail and ask the user to rebuild. + throw new ProjectConfigurationException("Unable to obtain the bootClasspath. This may happen if your javaCompile tasks didn't run but hugo did. You must rebuild your project or otherwise force javaCompile to run.", null) + } + return classpathFiles + } + + private JavaCompile getJavaCompile(File inputFile) { + String buildName = inputFile.name + String flavorName = inputFile.parentFile.name + + // If either one starts with a number or is 'folders', it's probably the result of a transform, keep moving + // up the dir structure until we find the right folders. + // Yes I know this is bad, but hopefully per-variant transforms will land soon. + File current = inputFile + while (Character.isDigit(buildName.charAt(0)) || Character.isDigit(flavorName.charAt(0)) || buildName.equals("folders") || flavorName.equals("folders")) { + current = current.parentFile + buildName = current.name + flavorName = current.parentFile.name + } + + def javaCompileTask = javaCompileTasks.get(Pair.of(flavorName, buildName)) + if (javaCompileTask == null) { + // Flavor might not exist + javaCompileTask = javaCompileTasks.get(Pair.of("", buildName)) + } + + return javaCompileTask; + } + + @Override + public String getName() { + return "hugo" + } + + @Override + Set getInputTypes() { + return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES) + } + + @Override + Set getScopes() { + return Collections.singleton(QualifiedContent.Scope.PROJECT) + } + + @Override + public boolean isIncremental() { + return true + } +} \ No newline at end of file