Merge "Add prebuilt ABI checker support to soong"
diff --git a/Android.bp b/Android.bp
index 97f786e..2c400cf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -248,6 +248,7 @@
         "java/java.go",
         "java/jdeps.go",
         "java/java_resources.go",
+        "java/kotlin.go",
         "java/prebuilt_apis.go",
         "java/proto.go",
         "java/sdk.go",
@@ -260,6 +261,7 @@
         "java/dexpreopt_test.go",
         "java/java_test.go",
         "java/jdeps_test.go",
+        "java/kotlin_test.go",
         "java/sdk_test.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/android/config.go b/android/config.go
index 393382b..956c4af 100644
--- a/android/config.go
+++ b/android/config.go
@@ -241,6 +241,7 @@
 	}
 
 	config.BuildOsVariant = config.Targets[BuildOs][0].String()
+	config.BuildOsCommonVariant = getCommonTargets(config.Targets[BuildOs])[0].String()
 
 	return testConfig
 }
diff --git a/java/androidmk.go b/java/androidmk.go
index b9e4298..ccb5109 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -33,9 +33,6 @@
 		} else {
 			fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", library.implementationAndResourcesJar.String())
 		}
-		if library.installFile == nil {
-			fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
-		}
 		if library.dexJarFile != nil {
 			fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String())
 		}
diff --git a/java/app.go b/java/app.go
index e44737e..335d9fc 100644
--- a/java/app.go
+++ b/java/app.go
@@ -79,8 +79,6 @@
 
 	appProperties appProperties
 
-	extraLinkFlags []string
-
 	installJniLibs []jniLib
 
 	bundleFile android.Path
@@ -155,29 +153,29 @@
 }
 
 func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
-	linkFlags := append([]string(nil), a.extraLinkFlags...)
+	aaptLinkFlags := []string{}
 
+	// Add TARGET_AAPT_CHARACTERISTICS values to AAPT link flags if they exist and --product flags were not provided.
 	hasProduct := false
 	for _, f := range a.aaptProperties.Aaptflags {
 		if strings.HasPrefix(f, "--product") {
 			hasProduct = true
+			break
 		}
 	}
-
-	// Product characteristics
 	if !hasProduct && len(ctx.Config().ProductAAPTCharacteristics()) > 0 {
-		linkFlags = append(linkFlags, "--product", ctx.Config().ProductAAPTCharacteristics())
+		aaptLinkFlags = append(aaptLinkFlags, "--product", ctx.Config().ProductAAPTCharacteristics())
 	}
 
 	if !Bool(a.aaptProperties.Aapt_include_all_resources) {
 		// Product AAPT config
 		for _, aaptConfig := range ctx.Config().ProductAAPTConfig() {
-			linkFlags = append(linkFlags, "-c", aaptConfig)
+			aaptLinkFlags = append(aaptLinkFlags, "-c", aaptConfig)
 		}
 
 		// Product AAPT preferred config
 		if len(ctx.Config().ProductAAPTPreferredConfig()) > 0 {
-			linkFlags = append(linkFlags, "--preferred-density", ctx.Config().ProductAAPTPreferredConfig())
+			aaptLinkFlags = append(aaptLinkFlags, "--preferred-density", ctx.Config().ProductAAPTPreferredConfig())
 		}
 	}
 
@@ -186,10 +184,10 @@
 
 	manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
 	if overridden {
-		linkFlags = append(linkFlags, "--rename-manifest-package "+manifestPackageName)
+		aaptLinkFlags = append(aaptLinkFlags, "--rename-manifest-package "+manifestPackageName)
 	}
 
-	a.aapt.buildActions(ctx, sdkContext(a), linkFlags...)
+	a.aapt.buildActions(ctx, sdkContext(a), aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
diff --git a/java/builder.go b/java/builder.go
index 8615664..40f72e1 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -60,26 +60,6 @@
 		"javacFlags", "bootClasspath", "classpath", "processorpath", "srcJars", "srcJarDir",
 		"outDir", "annoDir", "javaVersion")
 
-	kotlinc = pctx.AndroidGomaStaticRule("kotlinc",
-		blueprint.RuleParams{
-			Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" && mkdir -p "$classesDir" "$srcJarDir" && ` +
-				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-				`${config.GenKotlinBuildFileCmd} $classpath $classesDir $out.rsp $srcJarDir/list > $kotlinBuildFile &&` +
-				`${config.KotlincCmd} $kotlincFlags ` +
-				`-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile && ` +
-				`${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir`,
-			CommandDeps: []string{
-				"${config.KotlincCmd}",
-				"${config.KotlinCompilerJar}",
-				"${config.GenKotlinBuildFileCmd}",
-				"${config.SoongZipCmd}",
-				"${config.ZipSyncCmd}",
-			},
-			Rspfile:        "$out.rsp",
-			RspfileContent: `$in`,
-		},
-		"kotlincFlags", "classpath", "srcJars", "srcJarDir", "classesDir", "kotlinJvmTarget", "kotlinBuildFile")
-
 	turbine = pctx.AndroidStaticRule("turbine",
 		blueprint.RuleParams{
 			Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
@@ -177,35 +157,6 @@
 	protoRoot        bool
 }
 
-func TransformKotlinToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
-	srcFiles, srcJars android.Paths,
-	flags javaBuilderFlags) {
-
-	inputs := append(android.Paths(nil), srcFiles...)
-
-	var deps android.Paths
-	deps = append(deps, flags.kotlincClasspath...)
-	deps = append(deps, srcJars...)
-
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        kotlinc,
-		Description: "kotlinc",
-		Output:      outputFile,
-		Inputs:      inputs,
-		Implicits:   deps,
-		Args: map[string]string{
-			"classpath":       flags.kotlincClasspath.FormJavaClassPath("-classpath"),
-			"kotlincFlags":    flags.kotlincFlags,
-			"srcJars":         strings.Join(srcJars.Strings(), " "),
-			"classesDir":      android.PathForModuleOut(ctx, "kotlinc", "classes").String(),
-			"srcJarDir":       android.PathForModuleOut(ctx, "kotlinc", "srcJars").String(),
-			"kotlinBuildFile": android.PathForModuleOut(ctx, "kotlinc-build.xml").String(),
-			// http://b/69160377 kotlinc only supports -jvm-target 1.6 and 1.8
-			"kotlinJvmTarget": "1.8",
-		},
-	})
-}
-
 func TransformJavaToClasses(ctx android.ModuleContext, outputFile android.WritablePath, shardIdx int,
 	srcFiles, srcJars android.Paths, flags javaBuilderFlags, deps android.Paths) {
 
@@ -249,7 +200,7 @@
 		// ensure java does not fall back to the default bootclasspath.
 		bootClasspath = `--bootclasspath ""`
 	} else {
-		bootClasspath = strings.Join(flags.bootClasspath.FormTurbineClasspath("--bootclasspath"), " ")
+		bootClasspath = strings.Join(flags.bootClasspath.FormTurbineClasspath("--bootclasspath "), " ")
 	}
 
 	ctx.Build(pctx, android.BuildParams{
@@ -262,7 +213,7 @@
 			"javacFlags":    flags.javacFlags,
 			"bootClasspath": bootClasspath,
 			"srcJars":       strings.Join(srcJars.Strings(), " "),
-			"classpath":     strings.Join(flags.classpath.FormTurbineClasspath("--classpath"), " "),
+			"classpath":     strings.Join(flags.classpath.FormTurbineClasspath("--classpath "), " "),
 			"outDir":        android.PathForModuleOut(ctx, "turbine", "classes").String(),
 			"javaVersion":   flags.javaVersion,
 		},
@@ -464,7 +415,7 @@
 	}
 	flags := make([]string, len(*x))
 	for i, v := range *x {
-		flags[i] = optName + " " + v.String()
+		flags[i] = optName + v.String()
 	}
 
 	return flags
diff --git a/java/config/kotlin.go b/java/config/kotlin.go
index 432840e..2af7b3c 100644
--- a/java/config/kotlin.go
+++ b/java/config/kotlin.go
@@ -14,6 +14,8 @@
 
 package config
 
+import "strings"
+
 var (
 	KotlinStdlibJar     = "external/kotlinc/lib/kotlin-stdlib.jar"
 	KotlincIllegalFlags = []string{
@@ -25,5 +27,14 @@
 func init() {
 	pctx.SourcePathVariable("KotlincCmd", "external/kotlinc/bin/kotlinc")
 	pctx.SourcePathVariable("KotlinCompilerJar", "external/kotlinc/lib/kotlin-compiler.jar")
+	pctx.SourcePathVariable("KotlinKaptJar", "external/kotlinc/lib/kotlin-annotation-processing.jar")
 	pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar)
+
+	// These flags silence "Illegal reflective access" warnings when running kotlinc in OpenJDK9
+	pctx.StaticVariable("KotlincSuppressJDK9Warnings", strings.Join([]string{
+		"-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+		"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+		"-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+		"-J--add-opens=java.base/sun.net.www.protocol.jar=ALL-UNNAMED",
+	}, " "))
 }
diff --git a/java/dex.go b/java/dex.go
index a6d486a..913eee6 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -85,8 +85,8 @@
 func (j *Module) d8Flags(ctx android.ModuleContext, flags javaBuilderFlags) ([]string, android.Paths) {
 	d8Flags := j.dexCommonFlags(ctx)
 
-	d8Flags = append(d8Flags, flags.bootClasspath.FormTurbineClasspath("--lib")...)
-	d8Flags = append(d8Flags, flags.classpath.FormTurbineClasspath("--lib")...)
+	d8Flags = append(d8Flags, flags.bootClasspath.FormTurbineClasspath("--lib ")...)
+	d8Flags = append(d8Flags, flags.classpath.FormTurbineClasspath("--lib ")...)
 
 	var d8Deps android.Paths
 	d8Deps = append(d8Deps, flags.bootClasspath...)
@@ -215,6 +215,11 @@
 			},
 		})
 	}
+	if j.deviceProperties.UncompressDex {
+		alignedJavalibJar := android.PathForModuleOut(ctx, "aligned", jarName)
+		TransformZipAlign(ctx, alignedJavalibJar, javalibJar)
+		javalibJar = alignedJavalibJar
+	}
 
 	return javalibJar
 }
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 6a603cf..a06a666 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -85,23 +85,31 @@
 	// The encode dex rule requires unzipping and rezipping the classes.dex files, ensure that if it was uncompressed
 	// in the input it stays uncompressed in the output.
 	soongZipFlags := ""
+	tmpOutput := output
+	tmpDir := android.PathForModuleOut(ctx, "hiddenapi", "dex")
 	if uncompressDex {
 		soongZipFlags = "-L 0"
+		tmpOutput = android.PathForModuleOut(ctx, "hiddenapi", "unaligned", "unaligned.jar")
+		tmpDir = android.PathForModuleOut(ctx, "hiddenapi", "unaligned")
 	}
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIEncodeDexRule,
 		Description: "hiddenapi encode dex",
 		Input:       dexInput,
-		Output:      output,
+		Output:      tmpOutput,
 		Implicit:    flags,
 		Args: map[string]string{
 			"flags":         flags.String(),
-			"tmpDir":        android.PathForModuleOut(ctx, "hiddenapi", "dex").String(),
+			"tmpDir":        tmpDir.String(),
 			"soongZipFlags": soongZipFlags,
 		},
 	})
 
+	if uncompressDex {
+		TransformZipAlign(ctx, output, tmpOutput)
+	}
+
 	hiddenAPISaveDexInputs(ctx, dexInput)
 }
 
diff --git a/java/java.go b/java/java.go
index 9c4bd86..1e5c628 100644
--- a/java/java.go
+++ b/java/java.go
@@ -379,6 +379,7 @@
 	frameworkResTag       = dependencyTag{name: "framework-res"}
 	frameworkApkTag       = dependencyTag{name: "framework-apk"}
 	kotlinStdlibTag       = dependencyTag{name: "kotlin-stdlib"}
+	kotlinAnnotationsTag  = dependencyTag{name: "kotlin-annotations"}
 	proguardRaiseTag      = dependencyTag{name: "proguard-raise"}
 	certificateTag        = dependencyTag{name: "certificate"}
 	instrumentationForTag = dependencyTag{name: "instrumentation_for"}
@@ -483,6 +484,9 @@
 		// TODO(ccross): move this to a mutator pass that can tell if generated sources contain
 		// Kotlin files
 		ctx.AddVariationDependencies(nil, kotlinStdlibTag, "kotlin-stdlib")
+		if len(j.properties.Annotation_processors) > 0 {
+			ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations")
+		}
 	}
 
 	if j.shouldInstrumentStatic(ctx) {
@@ -564,6 +568,7 @@
 	systemModules      android.Path
 	aidlPreprocess     android.OptionalPath
 	kotlinStdlib       android.Paths
+	kotlinAnnotations  android.Paths
 }
 
 func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer) {
@@ -723,6 +728,8 @@
 				}
 			case kotlinStdlibTag:
 				deps.kotlinStdlib = dep.HeaderJars()
+			case kotlinAnnotationsTag:
+				deps.kotlinAnnotations = dep.HeaderJars()
 			}
 
 			deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
@@ -969,18 +976,28 @@
 		kotlinSrcFiles = append(kotlinSrcFiles, uniqueSrcFiles...)
 		kotlinSrcFiles = append(kotlinSrcFiles, srcFiles.FilterByExt(".kt")...)
 
-		flags.kotlincClasspath = append(flags.kotlincClasspath, deps.bootClasspath...)
-		flags.kotlincClasspath = append(flags.kotlincClasspath, deps.kotlinStdlib...)
-		flags.kotlincClasspath = append(flags.kotlincClasspath, deps.classpath...)
+		flags.classpath = append(flags.classpath, deps.kotlinStdlib...)
+		flags.classpath = append(flags.classpath, deps.kotlinAnnotations...)
+
+		flags.kotlincClasspath = append(flags.kotlincClasspath, flags.bootClasspath...)
+		flags.kotlincClasspath = append(flags.kotlincClasspath, flags.classpath...)
+
+		if len(flags.processorPath) > 0 {
+			// Use kapt for annotation processing
+			kaptSrcJar := android.PathForModuleOut(ctx, "kapt", "kapt-sources.jar")
+			kotlinKapt(ctx, kaptSrcJar, kotlinSrcFiles, srcJars, flags)
+			srcJars = append(srcJars, kaptSrcJar)
+			// Disable annotation processing in javac, it's already been handled by kapt
+			flags.processorPath = nil
+		}
 
 		kotlinJar := android.PathForModuleOut(ctx, "kotlin", jarName)
-		TransformKotlinToClasses(ctx, kotlinJar, kotlinSrcFiles, srcJars, flags)
+		kotlinCompile(ctx, kotlinJar, kotlinSrcFiles, srcJars, flags)
 		if ctx.Failed() {
 			return
 		}
 
 		// Make javac rule depend on the kotlinc rule
-		flags.classpath = append(flags.classpath, deps.kotlinStdlib...)
 		flags.classpath = append(flags.classpath, kotlinJar)
 
 		// Jar kotlin classes into the final jar after javac
@@ -1395,13 +1412,7 @@
 	j.deviceProperties.UncompressDex = j.shouldUncompressDex(ctx)
 	j.compile(ctx)
 
-	if Bool(j.properties.Installable) || ctx.Host() {
-		if j.deviceProperties.UncompressDex {
-			alignedOutputFile := android.PathForModuleOut(ctx, "aligned", ctx.ModuleName()+".jar")
-			TransformZipAlign(ctx, alignedOutputFile, j.outputFile)
-			j.outputFile = alignedOutputFile
-		}
-
+	if (Bool(j.properties.Installable) || ctx.Host()) && !android.DirectlyInAnyApex(ctx, ctx.ModuleName()) {
 		j.installFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
 			ctx.ModuleName()+".jar", j.outputFile)
 	}
diff --git a/java/java_test.go b/java/java_test.go
index 6437c0c..9f805bb 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -121,6 +121,7 @@
 		"core.current.stubs",
 		"core.platform.api.stubs",
 		"kotlin-stdlib",
+		"kotlin-annotations",
 	}
 
 	for _, extra := range extraModules {
@@ -571,68 +572,6 @@
 	}
 }
 
-func TestKotlin(t *testing.T) {
-	ctx := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java", "b.kt"],
-		}
-
-		java_library {
-			name: "bar",
-			srcs: ["b.kt"],
-			libs: ["foo"],
-			static_libs: ["baz"],
-		}
-
-		java_library {
-			name: "baz",
-			srcs: ["c.java"],
-		}
-		`)
-
-	fooKotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
-	fooJavac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
-	fooJar := ctx.ModuleForTests("foo", "android_common").Output("combined/foo.jar")
-
-	if len(fooKotlinc.Inputs) != 2 || fooKotlinc.Inputs[0].String() != "a.java" ||
-		fooKotlinc.Inputs[1].String() != "b.kt" {
-		t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, fooKotlinc.Inputs)
-	}
-
-	if len(fooJavac.Inputs) != 1 || fooJavac.Inputs[0].String() != "a.java" {
-		t.Errorf(`foo inputs %v != ["a.java"]`, fooJavac.Inputs)
-	}
-
-	if !strings.Contains(fooJavac.Args["classpath"], fooKotlinc.Output.String()) {
-		t.Errorf("foo classpath %v does not contain %q",
-			fooJavac.Args["classpath"], fooKotlinc.Output.String())
-	}
-
-	if !inList(fooKotlinc.Output.String(), fooJar.Inputs.Strings()) {
-		t.Errorf("foo jar inputs %v does not contain %q",
-			fooJar.Inputs.Strings(), fooKotlinc.Output.String())
-	}
-
-	fooHeaderJar := ctx.ModuleForTests("foo", "android_common").Output("turbine-combined/foo.jar")
-	bazHeaderJar := ctx.ModuleForTests("baz", "android_common").Output("turbine-combined/baz.jar")
-	barKotlinc := ctx.ModuleForTests("bar", "android_common").Rule("kotlinc")
-
-	if len(barKotlinc.Inputs) != 1 || barKotlinc.Inputs[0].String() != "b.kt" {
-		t.Errorf(`bar kotlinc inputs %v != ["b.kt"]`, barKotlinc.Inputs)
-	}
-
-	if !inList(fooHeaderJar.Output.String(), barKotlinc.Implicits.Strings()) {
-		t.Errorf(`expected %q in bar implicits %v`,
-			fooHeaderJar.Output.String(), barKotlinc.Implicits.Strings())
-	}
-
-	if !inList(bazHeaderJar.Output.String(), barKotlinc.Implicits.Strings()) {
-		t.Errorf(`expected %q in bar implicits %v`,
-			bazHeaderJar.Output.String(), barKotlinc.Implicits.Strings())
-	}
-}
-
 func TestTurbine(t *testing.T) {
 	ctx := testJava(t, `
 		java_library {
diff --git a/java/kotlin.go b/java/kotlin.go
new file mode 100644
index 0000000..c72f2de
--- /dev/null
+++ b/java/kotlin.go
@@ -0,0 +1,168 @@
+// Copyright 2019 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 java
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/binary"
+	"strings"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+var kotlinc = pctx.AndroidGomaStaticRule("kotlinc",
+	blueprint.RuleParams{
+		Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" && mkdir -p "$classesDir" "$srcJarDir" && ` +
+			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
+			`${config.GenKotlinBuildFileCmd} $classpath $classesDir $out.rsp $srcJarDir/list > $kotlinBuildFile &&` +
+			`${config.KotlincCmd} ${config.JavacHeapFlags} $kotlincFlags ` +
+			`-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile && ` +
+			`${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir`,
+		CommandDeps: []string{
+			"${config.KotlincCmd}",
+			"${config.KotlinCompilerJar}",
+			"${config.GenKotlinBuildFileCmd}",
+			"${config.SoongZipCmd}",
+			"${config.ZipSyncCmd}",
+		},
+		Rspfile:        "$out.rsp",
+		RspfileContent: `$in`,
+	},
+	"kotlincFlags", "classpath", "srcJars", "srcJarDir", "classesDir", "kotlinJvmTarget", "kotlinBuildFile")
+
+// kotlinCompile takes .java and .kt sources and srcJars, and compiles the .kt sources into a classes jar in outputFile.
+func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles, srcJars android.Paths,
+	flags javaBuilderFlags) {
+
+	var deps android.Paths
+	deps = append(deps, flags.kotlincClasspath...)
+	deps = append(deps, srcJars...)
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        kotlinc,
+		Description: "kotlinc",
+		Output:      outputFile,
+		Inputs:      srcFiles,
+		Implicits:   deps,
+		Args: map[string]string{
+			"classpath":       flags.kotlincClasspath.FormJavaClassPath("-classpath"),
+			"kotlincFlags":    flags.kotlincFlags,
+			"srcJars":         strings.Join(srcJars.Strings(), " "),
+			"classesDir":      android.PathForModuleOut(ctx, "kotlinc", "classes").String(),
+			"srcJarDir":       android.PathForModuleOut(ctx, "kotlinc", "srcJars").String(),
+			"kotlinBuildFile": android.PathForModuleOut(ctx, "kotlinc-build.xml").String(),
+			// http://b/69160377 kotlinc only supports -jvm-target 1.6 and 1.8
+			"kotlinJvmTarget": "1.8",
+		},
+	})
+}
+
+var kapt = pctx.AndroidGomaStaticRule("kapt",
+	blueprint.RuleParams{
+		Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && mkdir -p "$srcJarDir" "$kaptDir" && ` +
+			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
+			`${config.GenKotlinBuildFileCmd} $classpath "" $out.rsp $srcJarDir/list > $kotlinBuildFile &&` +
+			`${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} $kotlincFlags ` +
+			`-Xplugin=${config.KotlinKaptJar} ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:sources=$kaptDir/sources ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:classes=$kaptDir/classes ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptDir/stubs ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:aptMode=stubsAndApt ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:javacArguments=$encodedJavacFlags ` +
+			`$kaptProcessorPath ` +
+			`-Xbuild-file=$kotlinBuildFile && ` +
+			`${config.SoongZipCmd} -jar -o $out -C $kaptDir/sources -D $kaptDir/sources`,
+		CommandDeps: []string{
+			"${config.KotlincCmd}",
+			"${config.KotlinCompilerJar}",
+			"${config.KotlinKaptJar}",
+			"${config.GenKotlinBuildFileCmd}",
+			"${config.SoongZipCmd}",
+			"${config.ZipSyncCmd}",
+		},
+		Rspfile:        "$out.rsp",
+		RspfileContent: `$in`,
+	},
+	"kotlincFlags", "encodedJavacFlags", "kaptProcessorPath", "classpath", "srcJars", "srcJarDir", "kaptDir", "kotlinJvmTarget", "kotlinBuildFile")
+
+// kotlinKapt performs Kotlin-compatible annotation processing.  It takes .kt and .java sources and srcjars, and runs
+// annotation processors over all of them, producing a srcjar of generated code in outputFile.  The srcjar should be
+// added as an additional input to kotlinc and javac rules, and the javac rule should have annotation processing
+// disabled.
+func kotlinKapt(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles, srcJars android.Paths,
+	flags javaBuilderFlags) {
+
+	var deps android.Paths
+	deps = append(deps, flags.kotlincClasspath...)
+	deps = append(deps, srcJars...)
+	deps = append(deps, flags.processorPath...)
+
+	kaptProcessorPath := flags.processorPath.FormTurbineClasspath("-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=")
+
+	encodedJavacFlags := kaptEncodeFlags([][2]string{
+		{"-source", flags.javaVersion},
+		{"-target", flags.javaVersion},
+	})
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        kapt,
+		Description: "kapt",
+		Output:      outputFile,
+		Inputs:      srcFiles,
+		Implicits:   deps,
+		Args: map[string]string{
+			"classpath":         flags.kotlincClasspath.FormJavaClassPath("-classpath"),
+			"kotlincFlags":      flags.kotlincFlags,
+			"srcJars":           strings.Join(srcJars.Strings(), " "),
+			"srcJarDir":         android.PathForModuleOut(ctx, "kapt", "srcJars").String(),
+			"kotlinBuildFile":   android.PathForModuleOut(ctx, "kapt", "build.xml").String(),
+			"kaptProcessorPath": strings.Join(kaptProcessorPath, " "),
+			"kaptDir":           android.PathForModuleOut(ctx, "kapt/gen").String(),
+			"encodedJavacFlags": encodedJavacFlags,
+		},
+	})
+}
+
+// kapt converts a list of key, value pairs into a base64 encoded Java serialization, which is what kapt expects.
+func kaptEncodeFlags(options [][2]string) string {
+	buf := &bytes.Buffer{}
+
+	binary.Write(buf, binary.BigEndian, uint32(len(options)))
+	for _, option := range options {
+		binary.Write(buf, binary.BigEndian, uint16(len(option[0])))
+		buf.WriteString(option[0])
+		binary.Write(buf, binary.BigEndian, uint16(len(option[1])))
+		buf.WriteString(option[1])
+	}
+
+	header := &bytes.Buffer{}
+	header.Write([]byte{0xac, 0xed, 0x00, 0x05}) // java serialization header
+
+	if buf.Len() < 256 {
+		header.WriteByte(0x77) // blockdata
+		header.WriteByte(byte(buf.Len()))
+	} else {
+		header.WriteByte(0x7a) // blockdatalong
+		binary.Write(header, binary.BigEndian, uint32(buf.Len()))
+	}
+
+	return base64.StdEncoding.EncodeToString(append(header.Bytes(), buf.Bytes()...))
+}
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
new file mode 100644
index 0000000..1069f42
--- /dev/null
+++ b/java/kotlin_test.go
@@ -0,0 +1,189 @@
+// 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 java
+
+import (
+	"strconv"
+	"strings"
+	"testing"
+)
+
+func TestKotlin(t *testing.T) {
+	ctx := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java", "b.kt"],
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.kt"],
+			libs: ["foo"],
+			static_libs: ["baz"],
+		}
+
+		java_library {
+			name: "baz",
+			srcs: ["c.java"],
+		}
+		`)
+
+	fooKotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
+	fooJavac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+	fooJar := ctx.ModuleForTests("foo", "android_common").Output("combined/foo.jar")
+
+	if len(fooKotlinc.Inputs) != 2 || fooKotlinc.Inputs[0].String() != "a.java" ||
+		fooKotlinc.Inputs[1].String() != "b.kt" {
+		t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, fooKotlinc.Inputs)
+	}
+
+	if len(fooJavac.Inputs) != 1 || fooJavac.Inputs[0].String() != "a.java" {
+		t.Errorf(`foo inputs %v != ["a.java"]`, fooJavac.Inputs)
+	}
+
+	if !strings.Contains(fooJavac.Args["classpath"], fooKotlinc.Output.String()) {
+		t.Errorf("foo classpath %v does not contain %q",
+			fooJavac.Args["classpath"], fooKotlinc.Output.String())
+	}
+
+	if !inList(fooKotlinc.Output.String(), fooJar.Inputs.Strings()) {
+		t.Errorf("foo jar inputs %v does not contain %q",
+			fooJar.Inputs.Strings(), fooKotlinc.Output.String())
+	}
+
+	fooHeaderJar := ctx.ModuleForTests("foo", "android_common").Output("turbine-combined/foo.jar")
+	bazHeaderJar := ctx.ModuleForTests("baz", "android_common").Output("turbine-combined/baz.jar")
+	barKotlinc := ctx.ModuleForTests("bar", "android_common").Rule("kotlinc")
+
+	if len(barKotlinc.Inputs) != 1 || barKotlinc.Inputs[0].String() != "b.kt" {
+		t.Errorf(`bar kotlinc inputs %v != ["b.kt"]`, barKotlinc.Inputs)
+	}
+
+	if !inList(fooHeaderJar.Output.String(), barKotlinc.Implicits.Strings()) {
+		t.Errorf(`expected %q in bar implicits %v`,
+			fooHeaderJar.Output.String(), barKotlinc.Implicits.Strings())
+	}
+
+	if !inList(bazHeaderJar.Output.String(), barKotlinc.Implicits.Strings()) {
+		t.Errorf(`expected %q in bar implicits %v`,
+			bazHeaderJar.Output.String(), barKotlinc.Implicits.Strings())
+	}
+}
+
+func TestKapt(t *testing.T) {
+	ctx := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java", "b.kt"],
+			annotation_processors: ["bar"],
+		}
+
+		java_library_host {
+			name: "bar",
+		}
+		`)
+
+	kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
+	kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
+	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+
+	// Test that the kotlin and java sources are passed to kapt and kotlinc
+	if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" {
+		t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs)
+	}
+	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)
+	}
+
+	// Test that only the java sources are passed to javac
+	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
+		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
+	}
+
+	// Test that the kapt srcjar is a dependency of kotlinc and javac rules
+	if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) {
+		t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings())
+	}
+	if !inList(kapt.Output.String(), javac.Implicits.Strings()) {
+		t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings())
+	}
+
+	// Test that the kapt srcjar is extracted by the kotlinc and javac rules
+	if kotlinc.Args["srcJars"] != kapt.Output.String() {
+		t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+	}
+	if javac.Args["srcJars"] != kapt.Output.String() {
+		t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+	}
+}
+
+func TestKaptEncodeFlags(t *testing.T) {
+	// Compares the kaptEncodeFlags against the results of the example implementation at
+	// https://kotlinlang.org/docs/reference/kapt.html#apjavac-options-encoding
+	tests := []struct {
+		in  [][2]string
+		out string
+	}{
+		{
+			// empty input
+			in:  [][2]string{},
+			out: "rO0ABXcEAAAAAA==",
+		},
+		{
+			// common input
+			in: [][2]string{
+				{"-source", "1.8"},
+				{"-target", "1.8"},
+			},
+			out: "rO0ABXcgAAAAAgAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjg=",
+		},
+		{
+			// input that serializes to a 255 byte block
+			in: [][2]string{
+				{"-source", "1.8"},
+				{"-target", "1.8"},
+				{"a", strings.Repeat("b", 218)},
+			},
+			out: "rO0ABXf/AAAAAwAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjgAAWEA2mJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJi",
+		},
+		{
+			// input that serializes to a 256 byte block
+			in: [][2]string{
+				{"-source", "1.8"},
+				{"-target", "1.8"},
+				{"a", strings.Repeat("b", 219)},
+			},
+			out: "rO0ABXoAAAEAAAAAAwAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjgAAWEA22JiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYg==",
+		},
+		{
+			// input that serializes to a 257 byte block
+			in: [][2]string{
+				{"-source", "1.8"},
+				{"-target", "1.8"},
+				{"a", strings.Repeat("b", 220)},
+			},
+			out: "rO0ABXoAAAEBAAAAAwAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjgAAWEA3GJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI=",
+		},
+	}
+
+	for i, test := range tests {
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			got := kaptEncodeFlags(test.in)
+			if got != test.out {
+				t.Errorf("\nwant %q\n got %q", test.out, got)
+			}
+		})
+	}
+}