Merge "Use lint database from api_versions_public"
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
index 416e430..8afbe7e 100644
--- a/androidmk/parser/make_strings.go
+++ b/androidmk/parser/make_strings.go
@@ -234,10 +234,10 @@
 		if n != 0 {
 			split := splitFunc(s, n)
 			if n != -1 {
-				if len(split) > n {
+				if len(split) > n || len(split) == 0 {
 					panic("oops!")
 				} else {
-					n -= len(split)
+					n -= len(split) - 1
 				}
 			}
 			curMs.appendString(split[0])
diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go
index e243ece..7e842a5 100644
--- a/androidmk/parser/make_strings_test.go
+++ b/androidmk/parser/make_strings_test.go
@@ -75,6 +75,16 @@
 			genMakeString(""),
 		},
 	},
+	{
+		// "x$(var1)y bar"
+		in:  genMakeString("x", "var1", "y bar"),
+		sep: " ",
+		n:   2,
+		expected: []*MakeString{
+			genMakeString("x", "var1", "y"),
+			genMakeString("bar"),
+		},
+	},
 }
 
 func TestMakeStringSplitN(t *testing.T) {
diff --git a/apex/apex.go b/apex/apex.go
index b339815..a95b504 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -977,6 +977,7 @@
 
 // apexInfoMutator delegates the work of identifying which modules need an ApexInfo and apex
 // specific variant to modules that support the ApexInfoMutator.
+// It also propagates updatable=true to apps of updatable apexes
 func apexInfoMutator(mctx android.TopDownMutatorContext) {
 	if !mctx.Module().Enabled() {
 		return
@@ -984,8 +985,8 @@
 
 	if a, ok := mctx.Module().(ApexInfoMutator); ok {
 		a.ApexInfoMutator(mctx)
-		return
 	}
+	enforceAppUpdatability(mctx)
 }
 
 // apexStrictUpdatibilityLintMutator propagates strict_updatability_linting to transitive deps of a mainline module
@@ -1016,6 +1017,22 @@
 	}
 }
 
+// enforceAppUpdatability propagates updatable=true to apps of updatable apexes
+func enforceAppUpdatability(mctx android.TopDownMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if apex, ok := mctx.Module().(*apexBundle); ok && apex.Updatable() {
+		// checking direct deps is sufficient since apex->apk is a direct edge, even when inherited via apex_defaults
+		mctx.VisitDirectDeps(func(module android.Module) {
+			// ignore android_test_app
+			if app, ok := module.(*java.AndroidApp); ok {
+				app.SetUpdatable(true)
+			}
+		})
+	}
+}
+
 // TODO: b/215736885 Whittle the denylist
 // Transitive deps of certain mainline modules baseline NewApi errors
 // Skip these mainline modules for now
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 7b29058..bcb55af 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2394,6 +2394,7 @@
 			key: "myapex.key",
 			apps: ["AppFoo"],
 			min_sdk_version: "29",
+			updatable: false,
 		}
 
 		apex_key {
@@ -9355,6 +9356,69 @@
 	}
 }
 
+// updatable apexes should propagate updatable=true to its apps
+func TestUpdatableApexEnforcesAppUpdatability(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: %v,
+			apps: [
+				"myapp",
+			],
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		android_app {
+			name: "myapp",
+			updatable: %v,
+			apex_available: [
+				"myapex",
+			],
+			sdk_version: "current",
+			min_sdk_version: "30",
+		}
+		`
+	testCases := []struct {
+		name                      string
+		apex_is_updatable_bp      bool
+		app_is_updatable_bp       bool
+		app_is_updatable_expected bool
+	}{
+		{
+			name:                      "Non-updatable apex respects updatable property of non-updatable app",
+			apex_is_updatable_bp:      false,
+			app_is_updatable_bp:       false,
+			app_is_updatable_expected: false,
+		},
+		{
+			name:                      "Non-updatable apex respects updatable property of updatable app",
+			apex_is_updatable_bp:      false,
+			app_is_updatable_bp:       true,
+			app_is_updatable_expected: true,
+		},
+		{
+			name:                      "Updatable apex respects updatable property of updatable app",
+			apex_is_updatable_bp:      true,
+			app_is_updatable_bp:       true,
+			app_is_updatable_expected: true,
+		},
+		{
+			name:                      "Updatable apex sets updatable=true on non-updatable app",
+			apex_is_updatable_bp:      true,
+			app_is_updatable_bp:       false,
+			app_is_updatable_expected: true,
+		},
+	}
+	for _, testCase := range testCases {
+		result := testApex(t, fmt.Sprintf(bp, testCase.apex_is_updatable_bp, testCase.app_is_updatable_bp))
+		myapp := result.ModuleForTests("myapp", "android_common").Module().(*java.AndroidApp)
+		android.AssertBoolEquals(t, testCase.name, testCase.app_is_updatable_expected, myapp.Updatable())
+	}
+}
+
 func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
diff --git a/cc/Android.bp b/cc/Android.bp
index 60d329e..ce94467 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -91,6 +91,7 @@
     ],
     testSrcs: [
         "afdo_test.go",
+        "binary_test.go",
         "cc_test.go",
         "compiler_test.go",
         "gen_test.go",
diff --git a/cc/binary_test.go b/cc/binary_test.go
index 1b8b4a8..cba5974 100644
--- a/cc/binary_test.go
+++ b/cc/binary_test.go
@@ -65,7 +65,7 @@
 	android.AssertStringListContains(t, "missing dependency on linker_scripts",
 		binFoo.Implicits.Strings(), "bar.ld")
 	android.AssertStringDoesContain(t, "missing flag for linker_scripts",
-		libfoo.Args["ldFlags"], "-Wl,--script,foo.ld")
+		binFoo.Args["ldFlags"], "-Wl,--script,foo.ld")
 	android.AssertStringDoesContain(t, "missing flag for linker_scripts",
-		libfoo.Args["ldFlags"], "-Wl,--script,bar.ld")
+		binFoo.Args["ldFlags"], "-Wl,--script,bar.ld")
 }
diff --git a/cc/config/global.go b/cc/config/global.go
index 3caf327..dc6310c 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -286,8 +286,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r450784d"
-	ClangDefaultShortVersion = "14.0.6"
+	ClangDefaultVersion      = "clang-r450784e"
+	ClangDefaultShortVersion = "14.0.7"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index fb9ac49..1f90843 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -46,6 +46,7 @@
 			"-misc-no-recursion",
 			"-misc-non-private-member-variables-in-classes",
 			"-misc-unused-parameters",
+			"-performance-no-int-to-ptr",
 			// the following groups are excluded by -*
 			// -altera-*
 			// -cppcoreguidelines-*
diff --git a/cc/tidy.go b/cc/tidy.go
index 03e967d..ac1521b 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -96,10 +96,15 @@
 	if !android.SubstringInList(flags.TidyFlags, "-header-filter=") {
 		defaultDirs := ctx.Config().Getenv("DEFAULT_TIDY_HEADER_DIRS")
 		headerFilter := "-header-filter="
+		// Default header filter should include only the module directory,
+		// not the out/soong/.../ModuleDir/...
+		// Otherwise, there will be too many warnings from generated files in out/...
+		// If a module wants to see warnings in the generated source files,
+		// it should specify its own -header-filter flag.
 		if defaultDirs == "" {
-			headerFilter += ctx.ModuleDir() + "/"
+			headerFilter += "^" + ctx.ModuleDir() + "/"
 		} else {
-			headerFilter += "\"(" + ctx.ModuleDir() + "/|" + defaultDirs + ")\""
+			headerFilter += "\"(^" + ctx.ModuleDir() + "/|" + defaultDirs + ")\""
 		}
 		flags.TidyFlags = append(flags.TidyFlags, headerFilter)
 	}
diff --git a/cmd/path_interposer/main.go b/cmd/path_interposer/main.go
index a4fe3e4..8b9de52 100644
--- a/cmd/path_interposer/main.go
+++ b/cmd/path_interposer/main.go
@@ -12,6 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// This tool tries to prohibit access to tools on the system on which the build
+// is run.
+//
+// The rationale is that if the build uses a binary that is not shipped in the
+// source tree, it is unknowable which version of that binary will be installed
+// and therefore the output of the build will be unpredictable. Therefore, we
+// should make every effort to use only tools under our control.
+//
+// This is currently implemented by a "sandbox" that sets $PATH to a specific,
+// single directory and creates a symlink for every binary in $PATH in it. That
+// symlink will point to path_interposer, which then uses an embedded
+// configuration to determine whether to allow access to the binary (in which
+// case it calls the original executable) or not (in which case it fails). It
+// can also optionally log invocations.
+//
+// This, of course, does not help if one invokes the tool in question with its
+// full path.
 package main
 
 import (
diff --git a/java/app.go b/java/app.go
index 768d9e9..5f14cef 100755
--- a/java/app.go
+++ b/java/app.go
@@ -299,10 +299,6 @@
 
 // If an updatable APK sets min_sdk_version, min_sdk_vesion of JNI libs should match with it.
 // This check is enforced for "updatable" APKs (including APK-in-APEX).
-// b/155209650: until min_sdk_version is properly supported, use sdk_version instead.
-// because, sdk_version is overridden by min_sdk_version (if set as smaller)
-// and sdkLinkType is checked with dependencies so we can be sure that the whole dependency tree
-// will meet the requirements.
 func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
 	// It's enough to check direct JNI deps' sdk_version because all transitive deps from JNI deps are checked in cc.checkLinkType()
 	ctx.VisitDirectDeps(func(m android.Module) {
@@ -313,10 +309,10 @@
 		// The domain of cc.sdk_version is "current" and <number>
 		// We can rely on android.SdkSpec to convert it to <number> so that "current" is
 		// handled properly regardless of sdk finalization.
-		jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.SdkVersion()).EffectiveVersion(ctx)
+		jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.MinSdkVersion()).EffectiveVersion(ctx)
 		if err != nil || minSdkVersion.LessThan(jniSdkVersion) {
-			ctx.OtherModuleErrorf(dep, "sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
-				dep.SdkVersion(), minSdkVersion, ctx.ModuleName())
+			ctx.OtherModuleErrorf(dep, "min_sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
+				dep.MinSdkVersion(), minSdkVersion, ctx.ModuleName())
 			return
 		}
 
@@ -842,6 +838,10 @@
 	return Bool(a.appProperties.Updatable)
 }
 
+func (a *AndroidApp) SetUpdatable(val bool) {
+	a.appProperties.Updatable = &val
+}
+
 func (a *AndroidApp) getCertString(ctx android.BaseModuleContext) string {
 	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName())
 	if overridden {
diff --git a/java/app_test.go b/java/app_test.go
index 8324dff..b83a333 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -427,7 +427,8 @@
 			name: "libjni",
 			stl: "none",
 			system_shared_libs: [],
-			sdk_version: "29",
+			sdk_version: "current",
+			min_sdk_version: "29",
 		}
 	`
 	fs := map[string][]byte{
@@ -481,12 +482,13 @@
 			name: "libjni",
 			stl: "none",
 			sdk_version: "current",
+			min_sdk_version: "current",
 		}
 	`
-	testJavaError(t, `"libjni" .*: sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp)
+	testJavaError(t, `"libjni" .*: min_sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp)
 }
 
-func TestUpdatableApps_ErrorIfDepSdkVersionIsHigher(t *testing.T) {
+func TestUpdatableApps_ErrorIfDepMinSdkVersionIsHigher(t *testing.T) {
 	bp := cc.GatherRequiredDepsForTest(android.Android) + `
 		android_app {
 			name: "foo",
@@ -503,6 +505,7 @@
 			shared_libs: ["libbar"],
 			system_shared_libs: [],
 			sdk_version: "27",
+			min_sdk_version: "27",
 		}
 
 		cc_library {
@@ -510,6 +513,7 @@
 			stl: "none",
 			system_shared_libs: [],
 			sdk_version: "current",
+			min_sdk_version: "current",
 		}
 	`
 	testJavaError(t, `"libjni" .*: links "libbar" built against newer API version "current"`, bp)
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index 1eae63f..e59146b 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -1270,7 +1270,28 @@
 	// Handle only the case where the first (or only) word is constant
 	words := ref.SplitN(" ", 2)
 	if !words[0].Const() {
-		return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
+		if len(words) == 1 {
+			expr := ctx.parseMakeString(node, ref)
+			return &callExpr{
+				object: &identifierExpr{"cfg"},
+				name:   "get",
+				args: []starlarkExpr{
+					expr,
+					&callExpr{
+						object: &identifierExpr{"g"},
+						name:   "get",
+						args: []starlarkExpr{
+							expr,
+							&stringLiteralExpr{literal: ""},
+						},
+						returnType: starlarkTypeUnknown,
+					},
+				},
+				returnType: starlarkTypeUnknown,
+			}
+		} else {
+			return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
+		}
 	}
 
 	if name, _, ok := ctx.maybeParseFunctionCall(node, ref); ok {
@@ -1574,11 +1595,21 @@
 		}
 	}
 
-	return &foreachExpr{
+	var result starlarkExpr = &foreachExpr{
 		varName: loopVarName,
 		list:    list,
 		action:  action,
 	}
+
+	if action.typ() == starlarkTypeList {
+		result = &callExpr{
+			name:       baseName + ".flatten_2d_list",
+			args:       []starlarkExpr{result},
+			returnType: starlarkTypeList,
+		}
+	}
+
+	return result
 }
 
 func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 7f236bb..a09764c 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -579,7 +579,7 @@
     pass
   if rblf.expand_wildcard("foo*.mk"):
     pass
-  if rblf.expand_wildcard("foo*.mk bar*.mk") == ["foo1.mk", "foo2.mk", "barxyz.mk"]:␤
+  if rblf.expand_wildcard("foo*.mk bar*.mk") == ["foo1.mk", "foo2.mk", "barxyz.mk"]:
     pass
 `,
 	},
@@ -1363,6 +1363,8 @@
 BOOT_KERNEL_MODULES_LIST := foo.ko
 BOOT_KERNEL_MODULES_LIST += bar.ko
 BOOT_KERNEL_MODULES_FILTER_2 := $(foreach m,$(BOOT_KERNEL_MODULES_LIST),%/$(m))
+NESTED_LISTS := $(foreach m,$(SOME_VAR),$(BOOT_KERNEL_MODULES_LIST))
+NESTED_LISTS_2 := $(foreach x,$(SOME_VAR),$(foreach y,$(x),prefix$(y)))
 
 FOREACH_WITH_IF := $(foreach module,\
   $(BOOT_KERNEL_MODULES_LIST),\
@@ -1382,6 +1384,8 @@
   g["BOOT_KERNEL_MODULES_LIST"] = ["foo.ko"]
   g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
   g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
+  g["NESTED_LISTS"] = rblf.flatten_2d_list([g["BOOT_KERNEL_MODULES_LIST"] for m in rblf.words(g.get("SOME_VAR", ""))])
+  g["NESTED_LISTS_2"] = rblf.flatten_2d_list([["prefix%s" % y for y in rblf.words(x)] for x in rblf.words(g.get("SOME_VAR", ""))])
   g["FOREACH_WITH_IF"] = [("" if rblf.filter(module, "foo.ko") else rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)) for module in g["BOOT_KERNEL_MODULES_LIST"]]
   # Same as above, but not assigning it to a variable allows it to be converted to statements
   for module in g["BOOT_KERNEL_MODULES_LIST"]:
@@ -1574,10 +1578,10 @@
   for x in rblf.words(g.get("MY_LIST_VAR", "")):
     _entry = {
       "foo/font.mk": ("foo/font", _font_init),
-    }.get("foo/%s.mk" % _x)
+    }.get("foo/%s.mk" % x)
     (_varmod, _varmod_init) = _entry if _entry else (None, None)
     if not _varmod_init:
-      rblf.mkerror("product.mk", "Cannot find %s" % ("foo/%s.mk" % _x))
+      rblf.mkerror("product.mk", "Cannot find %s" % ("foo/%s.mk" % x))
     _varmod_init(g, handle)
 `,
 	},
@@ -1595,6 +1599,27 @@
   g["MY_VAR"] = "foo"
 `,
 	},
+	{
+		desc:   "Complicated variable references",
+		mkname: "product.mk",
+		in: `
+MY_VAR := foo
+MY_VAR_2 := MY_VAR
+MY_VAR_3 := $($(MY_VAR_2))
+MY_VAR_4 := $(foo bar)
+MY_VAR_5 := $($(MY_VAR_2) bar)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_VAR"] = "foo"
+  g["MY_VAR_2"] = "MY_VAR"
+  g["MY_VAR_3"] = (cfg).get(g["MY_VAR_2"], (g).get(g["MY_VAR_2"], ""))
+  g["MY_VAR_4"] = rblf.mk2rbc_error("product.mk:5", "cannot handle invoking foo")
+  g["MY_VAR_5"] = rblf.mk2rbc_error("product.mk:6", "reference is too complex: $(MY_VAR_2) bar")
+`,
+	},
 }
 
 var known_variables = []struct {
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
index e49f3d4..d1cbd8f 100644
--- a/provenance/provenance_singleton.go
+++ b/provenance/provenance_singleton.go
@@ -36,7 +36,8 @@
 	mergeProvenanceMetaData = pctx.AndroidStaticRule("mergeProvenanceMetaData",
 		blueprint.RuleParams{
 			Command: `rm -rf $out $out.temp && ` +
-				`echo -e "# proto-file: build/soong/provenance/proto/provenance_metadata.proto\n# proto-message: ProvenanceMetaDataList" > $out && ` +
+				`echo "# proto-file: build/soong/provenance/proto/provenance_metadata.proto" > $out && ` +
+				`echo "# proto-message: ProvenanceMetaDataList" >> $out && ` +
 				`touch $out.temp && cat $out.temp $in | grep -v "^#.*" >> $out && rm -rf $out.temp`,
 		})
 )
diff --git a/provenance/tools/gen_provenance_metadata.py b/provenance/tools/gen_provenance_metadata.py
index b33f911..f3f4d1f 100644
--- a/provenance/tools/gen_provenance_metadata.py
+++ b/provenance/tools/gen_provenance_metadata.py
@@ -16,6 +16,7 @@
 
 import argparse
 import hashlib
+import os.path
 import sys
 
 import google.protobuf.text_format as text_format
@@ -51,6 +52,11 @@
     h.update(artifact_file.read())
   provenance_metadata.artifact_sha256 = h.hexdigest()
 
+  Log("Check if there is attestation for the artifact")
+  attestation_file_name = args.artifact_path + ".intoto.jsonl"
+  if os.path.isfile(attestation_file_name):
+    provenance_metadata.attestation_path = attestation_file_name
+
   text_proto = [
       "# proto-file: build/soong/provenance/proto/provenance_metadata.proto",
       "# proto-message: ProvenanceMetaData",
diff --git a/provenance/tools/gen_provenance_metadata_test.py b/provenance/tools/gen_provenance_metadata_test.py
index 2fc04bf..1f69b8f 100644
--- a/provenance/tools/gen_provenance_metadata_test.py
+++ b/provenance/tools/gen_provenance_metadata_test.py
@@ -100,6 +100,11 @@
     artifact_file = tempfile.mktemp()
     with open(artifact_file,"wt") as f:
       f.write(artifact_content)
+
+    attestation_file = artifact_file + ".intoto.jsonl"
+    with open(attestation_file, "wt") as af:
+      af.write("attestation file")
+
     metadata_file = tempfile.mktemp()
     cmd = ["gen_provenance_metadata"]
     cmd.extend(["--module_name", "a"])
@@ -117,9 +122,11 @@
       self.assertEqual(provenance_metadata.artifact_path, artifact_file)
       self.assertEqual(provenance_metadata.artifact_install_path, "b")
       self.assertEqual(provenance_metadata.artifact_sha256, sha256(artifact_content))
+      self.assertEqual(provenance_metadata.attestation_path, attestation_file)
 
     os.remove(artifact_file)
     os.remove(metadata_file)
+    os.remove(attestation_file)
 
 if __name__ == '__main__':
   unittest.main(verbosity=2)
\ No newline at end of file
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index a99fa1f..9d0c3de 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -796,6 +796,44 @@
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 `),
+		checkInfoContents(`
+[
+  {
+    "@type": "sdk",
+    "@name": "mysdk",
+    "java_header_libs": [
+      "exported-system-module",
+      "system-module"
+    ],
+    "java_sdk_libs": [
+      "myjavalib"
+    ],
+    "java_system_modules": [
+      "my-system-modules"
+    ]
+  },
+  {
+    "@type": "java_library",
+    "@name": "exported-system-module"
+  },
+  {
+    "@type": "java_system_modules",
+    "@name": "my-system-modules",
+    "@deps": [
+      "exported-system-module",
+      "system-module"
+    ]
+  },
+  {
+    "@type": "java_sdk_library",
+    "@name": "myjavalib"
+  },
+  {
+    "@type": "java_library",
+    "@name": "system-module"
+  }
+]
+`),
 	)
 }
 
diff --git a/sdk/sdk.go b/sdk/sdk.go
index c8c7b79..b37eaad 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -75,6 +75,8 @@
 
 	snapshotFile android.OptionalPath
 
+	infoFile android.OptionalPath
+
 	// The builder, preserved for testing.
 	builderForTests *snapshotBuilder
 }
@@ -191,27 +193,32 @@
 		}
 
 		// Generate the snapshot from the member info.
-		p := s.buildSnapshot(ctx, sdkVariants)
-		zip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), p.Base(), p)
-		s.snapshotFile = android.OptionalPathForPath(zip)
+		s.buildSnapshot(ctx, sdkVariants)
 	}
 }
 
 func (s *sdk) AndroidMkEntries() []android.AndroidMkEntries {
-	if !s.snapshotFile.Valid() {
+	if !s.snapshotFile.Valid() != !s.infoFile.Valid() {
+		panic("Snapshot (%q) and info file (%q) should both be set or neither should be set.")
+	} else if !s.snapshotFile.Valid() {
 		return []android.AndroidMkEntries{}
 	}
 
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "FAKE",
 		OutputFile: s.snapshotFile,
-		DistFiles:  android.MakeDefaultDistFiles(s.snapshotFile.Path()),
+		DistFiles:  android.MakeDefaultDistFiles(s.snapshotFile.Path(), s.infoFile.Path()),
 		Include:    "$(BUILD_PHONY_PACKAGE)",
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
 			func(w io.Writer, name, prefix, moduleDir string) {
 				// Allow the sdk to be built by simply passing its name on the command line.
 				fmt.Fprintln(w, ".PHONY:", s.Name())
 				fmt.Fprintln(w, s.Name()+":", s.snapshotFile.String())
+
+				// Allow the sdk info to be built by simply passing its name on the command line.
+				infoTarget := s.Name() + ".info"
+				fmt.Fprintln(w, ".PHONY:", infoTarget)
+				fmt.Fprintln(w, infoTarget+":", s.infoFile.String())
 			},
 		},
 	}}
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 40de150..ccbeb8d 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -263,7 +263,10 @@
 	result := testSdkWithFs(t, sdk, nil)
 
 	CheckSnapshot(t, result, "mysdk", "",
-		checkAllOtherCopyRules(`.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip`))
+		checkAllOtherCopyRules(`
+.intermediates/mysdk/common_os/mysdk-current.info -> mysdk-current.info
+.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip
+`))
 }
 
 type EmbeddedPropertiesStruct struct {
diff --git a/sdk/testing.go b/sdk/testing.go
index 062f200..b0f5fdc 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -142,6 +142,7 @@
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
+		infoContents:                 sdk.GetInfoContentsForTests(),
 		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
 		targetBuildRelease:           sdk.builderForTests.targetBuildRelease,
 	}
@@ -402,6 +403,17 @@
 	}
 }
 
+// Check that the snapshot's info contents are ciorrect.
+//
+// Both the expected and actual string are both trimmed before comparing.
+func checkInfoContents(expected string) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "info contents do not match",
+			expected, info.infoContents)
+	}
+}
+
 type resultChecker func(t *testing.T, result *android.TestResult)
 
 // snapshotTestPreparer registers a preparer that will be used to customize the specified
@@ -479,6 +491,9 @@
 	// The contents of the versioned Android.bp file
 	androidVersionedBpContents string
 
+	// The contents of the info file.
+	infoContents string
+
 	// The paths, relative to the snapshot root, of all files and directories copied into the
 	// snapshot.
 	snapshotContents []string
diff --git a/sdk/update.go b/sdk/update.go
index 5db604b..71bd042 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -15,6 +15,8 @@
 package sdk
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"sort"
@@ -219,9 +221,19 @@
 				exportedComponentsInfo = ctx.OtherModuleProvider(child, android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo)
 			}
 
+			var container android.SdkAware
+			if parent != ctx.Module() {
+				container = parent.(android.SdkAware)
+			}
+
 			export := memberTag.ExportMember()
 			s.memberVariantDeps = append(s.memberVariantDeps, sdkMemberVariantDep{
-				s, memberType, child.(android.SdkAware), export, exportedComponentsInfo,
+				sdkVariant:             s,
+				memberType:             memberType,
+				variant:                child.(android.SdkAware),
+				container:              container,
+				export:                 export,
+				exportedComponentsInfo: exportedComponentsInfo,
 			})
 
 			// Recurse down into the member's dependencies as it may have dependencies that need to be
@@ -311,7 +323,7 @@
 
 // buildSnapshot is the main function in this source file. It creates rules to copy
 // the contents (header files, stub libraries, etc) into the zip file.
-func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) android.OutputPath {
+func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) {
 
 	// Aggregate all the sdkMemberVariantDep instances from all the sdk variants.
 	hasLicenses := false
@@ -370,9 +382,9 @@
 	// Unversioned modules are not required in that case because the numbered version will be a
 	// finalized version of the snapshot that is intended to be kept separate from the
 	generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent
-	snapshotZipFileSuffix := ""
+	snapshotFileSuffix := ""
 	if generateVersioned {
-		snapshotZipFileSuffix = "-" + version
+		snapshotFileSuffix = "-" + version
 	}
 
 	currentBuildRelease := latestBuildRelease()
@@ -489,7 +501,7 @@
 	filesToZip := builder.filesToZip
 
 	// zip them all
-	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotFileSuffix)
 	outputZipFile := android.PathForModuleOut(ctx, zipPath).OutputPath
 	outputDesc := "Building snapshot for " + ctx.ModuleName()
 
@@ -502,7 +514,7 @@
 		zipFile = outputZipFile
 		desc = outputDesc
 	} else {
-		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotFileSuffix)
 		zipFile = android.PathForModuleOut(ctx, intermediatePath).OutputPath
 		desc = "Building intermediate snapshot for " + ctx.ModuleName()
 	}
@@ -527,7 +539,120 @@
 		})
 	}
 
-	return outputZipFile
+	modules := s.generateInfoData(ctx, memberVariantDeps)
+
+	// Output the modules information as pretty printed JSON.
+	info := newGeneratedFile(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix))
+	output, err := json.MarshalIndent(modules, "", "  ")
+	if err != nil {
+		ctx.ModuleErrorf("error generating %q: %s", info, err)
+	}
+	builder.infoContents = string(output)
+	info.generatedContents.UnindentedPrintf("%s", output)
+	info.build(pctx, ctx, nil)
+	infoPath := info.path
+	installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), infoPath.Base(), infoPath)
+	s.infoFile = android.OptionalPathForPath(installedInfo)
+
+	// Install the zip, making sure that the info file has been installed as well.
+	installedZip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), outputZipFile.Base(), outputZipFile, installedInfo)
+	s.snapshotFile = android.OptionalPathForPath(installedZip)
+}
+
+type moduleInfo struct {
+	// The type of the module, e.g. java_sdk_library
+	moduleType string
+	// The name of the module.
+	name string
+	// A list of additional dependencies of the module.
+	deps []string
+	// Additional dynamic properties.
+	dynamic map[string]interface{}
+}
+
+func (m *moduleInfo) MarshalJSON() ([]byte, error) {
+	buffer := bytes.Buffer{}
+
+	separator := ""
+	writeObjectPair := func(key string, value interface{}) {
+		buffer.WriteString(fmt.Sprintf("%s%q: ", separator, key))
+		b, err := json.Marshal(value)
+		if err != nil {
+			panic(err)
+		}
+		buffer.Write(b)
+		separator = ","
+	}
+
+	buffer.WriteString("{")
+	writeObjectPair("@type", m.moduleType)
+	writeObjectPair("@name", m.name)
+	if m.deps != nil {
+		writeObjectPair("@deps", m.deps)
+	}
+	for _, k := range android.SortedStringKeys(m.dynamic) {
+		v := m.dynamic[k]
+		writeObjectPair(k, v)
+	}
+	buffer.WriteString("}")
+	return buffer.Bytes(), nil
+}
+
+var _ json.Marshaler = (*moduleInfo)(nil)
+
+// generateInfoData creates a list of moduleInfo structures that will be marshalled into JSON.
+func (s *sdk) generateInfoData(ctx android.ModuleContext, memberVariantDeps []sdkMemberVariantDep) interface{} {
+	modules := []*moduleInfo{}
+	sdkInfo := moduleInfo{
+		moduleType: "sdk",
+		name:       ctx.ModuleName(),
+		dynamic:    map[string]interface{}{},
+	}
+	modules = append(modules, &sdkInfo)
+
+	name2Info := map[string]*moduleInfo{}
+	getModuleInfo := func(module android.Module) *moduleInfo {
+		name := module.Name()
+		info := name2Info[name]
+		if info == nil {
+			moduleType := ctx.OtherModuleType(module)
+			// Remove any suffix added when creating modules dynamically.
+			moduleType = strings.Split(moduleType, "__")[0]
+			info = &moduleInfo{
+				moduleType: moduleType,
+				name:       name,
+			}
+			name2Info[name] = info
+		}
+		return info
+	}
+
+	for _, memberVariantDep := range memberVariantDeps {
+		propertyName := memberVariantDep.memberType.SdkPropertyName()
+		var list []string
+		if v, ok := sdkInfo.dynamic[propertyName]; ok {
+			list = v.([]string)
+		}
+
+		memberName := memberVariantDep.variant.Name()
+		list = append(list, memberName)
+		sdkInfo.dynamic[propertyName] = android.SortedUniqueStrings(list)
+
+		if memberVariantDep.container != nil {
+			containerInfo := getModuleInfo(memberVariantDep.container)
+			containerInfo.deps = android.SortedUniqueStrings(append(containerInfo.deps, memberName))
+		}
+
+		// Make sure that the module info is created for each module.
+		getModuleInfo(memberVariantDep.variant)
+	}
+
+	for _, memberName := range android.SortedStringKeys(name2Info) {
+		info := name2Info[memberName]
+		modules = append(modules, info)
+	}
+
+	return modules
 }
 
 // filterOutComponents removes any item from the deps list that is a component of another item in
@@ -1033,6 +1158,10 @@
 	return contents.content.String()
 }
 
+func (s *sdk) GetInfoContentsForTests() string {
+	return s.builderForTests.infoContents
+}
+
 func (s *sdk) GetUnversionedAndroidBpContentsForTests() string {
 	contents := &generatedContents{}
 	generateFilteredBpContents(contents, s.builderForTests.bpFile, func(module *bpModule) bool {
@@ -1087,6 +1216,8 @@
 
 	// The target build release for which the snapshot is to be generated.
 	targetBuildRelease *buildRelease
+
+	infoContents string
 }
 
 func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
@@ -1322,6 +1453,11 @@
 	// The variant that is added to the sdk.
 	variant android.SdkAware
 
+	// The optional container of this member, i.e. the module that is depended upon by the sdk
+	// (possibly transitively) and whose dependency on this module is why it was added to the sdk.
+	// Is nil if this a direct dependency of the sdk.
+	container android.SdkAware
+
 	// True if the member should be exported, i.e. accessible, from outside the sdk.
 	export bool
 
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index 831a80f..b3092ea 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -31,18 +31,25 @@
 	LinuxOnlyPrebuilt bool
 }
 
+// These binaries can be run from $PATH, nonhermetically. There should be as
+// few as possible of these, since this means that the build depends on tools
+// that are not shipped in the source tree and whose behavior is therefore
+// unpredictable.
 var Allowed = PathConfig{
 	Symlink: true,
 	Log:     false,
 	Error:   false,
 }
 
+// This tool is specifically disallowed and calling it will result in an
+// "executable no found" error.
 var Forbidden = PathConfig{
 	Symlink: false,
 	Log:     true,
 	Error:   true,
 }
 
+// This tool is allowed, but access to it will be logged.
 var Log = PathConfig{
 	Symlink: true,
 	Log:     true,
@@ -52,13 +59,16 @@
 // The configuration used if the tool is not listed in the config below.
 // Currently this will create the symlink, but log and error when it's used. In
 // the future, I expect the symlink to be removed, and this will be equivalent
-// to Forbidden.
+// to Forbidden. This applies to every tool not specifically mentioned in the
+// configuration.
 var Missing = PathConfig{
 	Symlink: true,
 	Log:     true,
 	Error:   true,
 }
 
+// This is used for binaries for which we have prebuilt versions, but only for
+// Linux. Thus, their execution from $PATH is only allowed on Mac OS.
 var LinuxOnlyPrebuilt = PathConfig{
 	Symlink:           false,
 	Log:               true,
@@ -73,6 +83,8 @@
 	return Missing
 }
 
+// This list specifies whether a particular binary from $PATH is allowed to be
+// run during the build. For more documentation, see path_interposer.go .
 var Configuration = map[string]PathConfig{
 	"bash":    Allowed,
 	"dd":      Allowed,