Initial kotlin support
Allow java libraries to specify .kt sources, precompile them with
kotlin, and then pass them in the classpath to javac.
Bug: 65219535
Test: java_test.go
Change-Id: Ife22b6ef82ced9ec26a9e5392b2dadacbb16546f
diff --git a/java/builder.go b/java/builder.go
index efe6256..a03f892 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -50,6 +50,22 @@
},
"javacFlags", "sourcepath", "bootClasspath", "classpath", "outDir", "annoDir", "javaVersion")
+ kotlinc = pctx.AndroidGomaStaticRule("kotlinc",
+ blueprint.RuleParams{
+ // TODO(ccross): kotlinc doesn't support @ file for arguments, which will limit the
+ // maximum number of input files, especially on darwin.
+ Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
+ `${config.KotlincCmd} $classpath $kotlincFlags ` +
+ `-jvm-target $javaVersion -d $outDir $in && ` +
+ `${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir`,
+ CommandDeps: []string{
+ "${config.KotlincCmd}",
+ "${config.KotlinCompilerJar}",
+ "${config.SoongZipCmd}",
+ },
+ },
+ "kotlincFlags", "classpath", "outDir", "javaVersion")
+
errorprone = pctx.AndroidStaticRule("errorprone",
blueprint.RuleParams{
Command: `rm -rf "$outDir" "$annoDir" && mkdir -p "$outDir" "$annoDir" && ` +
@@ -131,10 +147,36 @@
aidlFlags string
javaVersion string
+ kotlincFlags string
+ kotlincClasspath classpath
+
protoFlags string
protoOutFlag string
}
+func TransformKotlinToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
+ srcFiles android.Paths, srcJars classpath,
+ flags javaBuilderFlags) {
+
+ classDir := android.PathForModuleOut(ctx, "classes-kt")
+
+ inputs := append(android.Paths(nil), srcFiles...)
+ inputs = append(inputs, srcJars...)
+
+ ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+ Rule: kotlinc,
+ Description: "kotlinc",
+ Output: outputFile,
+ Inputs: inputs,
+ Args: map[string]string{
+ "classpath": flags.kotlincClasspath.JavaClasspath(),
+ "kotlincFlags": flags.kotlincFlags,
+ "outDir": classDir.String(),
+ "javaVersion": flags.javaVersion,
+ },
+ })
+}
+
func TransformJavaToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
srcFiles android.Paths, srcJars classpath,
flags javaBuilderFlags, deps android.Paths) {
diff --git a/java/config/kotlin.go b/java/config/kotlin.go
new file mode 100644
index 0000000..35f9e9d
--- /dev/null
+++ b/java/config/kotlin.go
@@ -0,0 +1,25 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+var (
+ KotlinStdlibJar = "external/kotlinc/lib/kotlin-stdlib.jar"
+)
+
+func init() {
+ pctx.SourcePathVariable("KotlincCmd", "external/kotlinc/bin/kotlinc")
+ pctx.SourcePathVariable("KotlinCompilerJar", "external/kotlinc/lib/kotlin-compiler.jar")
+ pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar)
+}
diff --git a/java/java.go b/java/java.go
index 770c9c1..3382cf2 100644
--- a/java/java.go
+++ b/java/java.go
@@ -198,6 +198,7 @@
bootClasspathTag = dependencyTag{name: "bootclasspath"}
systemModulesTag = dependencyTag{name: "system modules"}
frameworkResTag = dependencyTag{name: "framework-res"}
+ kotlinStdlibTag = dependencyTag{name: "kotlin-stdlib"}
)
type sdkDep struct {
@@ -326,6 +327,12 @@
if j.hasSrcExt(".proto") {
protoDeps(ctx, &j.protoProperties)
}
+
+ if j.hasSrcExt(".kt") {
+ // TODO(ccross): move this to a mutator pass that can tell if generated sources contain
+ // Kotlin files
+ ctx.AddDependency(ctx.Module(), kotlinStdlibTag, "kotlin-stdlib")
+ }
}
func hasSrcExt(srcs []string, ext string) bool {
@@ -373,6 +380,7 @@
srcJars android.Paths
systemModules android.Path
aidlPreprocess android.OptionalPath
+ kotlinStdlib android.Paths
}
func (j *Module) collectDeps(ctx android.ModuleContext) deps {
@@ -424,6 +432,8 @@
// generated by framework-res.apk
// TODO(ccross): aapt java files should go in a src jar
}
+ case kotlinStdlibTag:
+ deps.kotlinStdlib = dep.ClasspathFiles()
default:
panic(fmt.Errorf("unknown dependency %q for %q", otherName, ctx.ModuleName()))
}
@@ -492,7 +502,33 @@
var jars android.Paths
- if len(srcFiles) > 0 {
+ if srcFiles.HasExt(".kt") {
+ // If there are kotlin files, compile them first but pass all the kotlin and java files
+ // kotlinc will use the java files to resolve types referenced by the kotlin files, but
+ // won't emit any classes for them.
+
+ flags.kotlincFlags = "-no-stdlib"
+ if ctx.Device() {
+ flags.kotlincFlags += " -no-jdk"
+ }
+
+ flags.kotlincClasspath = append(flags.kotlincClasspath, deps.kotlinStdlib...)
+ flags.kotlincClasspath = append(flags.kotlincClasspath, deps.classpath...)
+
+ kotlinJar := android.PathForModuleOut(ctx, "classes-kt.jar")
+ TransformKotlinToClasses(ctx, kotlinJar, srcFiles, srcJars, flags)
+ if ctx.Failed() {
+ return
+ }
+
+ // Make javac rule depend on the kotlinc rule
+ flags.classpath = append(flags.classpath, kotlinJar)
+ // Jar kotlin classes into the final jar after javac
+ jars = append(jars, kotlinJar)
+ jars = append(jars, deps.kotlinStdlib...)
+ }
+
+ if javaSrcFiles := srcFiles.FilterByExt(".java"); len(javaSrcFiles) > 0 {
var extraJarDeps android.Paths
if ctx.AConfig().IsEnvTrue("RUN_ERROR_PRONE") {
// If error-prone is enabled, add an additional rule to compile the java files into
@@ -501,13 +537,13 @@
// TODO(ccross): Once we always compile with javac9 we may be able to conditionally
// enable error-prone without affecting the output class files.
errorprone := android.PathForModuleOut(ctx, "classes-errorprone.list")
- RunErrorProne(ctx, errorprone, srcFiles, srcJars, flags)
+ RunErrorProne(ctx, errorprone, javaSrcFiles, srcJars, flags)
extraJarDeps = append(extraJarDeps, errorprone)
}
// Compile java sources into .class files
classes := android.PathForModuleOut(ctx, "classes-compiled.jar")
- TransformJavaToClasses(ctx, classes, srcFiles, srcJars, flags, extraJarDeps)
+ TransformJavaToClasses(ctx, classes, javaSrcFiles, srcJars, flags, extraJarDeps)
if ctx.Failed() {
return
}
diff --git a/java/java_test.go b/java/java_test.go
index 4729313..d64688f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -80,6 +80,7 @@
"android_stubs_current",
"android_system_stubs_current",
"android_test_stubs_current",
+ "kotlin-stdlib",
}
for _, extra := range extraModules {
@@ -115,6 +116,7 @@
"a.java": nil,
"b.java": nil,
"c.java": nil,
+ "b.kt": nil,
"a.jar": nil,
"b.jar": nil,
"res/a": nil,
@@ -613,6 +615,38 @@
}
}
+func TestKotlin(t *testing.T) {
+ ctx := testJava(t, `
+ java_library {
+ name: "foo",
+ srcs: ["a.java", "b.kt"],
+ }
+ `)
+
+ kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
+ javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+ jar := ctx.ModuleForTests("foo", "android_common").Output("classes.jar")
+
+ if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" ||
+ kotlinc.Inputs[1].String() != "b.kt" {
+ t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs)
+ }
+
+ if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
+ t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
+ }
+
+ if !strings.Contains(javac.Args["classpath"], kotlinc.Output.String()) {
+ t.Errorf("foo classpath %v does not contain %q",
+ javac.Args["classpath"], kotlinc.Output.String())
+ }
+
+ if !inList(kotlinc.Output.String(), jar.Inputs.Strings()) {
+ t.Errorf("foo jar inputs %v does not contain %q",
+ jar.Inputs.Strings(), kotlinc.Output.String())
+ }
+}
+
func fail(t *testing.T, errs []error) {
if len(errs) > 0 {
for _, err := range errs {