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

Implement new Transform API #124

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand Down
4 changes: 4 additions & 0 deletions hugo-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ android {
}
}
}

hugo {
enabled true
}
2 changes: 1 addition & 1 deletion hugo-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand Down
59 changes: 59 additions & 0 deletions hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoExec.groovy
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
83 changes: 30 additions & 53 deletions hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoPlugin.groovy
Original file line number Diff line number Diff line change
@@ -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<Project> {
@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 {
Expand All @@ -31,52 +43,17 @@ class HugoPlugin implements Plugin<Project> {
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)
}
}
156 changes: 156 additions & 0 deletions hugo-plugin/src/main/groovy/hugo/weaving/plugin/HugoTransform.groovy
Original file line number Diff line number Diff line change
@@ -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<Pair<String, String>, 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<TransformInput> inputs, Collection<TransformInput> 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<TransformInput> 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<QualifiedContent.ContentType> getInputTypes() {
return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES)
}

@Override
Set<QualifiedContent.Scope> getScopes() {
return Collections.singleton(QualifiedContent.Scope.PROJECT)
}

@Override
public boolean isIncremental() {
return true
}
}