Merge changes from topic "refactor-packagingbase"

* changes:
  Move GatherPackagingSpecs out of CopyDepsToZip
  Keep 'partition' in PackagingSpec
diff --git a/android/module.go b/android/module.go
index d0e2a76..66a5f60 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1470,8 +1470,10 @@
 }
 
 type propInfo struct {
-	Name string
-	Type string
+	Name   string
+	Type   string
+	Value  string
+	Values []string
 }
 
 func (m *ModuleBase) propertiesWithValues() []propInfo {
@@ -1511,18 +1513,60 @@
 				return
 			}
 			elKind := v.Type().Elem().Kind()
-			info = append(info, propInfo{name, elKind.String() + " " + kind.String()})
+			info = append(info, propInfo{Name: name, Type: elKind.String() + " " + kind.String(), Values: sliceReflectionValue(v)})
 		default:
-			info = append(info, propInfo{name, kind.String()})
+			info = append(info, propInfo{Name: name, Type: kind.String(), Value: reflectionValue(v)})
 		}
 	}
 
 	for _, p := range props {
 		propsWithValues("", reflect.ValueOf(p).Elem())
 	}
+	sort.Slice(info, func(i, j int) bool {
+		return info[i].Name < info[j].Name
+	})
 	return info
 }
 
+func reflectionValue(value reflect.Value) string {
+	switch value.Kind() {
+	case reflect.Bool:
+		return fmt.Sprintf("%t", value.Bool())
+	case reflect.Int64:
+		return fmt.Sprintf("%d", value.Int())
+	case reflect.String:
+		return fmt.Sprintf("%s", value.String())
+	case reflect.Struct:
+		if value.IsZero() {
+			return "{}"
+		}
+		length := value.NumField()
+		vals := make([]string, length, length)
+		for i := 0; i < length; i++ {
+			sTyp := value.Type().Field(i)
+			if proptools.ShouldSkipProperty(sTyp) {
+				continue
+			}
+			name := sTyp.Name
+			vals[i] = fmt.Sprintf("%s: %s", name, reflectionValue(value.Field(i)))
+		}
+		return fmt.Sprintf("%s{%s}", value.Type(), strings.Join(vals, ", "))
+	case reflect.Array, reflect.Slice:
+		vals := sliceReflectionValue(value)
+		return fmt.Sprintf("[%s]", strings.Join(vals, ", "))
+	}
+	return ""
+}
+
+func sliceReflectionValue(value reflect.Value) []string {
+	length := value.Len()
+	vals := make([]string, length, length)
+	for i := 0; i < length; i++ {
+		vals[i] = reflectionValue(value.Index(i))
+	}
+	return vals
+}
+
 func (m *ModuleBase) ComponentDepsMutator(BottomUpMutatorContext) {}
 
 func (m *ModuleBase) DepsMutator(BottomUpMutatorContext) {}
diff --git a/android/module_test.go b/android/module_test.go
index 1dcddf7..77ef146 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -563,6 +563,12 @@
 	Embedded_prop *string
 }
 
+type StructInSlice struct {
+	G string
+	H bool
+	I []string
+}
+
 type propsTestModule struct {
 	ModuleBase
 	DefaultableModuleBase
@@ -579,6 +585,8 @@
 			E *string
 		}
 		F *string `blueprint:"mutated"`
+
+		Slice_of_struct []StructInSlice
 	}
 }
 
@@ -621,7 +629,7 @@
 		}
 	`,
 			expectedProps: []propInfo{
-				propInfo{"Name", "string"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
 			},
 		},
 		{
@@ -634,10 +642,10 @@
 		}
 	`,
 			expectedProps: []propInfo{
-				propInfo{"A", "string"},
-				propInfo{"B", "bool"},
-				propInfo{"D", "int64"},
-				propInfo{"Name", "string"},
+				propInfo{Name: "A", Type: "string", Value: "abc"},
+				propInfo{Name: "B", Type: "bool", Value: "true"},
+				propInfo{Name: "D", Type: "int64", Value: "123"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
 			},
 		},
 		{
@@ -650,10 +658,10 @@
 	`,
 			expectedProps: []propInfo{
 				// for non-pointer cannot distinguish between unused and intentionally set to empty
-				propInfo{"A", "string"},
-				propInfo{"B", "bool"},
-				propInfo{"D", "int64"},
-				propInfo{"Name", "string"},
+				propInfo{Name: "A", Type: "string", Value: ""},
+				propInfo{Name: "B", Type: "bool", Value: "true"},
+				propInfo{Name: "D", Type: "int64", Value: "123"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
 			},
 		},
 		{
@@ -666,8 +674,8 @@
 		}
 	`,
 			expectedProps: []propInfo{
-				propInfo{"Nested.E", "string"},
-				propInfo{"Name", "string"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+				propInfo{Name: "Nested.E", Type: "string", Value: "abc"},
 			},
 		},
 		{
@@ -682,8 +690,8 @@
 		}
 	`,
 			expectedProps: []propInfo{
-				propInfo{"Name", "string"},
-				propInfo{"Arch.X86_64.A", "string"},
+				propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "abc"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
 			},
 		},
 		{
@@ -694,8 +702,34 @@
 		}
 	`,
 			expectedProps: []propInfo{
-				propInfo{"Embedded_prop", "string"},
-				propInfo{"Name", "string"},
+				propInfo{Name: "Embedded_prop", Type: "string", Value: "a"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+			},
+		},
+		{
+			desc: "struct slice",
+			bp: `test {
+			name: "foo",
+			slice_of_struct: [
+				{
+					g: "abc",
+					h: false,
+					i: ["baz"],
+				},
+				{
+					g: "def",
+					h: true,
+					i: [],
+				},
+			]
+		}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+				propInfo{Name: "Slice_of_struct", Type: "struct slice", Values: []string{
+					`android.StructInSlice{G: abc, H: false, I: [baz]}`,
+					`android.StructInSlice{G: def, H: true, I: []}`,
+				}},
 			},
 		},
 		{
@@ -705,19 +739,20 @@
 	name: "foo_defaults",
 	a: "a",
 	b: true,
+	c: ["default_c"],
 	embedded_prop:"a",
 	arch: {
 		x86_64: {
-			a: "a",
+			a: "x86_64 a",
 		},
 	},
 }
 test {
 	name: "foo",
 	defaults: ["foo_defaults"],
-	c: ["a"],
+	c: ["c"],
 	nested: {
-		e: "d",
+		e: "nested e",
 	},
 	target: {
 		linux: {
@@ -727,15 +762,15 @@
 }
 	`,
 			expectedProps: []propInfo{
-				propInfo{"A", "string"},
-				propInfo{"B", "bool"},
-				propInfo{"C", "string slice"},
-				propInfo{"Embedded_prop", "string"},
-				propInfo{"Nested.E", "string"},
-				propInfo{"Name", "string"},
-				propInfo{"Arch.X86_64.A", "string"},
-				propInfo{"Target.Linux.A", "string"},
-				propInfo{"Defaults", "string slice"},
+				propInfo{Name: "A", Type: "string", Value: "a"},
+				propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "x86_64 a"},
+				propInfo{Name: "B", Type: "bool", Value: "true"},
+				propInfo{Name: "C", Type: "string slice", Values: []string{"default_c", "c"}},
+				propInfo{Name: "Defaults", Type: "string slice", Values: []string{"foo_defaults"}},
+				propInfo{Name: "Embedded_prop", Type: "string", Value: "a"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+				propInfo{Name: "Nested.E", Type: "string", Value: "nested e"},
+				propInfo{Name: "Target.Linux.A", Type: "string", Value: "a"},
 			},
 		},
 	}
diff --git a/java/builder.go b/java/builder.go
index c48e3fa..c0fadd4 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -131,31 +131,28 @@
 
 	turbine, turbineRE = pctx.RemoteStaticRules("turbine",
 		blueprint.RuleParams{
-			Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
-				`$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.TurbineJar} --output $out.tmp ` +
-				`--temp_dir "$outDir" --sources @$out.rsp  --source_jars $srcJars ` +
+			Command: `$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.TurbineJar} $outputFlags ` +
+				`--sources @$out.rsp  --source_jars $srcJars ` +
 				`--javacopts ${config.CommonJdkFlags} ` +
-				`$javacFlags -source $javaVersion -target $javaVersion -- $bootClasspath $classpath && ` +
-				`${config.Ziptime} $out.tmp && ` +
-				`(if cmp -s $out.tmp $out ; then rm $out.tmp ; else mv $out.tmp $out ; fi )`,
+				`$javacFlags -source $javaVersion -target $javaVersion -- $turbineFlags && ` +
+				`(for o in $outputs; do if cmp -s $${o}.tmp $${o} ; then rm $${o}.tmp ; else mv $${o}.tmp $${o} ; fi; done )`,
 			CommandDeps: []string{
 				"${config.TurbineJar}",
 				"${config.JavaCmd}",
-				"${config.Ziptime}",
 			},
 			Rspfile:        "$out.rsp",
 			RspfileContent: "$in",
 			Restat:         true,
 		},
 		&remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "turbine"},
-			ExecStrategy:      "${config.RETurbineExecStrategy}",
-			Inputs:            []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"},
-			RSPFiles:          []string{"${out}.rsp"},
-			OutputFiles:       []string{"$out.tmp"},
-			OutputDirectories: []string{"$outDir"},
-			ToolchainInputs:   []string{"${config.JavaCmd}"},
-			Platform:          map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
-		}, []string{"javacFlags", "bootClasspath", "classpath", "srcJars", "outDir", "javaVersion"}, []string{"implicits"})
+			ExecStrategy:    "${config.RETurbineExecStrategy}",
+			Inputs:          []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"},
+			RSPFiles:        []string{"${out}.rsp"},
+			OutputFiles:     []string{"$rbeOutputs"},
+			ToolchainInputs: []string{"${config.JavaCmd}"},
+			Platform:        map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
+		},
+		[]string{"javacFlags", "turbineFlags", "outputFlags", "javaVersion", "outputs", "rbeOutputs", "srcJars"}, []string{"implicits"})
 
 	jar, jarRE = pctx.RemoteStaticRules("jar",
 		blueprint.RuleParams{
@@ -358,11 +355,8 @@
 		})
 }
 
-func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath,
-	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
-
+func turbineFlags(ctx android.ModuleContext, flags javaBuilderFlags) (string, android.Paths) {
 	var deps android.Paths
-	deps = append(deps, srcJars...)
 
 	classpath := flags.classpath
 
@@ -384,20 +378,31 @@
 	}
 
 	deps = append(deps, classpath...)
-	deps = append(deps, flags.processorPath...)
+	turbineFlags := bootClasspath + " " + classpath.FormTurbineClassPath("--classpath ")
+
+	return turbineFlags, deps
+}
+
+func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
+
+	turbineFlags, deps := turbineFlags(ctx, flags)
+
+	deps = append(deps, srcJars...)
 
 	rule := turbine
 	args := map[string]string{
-		"javacFlags":    flags.javacFlags,
-		"bootClasspath": bootClasspath,
-		"srcJars":       strings.Join(srcJars.Strings(), " "),
-		"classpath":     classpath.FormTurbineClassPath("--classpath "),
-		"outDir":        android.PathForModuleOut(ctx, "turbine", "classes").String(),
-		"javaVersion":   flags.javaVersion.String(),
+		"javacFlags":   flags.javacFlags,
+		"srcJars":      strings.Join(srcJars.Strings(), " "),
+		"javaVersion":  flags.javaVersion.String(),
+		"turbineFlags": turbineFlags,
+		"outputFlags":  "--output " + outputFile.String() + ".tmp",
+		"outputs":      outputFile.String(),
 	}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_TURBINE") {
 		rule = turbineRE
 		args["implicits"] = strings.Join(deps.Strings(), ",")
+		args["rbeOutputs"] = outputFile.String() + ".tmp"
 	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
@@ -409,6 +414,47 @@
 	})
 }
 
+// TurbineApt produces a rule to run annotation processors using turbine.
+func TurbineApt(ctx android.ModuleContext, outputSrcJar, outputResJar android.WritablePath,
+	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
+
+	turbineFlags, deps := turbineFlags(ctx, flags)
+
+	deps = append(deps, srcJars...)
+
+	deps = append(deps, flags.processorPath...)
+	turbineFlags += " " + flags.processorPath.FormTurbineClassPath("--processorpath ")
+	turbineFlags += " --processors " + strings.Join(flags.processors, " ")
+
+	outputs := android.WritablePaths{outputSrcJar, outputResJar}
+	outputFlags := "--gensrc_output " + outputSrcJar.String() + ".tmp " +
+		"--resource_output " + outputResJar.String() + ".tmp"
+
+	rule := turbine
+	args := map[string]string{
+		"javacFlags":   flags.javacFlags,
+		"srcJars":      strings.Join(srcJars.Strings(), " "),
+		"javaVersion":  flags.javaVersion.String(),
+		"turbineFlags": turbineFlags,
+		"outputFlags":  outputFlags,
+		"outputs":      strings.Join(outputs.Strings(), " "),
+	}
+	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_TURBINE") {
+		rule = turbineRE
+		args["implicits"] = strings.Join(deps.Strings(), ",")
+		args["rbeOutputs"] = outputSrcJar.String() + ".tmp," + outputResJar.String() + ".tmp"
+	}
+	ctx.Build(pctx, android.BuildParams{
+		Rule:            rule,
+		Description:     "turbine apt",
+		Output:          outputs[0],
+		ImplicitOutputs: outputs[1:],
+		Inputs:          srcFiles,
+		Implicits:       deps,
+		Args:            args,
+	})
+}
+
 // transformJavaToClasses takes source files and converts them to a jar containing .class files.
 // srcFiles is a list of paths to sources, srcJars is a list of paths to jar files that contain
 // sources.  flags contains various command line flags to be passed to the compiler.
@@ -670,6 +716,6 @@
 	} else if forceEmpty {
 		return `--bootclasspath ""`, nil
 	} else {
-		return "", nil
+		return "--system ${config.JavaHome}", nil
 	}
 }
diff --git a/java/java_test.go b/java/java_test.go
index f095c5e..4c93824 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -973,7 +973,7 @@
 
 	fooHeaderJar := filepath.Join("out", "soong", ".intermediates", "foo", "android_common", "turbine-combined", "foo.jar")
 	barTurbineJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine", "bar.jar")
-	android.AssertStringDoesContain(t, "bar turbine classpath", barTurbine.Args["classpath"], fooHeaderJar)
+	android.AssertStringDoesContain(t, "bar turbine classpath", barTurbine.Args["turbineFlags"], fooHeaderJar)
 	android.AssertStringDoesContain(t, "bar javac classpath", barJavac.Args["classpath"], fooHeaderJar)
 	android.AssertPathsRelativeToTopEquals(t, "bar turbine combineJar", []string{barTurbineJar, fooHeaderJar}, barTurbineCombined.Inputs)
 	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "prebuilts/sdk/14/public/android.jar")
diff --git a/java/kotlin.go b/java/kotlin.go
index 3e5cec0..ce79bae 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -118,7 +118,7 @@
 	})
 }
 
-var kapt = pctx.AndroidRemoteStaticRule("kapt", android.RemoteRuleSupports{Goma: true},
+var kaptStubs = pctx.AndroidRemoteStaticRule("kaptStubs", android.RemoteRuleSupports{Goma: true},
 	blueprint.RuleParams{
 		Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && ` +
 			`mkdir -p "$srcJarDir" "$kaptDir/sources" "$kaptDir/classes" && ` +
@@ -133,13 +133,12 @@
 			`-P plugin:org.jetbrains.kotlin.kapt3:classes=$kaptDir/classes ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptDir/stubs ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true ` +
-			`-P plugin:org.jetbrains.kotlin.kapt3:aptMode=stubsAndApt ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:aptMode=stubs ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:javacArguments=$encodedJavacFlags ` +
 			`$kaptProcessorPath ` +
 			`$kaptProcessor ` +
 			`-Xbuild-file=$kotlinBuildFile && ` +
-			`${config.SoongZipCmd} -jar -o $out -C $kaptDir/sources -D $kaptDir/sources && ` +
-			`${config.SoongZipCmd} -jar -o $classesJarOut -C $kaptDir/classes -D $kaptDir/classes && ` +
+			`${config.SoongZipCmd} -jar -o $out -C $kaptDir/stubs -D $kaptDir/stubs && ` +
 			`rm -rf "$srcJarDir"`,
 		CommandDeps: []string{
 			"${config.KotlincCmd}",
@@ -197,13 +196,14 @@
 	kotlinName := filepath.Join(ctx.ModuleDir(), ctx.ModuleSubDir(), ctx.ModuleName())
 	kotlinName = strings.ReplaceAll(kotlinName, "/", "__")
 
+	// First run kapt to generate .java stubs from .kt files
+	kaptStubsJar := android.PathForModuleOut(ctx, "kapt", "stubs.jar")
 	ctx.Build(pctx, android.BuildParams{
-		Rule:           kapt,
-		Description:    "kapt",
-		Output:         srcJarOutputFile,
-		ImplicitOutput: resJarOutputFile,
-		Inputs:         srcFiles,
-		Implicits:      deps,
+		Rule:        kaptStubs,
+		Description: "kapt stubs",
+		Output:      kaptStubsJar,
+		Inputs:      srcFiles,
+		Implicits:   deps,
 		Args: map[string]string{
 			"classpath":         flags.kotlincClasspath.FormJavaClassPath(""),
 			"kotlincFlags":      flags.kotlincFlags,
@@ -219,6 +219,11 @@
 			"classesJarOut":     resJarOutputFile.String(),
 		},
 	})
+
+	// Then run turbine to perform annotation processing on the stubs and any .java srcFiles.
+	javaSrcFiles := srcFiles.FilterByExt(".java")
+	turbineSrcJars := append(android.Paths{kaptStubsJar}, srcJars...)
+	TurbineApt(ctx, srcJarOutputFile, resJarOutputFile, javaSrcFiles, turbineSrcJars, flags)
 }
 
 // kapt converts a list of key, value pairs into a base64 encoded Java serialization, which is what kapt expects.
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index cac0af3..d51bc04 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -117,51 +117,71 @@
 
 		buildOS := ctx.Config().BuildOS.String()
 
-		kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
-		kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
-		javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+		foo := ctx.ModuleForTests("foo", "android_common")
+		kaptStubs := foo.Rule("kapt")
+		turbineApt := foo.Description("turbine apt")
+		kotlinc := foo.Rule("kotlinc")
+		javac := foo.Rule("javac")
 
 		bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
 		baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String()
 
 		// Test that the kotlin and java sources are passed to kapt and kotlinc
-		if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" {
-			t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs)
+		if len(kaptStubs.Inputs) != 2 || kaptStubs.Inputs[0].String() != "a.java" || kaptStubs.Inputs[1].String() != "b.kt" {
+			t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kaptStubs.Inputs)
 		}
 		if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" || kotlinc.Inputs[1].String() != "b.kt" {
 			t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs)
 		}
 
-		// Test that only the java sources are passed to javac
+		// Test that only the java sources are passed to turbine-apt and javac
+		if len(turbineApt.Inputs) != 1 || turbineApt.Inputs[0].String() != "a.java" {
+			t.Errorf(`foo turbine apt inputs %v != ["a.java"]`, turbineApt.Inputs)
+		}
 		if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
 			t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
 		}
 
-		// Test that the kapt srcjar is a dependency of kotlinc and javac rules
-		if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) {
-			t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings())
-		}
-		if !inList(kapt.Output.String(), javac.Implicits.Strings()) {
-			t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings())
+		// Test that the kapt stubs jar is a dependency of turbine-apt
+		if !inList(kaptStubs.Output.String(), turbineApt.Implicits.Strings()) {
+			t.Errorf("expected %q in turbine-apt implicits %v", kaptStubs.Output.String(), kotlinc.Implicits.Strings())
 		}
 
-		// Test that the kapt srcjar is extracted by the kotlinc and javac rules
-		if kotlinc.Args["srcJars"] != kapt.Output.String() {
-			t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		// Test that the turbine-apt srcjar is a dependency of kotlinc and javac rules
+		if !inList(turbineApt.Output.String(), kotlinc.Implicits.Strings()) {
+			t.Errorf("expected %q in kotlinc implicits %v", turbineApt.Output.String(), kotlinc.Implicits.Strings())
 		}
-		if javac.Args["srcJars"] != kapt.Output.String() {
-			t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		if !inList(turbineApt.Output.String(), javac.Implicits.Strings()) {
+			t.Errorf("expected %q in javac implicits %v", turbineApt.Output.String(), javac.Implicits.Strings())
+		}
+
+		// Test that the turbine-apt srcjar is extracted by the kotlinc and javac rules
+		if kotlinc.Args["srcJars"] != turbineApt.Output.String() {
+			t.Errorf("expected %q in kotlinc srcjars %v", turbineApt.Output.String(), kotlinc.Args["srcJars"])
+		}
+		if javac.Args["srcJars"] != turbineApt.Output.String() {
+			t.Errorf("expected %q in javac srcjars %v", turbineApt.Output.String(), kotlinc.Args["srcJars"])
 		}
 
 		// Test that the processors are passed to kapt
 		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
 			" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
-		if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
-			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
+		if kaptStubs.Args["kaptProcessorPath"] != expectedProcessorPath {
+			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kaptStubs.Args["kaptProcessorPath"])
 		}
 		expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
-		if kapt.Args["kaptProcessor"] != expectedProcessor {
-			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
+		if kaptStubs.Args["kaptProcessor"] != expectedProcessor {
+			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kaptStubs.Args["kaptProcessor"])
+		}
+
+		// Test that the processors are passed to turbine-apt
+		expectedProcessorPath = "--processorpath " + bar + " " + baz
+		if !strings.Contains(turbineApt.Args["turbineFlags"], expectedProcessorPath) {
+			t.Errorf("expected turbine-apt processorpath %q, got %q", expectedProcessorPath, turbineApt.Args["turbineFlags"])
+		}
+		expectedProcessor = "--processors com.bar com.baz"
+		if !strings.Contains(turbineApt.Args["turbineFlags"], expectedProcessor) {
+			t.Errorf("expected turbine-apt processor %q, got %q", expectedProcessor, turbineApt.Args["turbineFlags"])
 		}
 
 		// Test that the processors are not passed to javac
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 6f17272..6cdd07d 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -22,6 +22,7 @@
 	android.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
 	android.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
 	android.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
+	android.RegisterModuleType("rust_prebuilt_proc_macro", PrebuiltProcMacroFactory)
 }
 
 type PrebuiltProperties struct {
@@ -38,8 +39,42 @@
 	Properties PrebuiltProperties
 }
 
+type prebuiltProcMacroDecorator struct {
+	android.Prebuilt
+
+	*procMacroDecorator
+	Properties PrebuiltProperties
+}
+
+func PrebuiltProcMacroFactory() android.Module {
+	module, _ := NewPrebuiltProcMacro(android.HostSupportedNoCross)
+	return module.Init()
+}
+
+type rustPrebuilt interface {
+	prebuiltSrcs() []string
+	prebuilt() *android.Prebuilt
+}
+
+func NewPrebuiltProcMacro(hod android.HostOrDeviceSupported) (*Module, *prebuiltProcMacroDecorator) {
+	module, library := NewProcMacro(hod)
+	prebuilt := &prebuiltProcMacroDecorator{
+		procMacroDecorator: library,
+	}
+	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
+	return module, prebuilt
+}
+
 var _ compiler = (*prebuiltLibraryDecorator)(nil)
 var _ exportedFlagsProducer = (*prebuiltLibraryDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltLibraryDecorator)(nil)
+
+var _ compiler = (*prebuiltProcMacroDecorator)(nil)
+var _ exportedFlagsProducer = (*prebuiltProcMacroDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltProcMacroDecorator)(nil)
 
 func PrebuiltLibraryFactory() android.Module {
 	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported)
@@ -56,7 +91,7 @@
 	return module.Init()
 }
 
-func addSrcSupplier(module android.PrebuiltInterface, prebuilt *prebuiltLibraryDecorator) {
+func addSrcSupplier(module android.PrebuiltInterface, prebuilt rustPrebuilt) {
 	srcsSupplier := func(_ android.BaseModuleContext, _ android.Module) []string {
 		return prebuilt.prebuiltSrcs()
 	}
@@ -152,3 +187,44 @@
 func (prebuilt *prebuiltLibraryDecorator) prebuilt() *android.Prebuilt {
 	return &prebuilt.Prebuilt
 }
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuiltSrcs() []string {
+	srcs := prebuilt.Properties.Srcs
+	return srcs
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuilt() *android.Prebuilt {
+	return &prebuilt.Prebuilt
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerProps() []interface{} {
+	return append(prebuilt.procMacroDecorator.compilerProps(),
+		&prebuilt.Properties)
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+	prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
+	prebuilt.flagExporter.setProvider(ctx)
+
+	srcPath, paths := srcPathFromModuleSrcs(ctx, prebuilt.prebuiltSrcs())
+	if len(paths) > 0 {
+		ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
+	}
+	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
+	return srcPath
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) rustdoc(ctx ModuleContext, flags Flags,
+	deps PathDeps) android.OptionalPath {
+
+	return android.OptionalPath{}
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
+	deps = prebuilt.baseCompiler.compilerDeps(ctx, deps)
+	return deps
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) nativeCoverage() bool {
+	return false
+}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 974c096..f8a4bbd 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -33,6 +33,7 @@
 }
 
 type procMacroInterface interface {
+	ProcMacro() bool
 }
 
 var _ compiler = (*procMacroDecorator)(nil)
@@ -90,6 +91,10 @@
 	return rlibAutoDep
 }
 
+func (procMacro *procMacroDecorator) ProcMacro() bool {
+	return true
+}
+
 func (procMacro *procMacroDecorator) everInstallable() bool {
 	// Proc_macros are never installed
 	return false
diff --git a/rust/rust.go b/rust/rust.go
index 1c718a4..d627261 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -27,6 +27,7 @@
 	cc_config "android/soong/cc/config"
 	"android/soong/fuzz"
 	"android/soong/rust/config"
+	"android/soong/snapshot"
 )
 
 var pctx = android.NewPackageContext("android/soong/rust")
@@ -806,6 +807,13 @@
 	return mod.Properties.Installable
 }
 
+func (mod *Module) ProcMacro() bool {
+	if pm, ok := mod.compiler.(procMacroInterface); ok {
+		return pm.ProcMacro()
+	}
+	return false
+}
+
 func (mod *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain {
 	if mod.cachedToolchain == nil {
 		mod.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch())
@@ -920,12 +928,13 @@
 		}
 
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-		if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) {
+		if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) && !mod.ProcMacro() {
 			// If the module has been specifically configure to not be installed then
 			// hide from make as otherwise it will break when running inside make as the
 			// output path to install will not be specified. Not all uninstallable
 			// modules can be hidden from make as some are needed for resolving make
-			// side dependencies.
+			// side dependencies. In particular, proc-macros need to be captured in the
+			// host snapshot.
 			mod.HideFromMake()
 		} else if !mod.installable(apexInfo) {
 			mod.SkipInstall()
@@ -1046,7 +1055,7 @@
 }
 
 func (mod *Module) Prebuilt() *android.Prebuilt {
-	if p, ok := mod.compiler.(*prebuiltLibraryDecorator); ok {
+	if p, ok := mod.compiler.(rustPrebuilt); ok {
 		return p.prebuilt()
 	}
 	return nil
@@ -1501,6 +1510,7 @@
 }
 
 var _ android.HostToolProvider = (*Module)(nil)
+var _ snapshot.RelativeInstallPath = (*Module)(nil)
 
 func (mod *Module) HostToolPath() android.OptionalPath {
 	if !mod.Host() {
@@ -1508,6 +1518,10 @@
 	}
 	if binary, ok := mod.compiler.(*binaryDecorator); ok {
 		return android.OptionalPathForPath(binary.baseCompiler.path)
+	} else if pm, ok := mod.compiler.(*procMacroDecorator); ok {
+		// Even though proc-macros aren't strictly "tools", since they target the compiler
+		// and act as compiler plugins, we treat them similarly.
+		return android.OptionalPathForPath(pm.baseCompiler.path)
 	}
 	return android.OptionalPath{}
 }
diff --git a/scripts/Android.bp b/scripts/Android.bp
index a694b95..4c847a1 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -188,11 +188,3 @@
         "get_clang_version.py",
     ],
 }
-
-python_binary_host {
-    name: "list_image",
-    main: "list_image.py",
-    srcs: [
-        "list_image.py",
-    ],
-}
diff --git a/scripts/list_image.py b/scripts/list_image.py
deleted file mode 100644
index 7f76537..0000000
--- a/scripts/list_image.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2022 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.
-"""list_image is a tool that prints out content of an .img file.
-
-To print content of an image to stdout:
-  list_image foo.img
-"""
-from __future__ import print_function
-
-import argparse
-import os
-import sys
-import subprocess
-
-class ImageEntry(object):
-
-  def __init__(self, name, base_dir, is_directory=False):
-    self._name = name
-    self._base_dir = base_dir
-    self._is_directory = is_directory
-
-  @property
-  def name(self):
-    return self._name
-
-  @property
-  def full_path(self):
-    return os.path.join(self._base_dir, self._name)
-
-  @property
-  def is_directory(self):
-    return self._is_directory
-
-
-class Image(object):
-
-  def __init__(self, debugfs_path, img_path):
-    self._debugfs = debugfs_path
-    self._img_path = img_path
-
-  def list(self, path):
-    print(path)
-    process = subprocess.Popen([self._debugfs, '-R', 'ls -l -p %s' % path, self._img_path],
-                               stdout=subprocess.PIPE, stderr=subprocess.PIPE,
-                               universal_newlines=True)
-    stdout, _ = process.communicate()
-    res = str(stdout)
-    entries = []
-    for line in res.split('\n'):
-      if not line:
-        continue
-      parts = line.split('/')
-      if len(parts) != 8:
-        continue
-      name = parts[5]
-      if not name:
-        continue
-      bits = parts[2]
-      is_directory = bits[1] == '4'
-      entries.append(ImageEntry(name, path, is_directory))
-
-    for e in sorted(entries, key=lambda e: e.name):
-      yield e
-      if e.is_directory and e.name != '.' and e.name != '..':
-        yield from self.list(path + e.name + '/')
-
-
-def main(argv):
-  parser = argparse.ArgumentParser()
-
-  debugfs_default = None
-  if 'ANDROID_HOST_OUT' in os.environ:
-    debugfs_default = '%s/bin/debugfs_static' % os.environ['ANDROID_HOST_OUT']
-  parser.add_argument('--debugfs_path', help='The path to debugfs binary', default=debugfs_default)
-  parser.add_argument('img_path', type=str, help='.img file')
-  args = parser.parse_args(argv)
-
-  if not args.debugfs_path:
-    print('ANDROID_HOST_OUT environment variable is not defined, --debugfs_path must be set',
-          file=sys.stderr)
-    sys.exit(1)
-
-  for e in Image(args.debugfs_path, args.img_path).list('./'):
-    if e.is_directory:
-      continue
-    print(e.full_path)
-
-
-if __name__ == '__main__':
-  main(sys.argv[1:])
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
index 6b4e12b..1a75f3a 100644
--- a/snapshot/host_fake_snapshot.go
+++ b/snapshot/host_fake_snapshot.go
@@ -114,13 +114,13 @@
 		if !apexInfo.IsForPlatform() {
 			return
 		}
-		path := hostBinToolPath(module)
+		path := hostToolPath(module)
 		if path.Valid() && path.String() != "" {
 			outFile := filepath.Join(c.snapshotDir, path.String())
 			if !seen[outFile] {
 				seen[outFile] = true
 				outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
-				jsonData = append(jsonData, *hostBinJsonDesc(module))
+				jsonData = append(jsonData, *hostJsonDesc(module))
 			}
 		}
 	})
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
index e1d6154..9793218 100644
--- a/snapshot/host_snapshot.go
+++ b/snapshot/host_snapshot.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"path/filepath"
 	"sort"
+	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -62,6 +63,11 @@
 	installDir android.InstallPath
 }
 
+type ProcMacro interface {
+	ProcMacro() bool
+	CrateName() string
+}
+
 func hostSnapshotFactory() android.Module {
 	module := &hostSnapshot{}
 	initHostToolsModule(module)
@@ -94,7 +100,7 @@
 
 	// Create JSON file based on the direct dependencies
 	ctx.VisitDirectDeps(func(dep android.Module) {
-		desc := hostBinJsonDesc(dep)
+		desc := hostJsonDesc(dep)
 		if desc != nil {
 			jsonData = append(jsonData, *desc)
 		}
@@ -183,7 +189,7 @@
 }
 
 // Get host tools path and relative install string helpers
-func hostBinToolPath(m android.Module) android.OptionalPath {
+func hostToolPath(m android.Module) android.OptionalPath {
 	if provider, ok := m.(android.HostToolProvider); ok {
 		return provider.HostToolPath()
 	}
@@ -198,18 +204,30 @@
 	return outString
 }
 
-// Create JSON description for given module, only create descriptions for binary modueles which
-// provide a valid HostToolPath
-func hostBinJsonDesc(m android.Module) *SnapshotJsonFlags {
-	path := hostBinToolPath(m)
+// Create JSON description for given module, only create descriptions for binary modules
+// and rust_proc_macro modules which provide a valid HostToolPath
+func hostJsonDesc(m android.Module) *SnapshotJsonFlags {
+	path := hostToolPath(m)
 	relPath := hostRelativePathString(m)
+	procMacro := false
+	moduleStem := filepath.Base(path.String())
+	crateName := ""
+
+	if pm, ok := m.(ProcMacro); ok && pm.ProcMacro() {
+		procMacro = pm.ProcMacro()
+		moduleStem = strings.TrimSuffix(moduleStem, filepath.Ext(moduleStem))
+		crateName = pm.CrateName()
+	}
+
 	if path.Valid() && path.String() != "" {
 		return &SnapshotJsonFlags{
 			ModuleName:          m.Name(),
-			ModuleStemName:      filepath.Base(path.String()),
+			ModuleStemName:      moduleStem,
 			Filename:            path.String(),
 			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
 			RelativeInstallPath: relPath,
+			RustProcMacro:       procMacro,
+			CrateName:           crateName,
 		}
 	}
 	return nil
diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go
index 79d3cf6..4a14f2e 100644
--- a/snapshot/snapshot_base.go
+++ b/snapshot/snapshot_base.go
@@ -114,6 +114,8 @@
 	RelativeInstallPath string `json:",omitempty"`
 	Filename            string `json:",omitempty"`
 	ModuleStemName      string `json:",omitempty"`
+	RustProcMacro       bool   `json:",omitempty"`
+	CrateName           string `json:",omitempty"`
 
 	// dependencies
 	Required []string `json:",omitempty"`