Merge "Fix soong_ui with empty arguments"
diff --git a/Android.bp b/Android.bp
index ae31a60..32b89d1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -211,6 +211,7 @@
         "java/java.go",
         "java/proto.go",
         "java/resources.go",
+        "java/system_modules.go",
     ],
     testSrcs: [
         "java/java_test.go",
@@ -228,6 +229,7 @@
     srcs: [
         "java/config/config.go",
         "java/config/error_prone.go",
+        "java/config/kotlin.go",
         "java/config/makevars.go",
     ],
 }
diff --git a/android/config.go b/android/config.go
index 5abda26..dca998f 100644
--- a/android/config.go
+++ b/android/config.go
@@ -88,6 +88,9 @@
 	captureBuild      bool // true for tests, saves build parameters for each module
 	ignoreEnvironment bool // true for tests, returns empty from all Getenv calls
 
+	useOpenJDK9    bool // Use OpenJDK9, but possibly target 1.8
+	targetOpenJDK9 bool // Use OpenJDK9 and target 1.9
+
 	OncePer
 }
 
@@ -183,6 +186,10 @@
 		config: config,
 	}
 
+	if err := config.fromEnv(); err != nil {
+		panic(err)
+	}
+
 	return Config{config}
 }
 
@@ -273,9 +280,31 @@
 	config.Targets = targets
 	config.BuildOsVariant = targets[Host][0].String()
 
+	if err := config.fromEnv(); err != nil {
+		return Config{}, err
+	}
+
 	return Config{config}, nil
 }
 
+func (c *config) fromEnv() error {
+	switch c.Getenv("EXPERIMENTAL_USE_OPENJDK9") {
+	case "":
+		// Use OpenJDK8
+	case "1.8":
+		// Use OpenJDK9, but target 1.8
+		c.useOpenJDK9 = true
+	case "true":
+		// Use OpenJDK9 and target 1.9
+		c.useOpenJDK9 = true
+		c.targetOpenJDK9 = true
+	default:
+		return fmt.Errorf(`Invalid value for EXPERIMENTAL_USE_OPENJDK9, should be "", "1.8", or "true"`)
+	}
+
+	return nil
+}
+
 func (c *config) RemoveAbandonedFiles() bool {
 	return false
 }
@@ -518,6 +547,16 @@
 	return Bool(c.ProductVariables.UseGoma)
 }
 
+// Returns true if OpenJDK9 prebuilts are being used
+func (c *config) UseOpenJDK9() bool {
+	return c.useOpenJDK9
+}
+
+// Returns true if -source 1.9 -target 1.9 is being passed to javac
+func (c *config) TargetOpenJDK9() bool {
+	return c.targetOpenJDK9
+}
+
 func (c *config) ClangTidy() bool {
 	return Bool(c.ProductVariables.ClangTidy)
 }
diff --git a/android/paths.go b/android/paths.go
index 69a7b0d..09f760a 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -303,6 +303,39 @@
 	return list[:k]
 }
 
+// HasExt returns true of any of the paths have extension ext, otherwise false
+func (p Paths) HasExt(ext string) bool {
+	for _, path := range p {
+		if path.Ext() == ext {
+			return true
+		}
+	}
+
+	return false
+}
+
+// FilterByExt returns the subset of the paths that have extension ext
+func (p Paths) FilterByExt(ext string) Paths {
+	ret := make(Paths, 0, len(p))
+	for _, path := range p {
+		if path.Ext() == ext {
+			ret = append(ret, path)
+		}
+	}
+	return ret
+}
+
+// FilterOutByExt returns the subset of the paths that do not have extension ext
+func (p Paths) FilterOutByExt(ext string) Paths {
+	ret := make(Paths, 0, len(p))
+	for _, path := range p {
+		if path.Ext() != ext {
+			ret = append(ret, path)
+		}
+	}
+	return ret
+}
+
 // WritablePaths is a slice of WritablePaths, used for multiple outputs.
 type WritablePaths []WritablePath
 
diff --git a/android/variable.go b/android/variable.go
index 9dd9d25..16a6b11 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -92,8 +92,8 @@
 		}
 
 		Pdk struct {
-			Enabled *bool
-		}
+			Enabled *bool `android:"arch_variant"`
+		} `android:"arch_variant"`
 
 		Uml struct {
 			Cppflags []string
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 8a26171..0619b5c 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -16,6 +16,8 @@
 
 import (
 	"context"
+	"flag"
+	"fmt"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -45,7 +47,10 @@
 	log := logger.New(os.Stderr)
 	defer log.Cleanup()
 
-	if len(os.Args) < 2 || !inList("--make-mode", os.Args) {
+	if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
+		os.Args[1] == "--dumpvars-mode" ||
+		os.Args[1] == "--dumpvar-mode") {
+
 		log.Fatalln("The `soong` native UI is not yet available.")
 	}
 
@@ -66,7 +71,12 @@
 		Tracer:         trace,
 		StdioInterface: build.StdioImpl{},
 	}}
-	config := build.NewConfig(buildCtx, os.Args[1:]...)
+	var config build.Config
+	if os.Args[1] == "--dumpvars-mode" || os.Args[1] == "--dumpvar-mode" {
+		config = build.NewConfig(buildCtx)
+	} else {
+		config = build.NewConfig(buildCtx, os.Args[1:]...)
+	}
 
 	log.SetVerbose(config.IsVerbose())
 	build.SetupOutDir(buildCtx, config)
@@ -99,5 +109,129 @@
 	defer f.Shutdown()
 	build.FindSources(buildCtx, config, f)
 
-	build.Build(buildCtx, config, build.BuildAll)
+	if os.Args[1] == "--dumpvar-mode" {
+		dumpVar(buildCtx, config, os.Args[2:])
+	} else if os.Args[1] == "--dumpvars-mode" {
+		dumpVars(buildCtx, config, os.Args[2:])
+	} else {
+		build.Build(buildCtx, config, build.BuildAll)
+	}
+}
+
+func dumpVar(ctx build.Context, config build.Config, args []string) {
+	flags := flag.NewFlagSet("dumpvar", flag.ExitOnError)
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
+		fmt.Fprintln(os.Stderr, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
+		fmt.Fprintln(os.Stderr, "")
+
+		fmt.Fprintln(os.Stderr, "'report_config' is a special case that prints the human-readable config banner")
+		fmt.Fprintln(os.Stderr, "from the beginning of the build.")
+		fmt.Fprintln(os.Stderr, "")
+		flags.PrintDefaults()
+	}
+	abs := flags.Bool("abs", false, "Print the absolute path of the value")
+	flags.Parse(args)
+
+	if flags.NArg() != 1 {
+		flags.Usage()
+		os.Exit(1)
+	}
+
+	varName := flags.Arg(0)
+	if varName == "report_config" {
+		varData, err := build.DumpMakeVars(ctx, config, nil, build.BannerVars)
+		if err != nil {
+			ctx.Fatal(err)
+		}
+
+		fmt.Println(build.Banner(varData))
+	} else {
+		varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName})
+		if err != nil {
+			ctx.Fatal(err)
+		}
+
+		if *abs {
+			var res []string
+			for _, path := range strings.Fields(varData[varName]) {
+				if abs, err := filepath.Abs(path); err == nil {
+					res = append(res, abs)
+				} else {
+					ctx.Fatalln("Failed to get absolute path of", path, err)
+				}
+			}
+			fmt.Println(strings.Join(res, " "))
+		} else {
+			fmt.Println(varData[varName])
+		}
+	}
+}
+
+func dumpVars(ctx build.Context, config build.Config, args []string) {
+	flags := flag.NewFlagSet("dumpvars", flag.ExitOnError)
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
+		fmt.Fprintln(os.Stderr, "In dumpvars mode, dump the values of one or more legacy make variables, in")
+		fmt.Fprintln(os.Stderr, "shell syntax. The resulting output may be sourced directly into a shell to")
+		fmt.Fprintln(os.Stderr, "set corresponding shell variables.")
+		fmt.Fprintln(os.Stderr, "")
+
+		fmt.Fprintln(os.Stderr, "'report_config' is a special case that dumps a variable containing the")
+		fmt.Fprintln(os.Stderr, "human-readable config banner from the beginning of the build.")
+		fmt.Fprintln(os.Stderr, "")
+		flags.PrintDefaults()
+	}
+
+	varsStr := flags.String("vars", "", "Space-separated list of variables to dump")
+	absVarsStr := flags.String("abs-vars", "", "Space-separated list of variables to dump (using absolute paths)")
+
+	varPrefix := flags.String("var-prefix", "", "String to prepend to all variable names when dumping")
+	absVarPrefix := flags.String("abs-var-prefix", "", "String to prepent to all absolute path variable names when dumping")
+
+	flags.Parse(args)
+
+	if flags.NArg() != 0 {
+		flags.Usage()
+		os.Exit(1)
+	}
+
+	vars := strings.Fields(*varsStr)
+	absVars := strings.Fields(*absVarsStr)
+
+	allVars := append([]string{}, vars...)
+	allVars = append(allVars, absVars...)
+
+	if i := indexList("report_config", allVars); i != -1 {
+		allVars = append(allVars[:i], allVars[i+1:]...)
+		allVars = append(allVars, build.BannerVars...)
+	}
+
+	if len(allVars) == 0 {
+		return
+	}
+
+	varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
+	if err != nil {
+		ctx.Fatal(err)
+	}
+
+	for _, name := range vars {
+		if name == "report_config" {
+			fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData))
+		} else {
+			fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name])
+		}
+	}
+	for _, name := range absVars {
+		var res []string
+		for _, path := range strings.Fields(varData[name]) {
+			abs, err := filepath.Abs(path)
+			if err != nil {
+				ctx.Fatalln("Failed to get absolute path of", path, err)
+			}
+			res = append(res, abs)
+		}
+		fmt.Printf("%s%s='%s'\n", *absVarPrefix, name, strings.Join(res, " "))
+	}
 }
diff --git a/java/app.go b/java/app.go
index 490a03d..42ae236 100644
--- a/java/app.go
+++ b/java/app.go
@@ -89,7 +89,7 @@
 		publicResourcesFile, proguardOptionsFile, aaptJavaFileList :=
 			CreateResourceJavaFiles(ctx, aaptRJavaFlags, aaptDeps)
 		a.aaptJavaFileList = aaptJavaFileList
-		a.ExtraSrcLists = append(a.ExtraSrcLists, aaptJavaFileList)
+		// TODO(ccross):  export aapt generated java files as a src jar
 
 		if a.appProperties.Export_package_resources {
 			aaptPackageFlags := append([]string(nil), aaptFlags...)
diff --git a/java/builder.go b/java/builder.go
index 118f239..a03f892 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -40,7 +40,7 @@
 		blueprint.RuleParams{
 			Command: `rm -rf "$outDir" "$annoDir" && mkdir -p "$outDir" "$annoDir" && ` +
 				`${config.JavacWrapper}${config.JavacCmd} ${config.JavacHeapFlags} ${config.CommonJdkFlags} ` +
-				`$javacFlags $bootClasspath $classpath ` +
+				`$javacFlags $sourcepath $bootClasspath $classpath ` +
 				`-source $javaVersion -target $javaVersion ` +
 				`-d $outDir -s $annoDir @$out.rsp && ` +
 				`${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir`,
@@ -48,13 +48,29 @@
 			Rspfile:        "$out.rsp",
 			RspfileContent: "$in",
 		},
-		"javacFlags", "bootClasspath", "classpath", "outDir", "annoDir", "javaVersion")
+		"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" && ` +
 				`${config.ErrorProneCmd} ` +
-				`$javacFlags $bootClasspath $classpath ` +
+				`$javacFlags $sourcepath $bootClasspath $classpath ` +
 				`-source $javaVersion -target $javaVersion ` +
 				`-d $outDir -s $annoDir @$out.rsp && ` +
 				`${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir`,
@@ -67,7 +83,7 @@
 			Rspfile:        "$out.rsp",
 			RspfileContent: "$in",
 		},
-		"javacFlags", "bootClasspath", "classpath", "outDir", "annoDir", "javaVersion")
+		"javacFlags", "sourcepath", "bootClasspath", "classpath", "outDir", "annoDir", "javaVersion")
 
 	jar = pctx.AndroidStaticRule("jar",
 		blueprint.RuleParams{
@@ -126,58 +142,86 @@
 	dxFlags       string
 	bootClasspath classpath
 	classpath     classpath
+	systemModules classpath
 	desugarFlags  string
 	aidlFlags     string
 	javaVersion   string
 
+	kotlincFlags     string
+	kotlincClasspath classpath
+
 	protoFlags   string
 	protoOutFlag string
 }
 
-func TransformJavaToClasses(ctx android.ModuleContext, srcFiles, srcFileLists android.Paths,
-	flags javaBuilderFlags, deps android.Paths) android.ModuleOutPath {
+func TransformKotlinToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles android.Paths, srcJars classpath,
+	flags javaBuilderFlags) {
 
-	return transformJavaToClasses(ctx, srcFiles, srcFileLists, flags, deps,
-		"classes-compiled.jar", "", "javac", javac)
+	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 RunErrorProne(ctx android.ModuleContext, srcFiles, srcFileLists android.Paths,
-	flags javaBuilderFlags) android.Path {
+func TransformJavaToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles android.Paths, srcJars classpath,
+	flags javaBuilderFlags, deps android.Paths) {
+
+	transformJavaToClasses(ctx, outputFile, srcFiles, srcJars, flags, deps,
+		"", "javac", javac)
+}
+
+func RunErrorProne(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles android.Paths, srcJars classpath,
+	flags javaBuilderFlags) {
 
 	if config.ErrorProneJar == "" {
 		ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
-		return nil
 	}
 
-	return transformJavaToClasses(ctx, srcFiles, srcFileLists, flags, nil,
-		"classes-errorprone.list", "-errorprone", "errorprone", errorprone)
+	transformJavaToClasses(ctx, outputFile, srcFiles, srcJars, flags, nil,
+		"-errorprone", "errorprone", errorprone)
 }
 
 // transformJavaToClasses takes source files and converts them to a jar containing .class files.
-// srcFiles is a list of paths to sources, srcFileLists is a list of paths to files that contain
-// paths to sources.  There is no dependency on the sources passed through srcFileLists, those
-// must be added through the deps argument, which contains a list of paths that should be added
-// as implicit dependencies.  flags contains various command line flags to be passed to the
-// compiler.
+// srcFiles is a list of paths to sources, srcJars is a list of paths to jar files that contain
+// sources.  flags contains various command line flags to be passed to the compiler.
 //
 // This method may be used for different compilers, including javac and Error Prone.  The rule
 // argument specifies which command line to use and desc sets the description of the rule that will
 // be printed at build time.  The stem argument provides the file name of the output jar, and
 // suffix will be appended to various intermediate files and directories to avoid collisions when
 // this function is called twice in the same module directory.
-func transformJavaToClasses(ctx android.ModuleContext, srcFiles, srcFileLists android.Paths,
-	flags javaBuilderFlags, deps android.Paths, stem, suffix, desc string,
-	rule blueprint.Rule) android.ModuleOutPath {
+func transformJavaToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles android.Paths, srcJars classpath,
+	flags javaBuilderFlags, deps android.Paths,
+	intermediatesSuffix, desc string, rule blueprint.Rule) {
 
-	outputFile := android.PathForModuleOut(ctx, stem)
+	deps = append(deps, srcJars...)
 
-	javacFlags := flags.javacFlags
-	if len(srcFileLists) > 0 {
-		javacFlags += " " + android.JoinWithPrefix(srcFileLists.Strings(), "@")
+	var bootClasspath string
+	if flags.javaVersion == "1.9" {
+		deps = append(deps, flags.systemModules...)
+		bootClasspath = flags.systemModules.JavaSystemModules(ctx.Device())
+	} else {
+		deps = append(deps, flags.bootClasspath...)
+		bootClasspath = flags.bootClasspath.JavaBootClasspath(ctx.Device())
 	}
 
-	deps = append(deps, srcFileLists...)
-	deps = append(deps, flags.bootClasspath...)
 	deps = append(deps, flags.classpath...)
 
 	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
@@ -187,22 +231,19 @@
 		Inputs:      srcFiles,
 		Implicits:   deps,
 		Args: map[string]string{
-			"javacFlags":    javacFlags,
-			"bootClasspath": flags.bootClasspath.JavaBootClasspath(ctx.Device()),
+			"javacFlags":    flags.javacFlags,
+			"bootClasspath": bootClasspath,
+			"sourcepath":    srcJars.JavaSourcepath(),
 			"classpath":     flags.classpath.JavaClasspath(),
-			"outDir":        android.PathForModuleOut(ctx, "classes"+suffix).String(),
-			"annoDir":       android.PathForModuleOut(ctx, "anno"+suffix).String(),
+			"outDir":        android.PathForModuleOut(ctx, "classes"+intermediatesSuffix).String(),
+			"annoDir":       android.PathForModuleOut(ctx, "anno"+intermediatesSuffix).String(),
 			"javaVersion":   flags.javaVersion,
 		},
 	})
-
-	return outputFile
 }
 
-func TransformResourcesToJar(ctx android.ModuleContext, jarArgs []string,
-	deps android.Paths) android.Path {
-
-	outputFile := android.PathForModuleOut(ctx, "res.jar")
+func TransformResourcesToJar(ctx android.ModuleContext, outputFile android.WritablePath,
+	jarArgs []string, deps android.Paths) {
 
 	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
 		Rule:        jar,
@@ -213,18 +254,10 @@
 			"jarArgs": strings.Join(jarArgs, " "),
 		},
 	})
-
-	return outputFile
 }
 
-func TransformJarsToJar(ctx android.ModuleContext, stem string, jars android.Paths,
-	manifest android.OptionalPath, stripDirs bool) android.Path {
-
-	outputFile := android.PathForModuleOut(ctx, stem)
-
-	if len(jars) == 1 && !manifest.Valid() {
-		return jars[0]
-	}
+func TransformJarsToJar(ctx android.ModuleContext, outputFile android.WritablePath,
+	jars android.Paths, manifest android.OptionalPath, stripDirs bool) {
 
 	var deps android.Paths
 
@@ -248,18 +281,15 @@
 			"jarArgs": strings.Join(jarArgs, " "),
 		},
 	})
-
-	return outputFile
 }
 
-func TransformDesugar(ctx android.ModuleContext, classesJar android.Path,
-	flags javaBuilderFlags) android.Path {
+func TransformDesugar(ctx android.ModuleContext, outputFile android.WritablePath,
+	classesJar android.Path, flags javaBuilderFlags) {
 
-	outputFile := android.PathForModuleOut(ctx, "classes-desugar.jar")
 	dumpDir := android.PathForModuleOut(ctx, "desugar_dumped_classes")
 
 	javaFlags := ""
-	if ctx.AConfig().Getenv("EXPERIMENTAL_USE_OPENJDK9") != "" {
+	if ctx.AConfig().UseOpenJDK9() {
 		javaFlags = "--add-opens java.base/java.lang.invoke=ALL-UNNAMED"
 	}
 
@@ -284,17 +314,14 @@
 			"desugarFlags":   flags.desugarFlags,
 		},
 	})
-
-	return outputFile
 }
 
 // Converts a classes.jar file to classes*.dex, then combines the dex files with any resources
 // in the classes.jar file into a dex jar.
-func TransformClassesJarToDexJar(ctx android.ModuleContext, stem string, classesJar android.Path,
-	flags javaBuilderFlags) android.Path {
+func TransformClassesJarToDexJar(ctx android.ModuleContext, outputFile android.WritablePath,
+	classesJar android.Path, flags javaBuilderFlags) {
 
 	outDir := android.PathForModuleOut(ctx, "dex")
-	outputFile := android.PathForModuleOut(ctx, stem)
 
 	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
 		Rule:        dx,
@@ -306,12 +333,10 @@
 			"outDir":  outDir.String(),
 		},
 	})
-
-	return outputFile
 }
 
-func TransformJarJar(ctx android.ModuleContext, classesJar android.Path, rulesFile android.Path) android.ModuleOutPath {
-	outputFile := android.PathForModuleOut(ctx, "classes-jarjar.jar")
+func TransformJarJar(ctx android.ModuleContext, outputFile android.WritablePath,
+	classesJar android.Path, rulesFile android.Path) {
 	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
 		Rule:        jarjar,
 		Description: "jarjar",
@@ -322,12 +347,20 @@
 			"rulesFile": rulesFile.String(),
 		},
 	})
-
-	return outputFile
 }
 
 type classpath []android.Path
 
+// Returns a -sourcepath argument in the form javac expects.  If the list is empty returns
+// -sourcepath "" to ensure javac does not fall back to searching the classpath for sources.
+func (x *classpath) JavaSourcepath() string {
+	if len(*x) > 0 {
+		return "-sourcepath " + strings.Join(x.Strings(), ":")
+	} else {
+		return `-sourcepath ""`
+	}
+}
+
 // Returns a -classpath argument in the form java or javac expects
 func (x *classpath) JavaClasspath() string {
 	if len(*x) > 0 {
@@ -359,6 +392,21 @@
 	}
 }
 
+// Returns a --system argument in the form javac expects with -source 1.9.  If forceEmpty is true,
+// returns --system=none if the list is empty to ensure javac does not fall back to the default
+// system modules.
+func (x *classpath) JavaSystemModules(forceEmpty bool) string {
+	if len(*x) > 1 {
+		panic("more than one system module")
+	} else if len(*x) == 1 {
+		return "--system=" + strings.TrimSuffix((*x)[0].String(), "lib/modules")
+	} else if forceEmpty {
+		return "--system=none"
+	} else {
+		return ""
+	}
+}
+
 func (x *classpath) DesugarBootClasspath() []string {
 	if x == nil || *x == nil {
 		return nil
diff --git a/java/config/config.go b/java/config/config.go
index 70b8fe5..7d1fa29 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -27,6 +27,7 @@
 	pctx = android.NewPackageContext("android/soong/java/config")
 
 	DefaultBootclasspathLibraries = []string{"core-oj", "core-libart"}
+	DefaultSystemModules          = "core-system-modules"
 	DefaultLibraries              = []string{"ext", "framework", "okhttp"}
 )
 
@@ -47,9 +48,10 @@
 		// If a different javac is used the flag will be ignored and extra bridges will be inserted.
 		// The flag is implemented by https://android-review.googlesource.com/c/486427
 		`-XDskipDuplicateBridges=true`,
-	}, " "))
 
-	pctx.StaticVariable("DefaultJavaVersion", "1.8")
+		// b/65004097: prevent using java.lang.invoke.StringConcatFactory when using -target 1.9
+		`-XDstringConcat=inline`,
+	}, " "))
 
 	pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS)
 
@@ -57,7 +59,7 @@
 		if override := config.(android.Config).Getenv("OVERRIDE_ANDROID_JAVA_HOME"); override != "" {
 			return override, nil
 		}
-		if jdk9 := config.(android.Config).Getenv("EXPERIMENTAL_USE_OPENJDK9"); jdk9 != "" {
+		if config.(android.Config).UseOpenJDK9() {
 			return "prebuilts/jdk/jdk9/${hostPrebuiltTag}", nil
 		}
 		return "prebuilts/jdk/jdk8/${hostPrebuiltTag}", nil
@@ -71,6 +73,7 @@
 	pctx.SourcePathVariable("JavadocCmd", "${JavaToolchain}/javadoc")
 	pctx.SourcePathVariable("JlinkCmd", "${JavaToolchain}/jlink")
 	pctx.SourcePathVariable("JmodCmd", "${JavaToolchain}/jmod")
+	pctx.SourcePathVariable("JrtFsJar", "${JavaHome}/lib/jrt-fs.jar")
 
 	pctx.SourcePathVariable("JarArgsCmd", "build/soong/scripts/jar-args.sh")
 	pctx.StaticVariable("SoongZipCmd", filepath.Join("${bootstrap.ToolDir}", "soong_zip"))
@@ -86,17 +89,3 @@
 		return "", nil
 	})
 }
-
-func StripJavac9Flags(flags []string) []string {
-	var ret []string
-	for _, f := range flags {
-		switch {
-		case strings.HasPrefix(f, "-J--add-modules="):
-			// drop
-		default:
-			ret = append(ret, f)
-		}
-	}
-
-	return ret
-}
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/config/makevars.go b/java/config/makevars.go
index 937d597..1453a07 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -27,8 +27,13 @@
 func makeVarsProvider(ctx android.MakeVarsContext) {
 	ctx.Strict("TARGET_DEFAULT_JAVA_LIBRARIES", strings.Join(DefaultLibraries, " "))
 	ctx.Strict("TARGET_DEFAULT_BOOTCLASSPATH_LIBRARIES", strings.Join(DefaultBootclasspathLibraries, " "))
+	ctx.Strict("DEFAULT_SYSTEM_MODULES", DefaultSystemModules)
 
-	ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "${DefaultJavaVersion}")
+	if ctx.Config().TargetOpenJDK9() {
+		ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "1.9")
+	} else {
+		ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "1.8")
+	}
 
 	ctx.Strict("ANDROID_JAVA_HOME", "${JavaHome}")
 	ctx.Strict("ANDROID_JAVA_TOOLCHAIN", "${JavaToolchain}")
@@ -47,7 +52,7 @@
 		ctx.Strict("HOST_JAVAC", "${JavacCmd} ${CommonJdkFlags}")
 	}
 
-	if ctx.Config().IsEnvTrue("EXPERIMENTAL_USE_OPENJDK9") {
+	if ctx.Config().UseOpenJDK9() {
 		ctx.Strict("JLINK", "${JlinkCmd}")
 		ctx.Strict("JMOD", "${JmodCmd}")
 	}
diff --git a/java/gen.go b/java/gen.go
index e55be91..e12a71c 100644
--- a/java/gen.go
+++ b/java/gen.go
@@ -85,7 +85,7 @@
 }
 
 func (j *Module) genSources(ctx android.ModuleContext, srcFiles android.Paths,
-	flags javaBuilderFlags) (android.Paths, android.Paths) {
+	flags javaBuilderFlags) (android.Paths, classpath) {
 
 	var protoFiles android.Paths
 	outSrcFiles := make(android.Paths, 0, len(srcFiles))
@@ -106,16 +106,17 @@
 		}
 	}
 
-	var outSrcFileLists android.Paths
+	var outSrcJars classpath
 
 	if len(protoFiles) > 0 {
-		protoFileList := genProto(ctx, protoFiles,
+		protoSrcJar := android.PathForModuleGen(ctx, "proto.src.jar")
+		genProto(ctx, protoSrcJar, protoFiles,
 			flags.protoFlags, flags.protoOutFlag, "")
 
-		outSrcFileLists = append(outSrcFileLists, protoFileList)
+		outSrcJars = append(outSrcJars, protoSrcJar)
 	}
 
-	return outSrcFiles, outSrcFileLists
+	return outSrcFiles, outSrcJars
 }
 
 func LogtagsSingleton() blueprint.Singleton {
diff --git a/java/java.go b/java/java.go
index 5393b06..3382cf2 100644
--- a/java/java.go
+++ b/java/java.go
@@ -116,6 +116,14 @@
 
 	// List of classes to pass to javac to use as annotation processors
 	Annotation_processor_classes []string
+
+	Openjdk9 struct {
+		// List of source files that should only be used when passing -source 1.9
+		Srcs []string
+
+		// List of javac flags that should only be used when passing -source 1.9
+		Javacflags []string
+	}
 }
 
 type CompilerDeviceProperties struct {
@@ -134,6 +142,9 @@
 
 	// If true, export a copy of the module as a -hostdex module for host testing.
 	Hostdex *bool
+
+	// When targeting 1.9, override the modules to use with --system
+	System_modules *string
 }
 
 // Module contains the properties and members used by all java module types
@@ -158,9 +169,9 @@
 
 	logtagsSrcs android.Paths
 
-	// filelists of extra source files that should be included in the javac command line,
+	// jars containing source files that should be included in the javac command line,
 	// for example R.java generated by aapt for android apps
-	ExtraSrcLists android.Paths
+	ExtraSrcJars android.Paths
 
 	// installed file for binary dependency
 	installFile android.Path
@@ -185,26 +196,40 @@
 	staticLibTag     = dependencyTag{name: "staticlib"}
 	libTag           = dependencyTag{name: "javalib"}
 	bootClasspathTag = dependencyTag{name: "bootclasspath"}
+	systemModulesTag = dependencyTag{name: "system modules"}
 	frameworkResTag  = dependencyTag{name: "framework-res"}
+	kotlinStdlibTag  = dependencyTag{name: "kotlin-stdlib"}
 )
 
 type sdkDep struct {
 	useModule, useFiles, useDefaultLibs, invalidVersion bool
 
-	module string
-	jar    android.Path
-	aidl   android.Path
+	module        string
+	systemModules string
+
+	jar  android.Path
+	aidl android.Path
+}
+
+func sdkStringToNumber(ctx android.BaseContext, v string) int {
+	switch v {
+	case "", "current", "system_current", "test_current":
+		return 10000
+	default:
+		if i, err := strconv.Atoi(v); err != nil {
+			ctx.PropertyErrorf("sdk_version", "invalid sdk version")
+			return -1
+		} else {
+			return i
+		}
+	}
 }
 
 func decodeSdkDep(ctx android.BaseContext, v string) sdkDep {
-	switch v {
-	case "", "current", "system_current", "test_current":
-		// OK
-	default:
-		if _, err := strconv.Atoi(v); err != nil {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version")
-			return sdkDep{}
-		}
+	i := sdkStringToNumber(ctx, v)
+	if i == -1 {
+		// Invalid sdk version, error handled by sdkStringToNumber.
+		return sdkDep{}
 	}
 
 	toFile := func(v string) sdkDep {
@@ -240,8 +265,9 @@
 
 	toModule := func(m string) sdkDep {
 		return sdkDep{
-			useModule: true,
-			module:    m,
+			useModule:     true,
+			module:        m,
+			systemModules: m + "_system_modules",
 		}
 	}
 
@@ -266,20 +292,31 @@
 }
 
 func (j *Module) deps(ctx android.BottomUpMutatorContext) {
-	if !proptools.Bool(j.properties.No_standard_libs) {
-		if ctx.Device() {
+	if ctx.Device() {
+		if !proptools.Bool(j.properties.No_standard_libs) {
 			sdkDep := decodeSdkDep(ctx, j.deviceProperties.Sdk_version)
 			if sdkDep.useDefaultLibs {
 				ctx.AddDependency(ctx.Module(), bootClasspathTag, config.DefaultBootclasspathLibraries...)
+				if ctx.AConfig().TargetOpenJDK9() {
+					ctx.AddDependency(ctx.Module(), systemModulesTag, config.DefaultSystemModules)
+				}
 				if !proptools.Bool(j.properties.No_framework_libs) {
 					ctx.AddDependency(ctx.Module(), libTag, config.DefaultLibraries...)
 				}
-			}
-			if sdkDep.useModule {
+			} else if sdkDep.useModule {
+				if ctx.AConfig().TargetOpenJDK9() {
+					ctx.AddDependency(ctx.Module(), systemModulesTag, sdkDep.systemModules)
+				}
 				ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.module)
 			}
+		} else if j.deviceProperties.System_modules == nil {
+			ctx.PropertyErrorf("no_standard_libs",
+				"system_modules is required to be set when no_standard_libs is true, did you mean no_framework_libs?")
+		} else if *j.deviceProperties.System_modules != "none" && ctx.AConfig().TargetOpenJDK9() {
+			ctx.AddDependency(ctx.Module(), systemModulesTag, *j.deviceProperties.System_modules)
 		}
 	}
+
 	ctx.AddDependency(ctx.Module(), libTag, j.properties.Libs...)
 	ctx.AddDependency(ctx.Module(), staticLibTag, j.properties.Static_libs...)
 	ctx.AddDependency(ctx.Module(), libTag, j.properties.Annotation_processors...)
@@ -290,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 {
@@ -334,8 +377,10 @@
 	staticJars         android.Paths
 	staticJarResources android.Paths
 	aidlIncludeDirs    android.Paths
-	srcFileLists       android.Paths
+	srcJars            android.Paths
+	systemModules      android.Path
 	aidlPreprocess     android.OptionalPath
+	kotlinStdlib       android.Paths
 }
 
 func (j *Module) collectDeps(ctx android.ModuleContext) deps {
@@ -358,6 +403,15 @@
 			switch tag {
 			case android.DefaultsDepTag, android.SourceDepTag:
 				// Nothing to do
+			case systemModulesTag:
+				if deps.systemModules != nil {
+					panic("Found two system module dependencies")
+				}
+				sm := module.(*SystemModules)
+				if sm.outputFile == nil {
+					panic("Missing directory for system module dependency")
+				}
+				deps.systemModules = sm.outputFile
 			default:
 				ctx.ModuleErrorf("depends on non-java module %q", otherName)
 			}
@@ -376,8 +430,10 @@
 			if ctx.ModuleName() == "framework" {
 				// framework.jar has a one-off dependency on the R.java and Manifest.java files
 				// generated by framework-res.apk
-				deps.srcFileLists = append(deps.srcFileLists, module.(*AndroidApp).aaptJavaFileList)
+				// 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()))
 		}
@@ -397,19 +453,29 @@
 	var flags javaBuilderFlags
 
 	javacFlags := j.properties.Javacflags
-	if ctx.AConfig().Getenv("EXPERIMENTAL_USE_OPENJDK9") == "" {
-		javacFlags = config.StripJavac9Flags(javacFlags)
+	if ctx.AConfig().TargetOpenJDK9() {
+		javacFlags = append(javacFlags, j.properties.Openjdk9.Javacflags...)
+		j.properties.Srcs = append(j.properties.Srcs, j.properties.Openjdk9.Srcs...)
 	}
 
+	sdk := sdkStringToNumber(ctx, j.deviceProperties.Sdk_version)
 	if j.properties.Java_version != nil {
 		flags.javaVersion = *j.properties.Java_version
+	} else if ctx.Device() && sdk <= 23 {
+		flags.javaVersion = "1.7"
+	} else if ctx.Device() && sdk <= 26 || !ctx.AConfig().TargetOpenJDK9() {
+		flags.javaVersion = "1.8"
 	} else {
-		flags.javaVersion = "${config.DefaultJavaVersion}"
+		flags.javaVersion = "1.9"
 	}
 
 	flags.bootClasspath.AddPaths(deps.bootClasspath)
 	flags.classpath.AddPaths(deps.classpath)
 
+	if deps.systemModules != nil {
+		flags.systemModules = append(flags.systemModules, deps.systemModules)
+	}
+
 	if len(javacFlags) > 0 {
 		ctx.Variable(pctx, "javacFlags", strings.Join(javacFlags, " "))
 		flags.javacFlags = "$javacFlags"
@@ -427,17 +493,42 @@
 		flags = protoFlags(ctx, &j.protoProperties, flags)
 	}
 
-	var srcFileLists android.Paths
+	var srcJars classpath
+	srcFiles, srcJars = j.genSources(ctx, srcFiles, flags)
 
-	srcFiles, srcFileLists = j.genSources(ctx, srcFiles, flags)
+	srcJars = append(srcJars, deps.srcJars...)
 
-	srcFileLists = append(srcFileLists, deps.srcFileLists...)
-
-	srcFileLists = append(srcFileLists, j.ExtraSrcLists...)
+	srcJars = append(srcJars, j.ExtraSrcJars...)
 
 	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
@@ -445,12 +536,14 @@
 			// a rebuild when error-prone is turned off).
 			// TODO(ccross): Once we always compile with javac9 we may be able to conditionally
 			//    enable error-prone without affecting the output class files.
-			errorprone := RunErrorProne(ctx, srcFiles, srcFileLists, flags)
+			errorprone := android.PathForModuleOut(ctx, "classes-errorprone.list")
+			RunErrorProne(ctx, errorprone, javaSrcFiles, srcJars, flags)
 			extraJarDeps = append(extraJarDeps, errorprone)
 		}
 
 		// Compile java sources into .class files
-		classes := TransformJavaToClasses(ctx, srcFiles, srcFileLists, flags, extraJarDeps)
+		classes := android.PathForModuleOut(ctx, "classes-compiled.jar")
+		TransformJavaToClasses(ctx, classes, javaSrcFiles, srcJars, flags, extraJarDeps)
 		if ctx.Failed() {
 			return
 		}
@@ -477,7 +570,8 @@
 	}
 
 	if len(resArgs) > 0 {
-		resourceJar := TransformResourcesToJar(ctx, resArgs, resDeps)
+		resourceJar := android.PathForModuleOut(ctx, "res.jar")
+		TransformResourcesToJar(ctx, resourceJar, resArgs, resDeps)
 		if ctx.Failed() {
 			return
 		}
@@ -492,12 +586,23 @@
 
 	// Combine the classes built from sources, any manifests, and any static libraries into
 	// classes.jar.  If there is only one input jar this step will be skipped.
-	outputFile := TransformJarsToJar(ctx, "classes.jar", jars, manifest, false)
+	var outputFile android.Path
+
+	if len(jars) == 1 && !manifest.Valid() {
+		// Optimization: skip the combine step if there is nothing to do
+		outputFile = jars[0]
+	} else {
+		combinedJar := android.PathForModuleOut(ctx, "classes.jar")
+		TransformJarsToJar(ctx, combinedJar, jars, manifest, false)
+		outputFile = combinedJar
+	}
 
 	if j.properties.Jarjar_rules != nil {
 		jarjar_rules := android.PathForModuleSrc(ctx, *j.properties.Jarjar_rules)
 		// Transform classes.jar into classes-jarjar.jar
-		outputFile = TransformJarJar(ctx, outputFile, jarjar_rules)
+		jarjarFile := android.PathForModuleOut(ctx, "classes-jarjar.jar")
+		TransformJarJar(ctx, jarjarFile, outputFile, jarjar_rules)
+		outputFile = jarjarFile
 		if ctx.Failed() {
 			return
 		}
@@ -553,13 +658,17 @@
 
 		flags.desugarFlags = strings.Join(desugarFlags, " ")
 
-		desugarJar := TransformDesugar(ctx, outputFile, flags)
+		desugarJar := android.PathForModuleOut(ctx, "classes-desugar.jar")
+		TransformDesugar(ctx, desugarJar, outputFile, flags)
+		outputFile = desugarJar
 		if ctx.Failed() {
 			return
 		}
 
 		// Compile classes.jar into classes.dex and then javalib.jar
-		outputFile = TransformClassesJarToDexJar(ctx, "javalib.jar", desugarJar, flags)
+		javalibJar := android.PathForModuleOut(ctx, "javalib.jar")
+		TransformClassesJarToDexJar(ctx, javalibJar, desugarJar, flags)
+		outputFile = javalibJar
 		if ctx.Failed() {
 			return
 		}
@@ -734,7 +843,9 @@
 func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.classpathFiles = android.PathsForModuleSrc(ctx, j.properties.Jars)
 
-	j.combinedClasspathFile = TransformJarsToJar(ctx, "classes.jar", j.classpathFiles, android.OptionalPath{}, false)
+	outputFile := android.PathForModuleOut(ctx, "classes.jar")
+	TransformJarsToJar(ctx, outputFile, j.classpathFiles, android.OptionalPath{}, false)
+	j.combinedClasspathFile = outputFile
 }
 
 var _ Dependency = (*Import)(nil)
diff --git a/java/java_test.go b/java/java_test.go
index a86973d..d64688f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -50,9 +50,12 @@
 
 	os.Exit(run())
 }
-
 func testJava(t *testing.T, bp string) *android.TestContext {
-	config := android.TestArchConfig(buildDir, nil)
+	return testJavaWithEnv(t, bp, nil)
+}
+
+func testJavaWithEnv(t *testing.T, bp string, env map[string]string) *android.TestContext {
+	config := android.TestArchConfig(buildDir, env)
 
 	ctx := android.NewTestArchContext()
 	ctx.RegisterModuleType("android_app", android.ModuleFactoryAdaptor(AndroidAppFactory))
@@ -60,6 +63,7 @@
 	ctx.RegisterModuleType("java_library_host", android.ModuleFactoryAdaptor(LibraryHostFactory))
 	ctx.RegisterModuleType("java_import", android.ModuleFactoryAdaptor(ImportFactory))
 	ctx.RegisterModuleType("java_defaults", android.ModuleFactoryAdaptor(defaultsFactory))
+	ctx.RegisterModuleType("java_system_modules", android.ModuleFactoryAdaptor(SystemModulesFactory))
 	ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(genrule.FileGroupFactory))
 	ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(genrule.GenRuleFactory))
 	ctx.PreArchMutators(android.RegisterPrebuiltsPreArchMutators)
@@ -76,6 +80,7 @@
 		"android_stubs_current",
 		"android_system_stubs_current",
 		"android_test_stubs_current",
+		"kotlin-stdlib",
 	}
 
 	for _, extra := range extraModules {
@@ -84,15 +89,34 @@
 				name: "%s",
 				srcs: ["a.java"],
 				no_standard_libs: true,
+				system_modules: "core-system-modules",
 			}
 		`, extra)
 	}
 
+	if config.TargetOpenJDK9() {
+		systemModules := []string{
+			"core-system-modules",
+			"android_stubs_current_system_modules",
+			"android_system_stubs_current_system_modules",
+			"android_test_stubs_current_system_modules",
+		}
+
+		for _, extra := range systemModules {
+			bp += fmt.Sprintf(`
+			java_system_modules {
+				name: "%s",
+			}
+		`, extra)
+		}
+	}
+
 	ctx.MockFileSystem(map[string][]byte{
 		"Android.bp": []byte(bp),
 		"a.java":     nil,
 		"b.java":     nil,
 		"c.java":     nil,
+		"b.kt":       nil,
 		"a.jar":      nil,
 		"b.jar":      nil,
 		"res/a":      nil,
@@ -190,17 +214,20 @@
 	host          android.OsClass
 	properties    string
 	bootclasspath []string
+	system        string
 	classpath     []string
 }{
 	{
 		name:          "default",
 		bootclasspath: []string{"core-oj", "core-libart"},
+		system:        "core-system-modules",
 		classpath:     []string{"ext", "framework", "okhttp"},
 	},
 	{
 		name:          "blank sdk version",
 		properties:    `sdk_version: "",`,
 		bootclasspath: []string{"core-oj", "core-libart"},
+		system:        "core-system-modules",
 		classpath:     []string{"ext", "framework", "okhttp"},
 	},
 	{
@@ -208,6 +235,7 @@
 		name:          "sdk v14",
 		properties:    `sdk_version: "14",`,
 		bootclasspath: []string{`""`},
+		system:        "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
 		classpath:     []string{"prebuilts/sdk/14/android.jar"},
 	},
 	{
@@ -215,6 +243,7 @@
 		name:          "current",
 		properties:    `sdk_version: "current",`,
 		bootclasspath: []string{"android_stubs_current"},
+		system:        "android_stubs_current_system_modules",
 		classpath:     []string{},
 	},
 	{
@@ -222,6 +251,7 @@
 		name:          "system_current",
 		properties:    `sdk_version: "system_current",`,
 		bootclasspath: []string{"android_system_stubs_current"},
+		system:        "android_system_stubs_current_system_modules",
 		classpath:     []string{},
 	},
 	{
@@ -229,12 +259,22 @@
 		name:          "test_current",
 		properties:    `sdk_version: "test_current",`,
 		bootclasspath: []string{"android_test_stubs_current"},
+		system:        "android_test_stubs_current_system_modules",
 		classpath:     []string{},
 	},
 	{
 
 		name:          "nostdlib",
-		properties:    `no_standard_libs: true`,
+		properties:    `no_standard_libs: true, system_modules: "none"`,
+		system:        "none",
+		bootclasspath: []string{`""`},
+		classpath:     []string{},
+	},
+	{
+
+		name:          "nostdlib system_modules",
+		properties:    `no_standard_libs: true, system_modules: "core-system-modules"`,
+		system:        "core-system-modules",
 		bootclasspath: []string{`""`},
 		classpath:     []string{},
 	},
@@ -263,7 +303,7 @@
 	{
 		name:       "host supported nostdlib",
 		host:       android.Host,
-		properties: `host_supported: true, no_standard_libs: true`,
+		properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`,
 		classpath:  []string{},
 	},
 }
@@ -275,12 +315,17 @@
 			if testcase.moduleType != "" {
 				moduleType = testcase.moduleType
 			}
-			ctx := testJava(t, moduleType+` {
+
+			bp := moduleType + ` {
 				name: "foo",
 				srcs: ["a.java"],
-				`+testcase.properties+`
+				` + testcase.properties + `
+			}`
+
+			variant := "android_common"
+			if testcase.host == android.Host {
+				variant = android.BuildOs.String() + "_common"
 			}
-			`)
 
 			convertModulesToPaths := func(cp []string) []string {
 				ret := make([]string, len(cp))
@@ -293,33 +338,63 @@
 			bootclasspath := convertModulesToPaths(testcase.bootclasspath)
 			classpath := convertModulesToPaths(testcase.classpath)
 
-			variant := "android_common"
-			if testcase.host == android.Host {
-				variant = android.BuildOs.String() + "_common"
-			}
-			javac := ctx.ModuleForTests("foo", variant).Rule("javac")
-
-			got := strings.TrimPrefix(javac.Args["bootClasspath"], "-bootclasspath ")
 			bc := strings.Join(bootclasspath, ":")
-			if got != bc {
-				t.Errorf("bootclasspath expected %q != got %q", bc, got)
+			if bc != "" {
+				bc = "-bootclasspath " + bc
 			}
 
-			got = strings.TrimPrefix(javac.Args["classpath"], "-classpath ")
 			c := strings.Join(classpath, ":")
-			if got != c {
-				t.Errorf("classpath expected %q != got %q", c, got)
+			if c != "" {
+				c = "-classpath " + c
+			}
+			system := ""
+			if testcase.system == "none" {
+				system = "--system=none"
+			} else if testcase.system != "" {
+				system = "--system=" + filepath.Join(buildDir, ".intermediates", testcase.system, "android_common", "system") + "/"
 			}
 
-			var deps []string
-			if len(bootclasspath) > 0 && bootclasspath[0] != `""` {
-				deps = append(deps, bootclasspath...)
-			}
-			deps = append(deps, classpath...)
+			t.Run("1.8", func(t *testing.T) {
+				// Test default javac 1.8
+				ctx := testJava(t, bp)
 
-			if !reflect.DeepEqual(javac.Implicits.Strings(), deps) {
-				t.Errorf("implicits expected %q != got %q", deps, javac.Implicits.Strings())
-			}
+				javac := ctx.ModuleForTests("foo", variant).Rule("javac")
+
+				got := javac.Args["bootClasspath"]
+				if got != bc {
+					t.Errorf("bootclasspath expected %q != got %q", bc, got)
+				}
+
+				got = javac.Args["classpath"]
+				if got != c {
+					t.Errorf("classpath expected %q != got %q", c, got)
+				}
+
+				var deps []string
+				if len(bootclasspath) > 0 && bootclasspath[0] != `""` {
+					deps = append(deps, bootclasspath...)
+				}
+				deps = append(deps, classpath...)
+
+				if !reflect.DeepEqual(javac.Implicits.Strings(), deps) {
+					t.Errorf("implicits expected %q != got %q", deps, javac.Implicits.Strings())
+				}
+			})
+
+			// Test again with javac 1.9
+			t.Run("1.9", func(t *testing.T) {
+				ctx := testJavaWithEnv(t, bp, map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"})
+
+				javac := ctx.ModuleForTests("foo", variant).Rule("javac")
+				got := javac.Args["bootClasspath"]
+				expected := system
+				if testcase.system == "bootclasspath" {
+					expected = bc
+				}
+				if got != expected {
+					t.Errorf("bootclasspath expected %q != got %q", expected, got)
+				}
+			})
 		})
 	}
 
@@ -540,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 {
diff --git a/java/proto.go b/java/proto.go
index dd8cabd..fc259a5 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -30,20 +30,21 @@
 		blueprint.RuleParams{
 			Command: `rm -rf $outDir && mkdir -p $outDir && ` +
 				`$protocCmd $protoOut=$protoOutFlags:$outDir $protoFlags $in && ` +
-				`find $outDir -name "*.java" > $out`,
-			CommandDeps: []string{"$protocCmd"},
+				`${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir`,
+			CommandDeps: []string{
+				"$protocCmd",
+				"${config.SoongZipCmd}",
+			},
 		}, "protoFlags", "protoOut", "protoOutFlags", "outDir")
 )
 
-func genProto(ctx android.ModuleContext, protoFiles android.Paths,
-	protoFlags string, protoOut, protoOutFlags string) android.WritablePath {
-
-	protoFileList := android.PathForModuleGen(ctx, "proto.filelist")
+func genProto(ctx android.ModuleContext, outputSrcJar android.WritablePath,
+	protoFiles android.Paths, protoFlags string, protoOut, protoOutFlags string) {
 
 	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
 		Rule:        proto,
 		Description: "protoc " + protoFiles[0].Rel(),
-		Output:      protoFileList,
+		Output:      outputSrcJar,
 		Inputs:      protoFiles,
 		Args: map[string]string{
 			"outDir":        android.ProtoDir(ctx).String(),
@@ -52,8 +53,6 @@
 			"protoFlags":    protoFlags,
 		},
 	})
-
-	return protoFileList
 }
 
 func protoDeps(ctx android.BottomUpMutatorContext, p *android.ProtoProperties) {
diff --git a/java/system_modules.go b/java/system_modules.go
new file mode 100644
index 0000000..ddfc5cf
--- /dev/null
+++ b/java/system_modules.go
@@ -0,0 +1,144 @@
+// 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 (
+	"fmt"
+	"io"
+	"strings"
+
+	"github.com/google/blueprint"
+
+	"android/soong/android"
+)
+
+// OpenJDK 9 introduces the concept of "system modules", which replace the bootclasspath.  This
+// file will produce the rules necessary to convert each unique set of bootclasspath jars into
+// system modules in a runtime image using the jmod and jlink tools.
+
+func init() {
+	android.RegisterModuleType("java_system_modules", SystemModulesFactory)
+
+	pctx.SourcePathVariable("moduleInfoJavaPath", "build/soong/scripts/jars-to-module-info-java.sh")
+}
+
+var (
+	jarsTosystemModules = pctx.AndroidStaticRule("jarsTosystemModules", blueprint.RuleParams{
+		Command: `rm -rf ${outDir} ${workDir} && mkdir -p ${workDir}/jmod && ` +
+			`${moduleInfoJavaPath} ${moduleName} $in > ${workDir}/module-info.java && ` +
+			`${config.JavacCmd} --system=none --patch-module=java.base=${classpath} ${workDir}/module-info.java && ` +
+			`${config.SoongZipCmd} -jar -o ${workDir}/classes.jar -C ${workDir} -f ${workDir}/module-info.class && ` +
+			`${config.MergeZipsCmd} -j ${workDir}/module.jar ${workDir}/classes.jar $in && ` +
+			`${config.JmodCmd} create --module-version 9 --target-platform android ` +
+			`  --class-path ${workDir}/module.jar ${workDir}/jmod/${moduleName}.jmod && ` +
+			`${config.JlinkCmd} --module-path ${workDir}/jmod --add-modules ${moduleName} --output ${outDir} && ` +
+			`cp ${config.JrtFsJar} ${outDir}/lib/`,
+		CommandDeps: []string{
+			"${moduleInfoJavaPath}",
+			"${config.JavacCmd}",
+			"${config.SoongZipCmd}",
+			"${config.MergeZipsCmd}",
+			"${config.JmodCmd}",
+			"${config.JlinkCmd}",
+			"${config.JrtFsJar}",
+		},
+	},
+		"moduleName", "classpath", "outDir", "workDir")
+)
+
+func TransformJarsToSystemModules(ctx android.ModuleContext, moduleName string, jars android.Paths) android.WritablePath {
+	outDir := android.PathForModuleOut(ctx, "system")
+	workDir := android.PathForModuleOut(ctx, "modules")
+	outputFile := android.PathForModuleOut(ctx, "system/lib/modules")
+	outputs := android.WritablePaths{
+		outputFile,
+		android.PathForModuleOut(ctx, "system/lib/jrt-fs.jar"),
+		android.PathForModuleOut(ctx, "system/release"),
+	}
+
+	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+		Rule:        jarsTosystemModules,
+		Description: "system modules",
+		Outputs:     outputs,
+		Inputs:      jars,
+		Args: map[string]string{
+			"moduleName": moduleName,
+			"classpath":  strings.Join(jars.Strings(), ":"),
+			"workDir":    workDir.String(),
+			"outDir":     outDir.String(),
+		},
+	})
+
+	return outputFile
+}
+
+func SystemModulesFactory() android.Module {
+	module := &SystemModules{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+	return module
+}
+
+type SystemModules struct {
+	android.ModuleBase
+
+	properties SystemModulesProperties
+
+	outputFile android.Path
+}
+
+type SystemModulesProperties struct {
+	// List of java library modules that should be included in the system modules
+	Libs []string
+
+	// List of prebuilt jars that should be included in the system modules
+	Jars []string
+
+	// Sdk version that should be included in the system modules
+	Sdk_version *string
+}
+
+func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var jars android.Paths
+
+	ctx.VisitDirectDeps(func(module blueprint.Module) {
+		if ctx.OtherModuleDependencyTag(module) == libTag {
+			dep, _ := module.(Dependency)
+			jars = append(jars, dep.ClasspathFiles()...)
+		}
+	})
+
+	jars = append(jars, android.PathsForModuleSrc(ctx, system.properties.Jars)...)
+
+	if ctx.AConfig().TargetOpenJDK9() {
+		system.outputFile = TransformJarsToSystemModules(ctx, "java.base", jars)
+	}
+}
+
+func (system *SystemModules) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), libTag, system.properties.Libs...)
+}
+
+func (system *SystemModules) AndroidMk() android.AndroidMkData {
+	return android.AndroidMkData{
+		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+			if system.outputFile != nil {
+				makevar := "SOONG_SYSTEM_MODULES_" + name
+				fmt.Fprintln(w)
+				fmt.Fprintln(w, makevar, ":=", system.outputFile.String())
+				fmt.Fprintln(w, ".KATI_READONLY", ":=", makevar)
+			}
+		},
+	}
+}
diff --git a/scripts/jars-to-module-info-java.sh b/scripts/jars-to-module-info-java.sh
new file mode 100755
index 0000000..44be549
--- /dev/null
+++ b/scripts/jars-to-module-info-java.sh
@@ -0,0 +1,20 @@
+#!/bin/bash -e
+
+# Extracts the Java package names of all classes in the .jar files and writes a module-info.java
+# file to stdout that exports all of those packages.
+
+if [ -z "$1" ]; then
+  echo "usage: $0 <module name> <jar1> [<jar2> ...]" >&2
+  exit 1
+fi
+
+module_name=$1
+shift
+
+echo "module ${module_name} {"
+for j in "$@"; do zipinfo -1 $j ; done \
+  | grep -E '/[^/]*\.class$' \
+  | sed 's|\(.*\)/[^/]*\.class$|    exports \1;|g' \
+  | sed 's|/|.|g' \
+  | sort -u
+echo "}"
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index e6c3f56..fb20d63 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -15,6 +15,7 @@
 package build
 
 import (
+	"bytes"
 	"fmt"
 	"strings"
 )
@@ -78,6 +79,49 @@
 	return ret, nil
 }
 
+// Variables to print out in the top banner
+var BannerVars = []string{
+	"PLATFORM_VERSION_CODENAME",
+	"PLATFORM_VERSION",
+	"TARGET_PRODUCT",
+	"TARGET_BUILD_VARIANT",
+	"TARGET_BUILD_TYPE",
+	"TARGET_BUILD_APPS",
+	"TARGET_ARCH",
+	"TARGET_ARCH_VARIANT",
+	"TARGET_CPU_VARIANT",
+	"TARGET_2ND_ARCH",
+	"TARGET_2ND_ARCH_VARIANT",
+	"TARGET_2ND_CPU_VARIANT",
+	"HOST_ARCH",
+	"HOST_2ND_ARCH",
+	"HOST_OS",
+	"HOST_OS_EXTRA",
+	"HOST_CROSS_OS",
+	"HOST_CROSS_ARCH",
+	"HOST_CROSS_2ND_ARCH",
+	"HOST_BUILD_TYPE",
+	"BUILD_ID",
+	"OUT_DIR",
+	"AUX_OS_VARIANT_LIST",
+	"TARGET_BUILD_PDK",
+	"PDK_FUSION_PLATFORM_ZIP",
+}
+
+func Banner(make_vars map[string]string) string {
+	b := &bytes.Buffer{}
+
+	fmt.Fprintln(b, "============================================")
+	for _, name := range BannerVars {
+		if make_vars[name] != "" {
+			fmt.Fprintf(b, "%s=%s\n", name, make_vars[name])
+		}
+	}
+	fmt.Fprint(b, "============================================")
+
+	return b.String()
+}
+
 func runMakeProductConfig(ctx Context, config Config) {
 	// Variables to export into the environment of Kati/Ninja
 	exportEnvVars := []string{
@@ -99,35 +143,6 @@
 		"CCACHE_CPP2",
 	}
 
-	// Variables to print out in the top banner
-	bannerVars := []string{
-		"PLATFORM_VERSION_CODENAME",
-		"PLATFORM_VERSION",
-		"TARGET_PRODUCT",
-		"TARGET_BUILD_VARIANT",
-		"TARGET_BUILD_TYPE",
-		"TARGET_BUILD_APPS",
-		"TARGET_ARCH",
-		"TARGET_ARCH_VARIANT",
-		"TARGET_CPU_VARIANT",
-		"TARGET_2ND_ARCH",
-		"TARGET_2ND_ARCH_VARIANT",
-		"TARGET_2ND_CPU_VARIANT",
-		"HOST_ARCH",
-		"HOST_2ND_ARCH",
-		"HOST_OS",
-		"HOST_OS_EXTRA",
-		"HOST_CROSS_OS",
-		"HOST_CROSS_ARCH",
-		"HOST_CROSS_2ND_ARCH",
-		"HOST_BUILD_TYPE",
-		"BUILD_ID",
-		"OUT_DIR",
-		"AUX_OS_VARIANT_LIST",
-		"TARGET_BUILD_PDK",
-		"PDK_FUSION_PLATFORM_ZIP",
-	}
-
 	allVars := append(append([]string{
 		// Used to execute Kati and Ninja
 		"NINJA_GOALS",
@@ -135,7 +150,7 @@
 
 		// To find target/product/<DEVICE>
 		"TARGET_DEVICE",
-	}, exportEnvVars...), bannerVars...)
+	}, exportEnvVars...), BannerVars...)
 
 	make_vars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true)
 	if err != nil {
@@ -143,13 +158,7 @@
 	}
 
 	// Print the banner like make does
-	fmt.Fprintln(ctx.Stdout(), "============================================")
-	for _, name := range bannerVars {
-		if make_vars[name] != "" {
-			fmt.Fprintf(ctx.Stdout(), "%s=%s\n", name, make_vars[name])
-		}
-	}
-	fmt.Fprintln(ctx.Stdout(), "============================================")
+	fmt.Fprintln(ctx.Stdout(), Banner(make_vars))
 
 	// Populate the environment
 	env := config.Environment()