2
0
mirror of synced 2025-02-22 22:38:18 +00:00

misc/androidstudio: add support for the android gradle plugin

The gobind plugin exposes a set of Go packages as a AAR file ready to be
used by and Android project. Unfortunately, being a library project
limits the Go packages to access only the Java API from the standard
library and the Android SDK.

This CL tightens the integration with the Android plugin to support access
to project dependencies such as the Android Support Library, to the generated
R.* resource classes and finally to the Android databinding classes.

When the gradle project has loaded the Android plugin, the generation of
the Go library is split in two.
First, the gobind tool generates the Java classes for the bound Go
packages. In this step, Go packages can access the standard Java and Android
libraries as well as project dependencies. After this step, Android
databinding layout files can refer to Go classes.
In step two, the gomobile tool generates the JNI libraries
with the Go implementation of the generated Java classes. In this
step, Go can access the standard Java and Android libraries,
dependencies as well as R.* and generated databinding classes.

Change-Id: If853ecabdbd01eec5f89d064a6bc715cb20a4d83
Reviewed-on: https://go-review.googlesource.com/30094
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Elias Naur 2016-09-30 12:53:57 +02:00
parent e76ec53021
commit 4db6347e33
3 changed files with 143 additions and 29 deletions

View File

@ -18,14 +18,17 @@ gobind {
// Optional list of architectures. Defaults to all supported architectures.
GOARCH="arm amd64"
// Absolute path to the gomobile binary
// Absolute path to the gomobile binary. Optional.
GOMOBILE "/mypath/bin/gomobile"
// Absolute path to the go binary
// Absolute path to the gomobile binary. Optional.
GOBIND "/mypath/bin/gobind"
// Absolute path to the go binary. Optional.
GO "/usr/local/go/bin/go"
// Pass extra parameters to command line
// GOMOBILEFLAGS "-javapkg my.java.package"
// Pass extra parameters to command line. Optional.
GOMOBILEFLAGS "-javapkg my.java.package"
}
</pre>

View File

@ -27,7 +27,7 @@ dependencies {
testCompile 'junit:junit:4.11'
}
version = '0.2.6'
version = '0.2.7'
pluginBundle {
website = 'https://golang.org/x/mobile'

View File

@ -11,7 +11,11 @@ import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.Task
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.golang.mobile.OutputFileTask
@ -21,31 +25,81 @@ import org.golang.mobile.AARPublishArtifact
* GobindPlugin configures the default project that builds .AAR file
* from a go package, using gomobile bind command.
* For gomobile bind command, see https://golang.org/x/mobile/cmd/gomobile
*
* If the project has the android or android library plugin loaded, GobindPlugin
* hooks into the android build lifecycle in two steps. First, the Java classes are
* generated and registered with the android plugin. Then, when the databinding
* classes and the R classes are generated and compiled, the GobindPlugin generates
* the JNI libraries. By splitting the binding in two steps, the Android databinding
* machinery can resolve Go classes, and Go code can access the resulting databinding
* classes as well as the R resource classes.
*/
class GobindPlugin implements Plugin<Project> {
void apply(Project project) {
project.configurations.create("default")
project.extensions.create('gobind', GobindExtension)
// If the android or android library plugin is loaded, integrate
// directly with the android build cycle
if (project.plugins.hasPlugin("android") ||
project.plugins.hasPlugin("com.android.application")) {
project.android.applicationVariants.all { variant ->
handleVariant(project, variant)
}
return
}
if (project.plugins.hasPlugin("android-library") ||
project.plugins.hasPlugin("com.android.library")) {
project.android.libraryVariants.all { variant ->
handleVariant(project, variant)
}
return
}
Task gobindTask = project.tasks.create("gobind", GobindTask)
gobindTask.outputFile = project.file(project.name+".aar")
// Library mode: generate and declare the .aar file for parent
// projects to include.
project.configurations.create("default")
Task gomobileTask = project.tasks.create("gobind", GomobileTask)
gomobileTask.outputFile = project.file(project.name+".aar")
project.artifacts.add("default", new AARPublishArtifact(
'mylib',
null,
gobindTask))
gomobileTask))
Task cleanTask = project.tasks.create("clean", {
project.delete(project.name+".aar")
})
}
private static void handleVariant(Project project, def variant) {
File outputDir = project.file("$project.buildDir/generated/source/gobind/$variant.dirName")
// First, generate the Java classes with the gobind tool.
Task bindTask = project.tasks.create("gobind${variant.name.capitalize()}", GobindTask)
bindTask.outputDir = outputDir
bindTask.classpath = variant.javaCompile.classpath
bindTask.bootClasspath = variant.javaCompile.options.bootClasspath
// TODO: Detect when updating the Java classes is redundant.
bindTask.outputs.upToDateWhen { false }
variant.registerJavaGeneratingTask(bindTask, outputDir)
// Then, generate the JNI libraries with the gomobile tool.
Task libTask = project.tasks.create("gomobile${variant.name.capitalize()}", GomobileTask)
libTask.bootClasspath = variant.javaCompile.options.bootClasspath
// Add the R and databinding classes to the gomobile classpath.
libTask.classpath = project.files(variant.javaCompile.classpath, variant.javaCompile.destinationDir)
// Dump the JNI libraries in the known project jniLibs directory.
// TODO: Use a directory below build for the libraries instead. Adding a jni directory to the jniLibs
// property of android.sourceSets only works, but only if the directory changes every build.
libTask.libsDir = project.file("src/main/jniLibs")
// TODO: Detect when building the existing JNI libraries is redundant.
libTask.outputs.upToDateWhen { false }
libTask.dependsOn(bindTask)
variant.javaCompile.finalizedBy(libTask)
}
}
class GobindTask extends DefaultTask implements OutputFileTask {
@OutputFile
File outputFile
class BindTask extends DefaultTask {
String bootClasspath
@TaskAction
def gobind() {
def run(String cmd, String cmdPath, List<String> cmdArgs) {
def pkg = project.gobind.pkg.trim()
def gopath = (project.gobind.GOPATH ?: System.getenv("GOPATH"))?.trim()
if (!pkg || !gopath) {
@ -61,16 +115,15 @@ class GobindTask extends DefaultTask implements OutputFileTask {
paths = paths + "/usr/local/go/bin"
}
def gomobile = (project.gobind.GOMOBILE ?: findExecutable("gomobile", paths))?.trim()
def exe = (cmdPath ?: findExecutable(cmd, paths))?.trim()
def gobin = (project.gobind.GO ?: findExecutable("go", paths))?.trim()
def gomobileFlags = project.gobind.GOMOBILEFLAGS?.trim()
def goarch = project.gobind.GOARCH?.trim()
if (!gomobile || !gobin) {
throw new GradleException('failed to find gomobile/go tools. Set gobind.GOMOBILE and gobind.GO')
if (!exe || !gobin) {
throw new GradleException('failed to find ${cmd}/go tools. Set gobind.GOBIND, gobind.GOMOBILE, and gobind.GO')
}
paths = [findDir(gomobile), findDir(gobin), paths].flatten()
paths = [findDir(exe), findDir(gobin), paths].flatten()
def androidHome = ""
try {
@ -86,20 +139,16 @@ class GobindTask extends DefaultTask implements OutputFileTask {
}
project.exec {
executable(gomobile)
executable(exe)
def cmd = ["bind", "-i", "-o", project.name+".aar", "-target"]
if (goarch) {
cmd = cmd+goarch.split(" ").collect{ 'android/'+it }.join(",")
} else {
cmd << "android"
}
if (bootClasspath)
cmdArgs.addAll(["-bootclasspath", bootClasspath])
if (gomobileFlags) {
cmd.addAll(gomobileFlags.split(" "))
cmdArgs.addAll(gomobileFlags.split(" "))
}
cmd.addAll(pkg.split(" "))
cmdArgs.addAll(pkg.split(" "))
args(cmd)
args(cmdArgs)
if (!androidHome?.trim()) {
throw new GradleException('Neither sdk.dir or ANDROID_HOME is set')
}
@ -136,6 +185,65 @@ class GobindTask extends DefaultTask implements OutputFileTask {
}
}
class GobindTask extends BindTask {
@OutputDirectory
File outputDir
FileCollection classpath
@TaskAction
def gobind() {
run("gobind", project.gobind.GOBIND, ["-lang", "java", "-classpath", classpath.join(File.pathSeparator), "-outdir", outputDir.getAbsolutePath()])
}
}
class GomobileTask extends BindTask implements OutputFileTask {
@Optional
@OutputFile
File outputFile
@Optional
@OutputDirectory
File libsDir
FileCollection classpath
@TaskAction
def gomobile() {
if (outputFile == null) {
outputFile = File.createTempFile("gobind-", ".aar")
}
def cmd = ["bind", "-i"]
if (classpath) {
cmd << "-classpath"
cmd << classpath.join(File.pathSeparator)
}
cmd << "-o"
cmd << outputFile.getAbsolutePath()
cmd << "-target"
def goarch = project.gobind.GOARCH?.trim()
if (goarch) {
cmd = cmd+goarch.split(" ").collect{ 'android/'+it }.join(",")
} else {
cmd << "android"
}
run("gomobile", project.gobind.GOMOBILE, cmd)
// If libsDir is set, unpack (only) the JNI libraries to it.
if (libsDir != null) {
project.delete project.fileTree(dir: libsDir, include: '*/libgojni.so')
def zipFile = new java.util.zip.ZipFile(outputFile)
zipFile.entries().findAll { !it.directory && it.name.startsWith("jni/") }.each {
def libFile = new File(libsDir, it.name.substring(4))
libFile.parentFile.mkdirs()
zipFile.getInputStream(it).withStream {
libFile.append(it)
}
}
outputFile.delete()
}
}
}
class GobindExtension {
// Package to bind. Separate multiple packages with spaces. (required)
def String pkg = ""
@ -152,6 +260,9 @@ class GobindExtension {
// GOMOBILE: path to gomobile binary. (can omit if 'gomobile' is under GOPATH)
def String GOMOBILE = ""
// GOBIND: path to gobind binary. (can omit if 'gobind' is under GOPATH)
def String GOBIND = ""
// GOMOBILEFLAGS: extra flags to be passed to gomobile command. (optional)
def String GOMOBILEFLAGS = ""
}