| /* |
| * Copyright 2015 The Go Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style |
| * license that can be found in the LICENSE file. |
| */ |
| |
| package org.golang.mobile |
| |
| import org.gradle.api.DefaultTask |
| 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.gradle.api.tasks.compile.JavaCompile |
| |
| import org.golang.mobile.OutputFileTask |
| 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.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 |
| } |
| |
| // 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, |
| gomobileTask)) |
| } |
| |
| 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.javaCompile = variant.javaCompile |
| bindTask.bootClasspath = variant.javaCompile.options.bootClasspath |
| // TODO: Detect when updating the Java classes is redundant. |
| bindTask.outputs.upToDateWhen { false } |
| variant.registerJavaGeneratingTask(bindTask, new File(outputDir, "java")) |
| // 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 |
| libTask.javaCompile = variant.javaCompile |
| // 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) |
| // Ensure gomobile bind completes before depending on its output. |
| variant.javaCompile.dependsOn(libTask) |
| } |
| } |
| |
| class BindTask extends DefaultTask { |
| String bootClasspath |
| |
| 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) { |
| throw new GradleException('gobind.pkg and gobind.GOPATH must be set') |
| } |
| |
| def paths = (gopath.split(File.pathSeparator).collect{ "$it/bin" } + |
| System.getenv("PATH").split(File.pathSeparator)).flatten() |
| // Default installation path of go distribution. |
| if (isWindows()) { |
| paths = paths + "c:\\Go\\bin" |
| } else { |
| paths = paths + "/usr/local/go/bin" |
| } |
| |
| def exe = (cmdPath ?: findExecutable(cmd, paths))?.trim() |
| def gobin = (project.gobind.GO ?: findExecutable("go", paths))?.trim() |
| def gomobileFlags = project.gobind.GOMOBILEFLAGS?.trim() |
| |
| if (!exe || !gobin) { |
| throw new GradleException('failed to find ${cmd}/go tools. Set gobind.GOBIND, gobind.GOMOBILE, and gobind.GO') |
| } |
| |
| paths = [findDir(exe), findDir(gobin), paths].flatten() |
| |
| def androidHome = "" |
| try { |
| Properties properties = new Properties() |
| properties.load(project.rootProject.file('local.properties').newDataInputStream()) |
| androidHome = properties.getProperty('sdk.dir') |
| } catch (all) { |
| logger.info("failed to load local.properties.") |
| } |
| if (!androidHome?.trim()) { |
| // fallback to ANDROID_HOME |
| androidHome = System.getenv("ANDROID_HOME") |
| } |
| |
| project.exec { |
| executable(exe) |
| |
| if (bootClasspath) |
| cmdArgs.addAll(["-bootclasspath", bootClasspath]) |
| if (gomobileFlags) { |
| cmdArgs.addAll(gomobileFlags.split(" ")) |
| } |
| cmdArgs.addAll(pkg.split(" ")) |
| |
| args(cmdArgs) |
| if (!androidHome?.trim()) { |
| throw new GradleException('Neither sdk.dir or ANDROID_HOME is set') |
| } |
| environment("GOPATH", gopath) |
| environment("GOOS", "android") |
| environment("PATH", paths.join(File.pathSeparator)) |
| environment("ANDROID_HOME", androidHome) |
| } |
| } |
| |
| def isWindows() { |
| return System.getProperty("os.name").startsWith("Windows") |
| } |
| |
| def findExecutable(String name, ArrayList<String> paths) { |
| if (isWindows() && !name.endsWith(".exe")) { |
| name = name + ".exe" |
| } |
| for (p in paths) { |
| def f = new File(p + File.separator + name) |
| if (f.exists()) { |
| return p + File.separator + name |
| } |
| } |
| throw new GradleException('binary ' + name + ' is not found in $PATH (' + paths + ')') |
| } |
| |
| def findDir(String binpath) { |
| if (!binpath) { |
| return "" |
| } |
| |
| def f = new File(binpath) |
| return f.getParentFile().getAbsolutePath(); |
| } |
| } |
| |
| class GobindTask extends BindTask { |
| @OutputDirectory |
| File outputDir |
| |
| JavaCompile javaCompile |
| |
| @TaskAction |
| def gobind() { |
| run("gobind", project.gobind.GOBIND, ["-lang", "java", "-classpath", javaCompile.classpath.join(File.pathSeparator), "-outdir", outputDir.getAbsolutePath()]) |
| } |
| } |
| |
| class GomobileTask extends BindTask implements OutputFileTask { |
| @Optional |
| @OutputFile |
| File outputFile |
| |
| @Optional |
| @OutputDirectory |
| File libsDir |
| |
| JavaCompile javaCompile |
| |
| @TaskAction |
| def gomobile() { |
| if (outputFile == null) { |
| outputFile = File.createTempFile("gobind-", ".aar") |
| } |
| def cmd = ["bind"] |
| // Add the generated R and databinding classes to the classpath. |
| if (javaCompile) { |
| def classpath = project.files(javaCompile.classpath, javaCompile.destinationDir) |
| 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 = "" |
| |
| // GOPATH: necessary for gomobile tool. (required) |
| def String GOPATH = System.getenv("GOPATH") |
| |
| // GOARCH: (List of) GOARCH to include. |
| def String GOARCH = "" |
| |
| // GO: path to go tool. (can omit if 'go' is in the paths visible by Android Studio) |
| def String GO = "" |
| |
| // 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 = "" |
| } |