Merge "Track the current java_sdk_library(_import) deps"
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 726746b..2448acc 100644
--- a/androidmk/androidmk/androidmk_test.go
+++ b/androidmk/androidmk/androidmk_test.go
@@ -876,7 +876,7 @@
 prebuilt_etc {
 	name: "etc.test1",
 	src: "mymod",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 
 }
 `,
@@ -896,7 +896,7 @@
 	name: "etc.test1",
 
 	src: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 
 }
 `,
@@ -913,7 +913,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
     device_specific: true,
 
 }
@@ -931,7 +931,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	product_specific: true,
 
 
@@ -950,7 +950,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	product_specific: true,
 
 }
@@ -968,7 +968,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	system_ext_specific: true,
 
 }
@@ -986,7 +986,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	system_ext_specific: true,
 
 
@@ -1005,7 +1005,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	proprietary: true,
 
 }
@@ -1023,7 +1023,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	proprietary: true,
 
 }
@@ -1041,7 +1041,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	proprietary: true,
 
 }
@@ -1059,7 +1059,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	recovery: true,
 
 }
@@ -1098,7 +1098,7 @@
 	name: "foo",
 
 	src: "foo.txt",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1174,7 +1174,7 @@
 	name: "foo",
 
 	src: "foo.txt",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1193,7 +1193,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1212,7 +1212,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1231,7 +1231,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 	proprietary: true,
 }
 `,
@@ -1251,7 +1251,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 	proprietary: true,
 }
 `,
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index e731750..689cbd1 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -533,7 +533,7 @@
 		updated = true
 	} else if trimmedPath := strings.TrimPrefix(path, f.prefix+"/"); trimmedPath != path {
 		m.Properties = append(m.Properties, &parser.Property{
-			Name:  "sub_dir",
+			Name:  "relative_install_path",
 			Value: &parser.String{Value: trimmedPath},
 		})
 		updated = true
@@ -581,6 +581,8 @@
 		// 'srcs' --> 'src' conversion
 		convertToSingleSource(mod, "src")
 
+		renameProperty(mod, "sub_dir", "relative_install_dir")
+
 		// The rewriter converts LOCAL_MODULE_PATH attribute into a struct attribute
 		// 'local_module_path'. Analyze its contents and create the correct sub_dir:,
 		// filename: and boolean attributes combination
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index 64a7b93..8988177 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -742,6 +742,22 @@
 		}
 		`,
 		},
+		{
+			name: "prebuilt_etc sub_dir",
+			in: `
+			prebuilt_etc {
+			name: "foo",
+			src: "bar",
+			sub_dir: "baz",
+		}
+		`,
+			out: `prebuilt_etc {
+			name: "foo",
+			src: "bar",
+			relative_install_dir: "baz",
+		}
+		`,
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
diff --git a/cc/pgo.go b/cc/pgo.go
index 88903bb..9298e7a 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -34,7 +34,7 @@
 
 	globalPgoProfileProjects = []string{
 		"toolchain/pgo-profiles",
-		"vendor/google_data/pgo-profiles",
+		"vendor/google_data/pgo_profile",
 	}
 )
 
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index df6d79d..0f7b8df 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -42,9 +42,12 @@
 	// Source file of this prebuilt.
 	Src *string `android:"path,arch_variant"`
 
-	// optional subdirectory under which this file is installed into
+	// optional subdirectory under which this file is installed into, cannot be specified with relative_install_path, prefer relative_install_path
 	Sub_dir *string `android:"arch_variant"`
 
+	// optional subdirectory under which this file is installed into, cannot be specified with sub_dir
+	Relative_install_path *string `android:"arch_variant"`
+
 	// optional name for the installed file. If unspecified, name of the module is used as the file name
 	Filename *string `android:"arch_variant"`
 
@@ -158,7 +161,10 @@
 }
 
 func (p *PrebuiltEtc) SubDir() string {
-	return android.String(p.properties.Sub_dir)
+	if subDir := proptools.String(p.properties.Sub_dir); subDir != "" {
+		return subDir
+	}
+	return proptools.String(p.properties.Relative_install_path)
 }
 
 func (p *PrebuiltEtc) Installable() bool {
@@ -181,13 +187,17 @@
 	}
 	p.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
 
+	if p.properties.Sub_dir != nil && p.properties.Relative_install_path != nil {
+		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
+	}
+
 	// If soc install dir was specified and SOC specific is set, set the installDirPath to the specified
 	// socInstallDirBase.
 	installBaseDir := p.installDirBase
 	if ctx.SocSpecific() && p.socInstallDirBase != "" {
 		installBaseDir = p.socInstallDirBase
 	}
-	p.installDirPath = android.PathForModuleInstall(ctx, installBaseDir, proptools.String(p.properties.Sub_dir))
+	p.installDirPath = android.PathForModuleInstall(ctx, installBaseDir, p.SubDir())
 
 	// This ensures that outputFilePath has the correct name for others to
 	// use, as the source file may have a different name.
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index 4ce1984..8fc36c2 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -49,7 +49,7 @@
 	os.Exit(run())
 }
 
-func testPrebuiltEtc(t *testing.T, bp string) (*android.TestContext, android.Config) {
+func testPrebuiltEtcContext(t *testing.T, bp string) (*android.TestContext, android.Config) {
 	fs := map[string][]byte{
 		"foo.conf": nil,
 		"bar.conf": nil,
@@ -67,6 +67,14 @@
 	ctx.RegisterModuleType("prebuilt_firmware", PrebuiltFirmwareFactory)
 	ctx.RegisterModuleType("prebuilt_dsp", PrebuiltDSPFactory)
 	ctx.Register(config)
+
+	return ctx, config
+}
+
+func testPrebuiltEtc(t *testing.T, bp string) (*android.TestContext, android.Config) {
+	t.Helper()
+
+	ctx, config := testPrebuiltEtcContext(t, bp)
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
@@ -75,6 +83,24 @@
 	return ctx, config
 }
 
+func testPrebuiltEtcError(t *testing.T, pattern, bp string) {
+	t.Helper()
+
+	ctx, config := testPrebuiltEtcContext(t, bp)
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	if len(errs) > 0 {
+		android.FailIfNoMatchingErrors(t, pattern, errs)
+		return
+	}
+
+	_, errs = ctx.PrepareBuildActions(config)
+	if len(errs) > 0 {
+		android.FailIfNoMatchingErrors(t, pattern, errs)
+		return
+	}
+
+	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
+}
 func TestPrebuiltEtcVariants(t *testing.T) {
 	ctx, _ := testPrebuiltEtc(t, `
 		prebuilt_etc {
@@ -184,6 +210,33 @@
 	}
 }
 
+func TestPrebuiltEtcRelativeInstallPathInstallDirPath(t *testing.T) {
+	ctx, _ := testPrebuiltEtc(t, `
+		prebuilt_etc {
+			name: "foo.conf",
+			src: "foo.conf",
+			relative_install_path: "bar",
+		}
+	`)
+
+	p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
+	expected := buildDir + "/target/product/test_device/system/etc/bar"
+	if p.installDirPath.String() != expected {
+		t.Errorf("expected %q, got %q", expected, p.installDirPath.String())
+	}
+}
+
+func TestPrebuiltEtcCannotSetRelativeInstallPathAndSubDir(t *testing.T) {
+	testPrebuiltEtcError(t, "relative_install_path is set. Cannot set sub_dir", `
+		prebuilt_etc {
+			name: "foo.conf",
+			src: "foo.conf",
+			sub_dir: "bar",
+			relative_install_path: "bar",
+		}
+	`)
+}
+
 func TestPrebuiltEtcHost(t *testing.T) {
 	ctx, _ := testPrebuiltEtc(t, `
 		prebuilt_etc_host {
diff --git a/java/config/config.go b/java/config/config.go
index 23b593a..ffe219a 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -111,7 +111,7 @@
 	pctx.SourcePathVariable("JavaKytheExtractorJar", "prebuilts/build-tools/common/framework/javac_extractor.jar")
 	pctx.SourcePathVariable("Ziptime", "prebuilts/build-tools/${hostPrebuiltTag}/bin/ziptime")
 
-	pctx.SourcePathVariable("GenKotlinBuildFileCmd", "build/soong/scripts/gen-kotlin-build-file.sh")
+	pctx.HostBinToolVariable("GenKotlinBuildFileCmd", "gen-kotlin-build-file.py")
 
 	pctx.SourcePathVariable("JarArgsCmd", "build/soong/scripts/jar-args.sh")
 	pctx.SourcePathVariable("PackageCheckCmd", "build/soong/scripts/package-check.sh")
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index ce624bf..130b634 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -59,10 +59,9 @@
 
 var _ hiddenAPIIntf = (*hiddenAPI)(nil)
 
-func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, dexJar android.ModuleOutPath,
+func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, name string, primary bool, dexJar android.ModuleOutPath,
 	implementationJar android.Path, uncompressDex bool) android.ModuleOutPath {
 	if !ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {
-		name := ctx.ModuleName()
 
 		// Modules whose names are of the format <x>-hiddenapi provide hiddenapi information
 		// for the boot jar module <x>. Otherwise, the module provides information for itself.
@@ -90,7 +89,14 @@
 			// the gathered information in the generated dex file.
 			if name == bootJarName {
 				hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", name+".jar")
-				h.bootDexJarPath = dexJar
+
+				// More than one library with the same classes can be encoded but only one can
+				// be added to the global set of flags, otherwise it will result in duplicate
+				// classes which is an error. Therefore, only add the dex jar of one of them
+				// to the global set of flags.
+				if primary {
+					h.bootDexJarPath = dexJar
+				}
 				hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexJar, uncompressDex)
 				dexJar = hiddenAPIJar
 			}
diff --git a/java/java.go b/java/java.go
index 2829be7..aa843ee 100644
--- a/java/java.go
+++ b/java/java.go
@@ -342,6 +342,12 @@
 	// otherwise provides defaults libraries to add to the bootclasspath.
 	System_modules *string
 
+	// The name of the module as used in build configuration.
+	//
+	// Allows a library to separate its actual name from the name used in
+	// build configuration, e.g.ctx.Config().BootJars().
+	ConfigurationName *string `blueprint:"mutated"`
+
 	// set the name of the output
 	Stem *string
 
@@ -1631,8 +1637,11 @@
 			return
 		}
 
+		configurationName := j.ConfigurationName()
+		primary := configurationName == ctx.ModuleName()
+
 		// Hidden API CSV generation and dex encoding
-		dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, dexOutputFile, j.implementationJarFile,
+		dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, configurationName, primary, dexOutputFile, j.implementationJarFile,
 			proptools.Bool(j.deviceProperties.Uncompress_dex))
 
 		// merge dex jar with resources if necessary
@@ -1909,6 +1918,10 @@
 	return proptools.StringDefault(j.deviceProperties.Stem, j.Name())
 }
 
+func (j *Module) ConfigurationName() string {
+	return proptools.StringDefault(j.deviceProperties.ConfigurationName, j.BaseModuleName())
+}
+
 func (j *Module) JacocoReportClassesFile() android.Path {
 	return j.jacocoReportClassesFile
 }
diff --git a/java/kotlin.go b/java/kotlin.go
index 673970b..e3356be 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -31,7 +31,9 @@
 		Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" "$emptyDir" && ` +
 			`mkdir -p "$classesDir" "$srcJarDir" "$emptyDir" && ` +
 			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-			`${config.GenKotlinBuildFileCmd} $classpath "$name" $classesDir $out.rsp $srcJarDir/list > $kotlinBuildFile &&` +
+			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
+			` --out_dir "$classesDir" --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
+			` --out "$kotlinBuildFile" && ` +
 			`${config.KotlincCmd} ${config.JavacHeapFlags} $kotlincFlags ` +
 			`-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile -kotlin-home $emptyDir && ` +
 			`${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir && ` +
@@ -74,7 +76,7 @@
 		Inputs:      srcFiles,
 		Implicits:   deps,
 		Args: map[string]string{
-			"classpath":       flags.kotlincClasspath.FormJavaClassPath("-classpath"),
+			"classpath":       flags.kotlincClasspath.FormJavaClassPath(""),
 			"kotlincFlags":    flags.kotlincFlags,
 			"srcJars":         strings.Join(srcJars.Strings(), " "),
 			"classesDir":      android.PathForModuleOut(ctx, "kotlinc", "classes").String(),
@@ -93,7 +95,9 @@
 		Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && ` +
 			`mkdir -p "$srcJarDir" "$kaptDir/sources" "$kaptDir/classes" && ` +
 			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-			`${config.GenKotlinBuildFileCmd} $classpath "$name" "" $out.rsp $srcJarDir/list > $kotlinBuildFile &&` +
+			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
+			` --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
+			` --out "$kotlinBuildFile" && ` +
 			`${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} $kotlincFlags ` +
 			`-Xplugin=${config.KotlinKaptJar} ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:sources=$kaptDir/sources ` +
@@ -162,7 +166,7 @@
 		Inputs:         srcFiles,
 		Implicits:      deps,
 		Args: map[string]string{
-			"classpath":         flags.kotlincClasspath.FormJavaClassPath("-classpath"),
+			"classpath":         flags.kotlincClasspath.FormJavaClassPath(""),
 			"kotlincFlags":      flags.kotlincFlags,
 			"srcJars":           strings.Join(srcJars.Strings(), " "),
 			"srcJarDir":         android.PathForModuleOut(ctx, "kapt", "srcJars").String(),
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 91869ed..8f8f8ce 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1087,15 +1087,22 @@
 
 // Creates the implementation java library
 func (module *SdkLibrary) createImplLibrary(mctx android.DefaultableHookContext) {
+
+	moduleNamePtr := proptools.StringPtr(module.BaseModuleName())
+
 	props := struct {
-		Name       *string
-		Visibility []string
-		Instrument bool
+		Name              *string
+		Visibility        []string
+		Instrument        bool
+		ConfigurationName *string
 	}{
 		Name:       proptools.StringPtr(module.implLibraryModuleName()),
 		Visibility: module.sdkLibraryProperties.Impl_library_visibility,
 		// Set the instrument property to ensure it is instrumented when instrumentation is required.
 		Instrument: true,
+
+		// Make the created library behave as if it had the same name as this module.
+		ConfigurationName: moduleNamePtr,
 	}
 
 	properties := []interface{}{
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 7f2ddb8..d962a41 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -152,5 +152,17 @@
 python_binary_host {
     name: "lint-project-xml",
     main: "lint-project-xml.py",
-    srcs: ["lint-project-xml.py"],
+    srcs: [
+        "lint-project-xml.py",
+        "ninja_rsp.py",
+    ],
+}
+
+python_binary_host {
+    name: "gen-kotlin-build-file.py",
+    main: "gen-kotlin-build-file.py",
+    srcs: [
+        "gen-kotlin-build-file.py",
+        "ninja_rsp.py",
+    ],
 }
diff --git a/scripts/gen-kotlin-build-file.py b/scripts/gen-kotlin-build-file.py
new file mode 100644
index 0000000..83b4cd8
--- /dev/null
+++ b/scripts/gen-kotlin-build-file.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generates kotlinc module xml file to drive kotlinc
+
+import argparse
+import os
+
+from ninja_rsp import NinjaRspFileReader
+
+def parse_args():
+  """Parse commandline arguments."""
+
+  def convert_arg_line_to_args(arg_line):
+    for arg in arg_line.split():
+      if arg.startswith('#'):
+        return
+      if not arg.strip():
+        continue
+      yield arg
+
+  parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
+  parser.convert_arg_line_to_args = convert_arg_line_to_args
+  parser.add_argument('--out', dest='out',
+                      help='file to which the module.xml contents will be written.')
+  parser.add_argument('--classpath', dest='classpath', action='append', default=[],
+                      help='classpath to pass to kotlinc.')
+  parser.add_argument('--name', dest='name',
+                      help='name of the module.')
+  parser.add_argument('--out_dir', dest='out_dir',
+                      help='directory to which kotlinc will write output files.')
+  parser.add_argument('--srcs', dest='srcs', action='append', default=[],
+                      help='file containing whitespace separated list of source files.')
+  parser.add_argument('--common_srcs', dest='common_srcs', action='append', default=[],
+                      help='file containing whitespace separated list of common multiplatform source files.')
+
+  return parser.parse_args()
+
+def main():
+  """Program entry point."""
+  args = parse_args()
+
+  if not args.out:
+    raise RuntimeError('--out argument is required')
+
+  if not args.name:
+    raise RuntimeError('--name argument is required')
+
+  with open(args.out, 'w') as f:
+    # Print preamble
+    f.write('<modules>\n')
+    f.write('  <module name="%s" type="java-production" outputDir="%s">\n' % (args.name, args.out_dir or ''))
+
+    # Print classpath entries
+    for c in args.classpath:
+      for entry in c.split(':'):
+        path = os.path.abspath(entry)
+        f.write('    <classpath path="%s"/>\n' % path)
+
+    # For each rsp file, print source entries
+    for rsp_file in args.srcs:
+      for src in NinjaRspFileReader(rsp_file):
+        path = os.path.abspath(src)
+        if src.endswith('.java'):
+          f.write('    <javaSourceRoots path="%s"/>\n' % path)
+        elif src.endswith('.kt'):
+          f.write('    <sources path="%s"/>\n' % path)
+        else:
+          raise RuntimeError('unknown source file type %s' % file)
+
+    for rsp_file in args.common_srcs:
+      for src in NinjaRspFileReader(rsp_file):
+        path = os.path.abspath(src)
+        f.write('    <sources path="%s"/>\n' % path)
+        f.write('    <commonSources path="%s"/>\n' % path)
+
+    f.write('  </module>\n')
+    f.write('</modules>\n')
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/gen-kotlin-build-file.sh b/scripts/gen-kotlin-build-file.sh
deleted file mode 100755
index 177ca1b..0000000
--- a/scripts/gen-kotlin-build-file.sh
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/bash -e
-
-# Copyright 2018 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Generates kotlinc module xml file to standard output based on rsp files
-
-if [[ -z "$1" ]]; then
-  echo "usage: $0 <classpath> <name> <outDir> <rspFiles>..." >&2
-  exit 1
-fi
-
-# Classpath variable has a tendency to be prefixed by "-classpath", remove it.
-if [[ $1 == "-classpath" ]]; then
-  shift
-fi;
-
-classpath=$1
-name=$2
-out_dir=$3
-shift 3
-
-# Path in the build file may be relative to the build file, we need to make them
-# absolute
-prefix="$(pwd)"
-
-get_abs_path () {
-  local file="$1"
-  if [[ "${file:0:1}" == '/' ]] ; then
-    echo "${file}"
-  else
-    echo "${prefix}/${file}"
-  fi
-}
-
-# Print preamble
-echo "<modules><module name=\"${name}\" type=\"java-production\" outputDir=\"${out_dir}\">"
-
-# Print classpath entries
-for file in $(echo "$classpath" | tr ":" "\n"); do
-  path="$(get_abs_path "$file")"
-  echo "  <classpath path=\"${path}\"/>"
-done
-
-# For each rsp file, print source entries
-while (( "$#" )); do
-  for file in $(cat "$1"); do
-    path="$(get_abs_path "$file")"
-    if [[ $file == *.java ]]; then
-      echo "  <javaSourceRoots path=\"${path}\"/>"
-    elif [[ $file == *.kt ]]; then
-      echo "  <sources path=\"${path}\"/>"
-    else
-      echo "Unknown source file type ${file}"
-      exit 1
-    fi
-  done
-
-  shift
-done
-
-echo "</module></modules>"
diff --git a/scripts/lint-project-xml.py b/scripts/lint-project-xml.py
index 38c57ca..f1ef85d 100755
--- a/scripts/lint-project-xml.py
+++ b/scripts/lint-project-xml.py
@@ -19,6 +19,8 @@
 
 import argparse
 
+from ninja_rsp import NinjaRspFileReader
+
 
 def check_action(check_type):
   """
@@ -91,74 +93,6 @@
   return parser.parse_args()
 
 
-class NinjaRspFileReader:
-  """
-  Reads entries from a Ninja rsp file.  Ninja escapes any entries in the file that contain a
-  non-standard character by surrounding the whole entry with single quotes, and then replacing
-  any single quotes in the entry with the escape sequence '\''.
-  """
-
-  def __init__(self, filename):
-    self.f = open(filename, 'r')
-    self.r = self.character_reader(self.f)
-
-  def __iter__(self):
-    return self
-
-  def character_reader(self, f):
-    """Turns a file into a generator that returns one character at a time."""
-    while True:
-      c = f.read(1)
-      if c:
-        yield c
-      else:
-        return
-
-  def __next__(self):
-    entry = self.read_entry()
-    if entry:
-      return entry
-    else:
-      raise StopIteration
-
-  def read_entry(self):
-    c = next(self.r, "")
-    if not c:
-      return ""
-    elif c == "'":
-      return self.read_quoted_entry()
-    else:
-      entry = c
-      for c in self.r:
-        if c == " " or c == "\n":
-          break
-        entry += c
-      return entry
-
-  def read_quoted_entry(self):
-    entry = ""
-    for c in self.r:
-      if c == "'":
-        # Either the end of the quoted entry, or the beginning of an escape sequence, read the next
-        # character to find out.
-        c = next(self.r)
-        if not c or c == " " or c == "\n":
-          # End of the item
-          return entry
-        elif c == "\\":
-          # Escape sequence, expect a '
-          c = next(self.r)
-          if c != "'":
-            # Malformed escape sequence
-            raise "malformed escape sequence %s'\\%s" % (entry, c)
-          entry += "'"
-        else:
-          raise "malformed escape sequence %s'%s" % (entry, c)
-      else:
-        entry += c
-    raise "unterminated quoted entry %s" % entry
-
-
 def write_project_xml(f, args):
   test_attr = "test='true' " if args.test else ""
 
diff --git a/scripts/ninja_rsp.py b/scripts/ninja_rsp.py
new file mode 100644
index 0000000..004ce47
--- /dev/null
+++ b/scripts/ninja_rsp.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""This file reads entries from a Ninja rsp file."""
+
+class NinjaRspFileReader:
+  """
+  Reads entries from a Ninja rsp file.  Ninja escapes any entries in the file that contain a
+  non-standard character by surrounding the whole entry with single quotes, and then replacing
+  any single quotes in the entry with the escape sequence '\''.
+  """
+
+  def __init__(self, filename):
+    self.f = open(filename, 'r')
+    self.r = self.character_reader(self.f)
+
+  def __iter__(self):
+    return self
+
+  def character_reader(self, f):
+    """Turns a file into a generator that returns one character at a time."""
+    while True:
+      c = f.read(1)
+      if c:
+        yield c
+      else:
+        return
+
+  def __next__(self):
+    entry = self.read_entry()
+    if entry:
+      return entry
+    else:
+      raise StopIteration
+
+  def read_entry(self):
+    c = next(self.r, "")
+    if not c:
+      return ""
+    elif c == "'":
+      return self.read_quoted_entry()
+    else:
+      entry = c
+      for c in self.r:
+        if c == " " or c == "\n":
+          break
+        entry += c
+      return entry
+
+  def read_quoted_entry(self):
+    entry = ""
+    for c in self.r:
+      if c == "'":
+        # Either the end of the quoted entry, or the beginning of an escape sequence, read the next
+        # character to find out.
+        c = next(self.r)
+        if not c or c == " " or c == "\n":
+          # End of the item
+          return entry
+        elif c == "\\":
+          # Escape sequence, expect a '
+          c = next(self.r)
+          if c != "'":
+            # Malformed escape sequence
+            raise "malformed escape sequence %s'\\%s" % (entry, c)
+          entry += "'"
+        else:
+          raise "malformed escape sequence %s'%s" % (entry, c)
+      else:
+        entry += c
+    raise "unterminated quoted entry %s" % entry