Merge "Allow other module types in androidx"
diff --git a/android/neverallow.go b/android/neverallow.go
index 1893e8b..23b6454 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -48,6 +48,7 @@
 var neverallows = []Rule{}
 
 func init() {
+	AddNeverAllowRules(createIncludeDirsRules()...)
 	AddNeverAllowRules(createTrebleRules()...)
 	AddNeverAllowRules(createLibcoreRules()...)
 	AddNeverAllowRules(createJavaDeviceForHostRules()...)
@@ -58,6 +59,42 @@
 	neverallows = append(neverallows, rules...)
 }
 
+func createIncludeDirsRules() []Rule {
+	// The list of paths that cannot be referenced using include_dirs
+	paths := []string{
+		"art",
+		"libcore",
+		"libnativehelper",
+		"external/apache-harmony",
+		"external/apache-xml",
+		"external/boringssl",
+		"external/bouncycastle",
+		"external/conscrypt",
+		"external/icu",
+		"external/okhttp",
+		"external/vixl",
+		"external/wycheproof",
+		"system/core/libnativebridge",
+		"system/core/libnativehelper",
+	}
+
+	// Create a composite matcher that will match if the value starts with any of the restricted
+	// paths. A / is appended to the prefix to ensure that restricting path X does not affect paths
+	// XY.
+	rules := make([]Rule, 0, len(paths))
+	for _, path := range paths {
+		rule :=
+			NeverAllow().
+				WithMatcher("include_dirs", StartsWith(path+"/")).
+				Because("include_dirs is deprecated, all usages of '" + path + "' have been migrated" +
+					" to use alternate mechanisms and so can no longer be used.")
+
+		rules = append(rules, rule)
+	}
+
+	return rules
+}
+
 func createTrebleRules() []Rule {
 	return []Rule{
 		NeverAllow().
@@ -156,9 +193,51 @@
 	}
 }
 
+type ValueMatcher interface {
+	test(string) bool
+	String() string
+}
+
+type equalMatcher struct {
+	expected string
+}
+
+func (m *equalMatcher) test(value string) bool {
+	return m.expected == value
+}
+
+func (m *equalMatcher) String() string {
+	return "=" + m.expected
+}
+
+type anyMatcher struct {
+}
+
+func (m *anyMatcher) test(value string) bool {
+	return true
+}
+
+func (m *anyMatcher) String() string {
+	return "=*"
+}
+
+var anyMatcherInstance = &anyMatcher{}
+
+type startsWithMatcher struct {
+	prefix string
+}
+
+func (m *startsWithMatcher) test(value string) bool {
+	return strings.HasPrefix(value, m.prefix)
+}
+
+func (m *startsWithMatcher) String() string {
+	return ".starts-with(" + m.prefix + ")"
+}
+
 type ruleProperty struct {
-	fields []string // e.x.: Vndk.Enabled
-	value  string   // e.x.: true
+	fields  []string // e.x.: Vndk.Enabled
+	matcher ValueMatcher
 }
 
 // A NeverAllow rule.
@@ -173,8 +252,12 @@
 
 	With(properties, value string) Rule
 
+	WithMatcher(properties string, matcher ValueMatcher) Rule
+
 	Without(properties, value string) Rule
 
+	WithoutMatcher(properties string, matcher ValueMatcher) Rule
+
 	Because(reason string) Rule
 }
 
@@ -218,21 +301,36 @@
 }
 
 func (r *rule) With(properties, value string) Rule {
+	return r.WithMatcher(properties, selectMatcher(value))
+}
+
+func (r *rule) WithMatcher(properties string, matcher ValueMatcher) Rule {
 	r.props = append(r.props, ruleProperty{
-		fields: fieldNamesForProperties(properties),
-		value:  value,
+		fields:  fieldNamesForProperties(properties),
+		matcher: matcher,
 	})
 	return r
 }
 
 func (r *rule) Without(properties, value string) Rule {
+	return r.WithoutMatcher(properties, selectMatcher(value))
+}
+
+func (r *rule) WithoutMatcher(properties string, matcher ValueMatcher) Rule {
 	r.unlessProps = append(r.unlessProps, ruleProperty{
-		fields: fieldNamesForProperties(properties),
-		value:  value,
+		fields:  fieldNamesForProperties(properties),
+		matcher: matcher,
 	})
 	return r
 }
 
+func selectMatcher(expected string) ValueMatcher {
+	if expected == "*" {
+		return anyMatcherInstance
+	}
+	return &equalMatcher{expected: expected}
+}
+
 func (r *rule) Because(reason string) Rule {
 	r.reason = reason
 	return r
@@ -253,10 +351,10 @@
 		s += " -type:" + v
 	}
 	for _, v := range r.props {
-		s += " " + strings.Join(v.fields, ".") + "=" + v.value
+		s += " " + strings.Join(v.fields, ".") + v.matcher.String()
 	}
 	for _, v := range r.unlessProps {
-		s += " -" + strings.Join(v.fields, ".") + "=" + v.value
+		s += " -" + strings.Join(v.fields, ".") + v.matcher.String()
 	}
 	if len(r.reason) != 0 {
 		s += " which is restricted because " + r.reason
@@ -280,6 +378,10 @@
 	return includeProps && !excludeProps
 }
 
+func StartsWith(prefix string) ValueMatcher {
+	return &startsWithMatcher{prefix}
+}
+
 // assorted utils
 
 func cleanPaths(paths []string) []string {
@@ -338,8 +440,8 @@
 			continue
 		}
 
-		check := func(v string) bool {
-			return prop.value == "*" || prop.value == v
+		check := func(value string) bool {
+			return prop.matcher.test(value)
 		}
 
 		if matchValue(propertiesValue, check) {
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index ee3c94f..02b4362 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -23,6 +23,29 @@
 	fs            map[string][]byte
 	expectedError string
 }{
+	// include_dir rule tests
+	{
+		name: "include_dir not allowed to reference art",
+		fs: map[string][]byte{
+			"other/Blueprints": []byte(`
+				cc_library {
+					name: "libother",
+					include_dirs: ["art/libdexfile/include"],
+				}`),
+		},
+		expectedError: "all usages of 'art' have been migrated",
+	},
+	{
+		name: "include_dir can reference another location",
+		fs: map[string][]byte{
+			"other/Blueprints": []byte(`
+				cc_library {
+					name: "libother",
+					include_dirs: ["another/include"],
+				}`),
+		},
+	},
+	// Treble rule tests
 	{
 		name: "no vndk.enabled under vendor directory",
 		fs: map[string][]byte{
@@ -217,6 +240,7 @@
 }
 
 type mockCcLibraryProperties struct {
+	Include_dirs     []string
 	Vendor_available *bool
 
 	Vndk struct {
diff --git a/android/onceper.go b/android/onceper.go
index ff865c2..481cdea 100644
--- a/android/onceper.go
+++ b/android/onceper.go
@@ -95,6 +95,16 @@
 	return s[0], s[1]
 }
 
+// OncePath is the same as Once, but returns the value cast to a Path
+func (once *OncePer) OncePath(key OnceKey, value func() Path) Path {
+	return once.Once(key, func() interface{} { return value() }).(Path)
+}
+
+// OncePath is the same as Once, but returns the value cast to a SourcePath
+func (once *OncePer) OnceSourcePath(key OnceKey, value func() SourcePath) SourcePath {
+	return once.Once(key, func() interface{} { return value() }).(SourcePath)
+}
+
 // OnceKey is an opaque type to be used as the key in calls to Once.
 type OnceKey struct {
 	key interface{}
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 1238ddc..48d070e 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -478,6 +478,16 @@
 	return c.Text(flag)
 }
 
+// OptionalFlag adds the specified raw text to the command line if it is not nil.  The text should not contain input or
+// output paths or the rule will not have them listed in its dependencies or outputs.
+func (c *RuleBuilderCommand) OptionalFlag(flag *string) *RuleBuilderCommand {
+	if flag != nil {
+		c.Text(*flag)
+	}
+
+	return c
+}
+
 // Flags adds the specified raw text to the command line.  The text should not contain input or output paths or the
 // rule will not have them listed in its dependencies or outputs.
 func (c *RuleBuilderCommand) Flags(flags []string) *RuleBuilderCommand {
diff --git a/apex/apex.go b/apex/apex.go
index 284af5c..4e6c0b1 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -449,6 +449,7 @@
 	ctx.AddFarVariationDependencies([]blueprint.Variation{
 		{Mutator: "arch", Variation: arch},
 		{Mutator: "image", Variation: imageVariation},
+		{Mutator: "test_per_src", Variation: ""}, // "" is the all-tests variant
 	}, testTag, tests...)
 }
 
@@ -492,6 +493,7 @@
 		ctx.AddFarVariationDependencies([]blueprint.Variation{
 			{Mutator: "arch", Variation: target.String()},
 			{Mutator: "image", Variation: a.getImageVariation(config)},
+			{Mutator: "test_per_src", Variation: ""}, // "" is the all-tests variant
 		}, testTag, a.properties.Tests...)
 
 		// Add native modules targetting both ABIs
@@ -692,6 +694,12 @@
 	return
 }
 
+func getCopyManifestForTestPerSrcExecutables(cc *cc.Module) (filesToCopy []android.Path, dirInApex string) {
+	dirInApex = filepath.Join("bin", cc.RelativeInstallPath())
+	filesToCopy = cc.TestPerSrcOutputFiles()
+	return
+}
+
 func getCopyManifestForPyBinary(py *python.Module) (fileToCopy android.Path, dirInApex string) {
 	dirInApex = "bin"
 	fileToCopy = py.HostToolPath().Path()
@@ -827,8 +835,22 @@
 				}
 			case testTag:
 				if cc, ok := child.(*cc.Module); ok {
-					fileToCopy, dirInApex := getCopyManifestForExecutable(cc)
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeTest, cc, nil})
+					if cc.TestPerSrcOutputFiles() != nil {
+						// Multiple-output test module (using `test_per_src`).
+						filesToCopy, dirInApex := getCopyManifestForTestPerSrcExecutables(cc)
+						for _, fileToCopy := range filesToCopy {
+							// Handle modules created as `test_per_src` variations of a single test module:
+							// replace the name of the original test module (`depName`, shared by all
+							// `test_per_src` variants of that module) with the name of the generated test
+							// binary.
+							moduleName := filepath.Base(fileToCopy.String())
+							filesInfo = append(filesInfo, apexFile{fileToCopy, moduleName, dirInApex, nativeTest, cc, nil})
+						}
+					} else {
+						// Single-output test module (not using `test_per_src`).
+						fileToCopy, dirInApex := getCopyManifestForExecutable(cc)
+						filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeTest, cc, nil})
+					}
 					return true
 				} else {
 					ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 5fc0738..bb0c4c5 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -98,6 +98,7 @@
 		ctx.BottomUp("image", cc.ImageMutator).Parallel()
 		ctx.BottomUp("link", cc.LinkageMutator).Parallel()
 		ctx.BottomUp("vndk", cc.VndkMutator).Parallel()
+		ctx.BottomUp("test_per_src", cc.TestPerSrcMutator).Parallel()
 		ctx.BottomUp("version", cc.VersionMutator).Parallel()
 		ctx.BottomUp("begin", cc.BeginMutator).Parallel()
 	})
@@ -197,8 +198,11 @@
 		"system/sepolicy/apex/otherapex-file_contexts":      nil,
 		"system/sepolicy/apex/commonapex-file_contexts":     nil,
 		"mylib.cpp":                            nil,
-		"mytest.cpp":                           nil,
 		"mylib_common.cpp":                     nil,
+		"mytest.cpp":                           nil,
+		"mytest1.cpp":                          nil,
+		"mytest2.cpp":                          nil,
+		"mytest3.cpp":                          nil,
 		"myprebuilt":                           nil,
 		"my_include":                           nil,
 		"vendor/foo/devkeys/test.x509.pem":     nil,
@@ -1367,6 +1371,7 @@
 			key: "myapex.key",
 			tests: [
 				"mytest",
+				"mytests",
 			],
 		}
 
@@ -1385,6 +1390,21 @@
 			static_executable: true,
 			stl: "none",
 		}
+
+		cc_test {
+			name: "mytests",
+			gtest: false,
+			srcs: [
+				"mytest1.cpp",
+				"mytest2.cpp",
+				"mytest3.cpp",
+			],
+			test_per_src: true,
+			relative_install_path: "test",
+			system_shared_libs: [],
+			static_executable: true,
+			stl: "none",
+		}
 	`)
 
 	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
@@ -1392,6 +1412,11 @@
 
 	// Ensure that test dep is copied into apex.
 	ensureContains(t, copyCmds, "image.apex/bin/test/mytest")
+
+	// Ensure that test deps built with `test_per_src` are copied into apex.
+	ensureContains(t, copyCmds, "image.apex/bin/test/mytest1")
+	ensureContains(t, copyCmds, "image.apex/bin/test/mytest2")
+	ensureContains(t, copyCmds, "image.apex/bin/test/mytest3")
 }
 
 func TestApexUsesOtherApex(t *testing.T) {
diff --git a/cc/cc.go b/cc/cc.go
index a0ab255..2cee807 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -40,7 +40,7 @@
 		ctx.BottomUp("link", LinkageMutator).Parallel()
 		ctx.BottomUp("vndk", VndkMutator).Parallel()
 		ctx.BottomUp("ndk_api", ndkApiMutator).Parallel()
-		ctx.BottomUp("test_per_src", testPerSrcMutator).Parallel()
+		ctx.BottomUp("test_per_src", TestPerSrcMutator).Parallel()
 		ctx.BottomUp("version", VersionMutator).Parallel()
 		ctx.BottomUp("begin", BeginMutator).Parallel()
 		ctx.BottomUp("sysprop", SyspropMutator).Parallel()
@@ -179,6 +179,9 @@
 }
 
 type ObjectLinkerProperties struct {
+	// list of modules that should only provide headers for this module.
+	Header_libs []string `android:"arch_variant,variant_prepend"`
+
 	// names of other cc_object modules to link into this module using partial linking
 	Objs []string `android:"arch_variant"`
 
@@ -369,6 +372,7 @@
 	vndkExtDepTag         = dependencyTag{name: "vndk extends", library: true}
 	runtimeDepTag         = dependencyTag{name: "runtime lib"}
 	coverageDepTag        = dependencyTag{name: "coverage"}
+	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 )
 
 // Module contains the properties and members used by all C/C++ module types, and implements
@@ -404,6 +408,9 @@
 
 	outputFile android.OptionalPath
 
+	// Test output files, in the case of a test module using `test_per_src`.
+	testPerSrcOutputFiles []android.Path
+
 	cachedToolchain config.Toolchain
 
 	subAndroidMkOnce map[subAndroidMkProvider]bool
@@ -426,6 +433,10 @@
 	return c.outputFile
 }
 
+func (c *Module) TestPerSrcOutputFiles() []android.Path {
+	return c.testPerSrcOutputFiles
+}
+
 func (c *Module) UnstrippedOutputFile() android.Path {
 	if c.linker != nil {
 		return c.linker.unstrippedOutputFilePath()
@@ -940,6 +951,29 @@
 }
 
 func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
+	// Handle the case of a test module split by `test_per_src` mutator.
+	if test, ok := c.linker.(testPerSrc); ok {
+		// The `test_per_src` mutator adds an extra variant named "", depending on all the
+		// other `test_per_src` variants of the test module. Collect the output files of
+		// these dependencies and record them in the `testPerSrcOutputFiles` for later use
+		// (see e.g. `apexBundle.GenerateAndroidBuildActions`).
+		if test.isAllTestsVariation() {
+			var testPerSrcOutputFiles []android.Path
+			for _, dep := range actx.GetDirectDepsWithTag(testPerSrcDepTag) {
+				if ccDep, ok := dep.(*Module); ok {
+					depOutputFile := ccDep.OutputFile().Path()
+					testPerSrcOutputFiles =
+						append(testPerSrcOutputFiles, depOutputFile)
+				}
+			}
+			c.testPerSrcOutputFiles = testPerSrcOutputFiles
+			// Set outputFile to an empty path, as this module does not produce an
+			// output file per se.
+			c.outputFile = android.OptionalPath{}
+			return
+		}
+	}
+
 	c.makeLinkType = c.getMakeLinkType(actx)
 
 	ctx := &moduleContext{
@@ -2083,6 +2117,7 @@
 		&VendorProperties{},
 		&BaseCompilerProperties{},
 		&BaseLinkerProperties{},
+		&ObjectLinkerProperties{},
 		&LibraryProperties{},
 		&FlagExporterProperties{},
 		&BinaryLinkerProperties{},
diff --git a/cc/cc_test.go b/cc/cc_test.go
index a1b753c..c619b5a 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2277,6 +2277,9 @@
 	ctx.ModuleForTests("fuzz_smoke_test", variant).Rule("cc")
 }
 
+func TestAidl(t *testing.T) {
+}
+
 func assertString(t *testing.T, got, expected string) {
 	t.Helper()
 	if got != expected {
diff --git a/cc/fuzz.go b/cc/fuzz.go
index d13e04a..3b0c5c8 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -102,5 +102,19 @@
 	module.compiler = fuzz
 	module.linker = fuzz
 	module.installer = fuzz
+
+	// The fuzzer runtime is not present for darwin host modules, disable cc_fuzz modules when targeting darwin.
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		disableDarwin := struct {
+			Target struct {
+				Darwin struct {
+					Enabled *bool
+				}
+			}
+		}{}
+		disableDarwin.Target.Darwin.Enabled = BoolPtr(false)
+		ctx.AppendProperties(&disableDarwin)
+	})
+
 	return module
 }
diff --git a/cc/gen.go b/cc/gen.go
index f8007e6..42b0cbe 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -120,6 +120,11 @@
 	headerBn := outDir.Join(ctx, aidlPackage, "Bn"+shortName+".h")
 	headerBp := outDir.Join(ctx, aidlPackage, "Bp"+shortName+".h")
 
+	baseDir := strings.TrimSuffix(aidlFile.String(), aidlFile.Rel())
+	if baseDir != "" {
+		aidlFlags += " -I" + baseDir
+	}
+
 	cmd := rule.Command()
 	cmd.BuiltTool(ctx, "aidl-cpp").
 		FlagWithDepFile("-d", depFile).
diff --git a/cc/gen_test.go b/cc/gen_test.go
index e4219d9..da3b4e8 100644
--- a/cc/gen_test.go
+++ b/cc/gen_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"path/filepath"
+	"strings"
 	"testing"
 )
 
@@ -42,7 +43,8 @@
 		ctx := testCc(t, `
 		filegroup {
 			name: "fg",
-			srcs: ["b.aidl"],
+			srcs: ["sub/c.aidl"],
+			path: "sub",
 		}
 
 		cc_library_shared {
@@ -59,6 +61,12 @@
 		if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.GlobalFlags) {
 			t.Errorf("missing aidl includes in global flags")
 		}
+
+		aidlCommand := aidl.RuleParams.Command
+		if !strings.Contains(aidlCommand, "-Isub") {
+			t.Errorf("aidl command for c.aidl should contain \"-Isub\", but was %q", aidlCommand)
+		}
+
 	})
 
 }
diff --git a/cc/makevars.go b/cc/makevars.go
index 78a32c8..2b49772 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -138,7 +138,6 @@
 
 	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(asanCflags, " "))
 	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", strings.Join(asanLdflags, " "))
-	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_STATIC_LIBRARIES", strings.Join(asanLibs, " "))
 
 	ctx.Strict("HWADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(hwasanCflags, " "))
 	ctx.Strict("HWADDRESS_SANITIZER_GLOBAL_OPTIONS", strings.Join(hwasanGlobalOptions, ","))
diff --git a/cc/object.go b/cc/object.go
index 50ecc38..9fa0ac9 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -66,6 +66,7 @@
 		deps.LateSharedLibs = append(deps.LateSharedLibs, "libc")
 	}
 
+	deps.HeaderLibs = append(deps.HeaderLibs, object.Properties.Header_libs...)
 	deps.ObjFiles = append(deps.ObjFiles, object.Properties.Objs...)
 	return deps
 }
diff --git a/cc/sanitize.go b/cc/sanitize.go
index c59f53a..b238b7e 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -33,7 +33,6 @@
 
 	asanCflags  = []string{"-fno-omit-frame-pointer"}
 	asanLdflags = []string{"-Wl,-u,__asan_preinit"}
-	asanLibs    = []string{"libasan"}
 
 	// TODO(pcc): Stop passing -hwasan-allow-ifunc here once it has been made
 	// the default.
@@ -391,7 +390,6 @@
 
 	if ctx.Device() {
 		if Bool(sanitize.Properties.Sanitize.Address) {
-			deps.StaticLibs = append(deps.StaticLibs, asanLibs...)
 			// Compiling asan and having libc_scudo in the same
 			// executable will cause the executable to crash.
 			// Remove libc_scudo since it is only used to override
diff --git a/cc/test.go b/cc/test.go
index 0a00aa1..aff8ba5 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -121,7 +121,9 @@
 type testPerSrc interface {
 	testPerSrc() bool
 	srcs() []string
+	isAllTestsVariation() bool
 	setSrc(string, string)
+	unsetSrc()
 }
 
 func (test *testBinary) testPerSrc() bool {
@@ -132,28 +134,55 @@
 	return test.baseCompiler.Properties.Srcs
 }
 
+func (test *testBinary) isAllTestsVariation() bool {
+	stem := test.binaryDecorator.Properties.Stem
+	return stem != nil && *stem == ""
+}
+
 func (test *testBinary) setSrc(name, src string) {
 	test.baseCompiler.Properties.Srcs = []string{src}
 	test.binaryDecorator.Properties.Stem = StringPtr(name)
 }
 
+func (test *testBinary) unsetSrc() {
+	test.baseCompiler.Properties.Srcs = nil
+	test.binaryDecorator.Properties.Stem = StringPtr("")
+}
+
 var _ testPerSrc = (*testBinary)(nil)
 
-func testPerSrcMutator(mctx android.BottomUpMutatorContext) {
+func TestPerSrcMutator(mctx android.BottomUpMutatorContext) {
 	if m, ok := mctx.Module().(*Module); ok {
 		if test, ok := m.linker.(testPerSrc); ok {
-			if test.testPerSrc() && len(test.srcs()) > 0 {
+			numTests := len(test.srcs())
+			if test.testPerSrc() && numTests > 0 {
 				if duplicate, found := checkDuplicate(test.srcs()); found {
 					mctx.PropertyErrorf("srcs", "found a duplicate entry %q", duplicate)
 					return
 				}
-				testNames := make([]string, len(test.srcs()))
+				testNames := make([]string, numTests)
 				for i, src := range test.srcs() {
 					testNames[i] = strings.TrimSuffix(filepath.Base(src), filepath.Ext(src))
 				}
+				// In addition to creating one variation per test source file,
+				// create an additional "all tests" variation named "", and have it
+				// depends on all other test_per_src variations. This is useful to
+				// create subsequent dependencies of a given module on all
+				// test_per_src variations created above: by depending on
+				// variation "", that module will transitively depend on all the
+				// other test_per_src variations without the need to know their
+				// name or even their number.
+				testNames = append(testNames, "")
 				tests := mctx.CreateLocalVariations(testNames...)
+				all_tests := tests[numTests]
+				all_tests.(*Module).linker.(testPerSrc).unsetSrc()
+				// Prevent the "all tests" variation from being installable nor
+				// exporting to Make, as it won't create any output file.
+				all_tests.(*Module).Properties.PreventInstall = true
+				all_tests.(*Module).Properties.HideFromMake = true
 				for i, src := range test.srcs() {
 					tests[i].(*Module).linker.(testPerSrc).setSrc(testNames[i], src)
+					mctx.AddInterVariantDependency(testPerSrcDepTag, all_tests, tests[i])
 				}
 			}
 		}
diff --git a/cc/testing.go b/cc/testing.go
index bf806bc..f0ad33b 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -265,6 +265,7 @@
 		"bar.c":       nil,
 		"a.proto":     nil,
 		"b.aidl":      nil,
+		"sub/c.aidl":  nil,
 		"my_include":  nil,
 		"foo.map.txt": nil,
 		"liba.so":     nil,
diff --git a/java/builder.go b/java/builder.go
index a48e8b1..22eff7c 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -148,16 +148,15 @@
 }
 
 type javaBuilderFlags struct {
-	javacFlags        string
-	bootClasspath     classpath
-	classpath         classpath
-	processorPath     classpath
-	processor         string
-	systemModules     classpath
-	systemModulesDeps android.Paths
-	aidlFlags         string
-	aidlDeps          android.Paths
-	javaVersion       string
+	javacFlags    string
+	bootClasspath classpath
+	classpath     classpath
+	processorPath classpath
+	processor     string
+	systemModules *systemModules
+	aidlFlags     string
+	aidlDeps      android.Paths
+	javaVersion   string
 
 	errorProneExtraJavacFlags string
 	errorProneProcessorPath   classpath
@@ -249,8 +248,9 @@
 
 	var bootClasspath string
 	if flags.javaVersion == "1.9" {
-		deps = append(deps, flags.systemModulesDeps...)
-		bootClasspath = flags.systemModules.FormJavaSystemModulesPath("--system=", ctx.Device())
+		var systemModuleDeps android.Paths
+		bootClasspath, systemModuleDeps = flags.systemModules.FormJavaSystemModulesPath(ctx.Device())
+		deps = append(deps, systemModuleDeps...)
 	} else {
 		deps = append(deps, flags.bootClasspath...)
 		if len(flags.bootClasspath) == 0 && ctx.Device() {
@@ -411,7 +411,7 @@
 	})
 }
 
-type classpath []android.Path
+type classpath android.Paths
 
 func (x *classpath) FormJavaClassPath(optName string) string {
 	if optName != "" && !strings.HasSuffix(optName, "=") && !strings.HasSuffix(optName, " ") {
@@ -424,21 +424,6 @@
 	}
 }
 
-// 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) FormJavaSystemModulesPath(optName string, forceEmpty bool) string {
-	if len(*x) > 1 {
-		panic("more than one system module")
-	} else if len(*x) == 1 {
-		return optName + (*x)[0].String()
-	} else if forceEmpty {
-		return optName + "none"
-	} else {
-		return ""
-	}
-}
-
 func (x *classpath) FormTurbineClasspath(optName string) []string {
 	if x == nil || *x == nil {
 		return nil
@@ -466,3 +451,21 @@
 	}
 	return ret
 }
+
+type systemModules struct {
+	dir  android.Path
+	deps android.Paths
+}
+
+// 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 *systemModules) FormJavaSystemModulesPath(forceEmpty bool) (string, android.Paths) {
+	if x != nil {
+		return "--system=" + x.dir.String(), x.deps
+	} else if forceEmpty {
+		return "--system=none", nil
+	} else {
+		return "", nil
+	}
+}
diff --git a/java/config/config.go b/java/config/config.go
index d017ae6..6a0a10a 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -47,6 +47,11 @@
 	}
 )
 
+const (
+	JavaVmFlags  = `-XX:OnError="cat hs_err_pid%p.log" -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads`
+	JavacVmFlags = `-J-XX:OnError="cat hs_err_pid%p.log" -J-XX:CICompilerCount=6 -J-XX:+UseDynamicNumberOfGCThreads`
+)
+
 func init() {
 	pctx.Import("github.com/google/blueprint/bootstrap")
 
@@ -69,8 +74,9 @@
 		// b/65004097: prevent using java.lang.invoke.StringConcatFactory when using -target 1.9
 		`-XDstringConcat=inline`,
 	}, " "))
-	pctx.StaticVariable("JavaVmFlags", "-XX:OnError=\"cat hs_err_pid%p.log\" -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads")
-	pctx.StaticVariable("JavacVmFlags", "-J-XX:OnError=\"cat hs_err_pid%p.log\" -J-XX:CICompilerCount=6 -J-XX:+UseDynamicNumberOfGCThreads")
+
+	pctx.StaticVariable("JavaVmFlags", JavaVmFlags)
+	pctx.StaticVariable("JavacVmFlags", JavacVmFlags)
 
 	pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS)
 
@@ -154,3 +160,41 @@
 	pctx.HostBinToolVariable("Class2Greylist", "class2greylist")
 	pctx.HostBinToolVariable("HiddenAPI", "hiddenapi")
 }
+
+// JavaCmd returns a SourcePath object with the path to the java command.
+func JavaCmd(ctx android.PathContext) android.SourcePath {
+	return javaTool(ctx, "java")
+}
+
+// JavadocCmd returns a SourcePath object with the path to the java command.
+func JavadocCmd(ctx android.PathContext) android.SourcePath {
+	return javaTool(ctx, "javadoc")
+}
+
+func javaTool(ctx android.PathContext, tool string) android.SourcePath {
+	type javaToolKey string
+
+	key := android.NewCustomOnceKey(javaToolKey(tool))
+
+	return ctx.Config().OnceSourcePath(key, func() android.SourcePath {
+		return javaToolchain(ctx).Join(ctx, tool)
+	})
+
+}
+
+var javaToolchainKey = android.NewOnceKey("javaToolchain")
+
+func javaToolchain(ctx android.PathContext) android.SourcePath {
+	return ctx.Config().OnceSourcePath(javaToolchainKey, func() android.SourcePath {
+		return javaHome(ctx).Join(ctx, "bin")
+	})
+}
+
+var javaHomeKey = android.NewOnceKey("javaHome")
+
+func javaHome(ctx android.PathContext) android.SourcePath {
+	return ctx.Config().OnceSourcePath(javaHomeKey, func() android.SourcePath {
+		// This is set up and guaranteed by soong_ui
+		return android.PathForSource(ctx, ctx.Config().Getenv("ANDROID_JAVA_HOME"))
+	})
+}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index ae810e1..78bfa80 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -15,126 +15,15 @@
 package java
 
 import (
-	"android/soong/android"
-	"android/soong/java/config"
 	"fmt"
 	"path/filepath"
 	"runtime"
 	"strings"
 
-	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
-)
 
-var (
-	javadoc = pctx.AndroidStaticRule("javadoc",
-		blueprint.RuleParams{
-			Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` +
-				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-				`${config.SoongJavacWrapper} ${config.JavadocCmd} -encoding UTF-8 @$out.rsp @$srcJarDir/list ` +
-				`$opts $bootclasspathArgs $classpathArgs $sourcepathArgs ` +
-				`-d $outDir -quiet  && ` +
-				`${config.SoongZipCmd} -write_if_changed -d -o $docZip -C $outDir -D $outDir && ` +
-				`${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir $postDoclavaCmds && ` +
-				`rm -rf "$srcJarDir"`,
-
-			CommandDeps: []string{
-				"${config.ZipSyncCmd}",
-				"${config.JavadocCmd}",
-				"${config.SoongZipCmd}",
-			},
-			CommandOrderOnly: []string{"${config.SoongJavacWrapper}"},
-			Rspfile:          "$out.rsp",
-			RspfileContent:   "$in",
-			Restat:           true,
-		},
-		"outDir", "srcJarDir", "stubsDir", "srcJars", "opts",
-		"bootclasspathArgs", "classpathArgs", "sourcepathArgs", "docZip", "postDoclavaCmds")
-
-	apiCheck = pctx.AndroidStaticRule("apiCheck",
-		blueprint.RuleParams{
-			Command: `( ${config.ApiCheckCmd} -JXmx1024m -J"classpath $classpath" $opts ` +
-				`$apiFile $apiFileToCheck $removedApiFile $removedApiFileToCheck ` +
-				`&& touch $out ) || (echo -e "$msg" ; exit 38)`,
-			CommandDeps: []string{
-				"${config.ApiCheckCmd}",
-			},
-		},
-		"classpath", "opts", "apiFile", "apiFileToCheck", "removedApiFile", "removedApiFileToCheck", "msg")
-
-	updateApi = pctx.AndroidStaticRule("updateApi",
-		blueprint.RuleParams{
-			Command: `( ( cp -f $srcApiFile $destApiFile && cp -f $srcRemovedApiFile $destRemovedApiFile ) ` +
-				`&& touch $out ) || (echo failed to update public API ; exit 38)`,
-		},
-		"srcApiFile", "destApiFile", "srcRemovedApiFile", "destRemovedApiFile")
-
-	metalava = pctx.AndroidStaticRule("metalava",
-		blueprint.RuleParams{
-			Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && ` +
-				`mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` +
-				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-				`${config.JavaCmd}  ${config.JavaVmFlags} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` +
-				`$bootclasspathArgs $classpathArgs $sourcepathArgs --no-banner --color --quiet --format=v2 ` +
-				`$opts && ` +
-				`${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir && ` +
-				`rm -rf "$srcJarDir"`,
-			CommandDeps: []string{
-				"${config.ZipSyncCmd}",
-				"${config.JavaCmd}",
-				"${config.MetalavaJar}",
-				"${config.SoongZipCmd}",
-			},
-			Rspfile:        "$out.rsp",
-			RspfileContent: "$in",
-			Restat:         true,
-		},
-		"outDir", "srcJarDir", "stubsDir", "srcJars", "javaVersion", "bootclasspathArgs",
-		"classpathArgs", "sourcepathArgs", "opts")
-
-	metalavaApiCheck = pctx.AndroidStaticRule("metalavaApiCheck",
-		blueprint.RuleParams{
-			Command: `( rm -rf "$srcJarDir" && mkdir -p "$srcJarDir" && ` +
-				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-				`${config.JavaCmd}  ${config.JavaVmFlags} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` +
-				`$bootclasspathArgs $classpathArgs $sourcepathArgs --no-banner --color --quiet --format=v2 ` +
-				`$opts && touch $out && rm -rf "$srcJarDir") || ` +
-				`( echo -e "$msg" ; exit 38 )`,
-			CommandDeps: []string{
-				"${config.ZipSyncCmd}",
-				"${config.JavaCmd}",
-				"${config.MetalavaJar}",
-			},
-			Rspfile:        "$out.rsp",
-			RspfileContent: "$in",
-		},
-		"srcJarDir", "srcJars", "javaVersion", "bootclasspathArgs", "classpathArgs", "sourcepathArgs", "opts", "msg")
-
-	nullabilityWarningsCheck = pctx.AndroidStaticRule("nullabilityWarningsCheck",
-		blueprint.RuleParams{
-			Command: `( diff $expected $actual && touch $out ) || ( echo -e "$msg" ; exit 38 )`,
-		},
-		"expected", "actual", "msg")
-
-	dokka = pctx.AndroidStaticRule("dokka",
-		blueprint.RuleParams{
-			Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && ` +
-				`mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` +
-				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-				`${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.DokkaJar} $srcJarDir ` +
-				`$classpathArgs -format dac -dacRoot /reference/kotlin -output $outDir $opts && ` +
-				`${config.SoongZipCmd} -write_if_changed -d -o $docZip -C $outDir -D $outDir && ` +
-				`${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir && ` +
-				`rm -rf "$srcJarDir"`,
-			CommandDeps: []string{
-				"${config.ZipSyncCmd}",
-				"${config.DokkaJar}",
-				"${config.MetalavaJar}",
-				"${config.SoongZipCmd}",
-			},
-			Restat: true,
-		},
-		"outDir", "srcJarDir", "stubsDir", "srcJars", "classpathArgs", "opts", "docZip")
+	"android/soong/android"
+	"android/soong/java/config"
 )
 
 func init() {
@@ -238,7 +127,7 @@
 
 	// proofread file contains all of the text content of the javadocs concatenated into one file,
 	// suitable for spell-checking and other goodness.
-	Proofread_file *string `android:"path"`
+	Proofread_file *string
 
 	// a todo file lists the program elements that are missing documentation.
 	// At some point, this might be improved to show more warnings.
@@ -404,14 +293,6 @@
 	doclavaStubsFlags string
 	doclavaDocsFlags  string
 	postDoclavaCmds   string
-
-	metalavaStubsFlags                string
-	metalavaAnnotationsFlags          string
-	metalavaMergeAnnoDirFlags         string
-	metalavaInclusionAnnotationsFlags string
-	metalavaApiLevelsAnnotationsFlags string
-
-	metalavaApiToXmlFlags string
 }
 
 func InitDroiddocModule(module android.DefaultableModule, hod android.HostOrDeviceSupported) {
@@ -454,23 +335,6 @@
 	ApiFilePath() android.Path
 }
 
-func transformUpdateApi(ctx android.ModuleContext, destApiFile, destRemovedApiFile,
-	srcApiFile, srcRemovedApiFile android.Path, output android.WritablePath) {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        updateApi,
-		Description: "Update API",
-		Output:      output,
-		Implicits: append(android.Paths{}, srcApiFile, srcRemovedApiFile,
-			destApiFile, destRemovedApiFile),
-		Args: map[string]string{
-			"destApiFile":        destApiFile.String(),
-			"srcApiFile":         srcApiFile.String(),
-			"destRemovedApiFile": destRemovedApiFile.String(),
-			"srcRemovedApiFile":  srcRemovedApiFile.String(),
-		},
-	})
-}
-
 //
 // Javadoc
 //
@@ -655,6 +519,7 @@
 				deps.classpath = append(deps.classpath, dep.SdkImplementationJars(ctx, j.sdkVersion())...)
 			case Dependency:
 				deps.classpath = append(deps.classpath, dep.HeaderJars()...)
+				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 			case android.SourceFileProducer:
 				checkProducesJars(ctx, dep)
 				deps.classpath = append(deps.classpath, dep.Srcs()...)
@@ -691,8 +556,7 @@
 			if sm.outputDir == nil && len(sm.outputDeps) == 0 {
 				panic("Missing directory for system module dependency")
 			}
-			deps.systemModules = sm.outputDir
-			deps.systemModulesDeps = sm.outputDeps
+			deps.systemModules = &systemModules{sm.outputDir, sm.outputDeps}
 		}
 	})
 	// do not pass exclude_srcs directly when expanding srcFiles since exclude_srcs
@@ -708,9 +572,6 @@
 	j.srcFiles = srcFiles.FilterOutByExt(".srcjar")
 	j.srcFiles = append(j.srcFiles, deps.srcs...)
 
-	j.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip")
-	j.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
-
 	if j.properties.Local_sourcepaths == nil && len(j.srcFiles) > 0 {
 		j.properties.Local_sourcepaths = append(j.properties.Local_sourcepaths, ".")
 	}
@@ -761,52 +622,43 @@
 func (j *Javadoc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	deps := j.collectDeps(ctx)
 
-	var implicits android.Paths
-	implicits = append(implicits, deps.bootClasspath...)
-	implicits = append(implicits, deps.classpath...)
+	j.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip")
 
-	var bootClasspathArgs, classpathArgs, sourcepathArgs string
+	outDir := android.PathForModuleOut(ctx, "out")
+	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
+
+	j.stubsSrcJar = nil
+
+	rule := android.NewRuleBuilder()
+
+	rule.Command().Text("rm -rf").Text(outDir.String())
+	rule.Command().Text("mkdir -p").Text(outDir.String())
+
+	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, j.srcJars)
 
 	javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
-	if len(deps.bootClasspath) > 0 {
-		var systemModules classpath
-		if deps.systemModules != nil {
-			systemModules = append(systemModules, deps.systemModules)
-		}
-		implicits = append(implicits, deps.systemModulesDeps...)
-		bootClasspathArgs = systemModules.FormJavaSystemModulesPath("--system ", ctx.Device())
-		bootClasspathArgs = bootClasspathArgs + " --patch-module java.base=."
-	}
-	if len(deps.classpath.Strings()) > 0 {
-		classpathArgs = "-classpath " + strings.Join(deps.classpath.Strings(), ":")
-	}
 
-	implicits = append(implicits, j.srcJars...)
-	implicits = append(implicits, j.argFiles...)
+	cmd := javadocSystemModulesCmd(ctx, rule, j.srcFiles, outDir, srcJarDir, srcJarList,
+		deps.systemModules, deps.classpath, j.sourcepaths)
 
-	opts := "-source " + javaVersion + " -J-Xmx1024m -XDignore.symbol.file -Xdoclint:none"
+	cmd.FlagWithArg("-source ", javaVersion).
+		Flag("-J-Xmx1024m").
+		Flag("-XDignore.symbol.file").
+		Flag("-Xdoclint:none")
 
-	sourcepathArgs = "-sourcepath " + strings.Join(j.sourcepaths.Strings(), ":")
+	rule.Command().
+		BuiltTool(ctx, "soong_zip").
+		Flag("-write_if_changed").
+		Flag("-d").
+		FlagWithOutput("-o ", j.docZip).
+		FlagWithArg("-C ", outDir.String()).
+		FlagWithArg("-D ", outDir.String())
 
-	ctx.Build(pctx, android.BuildParams{
-		Rule:           javadoc,
-		Description:    "Javadoc",
-		Output:         j.stubsSrcJar,
-		ImplicitOutput: j.docZip,
-		Inputs:         j.srcFiles,
-		Implicits:      implicits,
-		Args: map[string]string{
-			"outDir":            android.PathForModuleOut(ctx, "out").String(),
-			"srcJarDir":         android.PathForModuleOut(ctx, "srcjars").String(),
-			"stubsDir":          android.PathForModuleOut(ctx, "stubsDir").String(),
-			"srcJars":           strings.Join(j.srcJars.Strings(), " "),
-			"opts":              proptools.NinjaEscape(opts),
-			"bootclasspathArgs": bootClasspathArgs,
-			"classpathArgs":     classpathArgs,
-			"sourcepathArgs":    sourcepathArgs,
-			"docZip":            j.docZip.String(),
-		},
-	})
+	rule.Restat()
+
+	zipSyncCleanupCmd(rule, srcJarDir)
+
+	rule.Build(pctx, ctx, "javadoc", "javadoc")
 }
 
 //
@@ -871,58 +723,25 @@
 	}
 }
 
-func (d *Droiddoc) initBuilderFlags(ctx android.ModuleContext, implicits *android.Paths,
-	deps deps) (droiddocBuilderFlags, error) {
-	var flags droiddocBuilderFlags
-
-	*implicits = append(*implicits, deps.bootClasspath...)
-	*implicits = append(*implicits, deps.classpath...)
-
-	if len(deps.bootClasspath.Strings()) > 0 {
-		// For OpenJDK 8 we can use -bootclasspath to define the core libraries code.
-		flags.bootClasspathArgs = deps.bootClasspath.FormJavaClassPath("-bootclasspath")
-	}
-	flags.classpathArgs = deps.classpath.FormJavaClassPath("-classpath")
-	// Dokka doesn't support bootClasspath, so combine these two classpath vars for Dokka.
-	dokkaClasspath := classpath{}
-	dokkaClasspath = append(dokkaClasspath, deps.bootClasspath...)
-	dokkaClasspath = append(dokkaClasspath, deps.classpath...)
-	flags.dokkaClasspathArgs = dokkaClasspath.FormJavaClassPath("-classpath")
-
-	// TODO(nanzhang): Remove this if- statement once we finish migration for all Doclava
-	// based stubs generation.
-	// In the future, all the docs generation depends on Metalava stubs (droidstubs) srcjar
-	// dir. We need add the srcjar dir to -sourcepath arg, so that Javadoc can figure out
-	// the correct package name base path.
-	if len(d.Javadoc.properties.Local_sourcepaths) > 0 {
-		flags.sourcepathArgs = "-sourcepath " + strings.Join(d.Javadoc.sourcepaths.Strings(), ":")
-	} else {
-		flags.sourcepathArgs = "-sourcepath " + android.PathForModuleOut(ctx, "srcjars").String()
-	}
-
-	return flags, nil
-}
-
-func (d *Droiddoc) collectDoclavaDocsFlags(ctx android.ModuleContext, implicits *android.Paths,
-	jsilver, doclava android.Path) string {
-
-	*implicits = append(*implicits, jsilver)
-	*implicits = append(*implicits, doclava)
-
+func (d *Droiddoc) doclavaDocsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, docletPath classpath) {
 	var date string
 	if runtime.GOOS == "darwin" {
 		date = `date -r`
 	} else {
-		date = `date -d`
+		date = `date -d @`
 	}
 
 	// Droiddoc always gets "-source 1.8" because it doesn't support 1.9 sources.  For modules with 1.9
 	// sources, droiddoc will get sources produced by metalava which will have already stripped out the
 	// 1.9 language features.
-	args := " -source 1.8 -J-Xmx1600m -J-XX:-OmitStackTraceInFastThrow -XDignore.symbol.file " +
-		"-doclet com.google.doclava.Doclava -docletpath " + jsilver.String() + ":" + doclava.String() + " " +
-		"-hdf page.build " + ctx.Config().BuildId() + "-" + ctx.Config().BuildNumberFromFile() + " " +
-		`-hdf page.now "$(` + date + ` @$(cat ` + ctx.Config().Getenv("BUILD_DATETIME_FILE") + `) "+%d %b %Y %k:%M")" `
+	cmd.FlagWithArg("-source ", "1.8").
+		Flag("-J-Xmx1600m").
+		Flag("-J-XX:-OmitStackTraceInFastThrow").
+		Flag("-XDignore.symbol.file").
+		FlagWithArg("-doclet ", "com.google.doclava.Doclava").
+		FlagWithInputList("-docletpath ", docletPath.Paths(), ":").
+		FlagWithArg("-hdf page.build ", ctx.Config().BuildId()+"-"+ctx.Config().BuildNumberFromFile()).
+		FlagWithArg("-hdf page.now ", `"$(`+date+`$(cat `+ctx.Config().Getenv("BUILD_DATETIME_FILE")+`) "+%d %b %Y %k:%M")" `)
 
 	if String(d.properties.Custom_template) == "" {
 		// TODO: This is almost always droiddoc-templates-sdk
@@ -931,23 +750,22 @@
 
 	ctx.VisitDirectDepsWithTag(droiddocTemplateTag, func(m android.Module) {
 		if t, ok := m.(*ExportedDroiddocDir); ok {
-			*implicits = append(*implicits, t.deps...)
-			args = args + " -templatedir " + t.dir.String()
+			cmd.FlagWithArg("-templatedir ", t.dir.String()).Implicits(t.deps)
 		} else {
 			ctx.PropertyErrorf("custom_template", "module %q is not a droiddoc_template", ctx.OtherModuleName(m))
 		}
 	})
 
 	if len(d.properties.Html_dirs) > 0 {
-		htmlDir := d.properties.Html_dirs[0]
-		*implicits = append(*implicits, android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[0], "**/*")})...)
-		args = args + " -htmldir " + htmlDir
+		htmlDir := android.PathForModuleSrc(ctx, d.properties.Html_dirs[0])
+		cmd.FlagWithArg("-htmldir ", htmlDir.String()).
+			Implicits(android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[0], "**/*")}))
 	}
 
 	if len(d.properties.Html_dirs) > 1 {
-		htmlDir2 := d.properties.Html_dirs[1]
-		*implicits = append(*implicits, android.PathsForModuleSrc(ctx, []string{filepath.Join(htmlDir2, "**/*")})...)
-		args = args + " -htmldir2 " + htmlDir2
+		htmlDir2 := android.PathForModuleSrc(ctx, d.properties.Html_dirs[1])
+		cmd.FlagWithArg("-htmldir2 ", htmlDir2.String()).
+			Implicits(android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[1], "**/*")}))
 	}
 
 	if len(d.properties.Html_dirs) > 2 {
@@ -955,51 +773,43 @@
 	}
 
 	knownTags := android.PathsForModuleSrc(ctx, d.properties.Knowntags)
-	*implicits = append(*implicits, knownTags...)
+	cmd.FlagForEachInput("-knowntags ", knownTags)
 
-	for _, kt := range knownTags {
-		args = args + " -knowntags " + kt.String()
-	}
-
-	for _, hdf := range d.properties.Hdf {
-		args = args + " -hdf " + hdf
-	}
+	cmd.FlagForEachArg("-hdf ", d.properties.Hdf)
 
 	if String(d.properties.Proofread_file) != "" {
 		proofreadFile := android.PathForModuleOut(ctx, String(d.properties.Proofread_file))
-		args = args + " -proofread " + proofreadFile.String()
+		cmd.FlagWithOutput("-proofread ", proofreadFile)
 	}
 
 	if String(d.properties.Todo_file) != "" {
 		// tricky part:
 		// we should not compute full path for todo_file through PathForModuleOut().
 		// the non-standard doclet will get the full path relative to "-o".
-		args = args + " -todo " + String(d.properties.Todo_file)
+		cmd.FlagWithArg("-todo ", String(d.properties.Todo_file)).
+			ImplicitOutput(android.PathForModuleOut(ctx, String(d.properties.Todo_file)))
 	}
 
 	if String(d.properties.Resourcesdir) != "" {
 		// TODO: should we add files under resourcesDir to the implicits? It seems that
 		// resourcesDir is one sub dir of htmlDir
 		resourcesDir := android.PathForModuleSrc(ctx, String(d.properties.Resourcesdir))
-		args = args + " -resourcesdir " + resourcesDir.String()
+		cmd.FlagWithArg("-resourcesdir ", resourcesDir.String())
 	}
 
 	if String(d.properties.Resourcesoutdir) != "" {
 		// TODO: it seems -resourceoutdir reference/android/images/ didn't get generated anywhere.
-		args = args + " -resourcesoutdir " + String(d.properties.Resourcesoutdir)
+		cmd.FlagWithArg("-resourcesoutdir ", String(d.properties.Resourcesoutdir))
 	}
-	return args
 }
 
-func (d *Droiddoc) collectStubsFlags(ctx android.ModuleContext,
-	implicitOutputs *android.WritablePaths) string {
-	var doclavaFlags string
+func (d *Droiddoc) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.WritablePath) {
 	if apiCheckEnabled(d.properties.Check_api.Current, "current") ||
 		apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") ||
 		String(d.properties.Api_filename) != "" {
+
 		d.apiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.txt")
-		doclavaFlags += " -api " + d.apiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.apiFile)
+		cmd.FlagWithOutput("-api ", d.apiFile)
 		d.apiFilePath = d.apiFile
 	}
 
@@ -1007,223 +817,324 @@
 		apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") ||
 		String(d.properties.Removed_api_filename) != "" {
 		d.removedApiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_removed.txt")
-		doclavaFlags += " -removedApi " + d.removedApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.removedApiFile)
+		cmd.FlagWithOutput("-removedApi ", d.removedApiFile)
 	}
 
 	if String(d.properties.Private_api_filename) != "" {
 		d.privateApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_api_filename))
-		doclavaFlags += " -privateApi " + d.privateApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.privateApiFile)
+		cmd.FlagWithOutput("-privateApi ", d.privateApiFile)
 	}
 
 	if String(d.properties.Dex_api_filename) != "" {
 		d.dexApiFile = android.PathForModuleOut(ctx, String(d.properties.Dex_api_filename))
-		doclavaFlags += " -dexApi " + d.dexApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.dexApiFile)
+		cmd.FlagWithOutput("-dexApi ", d.dexApiFile)
 	}
 
 	if String(d.properties.Private_dex_api_filename) != "" {
 		d.privateDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_dex_api_filename))
-		doclavaFlags += " -privateDexApi " + d.privateDexApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.privateDexApiFile)
+		cmd.FlagWithOutput("-privateDexApi ", d.privateDexApiFile)
 	}
 
 	if String(d.properties.Removed_dex_api_filename) != "" {
 		d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename))
-		doclavaFlags += " -removedDexApi " + d.removedDexApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.removedDexApiFile)
+		cmd.FlagWithOutput("-removedDexApi ", d.removedDexApiFile)
 	}
 
 	if String(d.properties.Exact_api_filename) != "" {
 		d.exactApiFile = android.PathForModuleOut(ctx, String(d.properties.Exact_api_filename))
-		doclavaFlags += " -exactApi " + d.exactApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.exactApiFile)
+		cmd.FlagWithOutput("-exactApi ", d.exactApiFile)
 	}
 
 	if String(d.properties.Dex_mapping_filename) != "" {
 		d.apiMappingFile = android.PathForModuleOut(ctx, String(d.properties.Dex_mapping_filename))
-		doclavaFlags += " -apiMapping " + d.apiMappingFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.apiMappingFile)
+		cmd.FlagWithOutput("-apiMapping ", d.apiMappingFile)
 	}
 
 	if String(d.properties.Proguard_filename) != "" {
 		d.proguardFile = android.PathForModuleOut(ctx, String(d.properties.Proguard_filename))
-		doclavaFlags += " -proguard " + d.proguardFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.proguardFile)
+		cmd.FlagWithOutput("-proguard ", d.proguardFile)
 	}
 
 	if BoolDefault(d.properties.Create_stubs, true) {
-		doclavaFlags += " -stubs " + android.PathForModuleOut(ctx, "stubsDir").String()
+		cmd.FlagWithArg("-stubs ", stubsDir.String())
 	}
 
 	if Bool(d.properties.Write_sdk_values) {
-		doclavaFlags += " -sdkvalues " + android.PathForModuleOut(ctx, "out").String()
+		cmd.FlagWithArg("-sdkvalues ", android.PathForModuleOut(ctx, "out").String())
 	}
-
-	return doclavaFlags
 }
 
-func (d *Droiddoc) getPostDoclavaCmds(ctx android.ModuleContext, implicits *android.Paths) string {
-	var cmds string
+func (d *Droiddoc) postDoclavaCmds(ctx android.ModuleContext, rule *android.RuleBuilder) {
 	if String(d.properties.Static_doc_index_redirect) != "" {
-		static_doc_index_redirect := ctx.ExpandSource(String(d.properties.Static_doc_index_redirect),
-			"static_doc_index_redirect")
-		*implicits = append(*implicits, static_doc_index_redirect)
-		cmds = cmds + " && cp " + static_doc_index_redirect.String() + " " +
-			android.PathForModuleOut(ctx, "out", "index.html").String()
+		staticDocIndexRedirect := android.PathForModuleSrc(ctx, String(d.properties.Static_doc_index_redirect))
+		rule.Command().Text("cp").
+			Input(staticDocIndexRedirect).
+			Output(android.PathForModuleOut(ctx, "out", "index.html"))
 	}
 
 	if String(d.properties.Static_doc_properties) != "" {
-		static_doc_properties := ctx.ExpandSource(String(d.properties.Static_doc_properties),
-			"static_doc_properties")
-		*implicits = append(*implicits, static_doc_properties)
-		cmds = cmds + " && cp " + static_doc_properties.String() + " " +
-			android.PathForModuleOut(ctx, "out", "source.properties").String()
+		staticDocProperties := android.PathForModuleSrc(ctx, String(d.properties.Static_doc_properties))
+		rule.Command().Text("cp").
+			Input(staticDocProperties).
+			Output(android.PathForModuleOut(ctx, "out", "source.properties"))
 	}
-	return cmds
 }
 
-func (d *Droiddoc) transformDoclava(ctx android.ModuleContext, implicits android.Paths,
-	implicitOutputs android.WritablePaths,
-	bootclasspathArgs, classpathArgs, sourcepathArgs, opts, postDoclavaCmds string) {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:            javadoc,
-		Description:     "Doclava",
-		Output:          d.Javadoc.stubsSrcJar,
-		Inputs:          d.Javadoc.srcFiles,
-		Implicits:       implicits,
-		ImplicitOutputs: implicitOutputs,
-		Args: map[string]string{
-			"outDir":            android.PathForModuleOut(ctx, "out").String(),
-			"srcJarDir":         android.PathForModuleOut(ctx, "srcjars").String(),
-			"stubsDir":          android.PathForModuleOut(ctx, "stubsDir").String(),
-			"srcJars":           strings.Join(d.Javadoc.srcJars.Strings(), " "),
-			"opts":              proptools.NinjaEscape(opts),
-			"bootclasspathArgs": bootclasspathArgs,
-			"classpathArgs":     classpathArgs,
-			"sourcepathArgs":    sourcepathArgs,
-			"docZip":            d.Javadoc.docZip.String(),
-			"postDoclavaCmds":   postDoclavaCmds,
-		},
-	})
+func javadocCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
+	outDir, srcJarDir, srcJarList android.Path, sourcepaths android.Paths) *android.RuleBuilderCommand {
+
+	cmd := rule.Command().
+		BuiltTool(ctx, "soong_javac_wrapper").Tool(config.JavadocCmd(ctx)).
+		Flag(config.JavacVmFlags).
+		FlagWithArg("-encoding ", "UTF-8").
+		FlagWithRspFileInputList("@", srcs).
+		FlagWithInput("@", srcJarList)
+
+	// TODO(ccross): Remove this if- statement once we finish migration for all Doclava
+	// based stubs generation.
+	// In the future, all the docs generation depends on Metalava stubs (droidstubs) srcjar
+	// dir. We need add the srcjar dir to -sourcepath arg, so that Javadoc can figure out
+	// the correct package name base path.
+	if len(sourcepaths) > 0 {
+		cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":")
+	} else {
+		cmd.FlagWithArg("-sourcepath ", srcJarDir.String())
+	}
+
+	cmd.FlagWithArg("-d ", outDir.String()).
+		Flag("-quiet")
+
+	return cmd
 }
 
-func (d *Droiddoc) transformCheckApi(ctx android.ModuleContext, apiFile, removedApiFile android.Path,
-	checkApiClasspath classpath, msg, opts string, output android.WritablePath) {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        apiCheck,
-		Description: "Doclava Check API",
-		Output:      output,
-		Inputs:      nil,
-		Implicits: append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile},
-			checkApiClasspath...),
-		Args: map[string]string{
-			"msg":                   msg,
-			"classpath":             checkApiClasspath.FormJavaClassPath(""),
-			"opts":                  proptools.NinjaEscape(opts),
-			"apiFile":               apiFile.String(),
-			"apiFileToCheck":        d.apiFile.String(),
-			"removedApiFile":        removedApiFile.String(),
-			"removedApiFileToCheck": d.removedApiFile.String(),
-		},
-	})
+func javadocSystemModulesCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
+	outDir, srcJarDir, srcJarList android.Path, systemModules *systemModules,
+	classpath classpath, sourcepaths android.Paths) *android.RuleBuilderCommand {
+
+	cmd := javadocCmd(ctx, rule, srcs, outDir, srcJarDir, srcJarList, sourcepaths)
+
+	flag, deps := systemModules.FormJavaSystemModulesPath(ctx.Device())
+	cmd.Flag(flag).Implicits(deps)
+
+	cmd.FlagWithArg("--patch-module ", "java.base=.")
+
+	if len(classpath) > 0 {
+		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
+	}
+
+	return cmd
 }
 
-func (d *Droiddoc) transformDokka(ctx android.ModuleContext, implicits android.Paths,
-	classpathArgs, opts string) {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        dokka,
-		Description: "Dokka",
-		Output:      d.Javadoc.stubsSrcJar,
-		Inputs:      d.Javadoc.srcFiles,
-		Implicits:   implicits,
-		Args: map[string]string{
-			"outDir":        android.PathForModuleOut(ctx, "dokka-out").String(),
-			"srcJarDir":     android.PathForModuleOut(ctx, "dokka-srcjars").String(),
-			"stubsDir":      android.PathForModuleOut(ctx, "dokka-stubsDir").String(),
-			"srcJars":       strings.Join(d.Javadoc.srcJars.Strings(), " "),
-			"classpathArgs": classpathArgs,
-			"opts":          proptools.NinjaEscape(opts),
-			"docZip":        d.Javadoc.docZip.String(),
-		},
-	})
+func javadocBootclasspathCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
+	outDir, srcJarDir, srcJarList android.Path, bootclasspath, classpath classpath,
+	sourcepaths android.Paths) *android.RuleBuilderCommand {
+
+	cmd := javadocCmd(ctx, rule, srcs, outDir, srcJarDir, srcJarList, sourcepaths)
+
+	if len(bootclasspath) == 0 && ctx.Device() {
+		// explicitly specify -bootclasspath "" if the bootclasspath is empty to
+		// ensure java does not fall back to the default bootclasspath.
+		cmd.FlagWithArg("-bootclasspath ", `""`)
+	} else if len(bootclasspath) > 0 {
+		cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":")
+	}
+
+	if len(classpath) > 0 {
+		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
+	}
+
+	return cmd
+}
+
+func dokkaCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
+	outDir, srcJarDir android.Path, bootclasspath, classpath classpath) *android.RuleBuilderCommand {
+
+	// Dokka doesn't support bootClasspath, so combine these two classpath vars for Dokka.
+	dokkaClasspath := append(bootclasspath.Paths(), classpath.Paths()...)
+
+	return rule.Command().
+		BuiltTool(ctx, "dokka").
+		Flag(config.JavacVmFlags).
+		Flag(srcJarDir.String()).
+		FlagWithInputList("-classpath ", dokkaClasspath, ":").
+		FlagWithArg("-format ", "dac").
+		FlagWithArg("-dacRoot ", "/reference/kotlin").
+		FlagWithArg("-output ", outDir.String())
 }
 
 func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	deps := d.Javadoc.collectDeps(ctx)
 
+	d.Javadoc.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip")
+	d.Javadoc.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
+
 	jsilver := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jsilver.jar")
 	doclava := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "doclava.jar")
 	java8Home := ctx.Config().Getenv("ANDROID_JAVA8_HOME")
 	checkApiClasspath := classpath{jsilver, doclava, android.PathForSource(ctx, java8Home, "lib/tools.jar")}
 
-	var implicits android.Paths
-	implicits = append(implicits, d.Javadoc.srcJars...)
-	implicits = append(implicits, d.Javadoc.argFiles...)
+	outDir := android.PathForModuleOut(ctx, "out")
+	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
+	stubsDir := android.PathForModuleOut(ctx, "stubsDir")
 
-	var implicitOutputs android.WritablePaths
-	implicitOutputs = append(implicitOutputs, d.Javadoc.docZip)
-	for _, o := range d.Javadoc.properties.Out {
-		implicitOutputs = append(implicitOutputs, android.PathForModuleGen(ctx, o))
-	}
+	rule := android.NewRuleBuilder()
 
-	flags, err := d.initBuilderFlags(ctx, &implicits, deps)
-	if err != nil {
-		return
-	}
+	rule.Command().Text("rm -rf").Text(outDir.String()).Text(stubsDir.String())
+	rule.Command().Text("mkdir -p").Text(outDir.String()).Text(stubsDir.String())
 
-	flags.doclavaStubsFlags = d.collectStubsFlags(ctx, &implicitOutputs)
+	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
+
+	var cmd *android.RuleBuilderCommand
 	if Bool(d.properties.Dokka_enabled) {
-		d.transformDokka(ctx, implicits, flags.classpathArgs, d.Javadoc.args)
+		cmd = dokkaCmd(ctx, rule, outDir, srcJarDir, deps.bootClasspath, deps.classpath)
 	} else {
-		flags.doclavaDocsFlags = d.collectDoclavaDocsFlags(ctx, &implicits, jsilver, doclava)
-		flags.postDoclavaCmds = d.getPostDoclavaCmds(ctx, &implicits)
-		d.transformDoclava(ctx, implicits, implicitOutputs, flags.bootClasspathArgs, flags.classpathArgs,
-			flags.sourcepathArgs, flags.doclavaDocsFlags+flags.doclavaStubsFlags+" "+d.Javadoc.args,
-			flags.postDoclavaCmds)
+		cmd = javadocBootclasspathCmd(ctx, rule, d.Javadoc.srcFiles, outDir, srcJarDir, srcJarList,
+			deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths)
 	}
 
+	d.stubsFlags(ctx, cmd, stubsDir)
+
+	cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles)
+
+	var desc string
+	if Bool(d.properties.Dokka_enabled) {
+		desc = "dokka"
+	} else {
+		d.doclavaDocsFlags(ctx, cmd, classpath{jsilver, doclava})
+
+		for _, o := range d.Javadoc.properties.Out {
+			cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
+		}
+
+		d.postDoclavaCmds(ctx, rule)
+		desc = "doclava"
+	}
+
+	rule.Command().
+		BuiltTool(ctx, "soong_zip").
+		Flag("-write_if_changed").
+		Flag("-d").
+		FlagWithOutput("-o ", d.docZip).
+		FlagWithArg("-C ", outDir.String()).
+		FlagWithArg("-D ", outDir.String())
+
+	rule.Command().
+		BuiltTool(ctx, "soong_zip").
+		Flag("-write_if_changed").
+		Flag("-jar").
+		FlagWithOutput("-o ", d.stubsSrcJar).
+		FlagWithArg("-C ", stubsDir.String()).
+		FlagWithArg("-D ", stubsDir.String())
+
+	rule.Restat()
+
+	zipSyncCleanupCmd(rule, srcJarDir)
+
+	rule.Build(pctx, ctx, "javadoc", desc)
+
 	if apiCheckEnabled(d.properties.Check_api.Current, "current") &&
 		!ctx.Config().IsPdkBuild() {
-		apiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Api_file),
-			"check_api.current.api_file")
-		removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Removed_api_file),
-			"check_api.current_removed_api_file")
+
+		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file))
+		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file))
 
 		d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp")
-		d.transformCheckApi(ctx, apiFile, removedApiFile, checkApiClasspath,
-			fmt.Sprintf(`\n******************************\n`+
-				`You have tried to change the API from what has been previously approved.\n\n`+
-				`To make these errors go away, you have two choices:\n`+
-				`   1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+
-				`      errors above.\n\n`+
-				`   2. You can update current.txt by executing the following command:\n`+
-				`         make %s-update-current-api\n\n`+
-				`      To submit the revised current.txt to the main Android repository,\n`+
-				`      you will need approval.\n`+
-				`******************************\n`, ctx.ModuleName()), String(d.properties.Check_api.Current.Args),
-			d.checkCurrentApiTimestamp)
+
+		rule := android.NewRuleBuilder()
+
+		rule.Command().Text("( true")
+
+		rule.Command().
+			BuiltTool(ctx, "apicheck").
+			Flag("-JXmx1024m").
+			FlagWithInputList("-Jclasspath\\ ", checkApiClasspath.Paths(), ":").
+			OptionalFlag(d.properties.Check_api.Current.Args).
+			Input(apiFile).
+			Input(d.apiFile).
+			Input(removedApiFile).
+			Input(d.removedApiFile)
+
+		msg := fmt.Sprintf(`\n******************************\n`+
+			`You have tried to change the API from what has been previously approved.\n\n`+
+			`To make these errors go away, you have two choices:\n`+
+			`   1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+
+			`      errors above.\n\n`+
+			`   2. You can update current.txt by executing the following command:\n`+
+			`         make %s-update-current-api\n\n`+
+			`      To submit the revised current.txt to the main Android repository,\n`+
+			`      you will need approval.\n`+
+			`******************************\n`, ctx.ModuleName())
+
+		rule.Command().
+			Text("touch").Output(d.checkCurrentApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build(pctx, ctx, "doclavaCurrentApiCheck", "check current API")
 
 		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp")
-		transformUpdateApi(ctx, apiFile, removedApiFile, d.apiFile, d.removedApiFile,
-			d.updateCurrentApiTimestamp)
+
+		// update API rule
+		rule = android.NewRuleBuilder()
+
+		rule.Command().Text("( true")
+
+		rule.Command().
+			Text("cp").Flag("-f").
+			Input(d.apiFile).Flag(apiFile.String())
+
+		rule.Command().
+			Text("cp").Flag("-f").
+			Input(d.removedApiFile).Flag(removedApiFile.String())
+
+		msg = "failed to update public API"
+
+		rule.Command().
+			Text("touch").Output(d.updateCurrentApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build(pctx, ctx, "doclavaCurrentApiUpdate", "update current API")
 	}
 
 	if apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") &&
 		!ctx.Config().IsPdkBuild() {
-		apiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file),
-			"check_api.last_released.api_file")
-		removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Removed_api_file),
-			"check_api.last_released.removed_api_file")
+
+		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file))
+		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file))
 
 		d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp")
-		d.transformCheckApi(ctx, apiFile, removedApiFile, checkApiClasspath,
-			`\n******************************\n`+
-				`You have tried to change the API from what has been previously released in\n`+
-				`an SDK.  Please fix the errors listed above.\n`+
-				`******************************\n`, String(d.properties.Check_api.Last_released.Args),
-			d.checkLastReleasedApiTimestamp)
+
+		rule := android.NewRuleBuilder()
+
+		rule.Command().
+			Text("(").
+			BuiltTool(ctx, "apicheck").
+			Flag("-JXmx1024m").
+			FlagWithInputList("-Jclasspath\\ ", checkApiClasspath.Paths(), ":").
+			OptionalFlag(d.properties.Check_api.Last_released.Args).
+			Input(apiFile).
+			Input(d.apiFile).
+			Input(removedApiFile).
+			Input(d.removedApiFile)
+
+		msg := `\n******************************\n` +
+			`You have tried to change the API from what has been previously released in\n` +
+			`an SDK.  Please fix the errors listed above.\n` +
+			`******************************\n`
+
+		rule.Command().
+			Text("touch").Output(d.checkLastReleasedApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build(pctx, ctx, "doclavaLastApiCheck", "check last API")
 	}
 }
 
@@ -1319,34 +1230,12 @@
 	}
 }
 
-func (d *Droidstubs) initBuilderFlags(ctx android.ModuleContext, implicits *android.Paths,
-	deps deps) (droiddocBuilderFlags, error) {
-	var flags droiddocBuilderFlags
-
-	*implicits = append(*implicits, deps.bootClasspath...)
-	*implicits = append(*implicits, deps.classpath...)
-
-	// continue to use -bootclasspath even if Metalava under -source 1.9 is enabled
-	// since it doesn't support system modules yet.
-	if len(deps.bootClasspath.Strings()) > 0 {
-		// For OpenJDK 8 we can use -bootclasspath to define the core libraries code.
-		flags.bootClasspathArgs = deps.bootClasspath.FormJavaClassPath("-bootclasspath")
-	}
-	flags.classpathArgs = deps.classpath.FormJavaClassPath("-classpath")
-
-	flags.sourcepathArgs = "-sourcepath \"" + strings.Join(d.Javadoc.sourcepaths.Strings(), ":") + "\""
-	return flags, nil
-}
-
-func (d *Droidstubs) collectStubsFlags(ctx android.ModuleContext,
-	implicitOutputs *android.WritablePaths) string {
-	var metalavaFlags string
+func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.WritablePath) {
 	if apiCheckEnabled(d.properties.Check_api.Current, "current") ||
 		apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") ||
 		String(d.properties.Api_filename) != "" {
 		d.apiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.txt")
-		metalavaFlags = metalavaFlags + " --api " + d.apiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.apiFile)
+		cmd.FlagWithOutput("--api ", d.apiFile)
 		d.apiFilePath = d.apiFile
 	}
 
@@ -1354,159 +1243,144 @@
 		apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") ||
 		String(d.properties.Removed_api_filename) != "" {
 		d.removedApiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_removed.txt")
-		metalavaFlags = metalavaFlags + " --removed-api " + d.removedApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.removedApiFile)
+		cmd.FlagWithOutput("--removed-api ", d.removedApiFile)
 	}
 
 	if String(d.properties.Private_api_filename) != "" {
 		d.privateApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_api_filename))
-		metalavaFlags = metalavaFlags + " --private-api " + d.privateApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.privateApiFile)
+		cmd.FlagWithOutput("--private-api ", d.privateApiFile)
 	}
 
 	if String(d.properties.Dex_api_filename) != "" {
 		d.dexApiFile = android.PathForModuleOut(ctx, String(d.properties.Dex_api_filename))
-		metalavaFlags += " --dex-api " + d.dexApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.dexApiFile)
+		cmd.FlagWithOutput("--dex-api ", d.dexApiFile)
 	}
 
 	if String(d.properties.Private_dex_api_filename) != "" {
 		d.privateDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_dex_api_filename))
-		metalavaFlags = metalavaFlags + " --private-dex-api " + d.privateDexApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.privateDexApiFile)
+		cmd.FlagWithOutput("--private-dex-api ", d.privateDexApiFile)
 	}
 
 	if String(d.properties.Removed_dex_api_filename) != "" {
 		d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename))
-		metalavaFlags = metalavaFlags + " --removed-dex-api " + d.removedDexApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.removedDexApiFile)
+		cmd.FlagWithOutput("--removed-dex-api ", d.removedDexApiFile)
 	}
 
 	if String(d.properties.Exact_api_filename) != "" {
 		d.exactApiFile = android.PathForModuleOut(ctx, String(d.properties.Exact_api_filename))
-		metalavaFlags = metalavaFlags + " --exact-api " + d.exactApiFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.exactApiFile)
+		cmd.FlagWithOutput("--exact-api ", d.exactApiFile)
 	}
 
 	if String(d.properties.Dex_mapping_filename) != "" {
 		d.apiMappingFile = android.PathForModuleOut(ctx, String(d.properties.Dex_mapping_filename))
-		metalavaFlags = metalavaFlags + " --dex-api-mapping " + d.apiMappingFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.apiMappingFile)
+		cmd.FlagWithOutput("--dex-api-mapping ", d.apiMappingFile)
 	}
 
 	if String(d.properties.Proguard_filename) != "" {
 		d.proguardFile = android.PathForModuleOut(ctx, String(d.properties.Proguard_filename))
-		metalavaFlags += " --proguard " + d.proguardFile.String()
-		*implicitOutputs = append(*implicitOutputs, d.proguardFile)
+		cmd.FlagWithOutput("--proguard ", d.proguardFile)
 	}
 
 	if Bool(d.properties.Write_sdk_values) {
-		metalavaFlags = metalavaFlags + " --sdk-values " + android.PathForModuleOut(ctx, "out").String()
+		cmd.FlagWithArg("--sdk-values ", android.PathForModuleOut(ctx, "out").String())
 	}
 
 	if Bool(d.properties.Create_doc_stubs) {
-		metalavaFlags += " --doc-stubs " + android.PathForModuleOut(ctx, "stubsDir").String()
+		cmd.FlagWithArg("--doc-stubs ", stubsDir.String())
 	} else {
-		metalavaFlags += " --stubs " + android.PathForModuleOut(ctx, "stubsDir").String()
+		cmd.FlagWithArg("--stubs ", stubsDir.String())
 	}
-	return metalavaFlags
 }
 
-func (d *Droidstubs) collectAnnotationsFlags(ctx android.ModuleContext,
-	implicits *android.Paths, implicitOutputs *android.WritablePaths) (string, string) {
-	var flags, mergeAnnoDirFlags string
+func (d *Droidstubs) annotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
 	if Bool(d.properties.Annotations_enabled) {
-		flags += " --include-annotations"
+		cmd.Flag("--include-annotations")
+
 		validatingNullability :=
 			strings.Contains(d.Javadoc.args, "--validate-nullability-from-merged-stubs") ||
 				String(d.properties.Validate_nullability_from_list) != ""
 		migratingNullability := String(d.properties.Previous_api) != ""
+
 		if !(migratingNullability || validatingNullability) {
 			ctx.PropertyErrorf("previous_api",
 				"has to be non-empty if annotations was enabled (unless validating nullability)")
 		}
+
 		if migratingNullability {
 			previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api))
-			*implicits = append(*implicits, previousApi)
-			flags += " --migrate-nullness " + previousApi.String()
+			cmd.FlagWithInput("--migrate-nullness ", previousApi)
 		}
+
 		if s := String(d.properties.Validate_nullability_from_list); s != "" {
-			flags += " --validate-nullability-from-list " + android.PathForModuleSrc(ctx, s).String()
+			cmd.FlagWithInput("--validate-nullability-from-list ", android.PathForModuleSrc(ctx, s))
 		}
+
 		if validatingNullability {
 			d.nullabilityWarningsFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_nullability_warnings.txt")
-			*implicitOutputs = append(*implicitOutputs, d.nullabilityWarningsFile)
-			flags += " --nullability-warnings-txt " + d.nullabilityWarningsFile.String()
+			cmd.FlagWithOutput("--nullability-warnings-txt ", d.nullabilityWarningsFile)
 		}
 
 		d.annotationsZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"_annotations.zip")
-		*implicitOutputs = append(*implicitOutputs, d.annotationsZip)
-
-		flags += " --extract-annotations " + d.annotationsZip.String()
+		cmd.FlagWithOutput("--extract-annotations ", d.annotationsZip)
 
 		if len(d.properties.Merge_annotations_dirs) == 0 {
 			ctx.PropertyErrorf("merge_annotations_dirs",
 				"has to be non-empty if annotations was enabled!")
 		}
-		ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) {
-			if t, ok := m.(*ExportedDroiddocDir); ok {
-				*implicits = append(*implicits, t.deps...)
-				mergeAnnoDirFlags += " --merge-qualifier-annotations " + t.dir.String()
-			} else {
-				ctx.PropertyErrorf("merge_annotations_dirs",
-					"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
-			}
-		})
-		flags += mergeAnnoDirFlags
-		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
-		flags += " --hide HiddenTypedefConstant --hide SuperfluousPrefix --hide AnnotationExtraction"
-	}
 
-	return flags, mergeAnnoDirFlags
+		d.mergeAnnoDirFlags(ctx, cmd)
+
+		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
+		cmd.FlagWithArg("--hide ", "HiddenTypedefConstant").
+			FlagWithArg("--hide ", "SuperfluousPrefix").
+			FlagWithArg("--hide ", "AnnotationExtraction")
+	}
 }
 
-func (d *Droidstubs) collectInclusionAnnotationsFlags(ctx android.ModuleContext,
-	implicits *android.Paths, implicitOutputs *android.WritablePaths) string {
-	var flags string
+func (d *Droidstubs) mergeAnnoDirFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
+	ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) {
+		if t, ok := m.(*ExportedDroiddocDir); ok {
+			cmd.FlagWithArg("--merge-qualifier-annotations ", t.dir.String()).Implicits(t.deps)
+		} else {
+			ctx.PropertyErrorf("merge_annotations_dirs",
+				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
+		}
+	})
+}
+
+func (d *Droidstubs) inclusionAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
 	ctx.VisitDirectDepsWithTag(metalavaMergeInclusionAnnotationsDirTag, func(m android.Module) {
 		if t, ok := m.(*ExportedDroiddocDir); ok {
-			*implicits = append(*implicits, t.deps...)
-			flags += " --merge-inclusion-annotations " + t.dir.String()
+			cmd.FlagWithArg("--merge-inclusion-annotations ", t.dir.String()).Implicits(t.deps)
 		} else {
 			ctx.PropertyErrorf("merge_inclusion_annotations_dirs",
 				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
 		}
 	})
-
-	return flags
 }
 
-func (d *Droidstubs) collectAPILevelsAnnotationsFlags(ctx android.ModuleContext,
-	implicits *android.Paths, implicitOutputs *android.WritablePaths) string {
-	var flags string
+func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
 	if Bool(d.properties.Api_levels_annotations_enabled) {
 		d.apiVersionsXml = android.PathForModuleOut(ctx, "api-versions.xml")
-		*implicitOutputs = append(*implicitOutputs, d.apiVersionsXml)
 
 		if len(d.properties.Api_levels_annotations_dirs) == 0 {
 			ctx.PropertyErrorf("api_levels_annotations_dirs",
 				"has to be non-empty if api levels annotations was enabled!")
 		}
 
-		flags = " --generate-api-levels " + d.apiVersionsXml.String() + " --apply-api-levels " +
-			d.apiVersionsXml.String() + " --current-version " + ctx.Config().PlatformSdkVersion() +
-			" --current-codename " + ctx.Config().PlatformSdkCodename() + " "
+		cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
+		cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml)
+		cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion())
+		cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
 
 		ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
 			if t, ok := m.(*ExportedDroiddocDir); ok {
-				var androidJars android.Paths
 				for _, dep := range t.deps {
 					if strings.HasSuffix(dep.String(), "android.jar") {
-						androidJars = append(androidJars, dep)
+						cmd.Implicit(dep)
 					}
 				}
-				*implicits = append(*implicits, androidJars...)
-				flags += " --android-jar-pattern " + t.dir.String() + "/%/public/android.jar "
+				cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/android.jar")
 			} else {
 				ctx.PropertyErrorf("api_levels_annotations_dirs",
 					"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
@@ -1514,112 +1388,57 @@
 		})
 
 	}
-
-	return flags
 }
 
-func (d *Droidstubs) collectApiToXmlFlags(ctx android.ModuleContext, implicits *android.Paths,
-	implicitOutputs *android.WritablePaths) string {
-	var flags string
+func (d *Droidstubs) apiToXmlFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
 	if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() {
 		if d.apiFile.String() == "" {
 			ctx.ModuleErrorf("API signature file has to be specified in Metalava when jdiff is enabled.")
 		}
 
 		d.apiXmlFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.xml")
-		*implicitOutputs = append(*implicitOutputs, d.apiXmlFile)
-
-		flags = " --api-xml " + d.apiXmlFile.String()
+		cmd.FlagWithOutput("--api-xml ", d.apiXmlFile)
 
 		if String(d.properties.Check_api.Last_released.Api_file) == "" {
 			ctx.PropertyErrorf("check_api.last_released.api_file",
 				"has to be non-empty if jdiff was enabled!")
 		}
-		lastReleasedApi := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file),
-			"check_api.last_released.api_file")
-		*implicits = append(*implicits, lastReleasedApi)
 
+		lastReleasedApi := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file))
 		d.lastReleasedApiXmlFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_last_released_api.xml")
-		*implicitOutputs = append(*implicitOutputs, d.lastReleasedApiXmlFile)
+		cmd.FlagWithInput("--convert-to-jdiff ", lastReleasedApi).Output(d.lastReleasedApiXmlFile)
+	}
+}
 
-		flags += " --convert-to-jdiff " + lastReleasedApi.String() + " " +
-			d.lastReleasedApiXmlFile.String()
+func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion string, srcs android.Paths,
+	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths) *android.RuleBuilderCommand {
+	cmd := rule.Command().BuiltTool(ctx, "metalava").
+		Flag(config.JavacVmFlags).
+		FlagWithArg("-encoding ", "UTF-8").
+		FlagWithArg("-source ", javaVersion).
+		FlagWithRspFileInputList("@", srcs).
+		FlagWithInput("@", srcJarList)
+
+	if len(bootclasspath) > 0 {
+		cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":")
 	}
 
-	return flags
-}
+	if len(classpath) > 0 {
+		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
+	}
 
-func (d *Droidstubs) transformMetalava(ctx android.ModuleContext, implicits android.Paths,
-	implicitOutputs android.WritablePaths, javaVersion,
-	bootclasspathArgs, classpathArgs, sourcepathArgs, opts string) {
+	if len(sourcepaths) > 0 {
+		cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":")
+	} else {
+		cmd.FlagWithArg("-sourcepath ", `""`)
+	}
 
-	ctx.Build(pctx, android.BuildParams{
-		Rule:            metalava,
-		Description:     "Metalava",
-		Output:          d.Javadoc.stubsSrcJar,
-		Inputs:          d.Javadoc.srcFiles,
-		Implicits:       implicits,
-		ImplicitOutputs: implicitOutputs,
-		Args: map[string]string{
-			"outDir":            android.PathForModuleOut(ctx, "out").String(),
-			"srcJarDir":         android.PathForModuleOut(ctx, "srcjars").String(),
-			"stubsDir":          android.PathForModuleOut(ctx, "stubsDir").String(),
-			"srcJars":           strings.Join(d.Javadoc.srcJars.Strings(), " "),
-			"javaVersion":       javaVersion,
-			"bootclasspathArgs": bootclasspathArgs,
-			"classpathArgs":     classpathArgs,
-			"sourcepathArgs":    sourcepathArgs,
-			"opts":              proptools.NinjaEscape(opts),
-		},
-	})
-}
+	cmd.Flag("--no-banner").
+		Flag("--color").
+		Flag("--quiet").
+		Flag("--format=v2")
 
-func (d *Droidstubs) transformCheckApi(ctx android.ModuleContext,
-	apiFile, removedApiFile android.Path, implicits android.Paths,
-	javaVersion, bootclasspathArgs, classpathArgs, sourcepathArgs, opts, subdir, msg string,
-	output android.WritablePath) {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        metalavaApiCheck,
-		Description: "Metalava Check API",
-		Output:      output,
-		Inputs:      d.Javadoc.srcFiles,
-		Implicits: append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile},
-			implicits...),
-		Args: map[string]string{
-			"srcJarDir":         android.PathForModuleOut(ctx, subdir, "srcjars").String(),
-			"srcJars":           strings.Join(d.Javadoc.srcJars.Strings(), " "),
-			"javaVersion":       javaVersion,
-			"bootclasspathArgs": bootclasspathArgs,
-			"classpathArgs":     classpathArgs,
-			"sourcepathArgs":    sourcepathArgs,
-			"opts":              proptools.NinjaEscape(opts),
-			"msg":               msg,
-		},
-	})
-}
-
-func (d *Droidstubs) transformJdiff(ctx android.ModuleContext, implicits android.Paths,
-	implicitOutputs android.WritablePaths,
-	bootclasspathArgs, classpathArgs, sourcepathArgs, opts string) {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:            javadoc,
-		Description:     "Jdiff",
-		Output:          d.jdiffStubsSrcJar,
-		Inputs:          d.Javadoc.srcFiles,
-		Implicits:       implicits,
-		ImplicitOutputs: implicitOutputs,
-		Args: map[string]string{
-			"outDir":            android.PathForModuleOut(ctx, "jdiff-out").String(),
-			"srcJarDir":         android.PathForModuleOut(ctx, "jdiff-srcjars").String(),
-			"stubsDir":          android.PathForModuleOut(ctx, "jdiff-stubsDir").String(),
-			"srcJars":           strings.Join(d.Javadoc.srcJars.Strings(), " "),
-			"opts":              proptools.NinjaEscape(opts),
-			"bootclasspathArgs": bootclasspathArgs,
-			"classpathArgs":     classpathArgs,
-			"sourcepathArgs":    sourcepathArgs,
-			"docZip":            d.jdiffDocZip.String(),
-		},
-	})
+	return cmd
 }
 
 func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1627,29 +1446,29 @@
 
 	javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), sdkContext(d))
 
-	var implicits android.Paths
-	implicits = append(implicits, d.Javadoc.srcJars...)
-	implicits = append(implicits, d.Javadoc.argFiles...)
+	// Create rule for metalava
 
-	var implicitOutputs android.WritablePaths
-	for _, o := range d.Javadoc.properties.Out {
-		implicitOutputs = append(implicitOutputs, android.PathForModuleGen(ctx, o))
-	}
+	d.Javadoc.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
 
-	flags, err := d.initBuilderFlags(ctx, &implicits, deps)
-	metalavaCheckApiImplicits := implicits
-	jdiffImplicits := implicits
+	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
+	stubsDir := android.PathForModuleOut(ctx, "stubsDir")
 
-	if err != nil {
-		return
-	}
+	rule := android.NewRuleBuilder()
 
-	flags.metalavaStubsFlags = d.collectStubsFlags(ctx, &implicitOutputs)
-	flags.metalavaAnnotationsFlags, flags.metalavaMergeAnnoDirFlags =
-		d.collectAnnotationsFlags(ctx, &implicits, &implicitOutputs)
-	flags.metalavaInclusionAnnotationsFlags = d.collectInclusionAnnotationsFlags(ctx, &implicits, &implicitOutputs)
-	flags.metalavaApiLevelsAnnotationsFlags = d.collectAPILevelsAnnotationsFlags(ctx, &implicits, &implicitOutputs)
-	flags.metalavaApiToXmlFlags = d.collectApiToXmlFlags(ctx, &implicits, &implicitOutputs)
+	rule.Command().Text("rm -rf").Text(stubsDir.String())
+	rule.Command().Text("mkdir -p").Text(stubsDir.String())
+
+	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
+
+	cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
+		deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths)
+
+	d.stubsFlags(ctx, cmd, stubsDir)
+
+	d.annotationsFlags(ctx, cmd)
+	d.inclusionAnnotationsFlags(ctx, cmd)
+	d.apiLevelsAnnotationsFlags(ctx, cmd)
+	d.apiToXmlFlags(ctx, cmd)
 
 	if strings.Contains(d.Javadoc.args, "--generate-documentation") {
 		// Currently Metalava have the ability to invoke Javadoc in a seperate process.
@@ -1657,61 +1476,150 @@
 		// "--generate-documentation" arg. This is not needed when Metalava removes this feature.
 		d.Javadoc.args = d.Javadoc.args + " -nodocs "
 	}
-	d.transformMetalava(ctx, implicits, implicitOutputs, javaVersion,
-		flags.bootClasspathArgs, flags.classpathArgs, flags.sourcepathArgs,
-		flags.metalavaStubsFlags+flags.metalavaAnnotationsFlags+flags.metalavaInclusionAnnotationsFlags+
-			flags.metalavaApiLevelsAnnotationsFlags+flags.metalavaApiToXmlFlags+" "+d.Javadoc.args)
+
+	cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles)
+	for _, o := range d.Javadoc.properties.Out {
+		cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
+	}
+
+	rule.Command().
+		BuiltTool(ctx, "soong_zip").
+		Flag("-write_if_changed").
+		Flag("-jar").
+		FlagWithOutput("-o ", d.Javadoc.stubsSrcJar).
+		FlagWithArg("-C ", stubsDir.String()).
+		FlagWithArg("-D ", stubsDir.String())
+	rule.Restat()
+
+	zipSyncCleanupCmd(rule, srcJarDir)
+
+	rule.Build(pctx, ctx, "metalava", "metalava")
+
+	// Create rule for apicheck
 
 	if apiCheckEnabled(d.properties.Check_api.Current, "current") &&
 		!ctx.Config().IsPdkBuild() {
-		apiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Api_file),
-			"check_api.current.api_file")
-		removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Removed_api_file),
-			"check_api.current_removed_api_file")
+
+		if len(d.Javadoc.properties.Out) > 0 {
+			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
+		}
+
+		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file))
+		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file))
 
 		d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp")
-		opts := " " + d.Javadoc.args + " --check-compatibility:api:current " + apiFile.String() +
-			" --check-compatibility:removed:current " + removedApiFile.String() +
-			flags.metalavaInclusionAnnotationsFlags + flags.metalavaMergeAnnoDirFlags + " "
 
-		d.transformCheckApi(ctx, apiFile, removedApiFile, metalavaCheckApiImplicits,
-			javaVersion, flags.bootClasspathArgs, flags.classpathArgs, flags.sourcepathArgs, opts, "current-apicheck",
-			fmt.Sprintf(`\n******************************\n`+
-				`You have tried to change the API from what has been previously approved.\n\n`+
-				`To make these errors go away, you have two choices:\n`+
-				`   1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+
-				`      errors above.\n\n`+
-				`   2. You can update current.txt by executing the following command:\n`+
-				`         make %s-update-current-api\n\n`+
-				`      To submit the revised current.txt to the main Android repository,\n`+
-				`      you will need approval.\n`+
-				`******************************\n`, ctx.ModuleName()),
-			d.checkCurrentApiTimestamp)
+		rule := android.NewRuleBuilder()
+
+		rule.Command().Text("( true")
+
+		srcJarDir := android.PathForModuleOut(ctx, "current-apicheck", "srcjars")
+		srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
+
+		cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
+			deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths)
+
+		cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles).
+			FlagWithInput("--check-compatibility:api:current ", apiFile).
+			FlagWithInput("--check-compatibility:removed:current ", removedApiFile)
+
+		d.inclusionAnnotationsFlags(ctx, cmd)
+		d.mergeAnnoDirFlags(ctx, cmd)
+
+		zipSyncCleanupCmd(rule, srcJarDir)
+
+		msg := fmt.Sprintf(`\n******************************\n`+
+			`You have tried to change the API from what has been previously approved.\n\n`+
+			`To make these errors go away, you have two choices:\n`+
+			`   1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+
+			`      errors above.\n\n`+
+			`   2. You can update current.txt by executing the following command:\n`+
+			`         make %s-update-current-api\n\n`+
+			`      To submit the revised current.txt to the main Android repository,\n`+
+			`      you will need approval.\n`+
+			`******************************\n`, ctx.ModuleName())
+
+		rule.Command().
+			Text("touch").Output(d.checkCurrentApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build(pctx, ctx, "metalavaCurrentApiCheck", "metalava check current API")
 
 		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp")
-		transformUpdateApi(ctx, apiFile, removedApiFile, d.apiFile, d.removedApiFile,
-			d.updateCurrentApiTimestamp)
+
+		// update API rule
+		rule = android.NewRuleBuilder()
+
+		rule.Command().Text("( true")
+
+		rule.Command().
+			Text("cp").Flag("-f").
+			Input(d.apiFile).Flag(apiFile.String())
+
+		rule.Command().
+			Text("cp").Flag("-f").
+			Input(d.removedApiFile).Flag(removedApiFile.String())
+
+		msg = "failed to update public API"
+
+		rule.Command().
+			Text("touch").Output(d.updateCurrentApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build(pctx, ctx, "metalavaCurrentApiUpdate", "update current API")
 	}
 
 	if apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") &&
 		!ctx.Config().IsPdkBuild() {
-		apiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file),
-			"check_api.last_released.api_file")
-		removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Removed_api_file),
-			"check_api.last_released.removed_api_file")
+
+		if len(d.Javadoc.properties.Out) > 0 {
+			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
+		}
+
+		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file))
+		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file))
 
 		d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp")
-		opts := " " + d.Javadoc.args + " --check-compatibility:api:released " + apiFile.String() +
-			flags.metalavaInclusionAnnotationsFlags + " --check-compatibility:removed:released " +
-			removedApiFile.String() + flags.metalavaMergeAnnoDirFlags + " "
 
-		d.transformCheckApi(ctx, apiFile, removedApiFile, metalavaCheckApiImplicits,
-			javaVersion, flags.bootClasspathArgs, flags.classpathArgs, flags.sourcepathArgs, opts, "last-apicheck",
-			`\n******************************\n`+
-				`You have tried to change the API from what has been previously released in\n`+
-				`an SDK.  Please fix the errors listed above.\n`+
-				`******************************\n`,
-			d.checkLastReleasedApiTimestamp)
+		rule := android.NewRuleBuilder()
+
+		rule.Command().Text("( true")
+
+		srcJarDir := android.PathForModuleOut(ctx, "last-apicheck", "srcjars")
+		srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
+
+		cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
+			deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths)
+
+		cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles).
+			FlagWithInput("--check-compatibility:api:released ", apiFile)
+
+		d.inclusionAnnotationsFlags(ctx, cmd)
+
+		cmd.FlagWithInput("--check-compatibility:removed:released ", removedApiFile)
+
+		d.mergeAnnoDirFlags(ctx, cmd)
+
+		zipSyncCleanupCmd(rule, srcJarDir)
+
+		msg := `\n******************************\n` +
+			`You have tried to change the API from what has been previously released in\n` +
+			`an SDK.  Please fix the errors listed above.\n` +
+			`******************************\n`
+		rule.Command().
+			Text("touch").Output(d.checkLastReleasedApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build(pctx, ctx, "metalavaLastApiCheck", "metalava check last API")
 	}
 
 	if String(d.properties.Check_nullability_warnings) != "" {
@@ -1719,9 +1627,11 @@
 			ctx.PropertyErrorf("check_nullability_warnings",
 				"Cannot specify check_nullability_warnings unless validating nullability")
 		}
-		checkNullabilityWarnings := ctx.ExpandSource(String(d.properties.Check_nullability_warnings),
-			"check_nullability_warnings")
+
+		checkNullabilityWarnings := android.PathForModuleSrc(ctx, String(d.properties.Check_nullability_warnings))
+
 		d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, "check_nullability_warnings.timestamp")
+
 		msg := fmt.Sprintf(`\n******************************\n`+
 			`The warnings encountered during nullability annotation validation did\n`+
 			`not match the checked in file of expected warnings. The diffs are shown\n`+
@@ -1731,20 +1641,32 @@
 			`         cp %s %s\n`+
 			`       and submitting the updated file as part of your change.`,
 			d.nullabilityWarningsFile, checkNullabilityWarnings)
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        nullabilityWarningsCheck,
-			Description: "Nullability Warnings Check",
-			Output:      d.checkNullabilityWarningsTimestamp,
-			Implicits:   android.Paths{checkNullabilityWarnings, d.nullabilityWarningsFile},
-			Args: map[string]string{
-				"expected": checkNullabilityWarnings.String(),
-				"actual":   d.nullabilityWarningsFile.String(),
-				"msg":      msg,
-			},
-		})
+
+		rule := android.NewRuleBuilder()
+
+		rule.Command().
+			Text("(").
+			Text("diff").Input(checkNullabilityWarnings).Input(d.nullabilityWarningsFile).
+			Text("&&").
+			Text("touch").Output(d.checkNullabilityWarningsTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build(pctx, ctx, "nullabilityWarningsCheck", "nullability warnings check")
 	}
 
 	if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() {
+		if len(d.Javadoc.properties.Out) > 0 {
+			ctx.PropertyErrorf("out", "out property may not be combined with jdiff")
+		}
+
+		outDir := android.PathForModuleOut(ctx, "jdiff-out")
+		srcJarDir := android.PathForModuleOut(ctx, "jdiff-srcjars")
+		stubsDir := android.PathForModuleOut(ctx, "jdiff-stubsDir")
+
+		rule := android.NewRuleBuilder()
 
 		// Please sync with android-api-council@ before making any changes for the name of jdiffDocZip below
 		// since there's cron job downstream that fetch this .zip file periodically.
@@ -1752,21 +1674,49 @@
 		d.jdiffDocZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"jdiff-docs.zip")
 		d.jdiffStubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"jdiff-stubs.srcjar")
 
-		var jdiffImplicitOutputs android.WritablePaths
-		jdiffImplicitOutputs = append(jdiffImplicitOutputs, d.jdiffDocZip)
-
 		jdiff := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jdiff.jar")
-		jdiffImplicits = append(jdiffImplicits, android.Paths{jdiff, d.apiXmlFile, d.lastReleasedApiXmlFile}...)
 
-		opts := " -source 1.8 -J-Xmx1600m -XDignore.symbol.file " +
-			"-doclet jdiff.JDiff -docletpath " + jdiff.String() + " -quiet " +
-			"-newapi " + strings.TrimSuffix(d.apiXmlFile.Base(), d.apiXmlFile.Ext()) +
-			" -newapidir " + filepath.Dir(d.apiXmlFile.String()) +
-			" -oldapi " + strings.TrimSuffix(d.lastReleasedApiXmlFile.Base(), d.lastReleasedApiXmlFile.Ext()) +
-			" -oldapidir " + filepath.Dir(d.lastReleasedApiXmlFile.String())
+		rule.Command().Text("rm -rf").Text(outDir.String()).Text(stubsDir.String())
+		rule.Command().Text("mkdir -p").Text(outDir.String()).Text(stubsDir.String())
 
-		d.transformJdiff(ctx, jdiffImplicits, jdiffImplicitOutputs, flags.bootClasspathArgs, flags.classpathArgs,
-			flags.sourcepathArgs, opts)
+		srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
+
+		cmd := javadocBootclasspathCmd(ctx, rule, d.Javadoc.srcFiles, outDir, srcJarDir, srcJarList,
+			deps.bootClasspath, deps.classpath, d.sourcepaths)
+
+		cmd.Flag("-J-Xmx1600m").
+			Flag("-XDignore.symbol.file").
+			FlagWithArg("-doclet ", "jdiff.JDiff").
+			FlagWithInput("-docletpath ", jdiff).
+			Flag("-quiet").
+			FlagWithArg("-newapi ", strings.TrimSuffix(d.apiXmlFile.Base(), d.apiXmlFile.Ext())).
+			FlagWithArg("-newapidir ", filepath.Dir(d.apiXmlFile.String())).
+			Implicit(d.apiXmlFile).
+			FlagWithArg("-oldapi ", strings.TrimSuffix(d.lastReleasedApiXmlFile.Base(), d.lastReleasedApiXmlFile.Ext())).
+			FlagWithArg("-oldapidir ", filepath.Dir(d.lastReleasedApiXmlFile.String())).
+			Implicit(d.lastReleasedApiXmlFile)
+
+		rule.Command().
+			BuiltTool(ctx, "soong_zip").
+			Flag("-write_if_changed").
+			Flag("-d").
+			FlagWithOutput("-o ", d.jdiffDocZip).
+			FlagWithArg("-C ", outDir.String()).
+			FlagWithArg("-D ", outDir.String())
+
+		rule.Command().
+			BuiltTool(ctx, "soong_zip").
+			Flag("-write_if_changed").
+			Flag("-jar").
+			FlagWithOutput("-o ", d.jdiffStubsSrcJar).
+			FlagWithArg("-C ", stubsDir.String()).
+			FlagWithArg("-D ", stubsDir.String())
+
+		rule.Restat()
+
+		zipSyncCleanupCmd(rule, srcJarDir)
+
+		rule.Build(pctx, ctx, "jdiff", "jdiff")
 	}
 }
 
@@ -1841,3 +1791,25 @@
 
 	return module
 }
+
+func zipSyncCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
+	srcJarDir android.ModuleOutPath, srcJars android.Paths) android.OutputPath {
+
+	rule.Command().Text("rm -rf").Text(srcJarDir.String())
+	rule.Command().Text("mkdir -p").Text(srcJarDir.String())
+	srcJarList := srcJarDir.Join(ctx, "list")
+
+	rule.Temporary(srcJarList)
+
+	rule.Command().BuiltTool(ctx, "zipsync").
+		FlagWithArg("-d ", srcJarDir.String()).
+		FlagWithOutput("-l ", srcJarList).
+		FlagWithArg("-f ", `"*.java"`).
+		Inputs(srcJars)
+
+	return srcJarList
+}
+
+func zipSyncCleanupCmd(rule *android.RuleBuilder, srcJarDir android.ModuleOutPath) {
+	rule.Command().Text("rm -rf").Text(srcJarDir.String())
+}
diff --git a/java/gen.go b/java/gen.go
index b1c028d..69965ec 100644
--- a/java/gen.go
+++ b/java/gen.go
@@ -15,6 +15,8 @@
 package java
 
 import (
+	"strings"
+
 	"github.com/google/blueprint"
 
 	"android/soong/android"
@@ -63,6 +65,10 @@
 func genAidl(ctx android.ModuleContext, aidlFile android.Path, aidlFlags string, deps android.Paths) android.Path {
 	javaFile := android.GenPathWithExt(ctx, "aidl", aidlFile, "java")
 	depFile := javaFile.String() + ".d"
+	baseDir := strings.TrimSuffix(aidlFile.String(), aidlFile.Rel())
+	if baseDir != "" {
+		aidlFlags += " -I" + baseDir
+	}
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        aidl,
diff --git a/java/java.go b/java/java.go
index a49aad7..f3e10be 100644
--- a/java/java.go
+++ b/java/java.go
@@ -632,8 +632,7 @@
 	aidlIncludeDirs    android.Paths
 	srcs               android.Paths
 	srcJars            android.Paths
-	systemModules      android.Path
-	systemModulesDeps  android.Paths
+	systemModules      *systemModules
 	aidlPreprocess     android.OptionalPath
 	kotlinStdlib       android.Paths
 	kotlinAnnotations  android.Paths
@@ -844,8 +843,7 @@
 				if sm.outputDir == nil || len(sm.outputDeps) == 0 {
 					panic("Missing directory for system module dependency")
 				}
-				deps.systemModules = sm.outputDir
-				deps.systemModulesDeps = sm.outputDeps
+				deps.systemModules = &systemModules{sm.outputDir, sm.outputDeps}
 			}
 		}
 	})
@@ -973,10 +971,7 @@
 	}
 
 	// systemModules
-	if deps.systemModules != nil {
-		flags.systemModules = append(flags.systemModules, deps.systemModules)
-		flags.systemModulesDeps = append(flags.systemModulesDeps, deps.systemModulesDeps...)
-	}
+	flags.systemModules = deps.systemModules
 
 	// aidl flags.
 	flags.aidlFlags, flags.aidlDeps = j.aidlFlags(ctx, deps.aidlPreprocess, deps.aidlIncludeDirs)
diff --git a/java/java_test.go b/java/java_test.go
index 5942afe..f95f88b 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -192,6 +192,7 @@
 		"bar-doc/a.java":                 nil,
 		"bar-doc/b.java":                 nil,
 		"bar-doc/IFoo.aidl":              nil,
+		"bar-doc/IBar.aidl":              nil,
 		"bar-doc/known_oj_tags.txt":      nil,
 		"external/doclava/templates-sdk": nil,
 
@@ -754,11 +755,17 @@
 		    name: "droiddoc-templates-sdk",
 		    path: ".",
 		}
+		filegroup {
+		    name: "bar-doc-aidl-srcs",
+		    srcs: ["bar-doc/IBar.aidl"],
+		    path: "bar-doc",
+		}
 		droiddoc {
 		    name: "bar-doc",
 		    srcs: [
 		        "bar-doc/*.java",
 		        "bar-doc/IFoo.aidl",
+		        ":bar-doc-aidl-srcs",
 		    ],
 		    exclude_srcs: [
 		        "bar-doc/b.java"
@@ -776,18 +783,19 @@
 		}
 		`)
 
-	stubsJar := filepath.Join(buildDir, ".intermediates", "bar-doc", "android_common", "bar-doc-stubs.srcjar")
-	barDoc := ctx.ModuleForTests("bar-doc", "android_common").Output("bar-doc-stubs.srcjar")
-	if stubsJar != barDoc.Output.String() {
-		t.Errorf("expected stubs Jar [%q], got %q", stubsJar, barDoc.Output.String())
-	}
 	inputs := ctx.ModuleForTests("bar-doc", "android_common").Rule("javadoc").Inputs
 	var javaSrcs []string
 	for _, i := range inputs {
 		javaSrcs = append(javaSrcs, i.Base())
 	}
-	if len(javaSrcs) != 2 || javaSrcs[0] != "a.java" || javaSrcs[1] != "IFoo.java" {
-		t.Errorf("inputs of bar-doc must be []string{\"a.java\", \"IFoo.java\", but was %#v.", javaSrcs)
+	if len(javaSrcs) != 3 || javaSrcs[0] != "a.java" || javaSrcs[1] != "IFoo.java" || javaSrcs[2] != "IBar.java" {
+		t.Errorf("inputs of bar-doc must be []string{\"a.java\", \"IFoo.java\", \"IBar.java\", but was %#v.", javaSrcs)
+	}
+
+	aidlRule := ctx.ModuleForTests("bar-doc", "android_common").Output(inputs[2].String())
+	aidlFlags := aidlRule.Args["aidlFlags"]
+	if !strings.Contains(aidlFlags, "-Ibar-doc") {
+		t.Errorf("aidl flags for IBar.aidl should contain \"-Ibar-doc\", but was %q", aidlFlags)
 	}
 }
 
diff --git a/scripts/strip.sh b/scripts/strip.sh
index bd62619..4a1ed3f 100755
--- a/scripts/strip.sh
+++ b/scripts/strip.sh
@@ -59,7 +59,7 @@
     # ${CROSS_COMPILE}strip --strip-all does not strip .ARM.attributes,
     # so we tell llvm-strip to keep it too.
     if [ -z "${use_gnu_strip}" ]; then
-        "${CLANG_BIN}/llvm-strip" --strip-all -keep-section=.ARM.attributes "${infile}" -o "${outfile}.tmp"
+        "${CLANG_BIN}/llvm-strip" --strip-all --keep-section=.ARM.attributes "${infile}" -o "${outfile}.tmp"
     else
         "${CROSS_COMPILE}strip" --strip-all "${infile}" -o "${outfile}.tmp"
     fi
@@ -101,7 +101,7 @@
     rm -f "${outfile}.dynsyms" "${outfile}.funcsyms" "${outfile}.keep_symbols" "${outfile}.debug" "${outfile}.mini_debuginfo" "${outfile}.mini_debuginfo.xz"
     local fail=
     if [ -z "${use_gnu_strip}" ]; then
-        "${CLANG_BIN}/llvm-strip" --strip-all -keep-section=.ARM.attributes -remove-section=.comment "${infile}" -o "${outfile}.tmp" || fail=true
+        "${CLANG_BIN}/llvm-strip" --strip-all --keep-section=.ARM.attributes --remove-section=.comment "${infile}" -o "${outfile}.tmp" || fail=true
     else
         "${CROSS_COMPILE}strip" --strip-all -R .comment "${infile}" -o "${outfile}.tmp" || fail=true
     fi