Merge changes Ib29ede45,I1b2bfdfb

* changes:
  Create LLNDK vendor variants when DeviceVndkVersion is not set
  Add a new SingletonModule type
diff --git a/android/config.go b/android/config.go
index ddb2de3..a7e0b67 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1256,6 +1256,27 @@
 	return HasAnyPrefix(path, c.productVariables.CFIIncludePaths)
 }
 
+func (c *config) MemtagHeapDisabledForPath(path string) bool {
+	if len(c.productVariables.MemtagHeapExcludePaths) == 0 {
+		return false
+	}
+	return HasAnyPrefix(path, c.productVariables.MemtagHeapExcludePaths)
+}
+
+func (c *config) MemtagHeapAsyncEnabledForPath(path string) bool {
+	if len(c.productVariables.MemtagHeapAsyncIncludePaths) == 0 {
+		return false
+	}
+	return HasAnyPrefix(path, c.productVariables.MemtagHeapAsyncIncludePaths)
+}
+
+func (c *config) MemtagHeapSyncEnabledForPath(path string) bool {
+	if len(c.productVariables.MemtagHeapSyncIncludePaths) == 0 {
+		return false
+	}
+	return HasAnyPrefix(path, c.productVariables.MemtagHeapSyncIncludePaths)
+}
+
 func (c *config) VendorConfig(name string) VendorConfig {
 	return soongconfig.Config(c.productVariables.VendorVars[name])
 }
diff --git a/android/module.go b/android/module.go
index b0ad89b..17035bb 100644
--- a/android/module.go
+++ b/android/module.go
@@ -454,6 +454,7 @@
 	InstallForceOS() (*OsType, *ArchType)
 	HideFromMake()
 	IsHideFromMake() bool
+	IsSkipInstall() bool
 	MakeUninstallable()
 	ReplacedByPrebuilt()
 	IsReplacedByPrebuilt() bool
@@ -1398,6 +1399,12 @@
 	m.commonProperties.SkipInstall = true
 }
 
+// IsSkipInstall returns true if this variant is marked to not create install
+// rules when ctx.Install* are called.
+func (m *ModuleBase) IsSkipInstall() bool {
+	return m.commonProperties.SkipInstall
+}
+
 // Similar to HideFromMake, but if the AndroidMk entry would set
 // LOCAL_UNINSTALLABLE_MODULE then this variant may still output that entry
 // rather than leaving it out altogether. That happens in cases where it would
diff --git a/android/paths.go b/android/paths.go
index 10d8d0d..592b9e1 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1742,6 +1742,19 @@
 	return ioutil.WriteFile(absolutePath(path.String()), data, perm)
 }
 
+func RemoveAllOutputDir(path WritablePath) error {
+	return os.RemoveAll(absolutePath(path.String()))
+}
+
+func CreateOutputDirIfNonexistent(path WritablePath, perm os.FileMode) error {
+	dir := absolutePath(path.String())
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		return os.MkdirAll(dir, os.ModePerm)
+	} else {
+		return err
+	}
+}
+
 func absolutePath(path string) string {
 	if filepath.IsAbs(path) {
 		return path
diff --git a/android/queryview.go b/android/queryview.go
index 1b7e77d..9e3e45a 100644
--- a/android/queryview.go
+++ b/android/queryview.go
@@ -26,7 +26,6 @@
 // for calling the soong_build primary builder in the main build.ninja file.
 func init() {
 	RegisterSingletonType("bazel_queryview", BazelQueryViewSingleton)
-	RegisterSingletonType("bazel_converter", BazelConverterSingleton)
 }
 
 // BazelQueryViewSingleton is the singleton responsible for registering the
@@ -52,13 +51,7 @@
 
 func generateBuildActionsForBazelConversion(ctx SingletonContext, converterMode bool) {
 	name := "queryview"
-	additionalEnvVars := ""
 	descriptionTemplate := "[EXPERIMENTAL, PRE-PRODUCTION] Creating the Bazel QueryView workspace with %s at $outDir"
-	if converterMode {
-		name = "bp2build"
-		additionalEnvVars = "CONVERT_TO_BAZEL=true"
-		descriptionTemplate = "[EXPERIMENTAL, PRE-PRODUCTION] Converting all Android.bp to Bazel BUILD files with %s at $outDir"
-	}
 
 	// Create a build and rule statement, using the Bazel QueryView's WORKSPACE
 	// file as the output file marker.
@@ -74,9 +67,8 @@
 		blueprint.RuleParams{
 			Command: fmt.Sprintf(
 				"rm -rf ${outDir}/* && "+
-					"%s %s --bazel_queryview_dir ${outDir} %s && "+
+					"%s --bazel_queryview_dir ${outDir} %s && "+
 					"echo WORKSPACE: `cat %s` > ${outDir}/.queryview-depfile.d",
-				additionalEnvVars,
 				primaryBuilder.String(),
 				strings.Join(os.Args[1:], " "),
 				moduleListFilePath.String(), // Use the contents of Android.bp.list as the depfile.
diff --git a/android/register.go b/android/register.go
index 61889f6..ca658f5 100644
--- a/android/register.go
+++ b/android/register.go
@@ -37,6 +37,9 @@
 var singletons []singleton
 var preSingletons []singleton
 
+var bazelConverterSingletons []singleton
+var bazelConverterPreSingletons []singleton
+
 type mutator struct {
 	name            string
 	bottomUpMutator blueprint.BottomUpMutator
@@ -91,6 +94,14 @@
 	preSingletons = append(preSingletons, singleton{name, factory})
 }
 
+func RegisterBazelConverterSingletonType(name string, factory SingletonFactory) {
+	bazelConverterSingletons = append(bazelConverterSingletons, singleton{name, factory})
+}
+
+func RegisterBazelConverterPreSingletonType(name string, factory SingletonFactory) {
+	bazelConverterPreSingletons = append(bazelConverterPreSingletons, singleton{name, factory})
+}
+
 type Context struct {
 	*blueprint.Context
 	config Config
@@ -106,13 +117,17 @@
 // singletons, module types and mutators to register for converting Blueprint
 // files to semantically equivalent BUILD files.
 func (ctx *Context) RegisterForBazelConversion() {
+	for _, t := range bazelConverterPreSingletons {
+		ctx.RegisterPreSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
+	}
+
 	for _, t := range moduleTypes {
 		ctx.RegisterModuleType(t.name, ModuleFactoryAdaptor(t.factory))
 	}
 
-	bazelConverterSingleton := singleton{"bp2build", BazelConverterSingleton}
-	ctx.RegisterSingletonType(bazelConverterSingleton.name,
-		SingletonFactoryAdaptor(ctx, bazelConverterSingleton.factory))
+	for _, t := range bazelConverterSingletons {
+		ctx.RegisterSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
+	}
 
 	registerMutatorsForBazelConversion(ctx.Context)
 }
diff --git a/android/variable.go b/android/variable.go
index 7532797..41cd337 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -139,10 +139,6 @@
 			Enabled *bool
 		}
 
-		Experimental_mte struct {
-			Cflags []string `android:"arch_variant"`
-		} `android:"arch_variant"`
-
 		Native_coverage struct {
 			Src          *string  `android:"arch_variant"`
 			Srcs         []string `android:"arch_variant"`
@@ -264,7 +260,9 @@
 
 	DisableScudo *bool `json:",omitempty"`
 
-	Experimental_mte *bool `json:",omitempty"`
+	MemtagHeapExcludePaths      []string `json:",omitempty"`
+	MemtagHeapAsyncIncludePaths []string `json:",omitempty"`
+	MemtagHeapSyncIncludePaths  []string `json:",omitempty"`
 
 	VendorPath    *string `json:",omitempty"`
 	OdmPath       *string `json:",omitempty"`
diff --git a/apex/allowed_deps.txt b/apex/allowed_deps.txt
index 69bf64f..57b18de 100644
--- a/apex/allowed_deps.txt
+++ b/apex/allowed_deps.txt
@@ -384,6 +384,7 @@
 libring(minSdkVersion:29)
 libring-core(minSdkVersion:29)
 librustc_demangle.rust_sysroot(minSdkVersion:29)
+libsdk_proto(minSdkVersion:30)
 libsfplugin_ccodec_utils(minSdkVersion:29)
 libsonivoxwithoutjet(minSdkVersion:29)
 libspeexresampler(minSdkVersion:29)
@@ -466,7 +467,10 @@
 ndk_libunwind(minSdkVersion:16)
 net-utils-device-common(minSdkVersion:29)
 net-utils-framework-common(minSdkVersion:current)
+netd-client(minSdkVersion:29)
+netd_aidl_interface-java(minSdkVersion:29)
 netd_aidl_interface-unstable-java(minSdkVersion:29)
+netd_event_listener_interface-java(minSdkVersion:29)
 netd_event_listener_interface-ndk_platform(minSdkVersion:29)
 netd_event_listener_interface-unstable-ndk_platform(minSdkVersion:29)
 netlink-client(minSdkVersion:29)
diff --git a/apex/apex.go b/apex/apex.go
index a18e34b..5cd18ed 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -168,6 +168,10 @@
 	// used in tests.
 	Test_only_unsigned_payload *bool
 
+	// Whenever apex should be compressed, regardless of product flag used. Should be only
+	// used in tests.
+	Test_only_force_compression *bool
+
 	IsCoverageVariant bool `blueprint:"mutated"`
 
 	// List of sanitizer names that this APEX is enabled for
@@ -1241,6 +1245,11 @@
 	return proptools.Bool(a.properties.Test_only_unsigned_payload)
 }
 
+// See the test_only_force_compression property
+func (a *apexBundle) testOnlyShouldForceCompression() bool {
+	return proptools.Bool(a.properties.Test_only_force_compression)
+}
+
 // These functions are interfacing with cc/sanitizer.go. The entire APEX (along with all of its
 // members) can be sanitized, either forcibly, or by the global configuration. For some of the
 // sanitizers, extra dependencies can be forcibly added as well.
diff --git a/apex/builder.go b/apex/builder.go
index 106302b..bc1b566 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -763,9 +763,13 @@
 	})
 	a.outputFile = signedOutputFile
 
-	// Process APEX compression if enabled
+	// Process APEX compression if enabled or forced
+	if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && a.testOnlyShouldForceCompression() {
+		ctx.PropertyErrorf("test_only_force_compression", "not available")
+		return
+	}
 	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, true)
-	if compressionEnabled && apexType == imageApex {
+	if apexType == imageApex && (compressionEnabled || a.testOnlyShouldForceCompression()) {
 		a.isCompressed = true
 		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex.unsigned")
 
diff --git a/bazel/aquery.go b/bazel/aquery.go
index 69d4fde..404be8c 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -16,6 +16,8 @@
 
 import (
 	"encoding/json"
+	"fmt"
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -24,8 +26,14 @@
 // artifact contains relevant portions of Bazel's aquery proto, Artifact.
 // Represents a single artifact, whether it's a source file or a derived output file.
 type artifact struct {
-	Id       string
-	ExecPath string
+	Id             int
+	PathFragmentId int
+}
+
+type pathFragment struct {
+	Id       int
+	Label    string
+	ParentId int
 }
 
 // KeyValuePair represents Bazel's aquery proto, KeyValuePair.
@@ -38,9 +46,9 @@
 // Represents a data structure containing one or more files. Depsets in Bazel are an efficient
 // data structure for storing large numbers of file paths.
 type depSetOfFiles struct {
-	Id string
+	Id int
 	// TODO(cparsons): Handle non-flat depsets.
-	DirectArtifactIds []string
+	DirectArtifactIds []int
 }
 
 // action contains relevant portions of Bazel's aquery proto, Action.
@@ -48,9 +56,9 @@
 type action struct {
 	Arguments            []string
 	EnvironmentVariables []KeyValuePair
-	InputDepSetIds       []string
+	InputDepSetIds       []int
 	Mnemonic             string
-	OutputIds            []string
+	OutputIds            []int
 }
 
 // actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
@@ -59,6 +67,7 @@
 	Artifacts     []artifact
 	Actions       []action
 	DepSetOfFiles []depSetOfFiles
+	PathFragments []pathFragment
 }
 
 // BuildStatement contains information to register a build statement corresponding (one to one)
@@ -80,11 +89,20 @@
 	var aqueryResult actionGraphContainer
 	json.Unmarshal(aqueryJsonProto, &aqueryResult)
 
-	artifactIdToPath := map[string]string{}
-	for _, artifact := range aqueryResult.Artifacts {
-		artifactIdToPath[artifact.Id] = artifact.ExecPath
+	pathFragments := map[int]pathFragment{}
+	for _, pathFragment := range aqueryResult.PathFragments {
+		pathFragments[pathFragment.Id] = pathFragment
 	}
-	depsetIdToArtifactIds := map[string][]string{}
+	artifactIdToPath := map[int]string{}
+	for _, artifact := range aqueryResult.Artifacts {
+		artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
+		if err != nil {
+			// TODO(cparsons): Better error handling.
+			panic(err.Error())
+		}
+		artifactIdToPath[artifact.Id] = artifactPath
+	}
+	depsetIdToArtifactIds := map[int][]int{}
 	for _, depset := range aqueryResult.DepSetOfFiles {
 		depsetIdToArtifactIds[depset.Id] = depset.DirectArtifactIds
 	}
@@ -114,3 +132,18 @@
 
 	return buildStatements
 }
+
+func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
+	labels := []string{}
+	currId := id
+	// Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
+	for currId > 0 {
+		currFragment, ok := pathFragmentsMap[currId]
+		if !ok {
+			return "", fmt.Errorf("undefined path fragment id '%s'", currId)
+		}
+		labels = append([]string{currFragment.Label}, labels...)
+		currId = currFragment.ParentId
+	}
+	return filepath.Join(labels...), nil
+}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
new file mode 100644
index 0000000..49587f4
--- /dev/null
+++ b/bp2build/Android.bp
@@ -0,0 +1,23 @@
+bootstrap_go_package {
+    name: "soong-bp2build",
+    pkgPath: "android/soong/bp2build",
+    srcs: [
+        "androidbp_to_build_templates.go",
+        "bp2build.go",
+        "build_conversion.go",
+        "bzl_conversion.go",
+        "conversion.go",
+    ],
+    deps: [
+        "soong-android",
+    ],
+    testSrcs: [
+        "build_conversion_test.go",
+        "bzl_conversion_test.go",
+        "conversion_test.go",
+        "testing.go",
+    ],
+    pluginFor: [
+        "soong_build",
+    ],
+}
diff --git a/cmd/soong_build/queryview_templates.go b/bp2build/androidbp_to_build_templates.go
similarity index 98%
rename from cmd/soong_build/queryview_templates.go
rename to bp2build/androidbp_to_build_templates.go
index 359c0d8..75c3ccb 100644
--- a/cmd/soong_build/queryview_templates.go
+++ b/bp2build/androidbp_to_build_templates.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package main
+package bp2build
 
 const (
 	// The default `load` preamble for every generated BUILD file.
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
new file mode 100644
index 0000000..30f298a
--- /dev/null
+++ b/bp2build/bp2build.go
@@ -0,0 +1,77 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"os"
+)
+
+// The Bazel bp2build singleton is responsible for writing .bzl files that are equivalent to
+// Android.bp files that are capable of being built with Bazel.
+func init() {
+	android.RegisterBazelConverterPreSingletonType("androidbp_to_build", AndroidBpToBuildSingleton)
+}
+
+func AndroidBpToBuildSingleton() android.Singleton {
+	return &androidBpToBuildSingleton{
+		name: "bp2build",
+	}
+}
+
+type androidBpToBuildSingleton struct {
+	name      string
+	outputDir android.OutputPath
+}
+
+func (s *androidBpToBuildSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	s.outputDir = android.PathForOutput(ctx, s.name)
+	android.RemoveAllOutputDir(s.outputDir)
+
+	if !ctx.Config().IsEnvTrue("CONVERT_TO_BAZEL") {
+		return
+	}
+
+	ruleShims := CreateRuleShims(android.ModuleTypeFactories())
+
+	buildToTargets := GenerateSoongModuleTargets(ctx)
+
+	filesToWrite := CreateBazelFiles(ruleShims, buildToTargets)
+	for _, f := range filesToWrite {
+		if err := s.writeFile(ctx, f); err != nil {
+			ctx.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err)
+		}
+	}
+}
+
+func (s *androidBpToBuildSingleton) getOutputPath(ctx android.PathContext, dir string) android.OutputPath {
+	return s.outputDir.Join(ctx, dir)
+}
+
+func (s *androidBpToBuildSingleton) writeFile(ctx android.PathContext, f BazelFile) error {
+	return writeReadOnlyFile(ctx, s.getOutputPath(ctx, f.Dir), f.Basename, f.Contents)
+}
+
+// The auto-conversion directory should be read-only, sufficient for bazel query. The files
+// are not intended to be edited by end users.
+func writeReadOnlyFile(ctx android.PathContext, dir android.OutputPath, baseName, content string) error {
+	android.CreateOutputDirIfNonexistent(dir, os.ModePerm)
+	pathToFile := dir.Join(ctx, baseName)
+
+	// 0444 is read-only
+	err := android.WriteFileToOutputDir(pathToFile, []byte(content), 0444)
+
+	return err
+}
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
new file mode 100644
index 0000000..0329685
--- /dev/null
+++ b/bp2build/build_conversion.go
@@ -0,0 +1,305 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+type BazelAttributes struct {
+	Attrs map[string]string
+}
+
+type BazelTarget struct {
+	name    string
+	content string
+}
+
+type bpToBuildContext interface {
+	ModuleName(module blueprint.Module) string
+	ModuleDir(module blueprint.Module) string
+	ModuleSubDir(module blueprint.Module) string
+	ModuleType(module blueprint.Module) string
+
+	VisitAllModulesBlueprint(visit func(blueprint.Module))
+	VisitDirectDeps(module android.Module, visit func(android.Module))
+}
+
+// props is an unsorted map. This function ensures that
+// the generated attributes are sorted to ensure determinism.
+func propsToAttributes(props map[string]string) string {
+	var attributes string
+	for _, propName := range android.SortedStringKeys(props) {
+		if shouldGenerateAttribute(propName) {
+			attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
+		}
+	}
+	return attributes
+}
+
+func GenerateSoongModuleTargets(ctx bpToBuildContext) map[string][]BazelTarget {
+	buildFileToTargets := make(map[string][]BazelTarget)
+	ctx.VisitAllModulesBlueprint(func(m blueprint.Module) {
+		dir := ctx.ModuleDir(m)
+		t := generateSoongModuleTarget(ctx, m)
+		buildFileToTargets[ctx.ModuleDir(m)] = append(buildFileToTargets[dir], t)
+	})
+	return buildFileToTargets
+}
+
+// Convert a module and its deps and props into a Bazel macro/rule
+// representation in the BUILD file.
+func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
+	props := getBuildProperties(ctx, m)
+
+	// TODO(b/163018919): DirectDeps can have duplicate (module, variant)
+	// items, if the modules are added using different DependencyTag. Figure
+	// out the implications of that.
+	depLabels := map[string]bool{}
+	if aModule, ok := m.(android.Module); ok {
+		ctx.VisitDirectDeps(aModule, func(depModule android.Module) {
+			depLabels[qualifiedTargetLabel(ctx, depModule)] = true
+		})
+	}
+	attributes := propsToAttributes(props.Attrs)
+
+	depLabelList := "[\n"
+	for depLabel, _ := range depLabels {
+		depLabelList += fmt.Sprintf("        %q,\n", depLabel)
+	}
+	depLabelList += "    ]"
+
+	targetName := targetNameWithVariant(ctx, m)
+	return BazelTarget{
+		name: targetName,
+		content: fmt.Sprintf(
+			soongModuleTarget,
+			targetName,
+			ctx.ModuleName(m),
+			canonicalizeModuleType(ctx.ModuleType(m)),
+			ctx.ModuleSubDir(m),
+			depLabelList,
+			attributes),
+	}
+}
+
+func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) BazelAttributes {
+	var allProps map[string]string
+	// TODO: this omits properties for blueprint modules (blueprint_go_binary,
+	// bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
+	if aModule, ok := m.(android.Module); ok {
+		allProps = ExtractModuleProperties(aModule)
+	}
+
+	return BazelAttributes{
+		Attrs: allProps,
+	}
+}
+
+// Generically extract module properties and types into a map, keyed by the module property name.
+func ExtractModuleProperties(aModule android.Module) map[string]string {
+	ret := map[string]string{}
+
+	// Iterate over this android.Module's property structs.
+	for _, properties := range aModule.GetProperties() {
+		propertiesValue := reflect.ValueOf(properties)
+		// Check that propertiesValue is a pointer to the Properties struct, like
+		// *cc.BaseLinkerProperties or *java.CompilerProperties.
+		//
+		// propertiesValue can also be type-asserted to the structs to
+		// manipulate internal props, if needed.
+		if isStructPtr(propertiesValue.Type()) {
+			structValue := propertiesValue.Elem()
+			for k, v := range extractStructProperties(structValue, 0) {
+				ret[k] = v
+			}
+		} else {
+			panic(fmt.Errorf(
+				"properties must be a pointer to a struct, got %T",
+				propertiesValue.Interface()))
+		}
+	}
+
+	return ret
+}
+
+func isStructPtr(t reflect.Type) bool {
+	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
+}
+
+// prettyPrint a property value into the equivalent Starlark representation
+// recursively.
+func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
+	if isZero(propertyValue) {
+		// A property value being set or unset actually matters -- Soong does set default
+		// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
+		// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
+		//
+		// In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
+		// value of unset attributes.
+		return "", nil
+	}
+
+	var ret string
+	switch propertyValue.Kind() {
+	case reflect.String:
+		ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
+	case reflect.Bool:
+		ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
+	case reflect.Int, reflect.Uint, reflect.Int64:
+		ret = fmt.Sprintf("%v", propertyValue.Interface())
+	case reflect.Ptr:
+		return prettyPrint(propertyValue.Elem(), indent)
+	case reflect.Slice:
+		ret = "[\n"
+		for i := 0; i < propertyValue.Len(); i++ {
+			indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
+			if err != nil {
+				return "", err
+			}
+
+			if indexedValue != "" {
+				ret += makeIndent(indent + 1)
+				ret += indexedValue
+				ret += ",\n"
+			}
+		}
+		ret += makeIndent(indent)
+		ret += "]"
+	case reflect.Struct:
+		ret = "{\n"
+		// Sort and print the struct props by the key.
+		structProps := extractStructProperties(propertyValue, indent)
+		for _, k := range android.SortedStringKeys(structProps) {
+			ret += makeIndent(indent + 1)
+			ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
+		}
+		ret += makeIndent(indent)
+		ret += "}"
+	case reflect.Interface:
+		// TODO(b/164227191): implement pretty print for interfaces.
+		// Interfaces are used for for arch, multilib and target properties.
+		return "", nil
+	default:
+		return "", fmt.Errorf(
+			"unexpected kind for property struct field: %s", propertyValue.Kind())
+	}
+	return ret, nil
+}
+
+// Converts a reflected property struct value into a map of property names and property values,
+// which each property value correctly pretty-printed and indented at the right nest level,
+// since property structs can be nested. In Starlark, nested structs are represented as nested
+// dicts: https://docs.bazel.build/skylark/lib/dict.html
+func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
+	if structValue.Kind() != reflect.Struct {
+		panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
+	}
+
+	ret := map[string]string{}
+	structType := structValue.Type()
+	for i := 0; i < structValue.NumField(); i++ {
+		field := structType.Field(i)
+		if shouldSkipStructField(field) {
+			continue
+		}
+
+		fieldValue := structValue.Field(i)
+		if isZero(fieldValue) {
+			// Ignore zero-valued fields
+			continue
+		}
+
+		propertyName := proptools.PropertyNameForField(field.Name)
+		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
+		if err != nil {
+			panic(
+				fmt.Errorf(
+					"Error while parsing property: %q. %s",
+					propertyName,
+					err))
+		}
+		if prettyPrintedValue != "" {
+			ret[propertyName] = prettyPrintedValue
+		}
+	}
+
+	return ret
+}
+
+func isZero(value reflect.Value) bool {
+	switch value.Kind() {
+	case reflect.Func, reflect.Map, reflect.Slice:
+		return value.IsNil()
+	case reflect.Array:
+		valueIsZero := true
+		for i := 0; i < value.Len(); i++ {
+			valueIsZero = valueIsZero && isZero(value.Index(i))
+		}
+		return valueIsZero
+	case reflect.Struct:
+		valueIsZero := true
+		for i := 0; i < value.NumField(); i++ {
+			if value.Field(i).CanSet() {
+				valueIsZero = valueIsZero && isZero(value.Field(i))
+			}
+		}
+		return valueIsZero
+	case reflect.Ptr:
+		if !value.IsNil() {
+			return isZero(reflect.Indirect(value))
+		} else {
+			return true
+		}
+	default:
+		zeroValue := reflect.Zero(value.Type())
+		result := value.Interface() == zeroValue.Interface()
+		return result
+	}
+}
+
+func escapeString(s string) string {
+	s = strings.ReplaceAll(s, "\\", "\\\\")
+	return strings.ReplaceAll(s, "\"", "\\\"")
+}
+
+func makeIndent(indent int) string {
+	if indent < 0 {
+		panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
+	}
+	return strings.Repeat("    ", indent)
+}
+
+func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
+	name := ""
+	if c.ModuleSubDir(logicModule) != "" {
+		// TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
+		name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
+	} else {
+		name = c.ModuleName(logicModule)
+	}
+
+	return strings.Replace(name, "//", "", 1)
+}
+
+func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
+	return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
+}
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
new file mode 100644
index 0000000..8230ad8
--- /dev/null
+++ b/bp2build/build_conversion_test.go
@@ -0,0 +1,221 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"testing"
+)
+
+func TestGenerateSoongModuleTargets(t *testing.T) {
+	testCases := []struct {
+		bp                  string
+		expectedBazelTarget string
+	}{
+		{
+			bp: `custom {
+	name: "foo",
+}
+		`,
+			expectedBazelTarget: `soong_module(
+    name = "foo",
+    module_name = "foo",
+    module_type = "custom",
+    module_variant = "",
+    module_deps = [
+    ],
+)`,
+		},
+		{
+			bp: `custom {
+	name: "foo",
+	ramdisk: true,
+}
+		`,
+			expectedBazelTarget: `soong_module(
+    name = "foo",
+    module_name = "foo",
+    module_type = "custom",
+    module_variant = "",
+    module_deps = [
+    ],
+    ramdisk = True,
+)`,
+		},
+		{
+			bp: `custom {
+	name: "foo",
+	owner: "a_string_with\"quotes\"_and_\\backslashes\\\\",
+}
+		`,
+			expectedBazelTarget: `soong_module(
+    name = "foo",
+    module_name = "foo",
+    module_type = "custom",
+    module_variant = "",
+    module_deps = [
+    ],
+    owner = "a_string_with\"quotes\"_and_\\backslashes\\\\",
+)`,
+		},
+		{
+			bp: `custom {
+	name: "foo",
+	required: ["bar"],
+}
+		`,
+			expectedBazelTarget: `soong_module(
+    name = "foo",
+    module_name = "foo",
+    module_type = "custom",
+    module_variant = "",
+    module_deps = [
+    ],
+    required = [
+        "bar",
+    ],
+)`,
+		},
+		{
+			bp: `custom {
+	name: "foo",
+	target_required: ["qux", "bazqux"],
+}
+		`,
+			expectedBazelTarget: `soong_module(
+    name = "foo",
+    module_name = "foo",
+    module_type = "custom",
+    module_variant = "",
+    module_deps = [
+    ],
+    target_required = [
+        "qux",
+        "bazqux",
+    ],
+)`,
+		},
+		{
+			bp: `custom {
+	name: "foo",
+	dist: {
+		targets: ["goal_foo"],
+		tag: ".foo",
+	},
+	dists: [
+		{
+			targets: ["goal_bar"],
+			tag: ".bar",
+		},
+	],
+}
+		`,
+			expectedBazelTarget: `soong_module(
+    name = "foo",
+    module_name = "foo",
+    module_type = "custom",
+    module_variant = "",
+    module_deps = [
+    ],
+    dist = {
+        "tag": ".foo",
+        "targets": [
+            "goal_foo",
+        ],
+    },
+    dists = [
+        {
+            "tag": ".bar",
+            "targets": [
+                "goal_bar",
+            ],
+        },
+    ],
+)`,
+		},
+		{
+			bp: `custom {
+	name: "foo",
+	required: ["bar"],
+	target_required: ["qux", "bazqux"],
+	ramdisk: true,
+	owner: "custom_owner",
+	dists: [
+		{
+			tag: ".tag",
+			targets: ["my_goal"],
+		},
+	],
+}
+		`,
+			expectedBazelTarget: `soong_module(
+    name = "foo",
+    module_name = "foo",
+    module_type = "custom",
+    module_variant = "",
+    module_deps = [
+    ],
+    dists = [
+        {
+            "tag": ".tag",
+            "targets": [
+                "my_goal",
+            ],
+        },
+    ],
+    owner = "custom_owner",
+    ramdisk = True,
+    required = [
+        "bar",
+    ],
+    target_required = [
+        "qux",
+        "bazqux",
+    ],
+)`,
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
+		ctx := android.NewTestContext(config)
+		ctx.RegisterModuleType("custom", customModuleFactory)
+		ctx.Register()
+
+		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+		android.FailIfErrored(t, errs)
+		_, errs = ctx.PrepareBuildActions(config)
+		android.FailIfErrored(t, errs)
+
+		bp2BuildCtx := bp2buildBlueprintWrapContext{
+			bpCtx: ctx.Context.Context,
+		}
+
+		bazelTargets := GenerateSoongModuleTargets(&bp2BuildCtx)[dir]
+		if g, w := len(bazelTargets), 1; g != w {
+			t.Fatalf("Expected %d bazel target, got %d", w, g)
+		}
+
+		actualBazelTarget := bazelTargets[0]
+		if actualBazelTarget.content != testCase.expectedBazelTarget {
+			t.Errorf(
+				"Expected generated Bazel target to be '%s', got '%s'",
+				testCase.expectedBazelTarget,
+				actualBazelTarget,
+			)
+		}
+	}
+}
diff --git a/bp2build/bzl_conversion.go b/bp2build/bzl_conversion.go
new file mode 100644
index 0000000..04c4542
--- /dev/null
+++ b/bp2build/bzl_conversion.go
@@ -0,0 +1,230 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"fmt"
+	"reflect"
+	"runtime"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	// An allowlist of prop types that are surfaced from module props to rule
+	// attributes. (nested) dictionaries are notably absent here, because while
+	// Soong supports multi value typed and nested dictionaries, Bazel's rule
+	// attr() API supports only single-level string_dicts.
+	allowedPropTypes = map[string]bool{
+		"int":         true, // e.g. 42
+		"bool":        true, // e.g. True
+		"string_list": true, // e.g. ["a", "b"]
+		"string":      true, // e.g. "a"
+	}
+)
+
+type rule struct {
+	name  string
+	attrs string
+}
+
+type RuleShim struct {
+	// The rule class shims contained in a bzl file. e.g. ["cc_object", "cc_library", ..]
+	rules []string
+
+	// The generated string content of the bzl file.
+	content string
+}
+
+// Create <module>.bzl containing Bazel rule shims for every module type available in Soong and
+// user-specified Go plugins.
+//
+// This function reuses documentation generation APIs to ensure parity between modules-as-docs
+// and modules-as-code, including the names and types of morule properties.
+func CreateRuleShims(moduleTypeFactories map[string]android.ModuleFactory) map[string]RuleShim {
+	ruleShims := map[string]RuleShim{}
+	for pkg, rules := range generateRules(moduleTypeFactories) {
+		shim := RuleShim{
+			rules: make([]string, 0, len(rules)),
+		}
+		shim.content = "load(\"//build/bazel/queryview_rules:providers.bzl\", \"SoongModuleInfo\")\n"
+
+		bzlFileName := strings.ReplaceAll(pkg, "android/soong/", "")
+		bzlFileName = strings.ReplaceAll(bzlFileName, ".", "_")
+		bzlFileName = strings.ReplaceAll(bzlFileName, "/", "_")
+
+		for _, r := range rules {
+			shim.content += fmt.Sprintf(moduleRuleShim, r.name, r.attrs)
+			shim.rules = append(shim.rules, r.name)
+		}
+		sort.Strings(shim.rules)
+		ruleShims[bzlFileName] = shim
+	}
+	return ruleShims
+}
+
+// Generate the content of soong_module.bzl with the rule shim load statements
+// and mapping of module_type to rule shim map for every module type in Soong.
+func generateSoongModuleBzl(bzlLoads map[string]RuleShim) string {
+	var loadStmts string
+	var moduleRuleMap string
+	for _, bzlFileName := range android.SortedStringKeys(bzlLoads) {
+		loadStmt := "load(\"//build/bazel/queryview_rules:"
+		loadStmt += bzlFileName
+		loadStmt += ".bzl\""
+		ruleShim := bzlLoads[bzlFileName]
+		for _, rule := range ruleShim.rules {
+			loadStmt += fmt.Sprintf(", %q", rule)
+			moduleRuleMap += "    \"" + rule + "\": " + rule + ",\n"
+		}
+		loadStmt += ")\n"
+		loadStmts += loadStmt
+	}
+
+	return fmt.Sprintf(soongModuleBzl, loadStmts, moduleRuleMap)
+}
+
+func generateRules(moduleTypeFactories map[string]android.ModuleFactory) map[string][]rule {
+	// TODO: add shims for bootstrap/blueprint go modules types
+
+	rules := make(map[string][]rule)
+	// TODO: allow registration of a bzl rule when registring a factory
+	for _, moduleType := range android.SortedStringKeys(moduleTypeFactories) {
+		factory := moduleTypeFactories[moduleType]
+		factoryName := runtime.FuncForPC(reflect.ValueOf(factory).Pointer()).Name()
+		pkg := strings.Split(factoryName, ".")[0]
+		attrs := `{
+        "module_name": attr.string(mandatory = True),
+        "module_variant": attr.string(),
+        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
+`
+		attrs += getAttributes(factory)
+		attrs += "    },"
+
+		r := rule{
+			name:  canonicalizeModuleType(moduleType),
+			attrs: attrs,
+		}
+
+		rules[pkg] = append(rules[pkg], r)
+	}
+	return rules
+}
+
+type property struct {
+	name             string
+	starlarkAttrType string
+	properties       []property
+}
+
+const (
+	attributeIndent = "        "
+)
+
+func (p *property) attributeString() string {
+	if !shouldGenerateAttribute(p.name) {
+		return ""
+	}
+
+	if _, ok := allowedPropTypes[p.starlarkAttrType]; !ok {
+		// a struct -- let's just comment out sub-props
+		s := fmt.Sprintf(attributeIndent+"# %s start\n", p.name)
+		for _, nestedP := range p.properties {
+			s += "# " + nestedP.attributeString()
+		}
+		s += fmt.Sprintf(attributeIndent+"# %s end\n", p.name)
+		return s
+	}
+	return fmt.Sprintf(attributeIndent+"%q: attr.%s(),\n", p.name, p.starlarkAttrType)
+}
+
+func extractPropertyDescriptionsFromStruct(structType reflect.Type) []property {
+	properties := make([]property, 0)
+	for i := 0; i < structType.NumField(); i++ {
+		field := structType.Field(i)
+		if shouldSkipStructField(field) {
+			continue
+		}
+
+		properties = append(properties, extractPropertyDescriptions(field.Name, field.Type)...)
+	}
+	return properties
+}
+
+func extractPropertyDescriptions(name string, t reflect.Type) []property {
+	name = proptools.PropertyNameForField(name)
+
+	// TODO: handle android:paths tags, they should be changed to label types
+
+	starlarkAttrType := fmt.Sprintf("%s", t.Name())
+	props := make([]property, 0)
+
+	switch t.Kind() {
+	case reflect.Bool, reflect.String:
+		// do nothing
+	case reflect.Uint, reflect.Int, reflect.Int64:
+		starlarkAttrType = "int"
+	case reflect.Slice:
+		if t.Elem().Kind() != reflect.String {
+			// TODO: handle lists of non-strings (currently only list of Dist)
+			return []property{}
+		}
+		starlarkAttrType = "string_list"
+	case reflect.Struct:
+		props = extractPropertyDescriptionsFromStruct(t)
+	case reflect.Ptr:
+		return extractPropertyDescriptions(name, t.Elem())
+	case reflect.Interface:
+		// Interfaces are used for for arch, multilib and target properties, which are handled at runtime.
+		// These will need to be handled in a bazel-specific version of the arch mutator.
+		return []property{}
+	}
+
+	prop := property{
+		name:             name,
+		starlarkAttrType: starlarkAttrType,
+		properties:       props,
+	}
+
+	return []property{prop}
+}
+
+func getPropertyDescriptions(props []interface{}) []property {
+	// there may be duplicate properties, e.g. from defaults libraries
+	propertiesByName := make(map[string]property)
+	for _, p := range props {
+		for _, prop := range extractPropertyDescriptionsFromStruct(reflect.ValueOf(p).Elem().Type()) {
+			propertiesByName[prop.name] = prop
+		}
+	}
+
+	properties := make([]property, 0, len(propertiesByName))
+	for _, key := range android.SortedStringKeys(propertiesByName) {
+		properties = append(properties, propertiesByName[key])
+	}
+
+	return properties
+}
+
+func getAttributes(factory android.ModuleFactory) string {
+	attrs := ""
+	for _, p := range getPropertyDescriptions(factory().GetProperties()) {
+		attrs += p.attributeString()
+	}
+	return attrs
+}
diff --git a/bp2build/bzl_conversion_test.go b/bp2build/bzl_conversion_test.go
new file mode 100644
index 0000000..8bea3f6
--- /dev/null
+++ b/bp2build/bzl_conversion_test.go
@@ -0,0 +1,208 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+)
+
+var buildDir string
+
+func setUp() {
+	var err error
+	buildDir, err = ioutil.TempDir("", "bazel_queryview_test")
+	if err != nil {
+		panic(err)
+	}
+}
+
+func tearDown() {
+	os.RemoveAll(buildDir)
+}
+
+func TestMain(m *testing.M) {
+	run := func() int {
+		setUp()
+		defer tearDown()
+
+		return m.Run()
+	}
+
+	os.Exit(run())
+}
+
+func TestGenerateModuleRuleShims(t *testing.T) {
+	moduleTypeFactories := map[string]android.ModuleFactory{
+		"custom":          customModuleFactoryBase,
+		"custom_test":     customTestModuleFactoryBase,
+		"custom_defaults": customDefaultsModuleFactoryBasic,
+	}
+	ruleShims := CreateRuleShims(moduleTypeFactories)
+
+	if len(ruleShims) != 1 {
+		t.Errorf("Expected to generate 1 rule shim, but got %d", len(ruleShims))
+	}
+
+	ruleShim := ruleShims["bp2build"]
+	expectedRules := []string{
+		"custom",
+		"custom_defaults",
+		"custom_test_",
+	}
+
+	if len(ruleShim.rules) != len(expectedRules) {
+		t.Errorf("Expected %d rules, but got %d", len(expectedRules), len(ruleShim.rules))
+	}
+
+	for i, rule := range ruleShim.rules {
+		if rule != expectedRules[i] {
+			t.Errorf("Expected rule shim to contain %s, but got %s", expectedRules[i], rule)
+		}
+	}
+	expectedBzl := `load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
+
+def _custom_impl(ctx):
+    return [SoongModuleInfo()]
+
+custom = rule(
+    implementation = _custom_impl,
+    attrs = {
+        "module_name": attr.string(mandatory = True),
+        "module_variant": attr.string(),
+        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
+        "bool_prop": attr.bool(),
+        "bool_ptr_prop": attr.bool(),
+        "int64_ptr_prop": attr.int(),
+        # nested_props start
+#         "nested_prop": attr.string(),
+        # nested_props end
+        # nested_props_ptr start
+#         "nested_prop": attr.string(),
+        # nested_props_ptr end
+        "string_list_prop": attr.string_list(),
+        "string_prop": attr.string(),
+        "string_ptr_prop": attr.string(),
+    },
+)
+
+def _custom_defaults_impl(ctx):
+    return [SoongModuleInfo()]
+
+custom_defaults = rule(
+    implementation = _custom_defaults_impl,
+    attrs = {
+        "module_name": attr.string(mandatory = True),
+        "module_variant": attr.string(),
+        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
+        "bool_prop": attr.bool(),
+        "bool_ptr_prop": attr.bool(),
+        "int64_ptr_prop": attr.int(),
+        # nested_props start
+#         "nested_prop": attr.string(),
+        # nested_props end
+        # nested_props_ptr start
+#         "nested_prop": attr.string(),
+        # nested_props_ptr end
+        "string_list_prop": attr.string_list(),
+        "string_prop": attr.string(),
+        "string_ptr_prop": attr.string(),
+    },
+)
+
+def _custom_test__impl(ctx):
+    return [SoongModuleInfo()]
+
+custom_test_ = rule(
+    implementation = _custom_test__impl,
+    attrs = {
+        "module_name": attr.string(mandatory = True),
+        "module_variant": attr.string(),
+        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
+        "bool_prop": attr.bool(),
+        "bool_ptr_prop": attr.bool(),
+        "int64_ptr_prop": attr.int(),
+        # nested_props start
+#         "nested_prop": attr.string(),
+        # nested_props end
+        # nested_props_ptr start
+#         "nested_prop": attr.string(),
+        # nested_props_ptr end
+        "string_list_prop": attr.string_list(),
+        "string_prop": attr.string(),
+        "string_ptr_prop": attr.string(),
+        # test_prop start
+#         "test_string_prop": attr.string(),
+        # test_prop end
+    },
+)
+`
+
+	if ruleShim.content != expectedBzl {
+		t.Errorf(
+			"Expected the generated rule shim bzl to be:\n%s\nbut got:\n%s",
+			expectedBzl,
+			ruleShim.content)
+	}
+}
+
+func TestGenerateSoongModuleBzl(t *testing.T) {
+	ruleShims := map[string]RuleShim{
+		"file1": RuleShim{
+			rules:   []string{"a", "b"},
+			content: "irrelevant",
+		},
+		"file2": RuleShim{
+			rules:   []string{"c", "d"},
+			content: "irrelevant",
+		},
+	}
+	files := CreateBazelFiles(ruleShims, make(map[string][]BazelTarget))
+
+	var actualSoongModuleBzl BazelFile
+	for _, f := range files {
+		if f.Basename == "soong_module.bzl" {
+			actualSoongModuleBzl = f
+		}
+	}
+
+	expectedLoad := `load("//build/bazel/queryview_rules:file1.bzl", "a", "b")
+load("//build/bazel/queryview_rules:file2.bzl", "c", "d")
+`
+	expectedRuleMap := `soong_module_rule_map = {
+    "a": a,
+    "b": b,
+    "c": c,
+    "d": d,
+}`
+	if !strings.Contains(actualSoongModuleBzl.Contents, expectedLoad) {
+		t.Errorf(
+			"Generated soong_module.bzl:\n\n%s\n\n"+
+				"Could not find the load statement in the generated soong_module.bzl:\n%s",
+			actualSoongModuleBzl.Contents,
+			expectedLoad)
+	}
+
+	if !strings.Contains(actualSoongModuleBzl.Contents, expectedRuleMap) {
+		t.Errorf(
+			"Generated soong_module.bzl:\n\n%s\n\n"+
+				"Could not find the module -> rule map in the generated soong_module.bzl:\n%s",
+			actualSoongModuleBzl.Contents,
+			expectedRuleMap)
+	}
+}
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
new file mode 100644
index 0000000..cdfb38b
--- /dev/null
+++ b/bp2build/conversion.go
@@ -0,0 +1,118 @@
+package bp2build
+
+import (
+	"android/soong/android"
+	"reflect"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+type BazelFile struct {
+	Dir      string
+	Basename string
+	Contents string
+}
+
+func CreateBazelFiles(
+	ruleShims map[string]RuleShim,
+	buildToTargets map[string][]BazelTarget) []BazelFile {
+	files := make([]BazelFile, 0, len(ruleShims)+len(buildToTargets)+numAdditionalFiles)
+
+	// Write top level files: WORKSPACE and BUILD. These files are empty.
+	files = append(files, newFile("", "WORKSPACE", ""))
+	// Used to denote that the top level directory is a package.
+	files = append(files, newFile("", "BUILD", ""))
+
+	files = append(files, newFile(bazelRulesSubDir, "BUILD", ""))
+	files = append(files, newFile(bazelRulesSubDir, "providers.bzl", providersBzl))
+
+	for bzlFileName, ruleShim := range ruleShims {
+		files = append(files, newFile(bazelRulesSubDir, bzlFileName+".bzl", ruleShim.content))
+	}
+	files = append(files, newFile(bazelRulesSubDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims)))
+
+	files = append(files, createBuildFiles(buildToTargets)...)
+
+	return files
+}
+
+func createBuildFiles(buildToTargets map[string][]BazelTarget) []BazelFile {
+	files := make([]BazelFile, 0, len(buildToTargets))
+	for _, dir := range android.SortedStringKeys(buildToTargets) {
+		content := soongModuleLoad
+		targets := buildToTargets[dir]
+		sort.Slice(targets, func(i, j int) bool { return targets[i].name < targets[j].name })
+		for _, t := range targets {
+			content += "\n\n"
+			content += t.content
+		}
+		files = append(files, newFile(dir, "BUILD.bazel", content))
+	}
+	return files
+}
+
+func newFile(dir, basename, content string) BazelFile {
+	return BazelFile{
+		Dir:      dir,
+		Basename: basename,
+		Contents: content,
+	}
+}
+
+const (
+	bazelRulesSubDir = "build/bazel/queryview_rules"
+
+	// additional files:
+	//  * workspace file
+	//  * base BUILD file
+	//  * rules BUILD file
+	//  * rules providers.bzl file
+	//  * rules soong_module.bzl file
+	numAdditionalFiles = 5
+)
+
+var (
+	// Certain module property names are blocklisted/ignored here, for the reasons commented.
+	ignoredPropNames = map[string]bool{
+		"name":       true, // redundant, since this is explicitly generated for every target
+		"from":       true, // reserved keyword
+		"in":         true, // reserved keyword
+		"arch":       true, // interface prop type is not supported yet.
+		"multilib":   true, // interface prop type is not supported yet.
+		"target":     true, // interface prop type is not supported yet.
+		"visibility": true, // Bazel has native visibility semantics. Handle later.
+		"features":   true, // There is already a built-in attribute 'features' which cannot be overridden.
+	}
+)
+
+func shouldGenerateAttribute(prop string) bool {
+	return !ignoredPropNames[prop]
+}
+
+func shouldSkipStructField(field reflect.StructField) bool {
+	if field.PkgPath != "" {
+		// Skip unexported fields. Some properties are
+		// internal to Soong only, and these fields do not have PkgPath.
+		return true
+	}
+	// fields with tag `blueprint:"mutated"` are exported to enable modification in mutators, etc
+	// but cannot be set in a .bp file
+	if proptools.HasTag(field, "blueprint", "mutated") {
+		return true
+	}
+	return false
+}
+
+// FIXME(b/168089390): In Bazel, rules ending with "_test" needs to be marked as
+// testonly = True, forcing other rules that depend on _test rules to also be
+// marked as testonly = True. This semantic constraint is not present in Soong.
+// To work around, rename "*_test" rules to "*_test_".
+func canonicalizeModuleType(moduleName string) string {
+	if strings.HasSuffix(moduleName, "_test") {
+		return moduleName + "_"
+	}
+
+	return moduleName
+}
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
new file mode 100644
index 0000000..a38fa6a
--- /dev/null
+++ b/bp2build/conversion_test.go
@@ -0,0 +1,73 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"sort"
+	"testing"
+)
+
+func TestCreateBazelFiles_AddsTopLevelFiles(t *testing.T) {
+	files := CreateBazelFiles(map[string]RuleShim{}, map[string][]BazelTarget{})
+	expectedFilePaths := []struct {
+		dir      string
+		basename string
+	}{
+		{
+			dir:      "",
+			basename: "BUILD",
+		},
+		{
+			dir:      "",
+			basename: "WORKSPACE",
+		},
+		{
+			dir:      bazelRulesSubDir,
+			basename: "BUILD",
+		},
+		{
+			dir:      bazelRulesSubDir,
+			basename: "providers.bzl",
+		},
+		{
+			dir:      bazelRulesSubDir,
+			basename: "soong_module.bzl",
+		},
+	}
+
+	if g, w := len(files), len(expectedFilePaths); g != w {
+		t.Errorf("Expected %d files, got %d", w, g)
+	}
+
+	sort.Slice(files, func(i, j int) bool {
+		if dir1, dir2 := files[i].Dir, files[j].Dir; dir1 == dir2 {
+			return files[i].Basename < files[j].Basename
+		} else {
+			return dir1 < dir2
+		}
+	})
+
+	for i := range files {
+		if g, w := files[i], expectedFilePaths[i]; g.Dir != w.dir || g.Basename != w.basename {
+			t.Errorf("Did not find expected file %s/%s", g.Dir, g.Basename)
+		} else if g.Basename == "BUILD" || g.Basename == "WORKSPACE" {
+			if g.Contents != "" {
+				t.Errorf("Expected %s to have no content.", g)
+			}
+		} else if g.Contents == "" {
+			t.Errorf("Contents of %s unexpected empty.", g)
+		}
+	}
+}
diff --git a/bp2build/testing.go b/bp2build/testing.go
new file mode 100644
index 0000000..160412d
--- /dev/null
+++ b/bp2build/testing.go
@@ -0,0 +1,136 @@
+package bp2build
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+type nestedProps struct {
+	Nested_prop string
+}
+
+type customProps struct {
+	Bool_prop     bool
+	Bool_ptr_prop *bool
+	// Ensure that properties tagged `blueprint:mutated` are omitted
+	Int_prop         int `blueprint:"mutated"`
+	Int64_ptr_prop   *int64
+	String_prop      string
+	String_ptr_prop  *string
+	String_list_prop []string
+
+	Nested_props     nestedProps
+	Nested_props_ptr *nestedProps
+}
+
+type customModule struct {
+	android.ModuleBase
+
+	props customProps
+}
+
+// OutputFiles is needed because some instances of this module use dist with a
+// tag property which requires the module implements OutputFileProducer.
+func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
+	return android.PathsForTesting("path" + tag), nil
+}
+
+func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// nothing for now.
+}
+
+func customModuleFactoryBase() android.Module {
+	module := &customModule{}
+	module.AddProperties(&module.props)
+	return module
+}
+
+func customModuleFactory() android.Module {
+	m := customModuleFactoryBase()
+	android.InitAndroidModule(m)
+	return m
+}
+
+type testProps struct {
+	Test_prop struct {
+		Test_string_prop string
+	}
+}
+
+type customTestModule struct {
+	android.ModuleBase
+
+	props      customProps
+	test_props testProps
+}
+
+func (m *customTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// nothing for now.
+}
+
+func customTestModuleFactoryBase() android.Module {
+	m := &customTestModule{}
+	m.AddProperties(&m.props)
+	m.AddProperties(&m.test_props)
+	return m
+}
+
+func customTestModuleFactory() android.Module {
+	m := customTestModuleFactoryBase()
+	android.InitAndroidModule(m)
+	return m
+}
+
+type customDefaultsModule struct {
+	android.ModuleBase
+	android.DefaultsModuleBase
+}
+
+func customDefaultsModuleFactoryBase() android.DefaultsModule {
+	module := &customDefaultsModule{}
+	module.AddProperties(&customProps{})
+	return module
+}
+
+func customDefaultsModuleFactoryBasic() android.Module {
+	return customDefaultsModuleFactoryBase()
+}
+
+func customDefaultsModuleFactory() android.Module {
+	m := customDefaultsModuleFactoryBase()
+	android.InitDefaultsModule(m)
+	return m
+}
+
+type bp2buildBlueprintWrapContext struct {
+	bpCtx *blueprint.Context
+}
+
+func (ctx *bp2buildBlueprintWrapContext) ModuleName(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleName(module)
+}
+
+func (ctx *bp2buildBlueprintWrapContext) ModuleDir(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleDir(module)
+}
+
+func (ctx *bp2buildBlueprintWrapContext) ModuleSubDir(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleSubDir(module)
+}
+
+func (ctx *bp2buildBlueprintWrapContext) ModuleType(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleType(module)
+}
+
+func (ctx *bp2buildBlueprintWrapContext) VisitAllModulesBlueprint(visit func(blueprint.Module)) {
+	ctx.bpCtx.VisitAllModules(visit)
+}
+
+func (ctx *bp2buildBlueprintWrapContext) VisitDirectDeps(module android.Module, visit func(android.Module)) {
+	ctx.bpCtx.VisitDirectDeps(module, func(m blueprint.Module) {
+		if aModule, ok := m.(android.Module); ok {
+			visit(aModule)
+		}
+	})
+}
diff --git a/cc/cc.go b/cc/cc.go
index f45b654..ca2bd4f 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -57,14 +57,14 @@
 	})
 
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.TopDown("asan_deps", sanitizerDepsMutator(asan))
-		ctx.BottomUp("asan", sanitizerMutator(asan)).Parallel()
+		ctx.TopDown("asan_deps", sanitizerDepsMutator(Asan))
+		ctx.BottomUp("asan", sanitizerMutator(Asan)).Parallel()
 
 		ctx.TopDown("hwasan_deps", sanitizerDepsMutator(hwasan))
 		ctx.BottomUp("hwasan", sanitizerMutator(hwasan)).Parallel()
 
-		ctx.TopDown("fuzzer_deps", sanitizerDepsMutator(fuzzer))
-		ctx.BottomUp("fuzzer", sanitizerMutator(fuzzer)).Parallel()
+		ctx.TopDown("fuzzer_deps", sanitizerDepsMutator(Fuzzer))
+		ctx.BottomUp("fuzzer", sanitizerMutator(Fuzzer)).Parallel()
 
 		// cfi mutator shouldn't run before sanitizers that return true for
 		// incompatibleWithCfi()
@@ -369,10 +369,7 @@
 	// If set to false, this module becomes inaccessible from /vendor modules.
 	//
 	// The modules with vndk: {enabled: true} must define 'vendor_available'
-	// to either 'true' or 'false'. In this case, 'vendor_available: false' has
-	// a different meaning than that of non-VNDK modules.
-	// 'vendor_available: false' for a VNDK module means 'VNDK-private' that
-	// can only be depended on by VNDK libraries, not by non-VNDK vendor modules.
+	// to 'true'.
 	//
 	// Nothing happens if BOARD_VNDK_VERSION isn't set in the BoardConfig.mk
 	Vendor_available *bool
@@ -394,13 +391,6 @@
 	// vndk: {enabled: true} don't have to define 'product_available'. The VNDK
 	// library without 'product_available' may not be depended on by any other
 	// modules that has product variants including the product available VNDKs.
-	// However, for the modules with vndk: {enabled: true},
-	// 'product_available: false' creates the product variant that is available
-	// only for the other product available VNDK modules but not by non-VNDK
-	// product modules.
-	// In the case of the modules with vndk: {enabled: true}, if
-	// 'product_available' is defined, it must have the same value with the
-	// 'vendor_available'.
 	//
 	// Nothing happens if BOARD_VNDK_VERSION isn't set in the BoardConfig.mk
 	// and PRODUCT_PRODUCT_VNDK_VERSION isn't set.
@@ -430,6 +420,7 @@
 type ModuleContextIntf interface {
 	static() bool
 	staticBinary() bool
+	testBinary() bool
 	header() bool
 	binary() bool
 	object() bool
@@ -785,6 +776,14 @@
 	hideApexVariantFromMake bool
 }
 
+func (c *Module) SetPreventInstall() {
+	c.Properties.PreventInstall = true
+}
+
+func (c *Module) SetHideFromMake() {
+	c.Properties.HideFromMake = true
+}
+
 func (c *Module) Toc() android.OptionalPath {
 	if c.linker != nil {
 		if library, ok := c.linker.(libraryInterface); ok {
@@ -1026,7 +1025,7 @@
 
 // Returns true for dependency roots (binaries)
 // TODO(ccross): also handle dlopenable libraries
-func (c *Module) isDependencyRoot() bool {
+func (c *Module) IsDependencyRoot() bool {
 	if root, ok := c.linker.(interface {
 		isDependencyRoot() bool
 	}); ok {
@@ -1263,8 +1262,12 @@
 	return ctx.mod.staticBinary()
 }
 
+func (ctx *moduleContextImpl) testBinary() bool {
+	return ctx.mod.testBinary()
+}
+
 func (ctx *moduleContextImpl) header() bool {
-	return ctx.mod.header()
+	return ctx.mod.Header()
 }
 
 func (ctx *moduleContextImpl) binary() bool {
@@ -1421,6 +1424,10 @@
 	return nil
 }
 
+func (c *Module) IsPrebuilt() bool {
+	return c.Prebuilt() != nil
+}
+
 func (c *Module) Name() string {
 	name := c.ModuleBase.Name()
 	if p, ok := c.linker.(interface {
@@ -2847,7 +2854,7 @@
 				return baseName + ".vendor"
 			}
 
-			if c.inVendor() && vendorSuffixModules[baseName] {
+			if c.InVendor() && vendorSuffixModules[baseName] {
 				return baseName + ".vendor"
 			} else if c.InRecovery() && recoverySuffixModules[baseName] {
 				return baseName + ".recovery"
@@ -2959,7 +2966,17 @@
 	return false
 }
 
-func (c *Module) header() bool {
+func (c *Module) testBinary() bool {
+	if test, ok := c.linker.(interface {
+		testBinary() bool
+	}); ok {
+		return test.testBinary()
+	}
+	return false
+}
+
+// Header returns true if the module is a header-only variant. (See cc/library.go header()).
+func (c *Module) Header() bool {
 	if h, ok := c.linker.(interface {
 		header() bool
 	}); ok {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index be03913..3399e93 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -4478,3 +4478,138 @@
 		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
 	}
 }
+
+func checkHasImplicitDep(t *testing.T, m android.TestingModule, name string) {
+	implicits := m.Rule("ld").Implicits
+	for _, lib := range implicits {
+		if strings.Contains(lib.Rel(), name) {
+			return
+		}
+	}
+
+	t.Errorf("%q is not found in implicit deps of module %q", name, m.Module().(*Module).Name())
+}
+
+func checkDoesNotHaveImplicitDep(t *testing.T, m android.TestingModule, name string) {
+	implicits := m.Rule("ld").Implicits
+	for _, lib := range implicits {
+		if strings.Contains(lib.Rel(), name) {
+			t.Errorf("%q is found in implicit deps of module %q", name, m.Module().(*Module).Name())
+		}
+	}
+}
+
+func TestSanitizeMemtagHeap(t *testing.T) {
+	rootBp := `
+		cc_library_static {
+			name: "libstatic",
+			sanitize: { memtag_heap: true },
+		}
+
+		cc_library_shared {
+			name: "libshared",
+			sanitize: { memtag_heap: true },
+		}
+
+		cc_library {
+			name: "libboth",
+			sanitize: { memtag_heap: true },
+		}
+
+		cc_binary {
+			name: "binary",
+			shared_libs: [ "libshared" ],
+			static_libs: [ "libstatic" ],
+		}
+
+		cc_binary {
+			name: "binary_true",
+			sanitize: { memtag_heap: true },
+		}
+
+		cc_binary {
+			name: "binary_true_sync",
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }, },
+		}
+
+		cc_binary {
+			name: "binary_false",
+			sanitize: { memtag_heap: false },
+		}
+
+		cc_test {
+			name: "test",
+			gtest: false,
+		}
+
+		cc_test {
+			name: "test_true",
+			gtest: false,
+			sanitize: { memtag_heap: true },
+		}
+
+		cc_test {
+			name: "test_false",
+			gtest: false,
+			sanitize: { memtag_heap: false },
+		}
+
+		cc_test {
+			name: "test_true_async",
+			gtest: false,
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		`
+
+	subdirAsyncBp := `
+		cc_binary {
+			name: "binary_async",
+		}
+		`
+
+	subdirSyncBp := `
+		cc_binary {
+			name: "binary_sync",
+		}
+		`
+
+	mockFS := map[string][]byte{
+		"subdir_async/Android.bp": []byte(subdirAsyncBp),
+		"subdir_sync/Android.bp":  []byte(subdirSyncBp),
+	}
+
+	config := TestConfig(buildDir, android.Android, nil, rootBp, mockFS)
+	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.MemtagHeapAsyncIncludePaths = []string{"subdir_async"}
+	config.TestProductVariables.MemtagHeapSyncIncludePaths = []string{"subdir_sync"}
+	ctx := CreateTestContext(config)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp", "subdir_sync/Android.bp", "subdir_async/Android.bp"})
+	android.FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	android.FailIfErrored(t, errs)
+
+	variant := "android_arm64_armv8-a"
+	note_async := "note_memtag_heap_async"
+	note_sync := "note_memtag_heap_sync"
+	note_any := "note_memtag_"
+
+	checkDoesNotHaveImplicitDep(t, ctx.ModuleForTests("libshared", "android_arm64_armv8-a_shared"), note_any)
+	checkDoesNotHaveImplicitDep(t, ctx.ModuleForTests("libboth", "android_arm64_armv8-a_shared"), note_any)
+
+	checkDoesNotHaveImplicitDep(t, ctx.ModuleForTests("binary", variant), note_any)
+	checkHasImplicitDep(t, ctx.ModuleForTests("binary_true", variant), note_async)
+	checkHasImplicitDep(t, ctx.ModuleForTests("binary_true_sync", variant), note_sync)
+	checkDoesNotHaveImplicitDep(t, ctx.ModuleForTests("binary_false", variant), note_any)
+
+	checkHasImplicitDep(t, ctx.ModuleForTests("test", variant), note_sync)
+	checkHasImplicitDep(t, ctx.ModuleForTests("test_true", variant), note_async)
+	checkDoesNotHaveImplicitDep(t, ctx.ModuleForTests("test_false", variant), note_any)
+	checkHasImplicitDep(t, ctx.ModuleForTests("test_true_async", variant), note_async)
+
+	checkHasImplicitDep(t, ctx.ModuleForTests("binary_async", variant), note_async)
+	checkHasImplicitDep(t, ctx.ModuleForTests("binary_sync", variant), note_sync)
+}
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index 4bcad4b..0aa6866 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -18,11 +18,13 @@
 // For these libraries, the vendor variants must be installed even if the device
 // has VndkUseCoreVariant set.
 var VndkMustUseVendorVariantList = []string{
+	"android.hardware.authsecret-unstable-ndk_platform",
 	"android.hardware.automotive.occupant_awareness-ndk_platform",
 	"android.hardware.light-ndk_platform",
 	"android.hardware.identity-ndk_platform",
 	"android.hardware.nfc@1.2",
 	"android.hardware.memtrack-unstable-ndk_platform",
+	"android.hardware.oemlock-unstable-ndk_platform",
 	"android.hardware.power-ndk_platform",
 	"android.hardware.rebootescrow-ndk_platform",
 	"android.hardware.security.keymint-unstable-ndk_platform",
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 6b17c48..d7da5ab 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -41,6 +41,12 @@
 	// Specify who should be acknowledged for CVEs in the Android Security
 	// Bulletin.
 	Acknowledgement []string `json:"acknowledgement,omitempty"`
+	// Additional options to be passed to libfuzzer when run in Haiku.
+	Libfuzzer_options []string `json:"libfuzzer_options,omitempty"`
+	// Additional options to be passed to HWASAN when running on-device in Haiku.
+	Hwasan_options []string `json:"hwasan_options,omitempty"`
+	// Additional options to be passed to HWASAN when running on host in Haiku.
+	Asan_options []string `json:"asan_options,omitempty"`
 }
 
 func (f *FuzzConfig) String() string {
@@ -315,7 +321,7 @@
 	module, binary := NewBinary(hod)
 
 	binary.baseInstaller = NewFuzzInstaller()
-	module.sanitize.SetSanitizer(fuzzer, true)
+	module.sanitize.SetSanitizer(Fuzzer, true)
 
 	fuzz := &fuzzBinary{
 		binaryDecorator: binary,
diff --git a/cc/image.go b/cc/image.go
index 86c7e60..f89194f 100644
--- a/cc/image.go
+++ b/cc/image.go
@@ -26,36 +26,18 @@
 
 var _ android.ImageInterface = (*Module)(nil)
 
-type imageVariantType string
+type ImageVariantType string
 
 const (
-	coreImageVariant          imageVariantType = "core"
-	vendorImageVariant        imageVariantType = "vendor"
-	productImageVariant       imageVariantType = "product"
-	ramdiskImageVariant       imageVariantType = "ramdisk"
-	vendorRamdiskImageVariant imageVariantType = "vendor_ramdisk"
-	recoveryImageVariant      imageVariantType = "recovery"
-	hostImageVariant          imageVariantType = "host"
+	coreImageVariant          ImageVariantType = "core"
+	vendorImageVariant        ImageVariantType = "vendor"
+	productImageVariant       ImageVariantType = "product"
+	ramdiskImageVariant       ImageVariantType = "ramdisk"
+	vendorRamdiskImageVariant ImageVariantType = "vendor_ramdisk"
+	recoveryImageVariant      ImageVariantType = "recovery"
+	hostImageVariant          ImageVariantType = "host"
 )
 
-func (c *Module) getImageVariantType() imageVariantType {
-	if c.Host() {
-		return hostImageVariant
-	} else if c.inVendor() {
-		return vendorImageVariant
-	} else if c.InProduct() {
-		return productImageVariant
-	} else if c.InRamdisk() {
-		return ramdiskImageVariant
-	} else if c.InVendorRamdisk() {
-		return vendorRamdiskImageVariant
-	} else if c.InRecovery() {
-		return recoveryImageVariant
-	} else {
-		return coreImageVariant
-	}
-}
-
 const (
 	// VendorVariationPrefix is the variant prefix used for /vendor code that compiles
 	// against the VNDK.
@@ -75,7 +57,7 @@
 func (ctx *moduleContext) SocSpecific() bool {
 	// Additionally check if this module is inVendor() that means it is a "vendor" variant of a
 	// module. As well as SoC specific modules, vendor variants must be installed to /vendor.
-	return ctx.ModuleContext.SocSpecific() || ctx.mod.inVendor()
+	return ctx.ModuleContext.SocSpecific() || ctx.mod.InVendor()
 }
 
 func (ctx *moduleContextImpl) inProduct() bool {
@@ -83,7 +65,7 @@
 }
 
 func (ctx *moduleContextImpl) inVendor() bool {
-	return ctx.mod.inVendor()
+	return ctx.mod.InVendor()
 }
 
 func (ctx *moduleContextImpl) inRamdisk() bool {
@@ -119,7 +101,7 @@
 }
 
 // Returns true if the module is "vendor" variant. Usually these modules are installed in /vendor
-func (c *Module) inVendor() bool {
+func (c *Module) InVendor() bool {
 	return c.Properties.ImageVariationPrefix == VendorVariationPrefix
 }
 
diff --git a/cc/linkable.go b/cc/linkable.go
index 489063f..ab5a552 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -6,6 +6,59 @@
 	"github.com/google/blueprint"
 )
 
+// PlatformSanitizeable is an interface for sanitizing platform modules.
+type PlatformSanitizeable interface {
+	LinkableInterface
+
+	// SanitizePropDefined returns whether the Sanitizer properties struct for this module is defined.
+	SanitizePropDefined() bool
+
+	// IsDependencyRoot returns whether a module is of a type which cannot be a linkage dependency
+	// of another module. For example, cc_binary and rust_binary represent dependency roots as other
+	// modules cannot have linkage dependencies against these types.
+	IsDependencyRoot() bool
+
+	// IsSanitizerEnabled returns whether a sanitizer is enabled.
+	IsSanitizerEnabled(t SanitizerType) bool
+
+	// IsSanitizerExplicitlyDisabled returns whether a sanitizer has been explicitly disabled (set to false) rather
+	// than left undefined.
+	IsSanitizerExplicitlyDisabled(t SanitizerType) bool
+
+	// SanitizeDep returns the value of the SanitizeDep flag, which is set if a module is a dependency of a
+	// sanitized module.
+	SanitizeDep() bool
+
+	// SetSanitizer enables or disables the specified sanitizer type if it's supported, otherwise this should panic.
+	SetSanitizer(t SanitizerType, b bool)
+
+	// SetSanitizerDep returns true if the module is statically linked.
+	SetSanitizeDep(b bool)
+
+	// StaticallyLinked returns true if the module is statically linked.
+	StaticallyLinked() bool
+
+	// SetInSanitizerDir sets the module installation to the sanitizer directory.
+	SetInSanitizerDir()
+
+	// SanitizeNever returns true if this module should never be sanitized.
+	SanitizeNever() bool
+
+	// SanitizerSupported returns true if a sanitizer type is supported by this modules compiler.
+	SanitizerSupported(t SanitizerType) bool
+
+	// SanitizableDepTagChecker returns a SantizableDependencyTagChecker function type.
+	SanitizableDepTagChecker() SantizableDependencyTagChecker
+}
+
+// SantizableDependencyTagChecker functions check whether or not a dependency
+// tag can be sanitized. These functions should return true if the tag can be
+// sanitized, otherwise they should return false. These functions should also
+// handle all possible dependency tags in the dependency tree. For example,
+// Rust modules can depend on both Rust and CC libraries, so the Rust module
+// implementation should handle tags from both.
+type SantizableDependencyTagChecker func(tag blueprint.DependencyTag) bool
+
 // LinkableInterface is an interface for a type of module that is linkable in a C++ library.
 type LinkableInterface interface {
 	android.Module
@@ -27,6 +80,8 @@
 	SetShared()
 	Static() bool
 	Shared() bool
+	Header() bool
+	IsPrebuilt() bool
 	Toc() android.OptionalPath
 
 	Host() bool
@@ -40,6 +95,8 @@
 	InRecovery() bool
 	OnlyInRecovery() bool
 
+	InVendor() bool
+
 	UseSdk() bool
 	UseVndk() bool
 	MustUseVendorVariant() bool
@@ -56,6 +113,11 @@
 	IsSdkVariant() bool
 
 	SplitPerApiLevel() bool
+
+	// SetPreventInstall sets the PreventInstall property to 'true' for this module.
+	SetPreventInstall()
+	// SetHideFromMake sets the HideFromMake property to 'true' for this module.
+	SetHideFromMake()
 }
 
 var (
@@ -67,6 +129,26 @@
 	CoverageDepTag = dependencyTag{name: "coverage"}
 )
 
+// GetImageVariantType returns the ImageVariantType string value for the given module
+// (these are defined in cc/image.go).
+func GetImageVariantType(c LinkableInterface) ImageVariantType {
+	if c.Host() {
+		return hostImageVariant
+	} else if c.InVendor() {
+		return vendorImageVariant
+	} else if c.InProduct() {
+		return productImageVariant
+	} else if c.InRamdisk() {
+		return ramdiskImageVariant
+	} else if c.InVendorRamdisk() {
+		return vendorRamdiskImageVariant
+	} else if c.InRecovery() {
+		return recoveryImageVariant
+	} else {
+		return coreImageVariant
+	}
+}
+
 // SharedDepTag returns the dependency tag for any C++ shared libraries.
 func SharedDepTag() blueprint.DependencyTag {
 	return libraryDependencyTag{Kind: sharedLibraryDependency}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index bb92a88..af17490 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -71,7 +71,7 @@
 		"export_memory_stats=0", "max_malloc_fill_size=0"}
 )
 
-type sanitizerType int
+type SanitizerType int
 
 func boolPtr(v bool) *bool {
 	if v {
@@ -82,19 +82,20 @@
 }
 
 const (
-	asan sanitizerType = iota + 1
+	Asan SanitizerType = iota + 1
 	hwasan
 	tsan
 	intOverflow
 	cfi
 	scs
-	fuzzer
+	Fuzzer
+	memtag_heap
 )
 
 // Name of the sanitizer variation for this sanitizer type
-func (t sanitizerType) variationName() string {
+func (t SanitizerType) variationName() string {
 	switch t {
-	case asan:
+	case Asan:
 		return "asan"
 	case hwasan:
 		return "hwasan"
@@ -106,20 +107,24 @@
 		return "cfi"
 	case scs:
 		return "scs"
-	case fuzzer:
+	case memtag_heap:
+		return "memtag_heap"
+	case Fuzzer:
 		return "fuzzer"
 	default:
-		panic(fmt.Errorf("unknown sanitizerType %d", t))
+		panic(fmt.Errorf("unknown SanitizerType %d", t))
 	}
 }
 
 // This is the sanitizer names in SANITIZE_[TARGET|HOST]
-func (t sanitizerType) name() string {
+func (t SanitizerType) name() string {
 	switch t {
-	case asan:
+	case Asan:
 		return "address"
 	case hwasan:
 		return "hwaddress"
+	case memtag_heap:
+		return "memtag_heap"
 	case tsan:
 		return "thread"
 	case intOverflow:
@@ -128,15 +133,37 @@
 		return "cfi"
 	case scs:
 		return "shadow-call-stack"
-	case fuzzer:
+	case Fuzzer:
 		return "fuzzer"
 	default:
-		panic(fmt.Errorf("unknown sanitizerType %d", t))
+		panic(fmt.Errorf("unknown SanitizerType %d", t))
 	}
 }
 
-func (t sanitizerType) incompatibleWithCfi() bool {
-	return t == asan || t == fuzzer || t == hwasan
+func (*Module) SanitizerSupported(t SanitizerType) bool {
+	switch t {
+	case Asan:
+		return true
+	case hwasan:
+		return true
+	case tsan:
+		return true
+	case intOverflow:
+		return true
+	case cfi:
+		return true
+	case scs:
+		return true
+	case Fuzzer:
+		return true
+	default:
+		return false
+	}
+}
+
+// incompatibleWithCfi returns true if a sanitizer is incompatible with CFI.
+func (t SanitizerType) incompatibleWithCfi() bool {
+	return t == Asan || t == Fuzzer || t == hwasan
 }
 
 type SanitizeUserProps struct {
@@ -157,6 +184,7 @@
 	Integer_overflow *bool    `android:"arch_variant"`
 	Scudo            *bool    `android:"arch_variant"`
 	Scs              *bool    `android:"arch_variant"`
+	Memtag_heap      *bool    `android:"arch_variant"`
 
 	// A modifier for ASAN and HWASAN for write only instrumentation
 	Writeonly *bool `android:"arch_variant"`
@@ -168,6 +196,7 @@
 		Undefined        *bool    `android:"arch_variant"`
 		Cfi              *bool    `android:"arch_variant"`
 		Integer_overflow *bool    `android:"arch_variant"`
+		Memtag_heap      *bool    `android:"arch_variant"`
 		Misc_undefined   []string `android:"arch_variant"`
 		No_recover       []string `android:"arch_variant"`
 	} `android:"arch_variant"`
@@ -308,6 +337,11 @@
 			}
 			s.Writeonly = boolPtr(true)
 		}
+		if found, globalSanitizers = removeFromList("memtag_heap", globalSanitizers); found && s.Memtag_heap == nil {
+			if !ctx.Config().MemtagHeapDisabledForPath(ctx.ModuleDir()) {
+				s.Memtag_heap = boolPtr(true)
+			}
+		}
 
 		if len(globalSanitizers) > 0 {
 			ctx.ModuleErrorf("unknown global sanitizer option %s", globalSanitizers[0])
@@ -329,6 +363,25 @@
 		}
 	}
 
+	// cc_test targets default to SYNC MemTag.
+	if ctx.testBinary() && s.Memtag_heap == nil {
+		if !ctx.Config().MemtagHeapDisabledForPath(ctx.ModuleDir()) {
+			s.Memtag_heap = boolPtr(true)
+			s.Diag.Memtag_heap = boolPtr(true)
+		}
+	}
+
+	// Enable Memtag for all components in the include paths (for Aarch64 only)
+	if s.Memtag_heap == nil && ctx.Arch().ArchType == android.Arm64 {
+		if ctx.Config().MemtagHeapSyncEnabledForPath(ctx.ModuleDir()) {
+			s.Memtag_heap = boolPtr(true)
+			s.Diag.Memtag_heap = boolPtr(true)
+		} else if ctx.Config().MemtagHeapAsyncEnabledForPath(ctx.ModuleDir()) {
+			s.Memtag_heap = boolPtr(true)
+			s.Diag.Memtag_heap = boolPtr(false)
+		}
+	}
+
 	// Enable CFI for all components in the include paths (for Aarch64 only)
 	if s.Cfi == nil && ctx.Config().CFIEnabledForPath(ctx.ModuleDir()) && ctx.Arch().ArchType == android.Arm64 {
 		s.Cfi = boolPtr(true)
@@ -359,6 +412,11 @@
 		s.Scs = nil
 	}
 
+	// memtag_heap is only implemented on AArch64.
+	if ctx.Arch().ArchType != android.Arm64 {
+		s.Memtag_heap = nil
+	}
+
 	// Also disable CFI if ASAN is enabled.
 	if Bool(s.Address) || Bool(s.Hwaddress) {
 		s.Cfi = boolPtr(false)
@@ -413,7 +471,7 @@
 
 	if ctx.Os() != android.Windows && (Bool(s.All_undefined) || Bool(s.Undefined) || Bool(s.Address) || Bool(s.Thread) ||
 		Bool(s.Fuzzer) || Bool(s.Safestack) || Bool(s.Cfi) || Bool(s.Integer_overflow) || len(s.Misc_undefined) > 0 ||
-		Bool(s.Scudo) || Bool(s.Hwaddress) || Bool(s.Scs)) {
+		Bool(s.Scudo) || Bool(s.Hwaddress) || Bool(s.Scs) || Bool(s.Memtag_heap)) {
 		sanitize.Properties.SanitizerEnabled = true
 	}
 
@@ -680,9 +738,10 @@
 	return sanitize.Properties.InSanitizerDir
 }
 
-func (sanitize *sanitize) getSanitizerBoolPtr(t sanitizerType) *bool {
+// getSanitizerBoolPtr returns the SanitizerTypes associated bool pointer from SanitizeProperties.
+func (sanitize *sanitize) getSanitizerBoolPtr(t SanitizerType) *bool {
 	switch t {
-	case asan:
+	case Asan:
 		return sanitize.Properties.Sanitize.Address
 	case hwasan:
 		return sanitize.Properties.Sanitize.Hwaddress
@@ -694,32 +753,37 @@
 		return sanitize.Properties.Sanitize.Cfi
 	case scs:
 		return sanitize.Properties.Sanitize.Scs
-	case fuzzer:
+	case memtag_heap:
+		return sanitize.Properties.Sanitize.Memtag_heap
+	case Fuzzer:
 		return sanitize.Properties.Sanitize.Fuzzer
 	default:
-		panic(fmt.Errorf("unknown sanitizerType %d", t))
+		panic(fmt.Errorf("unknown SanitizerType %d", t))
 	}
 }
 
+// isUnsanitizedVariant returns true if no sanitizers are enabled.
 func (sanitize *sanitize) isUnsanitizedVariant() bool {
-	return !sanitize.isSanitizerEnabled(asan) &&
+	return !sanitize.isSanitizerEnabled(Asan) &&
 		!sanitize.isSanitizerEnabled(hwasan) &&
 		!sanitize.isSanitizerEnabled(tsan) &&
 		!sanitize.isSanitizerEnabled(cfi) &&
 		!sanitize.isSanitizerEnabled(scs) &&
-		!sanitize.isSanitizerEnabled(fuzzer)
+		!sanitize.isSanitizerEnabled(memtag_heap) &&
+		!sanitize.isSanitizerEnabled(Fuzzer)
 }
 
+// isVariantOnProductionDevice returns true if variant is for production devices (no non-production sanitizers enabled).
 func (sanitize *sanitize) isVariantOnProductionDevice() bool {
-	return !sanitize.isSanitizerEnabled(asan) &&
+	return !sanitize.isSanitizerEnabled(Asan) &&
 		!sanitize.isSanitizerEnabled(hwasan) &&
 		!sanitize.isSanitizerEnabled(tsan) &&
-		!sanitize.isSanitizerEnabled(fuzzer)
+		!sanitize.isSanitizerEnabled(Fuzzer)
 }
 
-func (sanitize *sanitize) SetSanitizer(t sanitizerType, b bool) {
+func (sanitize *sanitize) SetSanitizer(t SanitizerType, b bool) {
 	switch t {
-	case asan:
+	case Asan:
 		sanitize.Properties.Sanitize.Address = boolPtr(b)
 	case hwasan:
 		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
@@ -731,10 +795,12 @@
 		sanitize.Properties.Sanitize.Cfi = boolPtr(b)
 	case scs:
 		sanitize.Properties.Sanitize.Scs = boolPtr(b)
-	case fuzzer:
+	case memtag_heap:
+		sanitize.Properties.Sanitize.Memtag_heap = boolPtr(b)
+	case Fuzzer:
 		sanitize.Properties.Sanitize.Fuzzer = boolPtr(b)
 	default:
-		panic(fmt.Errorf("unknown sanitizerType %d", t))
+		panic(fmt.Errorf("unknown SanitizerType %d", t))
 	}
 	if b {
 		sanitize.Properties.SanitizerEnabled = true
@@ -743,7 +809,7 @@
 
 // Check if the sanitizer is explicitly disabled (as opposed to nil by
 // virtue of not being set).
-func (sanitize *sanitize) isSanitizerExplicitlyDisabled(t sanitizerType) bool {
+func (sanitize *sanitize) isSanitizerExplicitlyDisabled(t SanitizerType) bool {
 	if sanitize == nil {
 		return false
 	}
@@ -757,7 +823,7 @@
 // indirectly (via a mutator) sets the bool ptr to true, and you can't
 // distinguish between the cases. It isn't needed though - both cases can be
 // treated identically.
-func (sanitize *sanitize) isSanitizerEnabled(t sanitizerType) bool {
+func (sanitize *sanitize) isSanitizerEnabled(t SanitizerType) bool {
 	if sanitize == nil {
 		return false
 	}
@@ -766,7 +832,8 @@
 	return sanitizerVal != nil && *sanitizerVal == true
 }
 
-func isSanitizableDependencyTag(tag blueprint.DependencyTag) bool {
+// IsSanitizableDependencyTag returns true if the dependency tag is sanitizable.
+func IsSanitizableDependencyTag(tag blueprint.DependencyTag) bool {
 	switch t := tag.(type) {
 	case dependencyTag:
 		return t == reuseObjTag || t == objDepTag
@@ -777,6 +844,10 @@
 	}
 }
 
+func (m *Module) SanitizableDepTagChecker() SantizableDependencyTagChecker {
+	return IsSanitizableDependencyTag
+}
+
 // Determines if the current module is a static library going to be captured
 // as vendor snapshot. Such modules must create both cfi and non-cfi variants,
 // except for ones which explicitly disable cfi.
@@ -785,51 +856,58 @@
 		return false
 	}
 
-	c := mctx.Module().(*Module)
+	c := mctx.Module().(PlatformSanitizeable)
 
-	if !c.inVendor() {
+	if !c.InVendor() {
 		return false
 	}
 
-	if !c.static() {
+	if !c.StaticallyLinked() {
 		return false
 	}
 
-	if c.Prebuilt() != nil {
+	if c.IsPrebuilt() {
 		return false
 	}
 
-	return c.sanitize != nil &&
-		!Bool(c.sanitize.Properties.Sanitize.Never) &&
-		!c.sanitize.isSanitizerExplicitlyDisabled(cfi)
+	if !c.SanitizerSupported(cfi) {
+		return false
+	}
+
+	return c.SanitizePropDefined() &&
+		!c.SanitizeNever() &&
+		!c.IsSanitizerExplicitlyDisabled(cfi)
 }
 
 // Propagate sanitizer requirements down from binaries
-func sanitizerDepsMutator(t sanitizerType) func(android.TopDownMutatorContext) {
+func sanitizerDepsMutator(t SanitizerType) func(android.TopDownMutatorContext) {
 	return func(mctx android.TopDownMutatorContext) {
-		if c, ok := mctx.Module().(*Module); ok {
-			enabled := c.sanitize.isSanitizerEnabled(t)
+		if c, ok := mctx.Module().(PlatformSanitizeable); ok {
+			enabled := c.IsSanitizerEnabled(t)
 			if t == cfi && needsCfiForVendorSnapshot(mctx) {
 				// We shouldn't change the result of isSanitizerEnabled(cfi) to correctly
 				// determine defaultVariation in sanitizerMutator below.
 				// Instead, just mark SanitizeDep to forcefully create cfi variant.
 				enabled = true
-				c.sanitize.Properties.SanitizeDep = true
+				c.SetSanitizeDep(true)
 			}
 			if enabled {
+				isSanitizableDependencyTag := c.SanitizableDepTagChecker()
 				mctx.WalkDeps(func(child, parent android.Module) bool {
 					if !isSanitizableDependencyTag(mctx.OtherModuleDependencyTag(child)) {
 						return false
 					}
-					if d, ok := child.(*Module); ok && d.sanitize != nil &&
-						!Bool(d.sanitize.Properties.Sanitize.Never) &&
-						!d.sanitize.isSanitizerExplicitlyDisabled(t) {
+					if d, ok := child.(PlatformSanitizeable); ok && d.SanitizePropDefined() &&
+						!d.SanitizeNever() &&
+						!d.IsSanitizerExplicitlyDisabled(t) {
 						if t == cfi || t == hwasan || t == scs {
-							if d.static() {
-								d.sanitize.Properties.SanitizeDep = true
+							if d.StaticallyLinked() && d.SanitizerSupported(t) {
+								// Rust does not support some of these sanitizers, so we need to check if it's
+								// supported before setting this true.
+								d.SetSanitizeDep(true)
 							}
 						} else {
-							d.sanitize.Properties.SanitizeDep = true
+							d.SetSanitizeDep(true)
 						}
 					}
 					return true
@@ -847,9 +925,19 @@
 	}
 }
 
+func (c *Module) SanitizeNever() bool {
+	return Bool(c.sanitize.Properties.Sanitize.Never)
+}
+
+func (c *Module) IsSanitizerExplicitlyDisabled(t SanitizerType) bool {
+	return c.sanitize.isSanitizerExplicitlyDisabled(t)
+}
+
 // Propagate the ubsan minimal runtime dependency when there are integer overflow sanitized static dependencies.
 func sanitizerRuntimeDepsMutator(mctx android.TopDownMutatorContext) {
+	// Change this to PlatformSanitizable when/if non-cc modules support ubsan sanitizers.
 	if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
+		isSanitizableDependencyTag := c.SanitizableDepTagChecker()
 		mctx.WalkDeps(func(child, parent android.Module) bool {
 			if !isSanitizableDependencyTag(mctx.OtherModuleDependencyTag(child)) {
 				return false
@@ -985,6 +1073,20 @@
 			sanitizers = append(sanitizers, "shadow-call-stack")
 		}
 
+		if Bool(c.sanitize.Properties.Sanitize.Memtag_heap) && c.binary() {
+			noteDep := "note_memtag_heap_async"
+			if Bool(c.sanitize.Properties.Sanitize.Diag.Memtag_heap) {
+				noteDep = "note_memtag_heap_sync"
+			}
+			depTag := libraryDependencyTag{Kind: staticLibraryDependency, wholeStatic: true}
+			variations := append(mctx.Target().Variations(),
+				blueprint.Variation{Mutator: "link", Variation: "static"})
+			if c.Device() {
+				variations = append(variations, c.ImageVariation())
+			}
+			mctx.AddFarVariationDependencies(variations, depTag, noteDep)
+		}
+
 		if Bool(c.sanitize.Properties.Sanitize.Fuzzer) {
 			sanitizers = append(sanitizers, "fuzzer-no-link")
 		}
@@ -1057,7 +1159,7 @@
 					variations = append(variations, c.ImageVariation())
 				}
 				mctx.AddFarVariationDependencies(variations, depTag, deps...)
-			} else if !c.static() && !c.header() {
+			} else if !c.static() && !c.Header() {
 				// If we're using snapshots and in vendor, redirect to snapshot whenever possible
 				if c.VndkVersion() == mctx.DeviceConfig().VndkVersion() {
 					snapshots := vendorSnapshotSharedLibs(mctx.Config())
@@ -1098,16 +1200,52 @@
 	AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string)
 }
 
+func (c *Module) SanitizePropDefined() bool {
+	return c.sanitize != nil
+}
+
+func (c *Module) IsSanitizerEnabled(t SanitizerType) bool {
+	return c.sanitize.isSanitizerEnabled(t)
+}
+
+func (c *Module) SanitizeDep() bool {
+	return c.sanitize.Properties.SanitizeDep
+}
+
+func (c *Module) StaticallyLinked() bool {
+	return c.static()
+}
+
+func (c *Module) SetInSanitizerDir() {
+	if c.sanitize != nil {
+		c.sanitize.Properties.InSanitizerDir = true
+	}
+}
+
+func (c *Module) SetSanitizer(t SanitizerType, b bool) {
+	if c.sanitize != nil {
+		c.sanitize.SetSanitizer(t, b)
+	}
+}
+
+func (c *Module) SetSanitizeDep(b bool) {
+	if c.sanitize != nil {
+		c.sanitize.Properties.SanitizeDep = b
+	}
+}
+
+var _ PlatformSanitizeable = (*Module)(nil)
+
 // Create sanitized variants for modules that need them
-func sanitizerMutator(t sanitizerType) func(android.BottomUpMutatorContext) {
+func sanitizerMutator(t SanitizerType) func(android.BottomUpMutatorContext) {
 	return func(mctx android.BottomUpMutatorContext) {
-		if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
-			if c.isDependencyRoot() && c.sanitize.isSanitizerEnabled(t) {
+		if c, ok := mctx.Module().(PlatformSanitizeable); ok && c.SanitizePropDefined() {
+			if c.IsDependencyRoot() && c.IsSanitizerEnabled(t) {
 				modules := mctx.CreateVariations(t.variationName())
-				modules[0].(*Module).sanitize.SetSanitizer(t, true)
-			} else if c.sanitize.isSanitizerEnabled(t) || c.sanitize.Properties.SanitizeDep {
-				isSanitizerEnabled := c.sanitize.isSanitizerEnabled(t)
-				if c.static() || c.header() || t == asan || t == fuzzer {
+				modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
+			} else if c.IsSanitizerEnabled(t) || c.SanitizeDep() {
+				isSanitizerEnabled := c.IsSanitizerEnabled(t)
+				if c.StaticallyLinked() || c.Header() || t == Asan || t == Fuzzer {
 					// Static and header libs are split into non-sanitized and sanitized variants.
 					// Shared libs are not split. However, for asan and fuzzer, we split even for shared
 					// libs because a library sanitized for asan/fuzzer can't be linked from a library
@@ -1121,17 +1259,20 @@
 					// module. By setting it to the name of the sanitized variation, the dangling dependency
 					// is redirected to the sanitized variant of the dependent module.
 					defaultVariation := t.variationName()
+					// Not all PlatformSanitizeable modules support the CFI sanitizer
+					cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
 					mctx.SetDefaultDependencyVariation(&defaultVariation)
-					modules := mctx.CreateVariations("", t.variationName())
-					modules[0].(*Module).sanitize.SetSanitizer(t, false)
-					modules[1].(*Module).sanitize.SetSanitizer(t, true)
-					modules[0].(*Module).sanitize.Properties.SanitizeDep = false
-					modules[1].(*Module).sanitize.Properties.SanitizeDep = false
 
-					if mctx.Device() && t.incompatibleWithCfi() {
+					modules := mctx.CreateVariations("", t.variationName())
+					modules[0].(PlatformSanitizeable).SetSanitizer(t, false)
+					modules[1].(PlatformSanitizeable).SetSanitizer(t, true)
+					modules[0].(PlatformSanitizeable).SetSanitizeDep(false)
+					modules[1].(PlatformSanitizeable).SetSanitizeDep(false)
+
+					if mctx.Device() && t.incompatibleWithCfi() && cfiSupported {
 						// TODO: Make sure that cfi mutator runs "after" any of the sanitizers that
 						// are incompatible with cfi
-						modules[1].(*Module).sanitize.SetSanitizer(cfi, false)
+						modules[1].(PlatformSanitizeable).SetSanitizer(cfi, false)
 					}
 
 					// For cfi/scs/hwasan, we can export both sanitized and un-sanitized variants
@@ -1139,46 +1280,48 @@
 					// For other types of sanitizers, suppress the variation that is disabled.
 					if t != cfi && t != scs && t != hwasan {
 						if isSanitizerEnabled {
-							modules[0].(*Module).Properties.PreventInstall = true
-							modules[0].(*Module).Properties.HideFromMake = true
+							modules[0].(PlatformSanitizeable).SetPreventInstall()
+							modules[0].(PlatformSanitizeable).SetHideFromMake()
 						} else {
-							modules[1].(*Module).Properties.PreventInstall = true
-							modules[1].(*Module).Properties.HideFromMake = true
+							modules[1].(PlatformSanitizeable).SetPreventInstall()
+							modules[1].(PlatformSanitizeable).SetHideFromMake()
 						}
 					}
 
 					// Export the static lib name to make
-					if c.static() && c.ExportedToMake() {
+					if c.StaticallyLinked() && c.ExportedToMake() {
 						if t == cfi {
-							cfiStaticLibs(mctx.Config()).add(c, c.Name())
+							cfiStaticLibs(mctx.Config()).add(c, c.Module().Name())
 						} else if t == hwasan {
-							hwasanStaticLibs(mctx.Config()).add(c, c.Name())
+							hwasanStaticLibs(mctx.Config()).add(c, c.Module().Name())
 						}
 					}
 				} else {
 					// Shared libs are not split. Only the sanitized variant is created.
 					modules := mctx.CreateVariations(t.variationName())
-					modules[0].(*Module).sanitize.SetSanitizer(t, true)
-					modules[0].(*Module).sanitize.Properties.SanitizeDep = false
+					modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
+					modules[0].(PlatformSanitizeable).SetSanitizeDep(false)
 
 					// locate the asan libraries under /data/asan
-					if mctx.Device() && t == asan && isSanitizerEnabled {
-						modules[0].(*Module).sanitize.Properties.InSanitizerDir = true
+					if mctx.Device() && t == Asan && isSanitizerEnabled {
+						modules[0].(PlatformSanitizeable).SetInSanitizerDir()
 					}
 
 					if mctx.Device() && t.incompatibleWithCfi() {
 						// TODO: Make sure that cfi mutator runs "after" any of the sanitizers that
 						// are incompatible with cfi
-						modules[0].(*Module).sanitize.SetSanitizer(cfi, false)
+						modules[0].(PlatformSanitizeable).SetSanitizer(cfi, false)
 					}
 				}
 			}
-			c.sanitize.Properties.SanitizeDep = false
+			c.SetSanitizeDep(false)
 		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok && sanitizeable.IsSanitizerEnabled(mctx, t.name()) {
 			// APEX modules fall here
 			sanitizeable.AddSanitizerDependencies(mctx, t.name())
 			mctx.CreateVariations(t.variationName())
 		} else if c, ok := mctx.Module().(*Module); ok {
+			//TODO: When Rust modules have vendor support, enable this path for PlatformSanitizeable
+
 			// Check if it's a snapshot module supporting sanitizer
 			if s, ok := c.linker.(snapshotSanitizer); ok && s.isSanitizerEnabled(t) {
 				// Set default variation as above.
@@ -1203,23 +1346,23 @@
 type sanitizerStaticLibsMap struct {
 	// libsMap contains one list of modules per each image and each arch.
 	// e.g. libs[vendor]["arm"] contains arm modules installed to vendor
-	libsMap       map[imageVariantType]map[string][]string
+	libsMap       map[ImageVariantType]map[string][]string
 	libsMapLock   sync.Mutex
-	sanitizerType sanitizerType
+	sanitizerType SanitizerType
 }
 
-func newSanitizerStaticLibsMap(t sanitizerType) *sanitizerStaticLibsMap {
+func newSanitizerStaticLibsMap(t SanitizerType) *sanitizerStaticLibsMap {
 	return &sanitizerStaticLibsMap{
 		sanitizerType: t,
-		libsMap:       make(map[imageVariantType]map[string][]string),
+		libsMap:       make(map[ImageVariantType]map[string][]string),
 	}
 }
 
 // Add the current module to sanitizer static libs maps
 // Each module should pass its exported name as names of Make and Soong can differ.
-func (s *sanitizerStaticLibsMap) add(c *Module, name string) {
-	image := c.getImageVariantType()
-	arch := c.Arch().ArchType.String()
+func (s *sanitizerStaticLibsMap) add(c LinkableInterface, name string) {
+	image := GetImageVariantType(c)
+	arch := c.Module().Target().Arch.ArchType.String()
 
 	s.libsMapLock.Lock()
 	defer s.libsMapLock.Unlock()
@@ -1238,7 +1381,7 @@
 // See build/make/core/binary.mk for more details.
 func (s *sanitizerStaticLibsMap) exportToMake(ctx android.MakeVarsContext) {
 	for _, image := range android.SortedStringKeys(s.libsMap) {
-		archMap := s.libsMap[imageVariantType(image)]
+		archMap := s.libsMap[ImageVariantType(image)]
 		for _, arch := range android.SortedStringKeys(archMap) {
 			libs := archMap[arch]
 			sort.Strings(libs)
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index e3f3a4d..8979846 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -104,7 +104,7 @@
 }
 
 func (vendorSnapshotImage) inImage(m *Module) func() bool {
-	return m.inVendor
+	return m.InVendor
 }
 
 func (vendorSnapshotImage) available(m *Module) *bool {
@@ -507,8 +507,8 @@
 }
 
 type snapshotSanitizer interface {
-	isSanitizerEnabled(t sanitizerType) bool
-	setSanitizerVariation(t sanitizerType, enabled bool)
+	isSanitizerEnabled(t SanitizerType) bool
+	setSanitizerVariation(t SanitizerType, enabled bool)
 }
 
 type snapshotLibraryDecorator struct {
@@ -546,7 +546,7 @@
 func (p *snapshotLibraryDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
 	m := ctx.Module().(*Module)
 
-	if m.inVendor() && vendorSuffixModules(ctx.Config())[m.BaseModuleName()] {
+	if m.InVendor() && vendorSuffixModules(ctx.Config())[m.BaseModuleName()] {
 		p.androidMkSuffix = vendorSuffix
 	} else if m.InRecovery() && recoverySuffixModules(ctx.Config())[m.BaseModuleName()] {
 		p.androidMkSuffix = recoverySuffix
@@ -613,7 +613,7 @@
 	return false
 }
 
-func (p *snapshotLibraryDecorator) isSanitizerEnabled(t sanitizerType) bool {
+func (p *snapshotLibraryDecorator) isSanitizerEnabled(t SanitizerType) bool {
 	switch t {
 	case cfi:
 		return p.sanitizerProperties.Cfi.Src != nil
@@ -622,7 +622,7 @@
 	}
 }
 
-func (p *snapshotLibraryDecorator) setSanitizerVariation(t sanitizerType, enabled bool) {
+func (p *snapshotLibraryDecorator) setSanitizerVariation(t SanitizerType, enabled bool) {
 	if !enabled {
 		return
 	}
@@ -769,7 +769,7 @@
 	binName := in.Base()
 
 	m := ctx.Module().(*Module)
-	if m.inVendor() && vendorSuffixModules(ctx.Config())[m.BaseModuleName()] {
+	if m.InVendor() && vendorSuffixModules(ctx.Config())[m.BaseModuleName()] {
 		p.androidMkSuffix = vendorSuffix
 	} else if m.InRecovery() && recoverySuffixModules(ctx.Config())[m.BaseModuleName()] {
 		p.androidMkSuffix = recoverySuffix
@@ -868,7 +868,7 @@
 
 	m := ctx.Module().(*Module)
 
-	if m.inVendor() && vendorSuffixModules(ctx.Config())[m.BaseModuleName()] {
+	if m.InVendor() && vendorSuffixModules(ctx.Config())[m.BaseModuleName()] {
 		p.androidMkSuffix = vendorSuffix
 	} else if m.InRecovery() && recoverySuffixModules(ctx.Config())[m.BaseModuleName()] {
 		p.androidMkSuffix = recoverySuffix
diff --git a/cc/test.go b/cc/test.go
index 4ff5bf6..f715a8d 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -236,6 +236,10 @@
 	return BoolDefault(test.Properties.Gtest, true)
 }
 
+func (test *testDecorator) testBinary() bool {
+	return true
+}
+
 func (test *testDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	if !test.gtest() {
 		return flags
diff --git a/cc/testing.go b/cc/testing.go
index 8d92ea2..903f76c 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -445,6 +445,14 @@
 			stl: "none",
 			system_shared_libs: [],
 		}
+
+		cc_library_static {
+			name: "note_memtag_heap_async",
+		}
+
+		cc_library_static {
+			name: "note_memtag_heap_sync",
+		}
 	`
 
 	supportLinuxBionic := false
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 622ebec..0a89e47 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -247,7 +247,7 @@
 		if m.sanitize != nil {
 			// scs and hwasan export both sanitized and unsanitized variants for static and header
 			// Always use unsanitized variants of them.
-			for _, t := range []sanitizerType{scs, hwasan} {
+			for _, t := range []SanitizerType{scs, hwasan} {
 				if !l.shared() && m.sanitize.isSanitizerEnabled(t) {
 					return false
 				}
diff --git a/cc/vndk.go b/cc/vndk.go
index c1264f7..daae1f7 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -394,7 +394,7 @@
 
 		useCoreVariant := m.VndkVersion() == mctx.DeviceConfig().PlatformVndkVersion() &&
 			mctx.DeviceConfig().VndkUseCoreVariant() && !m.MustUseVendorVariant()
-		return lib.shared() && m.inVendor() && m.IsVndk() && !m.IsVndkExt() && !useCoreVariant
+		return lib.shared() && m.InVendor() && m.IsVndk() && !m.IsVndkExt() && !useCoreVariant
 	}
 	return false
 }
@@ -586,7 +586,7 @@
 	// !inVendor: There's product/vendor variants for VNDK libs. We only care about vendor variants.
 	// !installable: Snapshot only cares about "installable" modules.
 	// isSnapshotPrebuilt: Snapshotting a snapshot doesn't make sense.
-	if !m.inVendor() || !m.installable(apexInfo) || m.isSnapshotPrebuilt() {
+	if !m.InVendor() || !m.installable(apexInfo) || m.isSnapshotPrebuilt() {
 		return nil, "", false
 	}
 	l, ok := m.linker.(snapshotLibraryInterface)
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 441ea0d..6714978 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -20,6 +20,7 @@
         "golang-protobuf-proto",
         "soong",
         "soong-android",
+        "soong-bp2build",
         "soong-env",
         "soong-ui-metrics_proto",
     ],
@@ -27,10 +28,6 @@
         "main.go",
         "writedocs.go",
         "queryview.go",
-        "queryview_templates.go",
-    ],
-    testSrcs: [
-        "queryview_test.go",
     ],
     primaryBuilder: true,
 }
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index f5aa685..3657ea8 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -16,512 +16,82 @@
 
 import (
 	"android/soong/android"
-	"fmt"
+	"android/soong/bp2build"
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"reflect"
-	"strings"
 
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/bootstrap/bpdoc"
-	"github.com/google/blueprint/proptools"
 )
 
-var (
-	// An allowlist of prop types that are surfaced from module props to rule
-	// attributes. (nested) dictionaries are notably absent here, because while
-	// Soong supports multi value typed and nested dictionaries, Bazel's rule
-	// attr() API supports only single-level string_dicts.
-	allowedPropTypes = map[string]bool{
-		"int":         true, // e.g. 42
-		"bool":        true, // e.g. True
-		"string_list": true, // e.g. ["a", "b"]
-		"string":      true, // e.g. "a"
-	}
-
-	// Certain module property names are blocklisted/ignored here, for the reasons commented.
-	ignoredPropNames = map[string]bool{
-		"name":       true, // redundant, since this is explicitly generated for every target
-		"from":       true, // reserved keyword
-		"in":         true, // reserved keyword
-		"arch":       true, // interface prop type is not supported yet.
-		"multilib":   true, // interface prop type is not supported yet.
-		"target":     true, // interface prop type is not supported yet.
-		"visibility": true, // Bazel has native visibility semantics. Handle later.
-		"features":   true, // There is already a built-in attribute 'features' which cannot be overridden.
-	}
-)
-
-func targetNameWithVariant(c *blueprint.Context, logicModule blueprint.Module) string {
-	name := ""
-	if c.ModuleSubDir(logicModule) != "" {
-		// TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
-		name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
-	} else {
-		name = c.ModuleName(logicModule)
-	}
-
-	return strings.Replace(name, "//", "", 1)
+type queryviewContext struct {
+	bpCtx *blueprint.Context
 }
 
-func qualifiedTargetLabel(c *blueprint.Context, logicModule blueprint.Module) string {
-	return "//" +
-		packagePath(c, logicModule) +
-		":" +
-		targetNameWithVariant(c, logicModule)
+func (ctx *queryviewContext) ModuleName(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleName(module)
 }
 
-func packagePath(c *blueprint.Context, logicModule blueprint.Module) string {
-	return filepath.Dir(c.BlueprintFile(logicModule))
+func (ctx *queryviewContext) ModuleDir(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleDir(module)
 }
 
-func escapeString(s string) string {
-	s = strings.ReplaceAll(s, "\\", "\\\\")
-	return strings.ReplaceAll(s, "\"", "\\\"")
+func (ctx *queryviewContext) ModuleSubDir(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleSubDir(module)
 }
 
-func makeIndent(indent int) string {
-	if indent < 0 {
-		panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
-	}
-	return strings.Repeat("    ", indent)
+func (ctx *queryviewContext) ModuleType(module blueprint.Module) string {
+	return ctx.bpCtx.ModuleType(module)
 }
 
-// prettyPrint a property value into the equivalent Starlark representation
-// recursively.
-func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
-	if isZero(propertyValue) {
-		// A property value being set or unset actually matters -- Soong does set default
-		// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
-		// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
-		//
-		// In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
-		// value of unset attributes.
-		return "", nil
-	}
-
-	var ret string
-	switch propertyValue.Kind() {
-	case reflect.String:
-		ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
-	case reflect.Bool:
-		ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
-	case reflect.Int, reflect.Uint, reflect.Int64:
-		ret = fmt.Sprintf("%v", propertyValue.Interface())
-	case reflect.Ptr:
-		return prettyPrint(propertyValue.Elem(), indent)
-	case reflect.Slice:
-		ret = "[\n"
-		for i := 0; i < propertyValue.Len(); i++ {
-			indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
-			if err != nil {
-				return "", err
-			}
-
-			if indexedValue != "" {
-				ret += makeIndent(indent + 1)
-				ret += indexedValue
-				ret += ",\n"
-			}
-		}
-		ret += makeIndent(indent)
-		ret += "]"
-	case reflect.Struct:
-		ret = "{\n"
-		// Sort and print the struct props by the key.
-		structProps := extractStructProperties(propertyValue, indent)
-		for _, k := range android.SortedStringKeys(structProps) {
-			ret += makeIndent(indent + 1)
-			ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
-		}
-		ret += makeIndent(indent)
-		ret += "}"
-	case reflect.Interface:
-		// TODO(b/164227191): implement pretty print for interfaces.
-		// Interfaces are used for for arch, multilib and target properties.
-		return "", nil
-	default:
-		return "", fmt.Errorf(
-			"unexpected kind for property struct field: %s", propertyValue.Kind())
-	}
-	return ret, nil
+func (ctx *queryviewContext) VisitAllModulesBlueprint(visit func(blueprint.Module)) {
+	ctx.bpCtx.VisitAllModules(visit)
 }
 
-// Converts a reflected property struct value into a map of property names and property values,
-// which each property value correctly pretty-printed and indented at the right nest level,
-// since property structs can be nested. In Starlark, nested structs are represented as nested
-// dicts: https://docs.bazel.build/skylark/lib/dict.html
-func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
-	if structValue.Kind() != reflect.Struct {
-		panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
-	}
-
-	ret := map[string]string{}
-	structType := structValue.Type()
-	for i := 0; i < structValue.NumField(); i++ {
-		field := structType.Field(i)
-		if field.PkgPath != "" {
-			// Skip unexported fields. Some properties are
-			// internal to Soong only, and these fields do not have PkgPath.
-			continue
+func (ctx *queryviewContext) VisitDirectDeps(module android.Module, visit func(android.Module)) {
+	ctx.bpCtx.VisitDirectDeps(module, func(m blueprint.Module) {
+		if aModule, ok := m.(android.Module); ok {
+			visit(aModule)
 		}
-		if proptools.HasTag(field, "blueprint", "mutated") {
-			continue
-		}
-
-		fieldValue := structValue.Field(i)
-		if isZero(fieldValue) {
-			// Ignore zero-valued fields
-			continue
-		}
-
-		propertyName := proptools.PropertyNameForField(field.Name)
-		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
-		if err != nil {
-			panic(
-				fmt.Errorf(
-					"Error while parsing property: %q. %s",
-					propertyName,
-					err))
-		}
-		if prettyPrintedValue != "" {
-			ret[propertyName] = prettyPrintedValue
-		}
-	}
-
-	return ret
-}
-
-func isStructPtr(t reflect.Type) bool {
-	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
-}
-
-// Generically extract module properties and types into a map, keyed by the module property name.
-func extractModuleProperties(aModule android.Module) map[string]string {
-	ret := map[string]string{}
-
-	// Iterate over this android.Module's property structs.
-	for _, properties := range aModule.GetProperties() {
-		propertiesValue := reflect.ValueOf(properties)
-		// Check that propertiesValue is a pointer to the Properties struct, like
-		// *cc.BaseLinkerProperties or *java.CompilerProperties.
-		//
-		// propertiesValue can also be type-asserted to the structs to
-		// manipulate internal props, if needed.
-		if isStructPtr(propertiesValue.Type()) {
-			structValue := propertiesValue.Elem()
-			for k, v := range extractStructProperties(structValue, 0) {
-				ret[k] = v
-			}
-		} else {
-			panic(fmt.Errorf(
-				"properties must be a pointer to a struct, got %T",
-				propertiesValue.Interface()))
-		}
-
-	}
-
-	return ret
-}
-
-// FIXME(b/168089390): In Bazel, rules ending with "_test" needs to be marked as
-// testonly = True, forcing other rules that depend on _test rules to also be
-// marked as testonly = True. This semantic constraint is not present in Soong.
-// To work around, rename "*_test" rules to "*_test_".
-func canonicalizeModuleType(moduleName string) string {
-	if strings.HasSuffix(moduleName, "_test") {
-		return moduleName + "_"
-	}
-
-	return moduleName
-}
-
-type RuleShim struct {
-	// The rule class shims contained in a bzl file. e.g. ["cc_object", "cc_library", ..]
-	rules []string
-
-	// The generated string content of the bzl file.
-	content string
-}
-
-// Create <module>.bzl containing Bazel rule shims for every module type available in Soong and
-// user-specified Go plugins.
-//
-// This function reuses documentation generation APIs to ensure parity between modules-as-docs
-// and modules-as-code, including the names and types of module properties.
-func createRuleShims(packages []*bpdoc.Package) (map[string]RuleShim, error) {
-	var propToAttr func(prop bpdoc.Property, propName string) string
-	propToAttr = func(prop bpdoc.Property, propName string) string {
-		// dots are not allowed in Starlark attribute names. Substitute them with double underscores.
-		propName = strings.ReplaceAll(propName, ".", "__")
-		if !shouldGenerateAttribute(propName) {
-			return ""
-		}
-
-		// Canonicalize and normalize module property types to Bazel attribute types
-		starlarkAttrType := prop.Type
-		if starlarkAttrType == "list of string" {
-			starlarkAttrType = "string_list"
-		} else if starlarkAttrType == "int64" {
-			starlarkAttrType = "int"
-		} else if starlarkAttrType == "" {
-			var attr string
-			for _, nestedProp := range prop.Properties {
-				nestedAttr := propToAttr(nestedProp, propName+"__"+nestedProp.Name)
-				if nestedAttr != "" {
-					// TODO(b/167662930): Fix nested props resulting in too many attributes.
-					// Let's still generate these, but comment them out.
-					attr += "# " + nestedAttr
-				}
-			}
-			return attr
-		}
-
-		if !allowedPropTypes[starlarkAttrType] {
-			return ""
-		}
-
-		return fmt.Sprintf("        %q: attr.%s(),\n", propName, starlarkAttrType)
-	}
-
-	ruleShims := map[string]RuleShim{}
-	for _, pkg := range packages {
-		content := "load(\"//build/bazel/queryview_rules:providers.bzl\", \"SoongModuleInfo\")\n"
-
-		bzlFileName := strings.ReplaceAll(pkg.Path, "android/soong/", "")
-		bzlFileName = strings.ReplaceAll(bzlFileName, ".", "_")
-		bzlFileName = strings.ReplaceAll(bzlFileName, "/", "_")
-
-		rules := []string{}
-
-		for _, moduleTypeTemplate := range moduleTypeDocsToTemplates(pkg.ModuleTypes) {
-			attrs := `{
-        "module_name": attr.string(mandatory = True),
-        "module_variant": attr.string(),
-        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
-`
-			for _, prop := range moduleTypeTemplate.Properties {
-				attrs += propToAttr(prop, prop.Name)
-			}
-
-			moduleTypeName := moduleTypeTemplate.Name
-
-			// Certain SDK-related module types dynamically inject properties, instead of declaring
-			// them as structs. These properties are registered in an SdkMemberTypesRegistry. If
-			// the module type name matches, add these properties into the rule definition.
-			var registeredTypes []android.SdkMemberType
-			if moduleTypeName == "module_exports" || moduleTypeName == "module_exports_snapshot" {
-				registeredTypes = android.ModuleExportsMemberTypes.RegisteredTypes()
-			} else if moduleTypeName == "sdk" || moduleTypeName == "sdk_snapshot" {
-				registeredTypes = android.SdkMemberTypes.RegisteredTypes()
-			}
-			for _, memberType := range registeredTypes {
-				attrs += fmt.Sprintf("        %q: attr.string_list(),\n", memberType.SdkPropertyName())
-			}
-
-			attrs += "    },"
-
-			rule := canonicalizeModuleType(moduleTypeTemplate.Name)
-			content += fmt.Sprintf(moduleRuleShim, rule, attrs)
-			rules = append(rules, rule)
-		}
-
-		ruleShims[bzlFileName] = RuleShim{content: content, rules: rules}
-	}
-	return ruleShims, nil
+	})
 }
 
 func createBazelQueryView(ctx *android.Context, bazelQueryViewDir string) error {
-	blueprintCtx := ctx.Context
-	blueprintCtx.VisitAllModules(func(module blueprint.Module) {
-		buildFile, err := buildFileForModule(blueprintCtx, module, bazelQueryViewDir)
-		if err != nil {
-			panic(err)
-		}
-
-		buildFile.Write([]byte(generateSoongModuleTarget(blueprintCtx, module) + "\n\n"))
-		buildFile.Close()
-	})
-	var err error
-
-	// Write top level files: WORKSPACE and BUILD. These files are empty.
-	if err = writeReadOnlyFile(bazelQueryViewDir, "WORKSPACE", ""); err != nil {
-		return err
+	qvCtx := queryviewContext{
+		bpCtx: ctx.Context,
 	}
+	ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
+	buildToTargets := bp2build.GenerateSoongModuleTargets(&qvCtx)
 
-	// Used to denote that the top level directory is a package.
-	if err = writeReadOnlyFile(bazelQueryViewDir, "BUILD", ""); err != nil {
-		return err
-	}
-
-	packages, err := getPackages(ctx)
-	if err != nil {
-		return err
-	}
-	ruleShims, err := createRuleShims(packages)
-	if err != nil {
-		return err
-	}
-
-	// Write .bzl Starlark files into the bazel_rules top level directory (provider and rule definitions)
-	bazelRulesDir := bazelQueryViewDir + "/build/bazel/queryview_rules"
-	if err = writeReadOnlyFile(bazelRulesDir, "BUILD", ""); err != nil {
-		return err
-	}
-	if err = writeReadOnlyFile(bazelRulesDir, "providers.bzl", providersBzl); err != nil {
-		return err
-	}
-
-	for bzlFileName, ruleShim := range ruleShims {
-		if err = writeReadOnlyFile(bazelRulesDir, bzlFileName+".bzl", ruleShim.content); err != nil {
+	filesToWrite := bp2build.CreateBazelFiles(ruleShims, buildToTargets)
+	for _, f := range filesToWrite {
+		if err := writeReadOnlyFile(bazelQueryViewDir, f); err != nil {
 			return err
 		}
 	}
 
-	return writeReadOnlyFile(bazelRulesDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims))
+	return nil
 }
 
-// Generate the content of soong_module.bzl with the rule shim load statements
-// and mapping of module_type to rule shim map for every module type in Soong.
-func generateSoongModuleBzl(bzlLoads map[string]RuleShim) string {
-	var loadStmts string
-	var moduleRuleMap string
-	for bzlFileName, ruleShim := range bzlLoads {
-		loadStmt := "load(\"//build/bazel/queryview_rules:"
-		loadStmt += bzlFileName
-		loadStmt += ".bzl\""
-		for _, rule := range ruleShim.rules {
-			loadStmt += fmt.Sprintf(", %q", rule)
-			moduleRuleMap += "    \"" + rule + "\": " + rule + ",\n"
-		}
-		loadStmt += ")\n"
-		loadStmts += loadStmt
-	}
-
-	return fmt.Sprintf(soongModuleBzl, loadStmts, moduleRuleMap)
-}
-
-func shouldGenerateAttribute(prop string) bool {
-	return !ignoredPropNames[prop]
-}
-
-// props is an unsorted map. This function ensures that
-// the generated attributes are sorted to ensure determinism.
-func propsToAttributes(props map[string]string) string {
-	var attributes string
-	for _, propName := range android.SortedStringKeys(props) {
-		if shouldGenerateAttribute(propName) {
-			attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
-		}
-	}
-	return attributes
-}
-
-// Convert a module and its deps and props into a Bazel macro/rule
-// representation in the BUILD file.
-func generateSoongModuleTarget(
-	blueprintCtx *blueprint.Context,
-	module blueprint.Module) string {
-
-	var props map[string]string
-	if aModule, ok := module.(android.Module); ok {
-		props = extractModuleProperties(aModule)
-	}
-	attributes := propsToAttributes(props)
-
-	// TODO(b/163018919): DirectDeps can have duplicate (module, variant)
-	// items, if the modules are added using different DependencyTag. Figure
-	// out the implications of that.
-	depLabels := map[string]bool{}
-	blueprintCtx.VisitDirectDeps(module, func(depModule blueprint.Module) {
-		depLabels[qualifiedTargetLabel(blueprintCtx, depModule)] = true
-	})
-
-	depLabelList := "[\n"
-	for depLabel, _ := range depLabels {
-		depLabelList += fmt.Sprintf("        %q,\n", depLabel)
-	}
-	depLabelList += "    ]"
-
-	return fmt.Sprintf(
-		soongModuleTarget,
-		targetNameWithVariant(blueprintCtx, module),
-		blueprintCtx.ModuleName(module),
-		canonicalizeModuleType(blueprintCtx.ModuleType(module)),
-		blueprintCtx.ModuleSubDir(module),
-		depLabelList,
-		attributes)
-}
-
-func buildFileForModule(
-	ctx *blueprint.Context, module blueprint.Module, bazelQueryViewDir string) (*os.File, error) {
-	// Create nested directories for the BUILD file
-	dirPath := filepath.Join(bazelQueryViewDir, packagePath(ctx, module))
-	createDirectoryIfNonexistent(dirPath)
-	// Open the file for appending, and create it if it doesn't exist
-	f, err := os.OpenFile(
-		filepath.Join(dirPath, "BUILD.bazel"),
-		os.O_APPEND|os.O_CREATE|os.O_WRONLY,
-		0644)
-	if err != nil {
-		return nil, err
-	}
-
-	// If the file is empty, add the load statement for the `soong_module` rule
-	fi, err := f.Stat()
-	if err != nil {
-		return nil, err
-	}
-	if fi.Size() == 0 {
-		f.Write([]byte(soongModuleLoad + "\n"))
-	}
-
-	return f, nil
-}
-
-func createDirectoryIfNonexistent(dir string) {
-	if _, err := os.Stat(dir); os.IsNotExist(err) {
-		os.MkdirAll(dir, os.ModePerm)
-	}
-}
-
-// The QueryView directory should be read-only, sufficient for bazel query. The files
+// The auto-conversion directory should be read-only, sufficient for bazel query. The files
 // are not intended to be edited by end users.
-func writeReadOnlyFile(dir string, baseName string, content string) error {
-	createDirectoryIfNonexistent(dir)
-	pathToFile := filepath.Join(dir, baseName)
+func writeReadOnlyFile(dir string, f bp2build.BazelFile) error {
+	dir = filepath.Join(dir, f.Dir)
+	if err := createDirectoryIfNonexistent(dir); err != nil {
+		return err
+	}
+	pathToFile := filepath.Join(dir, f.Basename)
+
 	// 0444 is read-only
-	return ioutil.WriteFile(pathToFile, []byte(content), 0444)
+	err := ioutil.WriteFile(pathToFile, []byte(f.Contents), 0444)
+
+	return err
 }
 
-func isZero(value reflect.Value) bool {
-	switch value.Kind() {
-	case reflect.Func, reflect.Map, reflect.Slice:
-		return value.IsNil()
-	case reflect.Array:
-		valueIsZero := true
-		for i := 0; i < value.Len(); i++ {
-			valueIsZero = valueIsZero && isZero(value.Index(i))
-		}
-		return valueIsZero
-	case reflect.Struct:
-		valueIsZero := true
-		for i := 0; i < value.NumField(); i++ {
-			if value.Field(i).CanSet() {
-				valueIsZero = valueIsZero && isZero(value.Field(i))
-			}
-		}
-		return valueIsZero
-	case reflect.Ptr:
-		if !value.IsNil() {
-			return isZero(reflect.Indirect(value))
-		} else {
-			return true
-		}
-	default:
-		zeroValue := reflect.Zero(value.Type())
-		result := value.Interface() == zeroValue.Interface()
-		return result
+func createDirectoryIfNonexistent(dir string) error {
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		return os.MkdirAll(dir, os.ModePerm)
+	} else {
+		return err
 	}
 }
diff --git a/cmd/soong_build/queryview_test.go b/cmd/soong_build/queryview_test.go
deleted file mode 100644
index 9471a91..0000000
--- a/cmd/soong_build/queryview_test.go
+++ /dev/null
@@ -1,470 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"android/soong/android"
-	"io/ioutil"
-	"os"
-	"strings"
-	"testing"
-
-	"github.com/google/blueprint/bootstrap/bpdoc"
-)
-
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "bazel_queryview_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
-func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
-}
-
-type customModule struct {
-	android.ModuleBase
-}
-
-// OutputFiles is needed because some instances of this module use dist with a
-// tag property which requires the module implements OutputFileProducer.
-func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
-	return android.PathsForTesting("path" + tag), nil
-}
-
-func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// nothing for now.
-}
-
-func customModuleFactory() android.Module {
-	module := &customModule{}
-	android.InitAndroidModule(module)
-	return module
-}
-
-func TestGenerateBazelQueryViewFromBlueprint(t *testing.T) {
-	testCases := []struct {
-		bp                  string
-		expectedBazelTarget string
-	}{
-		{
-			bp: `custom {
-	name: "foo",
-}
-		`,
-			expectedBazelTarget: `soong_module(
-    name = "foo",
-    module_name = "foo",
-    module_type = "custom",
-    module_variant = "",
-    module_deps = [
-    ],
-)`,
-		},
-		{
-			bp: `custom {
-	name: "foo",
-	ramdisk: true,
-}
-		`,
-			expectedBazelTarget: `soong_module(
-    name = "foo",
-    module_name = "foo",
-    module_type = "custom",
-    module_variant = "",
-    module_deps = [
-    ],
-    ramdisk = True,
-)`,
-		},
-		{
-			bp: `custom {
-	name: "foo",
-	owner: "a_string_with\"quotes\"_and_\\backslashes\\\\",
-}
-		`,
-			expectedBazelTarget: `soong_module(
-    name = "foo",
-    module_name = "foo",
-    module_type = "custom",
-    module_variant = "",
-    module_deps = [
-    ],
-    owner = "a_string_with\"quotes\"_and_\\backslashes\\\\",
-)`,
-		},
-		{
-			bp: `custom {
-	name: "foo",
-	required: ["bar"],
-}
-		`,
-			expectedBazelTarget: `soong_module(
-    name = "foo",
-    module_name = "foo",
-    module_type = "custom",
-    module_variant = "",
-    module_deps = [
-    ],
-    required = [
-        "bar",
-    ],
-)`,
-		},
-		{
-			bp: `custom {
-	name: "foo",
-	target_required: ["qux", "bazqux"],
-}
-		`,
-			expectedBazelTarget: `soong_module(
-    name = "foo",
-    module_name = "foo",
-    module_type = "custom",
-    module_variant = "",
-    module_deps = [
-    ],
-    target_required = [
-        "qux",
-        "bazqux",
-    ],
-)`,
-		},
-		{
-			bp: `custom {
-	name: "foo",
-	dist: {
-		targets: ["goal_foo"],
-		tag: ".foo",
-	},
-	dists: [
-		{
-			targets: ["goal_bar"],
-			tag: ".bar",
-		},
-	],
-}
-		`,
-			expectedBazelTarget: `soong_module(
-    name = "foo",
-    module_name = "foo",
-    module_type = "custom",
-    module_variant = "",
-    module_deps = [
-    ],
-    dist = {
-        "tag": ".foo",
-        "targets": [
-            "goal_foo",
-        ],
-    },
-    dists = [
-        {
-            "tag": ".bar",
-            "targets": [
-                "goal_bar",
-            ],
-        },
-    ],
-)`,
-		},
-		{
-			bp: `custom {
-	name: "foo",
-	required: ["bar"],
-	target_required: ["qux", "bazqux"],
-	ramdisk: true,
-	owner: "custom_owner",
-	dists: [
-		{
-			tag: ".tag",
-			targets: ["my_goal"],
-		},
-	],
-}
-		`,
-			expectedBazelTarget: `soong_module(
-    name = "foo",
-    module_name = "foo",
-    module_type = "custom",
-    module_variant = "",
-    module_deps = [
-    ],
-    dists = [
-        {
-            "tag": ".tag",
-            "targets": [
-                "my_goal",
-            ],
-        },
-    ],
-    owner = "custom_owner",
-    ramdisk = True,
-    required = [
-        "bar",
-    ],
-    target_required = [
-        "qux",
-        "bazqux",
-    ],
-)`,
-		},
-	}
-
-	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.Register()
-
-		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-		android.FailIfErrored(t, errs)
-		_, errs = ctx.PrepareBuildActions(config)
-		android.FailIfErrored(t, errs)
-
-		module := ctx.ModuleForTests("foo", "").Module().(*customModule)
-		blueprintCtx := ctx.Context.Context
-
-		actualBazelTarget := generateSoongModuleTarget(blueprintCtx, module)
-		if actualBazelTarget != testCase.expectedBazelTarget {
-			t.Errorf(
-				"Expected generated Bazel target to be '%s', got '%s'",
-				testCase.expectedBazelTarget,
-				actualBazelTarget,
-			)
-		}
-	}
-}
-
-func createPackageFixtures() []*bpdoc.Package {
-	properties := []bpdoc.Property{
-		bpdoc.Property{
-			Name: "int64_prop",
-			Type: "int64",
-		},
-		bpdoc.Property{
-			Name: "int_prop",
-			Type: "int",
-		},
-		bpdoc.Property{
-			Name: "bool_prop",
-			Type: "bool",
-		},
-		bpdoc.Property{
-			Name: "string_prop",
-			Type: "string",
-		},
-		bpdoc.Property{
-			Name: "string_list_prop",
-			Type: "list of string",
-		},
-		bpdoc.Property{
-			Name: "nested_prop",
-			Type: "",
-			Properties: []bpdoc.Property{
-				bpdoc.Property{
-					Name: "int_prop",
-					Type: "int",
-				},
-				bpdoc.Property{
-					Name: "bool_prop",
-					Type: "bool",
-				},
-				bpdoc.Property{
-					Name: "string_prop",
-					Type: "string",
-				},
-			},
-		},
-		bpdoc.Property{
-			Name: "unknown_type",
-			Type: "unknown",
-		},
-	}
-
-	fooPropertyStruct := &bpdoc.PropertyStruct{
-		Name:       "FooProperties",
-		Properties: properties,
-	}
-
-	moduleTypes := []*bpdoc.ModuleType{
-		&bpdoc.ModuleType{
-			Name: "foo_library",
-			PropertyStructs: []*bpdoc.PropertyStruct{
-				fooPropertyStruct,
-			},
-		},
-
-		&bpdoc.ModuleType{
-			Name: "foo_binary",
-			PropertyStructs: []*bpdoc.PropertyStruct{
-				fooPropertyStruct,
-			},
-		},
-		&bpdoc.ModuleType{
-			Name: "foo_test",
-			PropertyStructs: []*bpdoc.PropertyStruct{
-				fooPropertyStruct,
-			},
-		},
-	}
-
-	return [](*bpdoc.Package){
-		&bpdoc.Package{
-			Name:        "foo_language",
-			Path:        "android/soong/foo",
-			ModuleTypes: moduleTypes,
-		},
-	}
-}
-
-func TestGenerateModuleRuleShims(t *testing.T) {
-	ruleShims, err := createRuleShims(createPackageFixtures())
-	if err != nil {
-		panic(err)
-	}
-
-	if len(ruleShims) != 1 {
-		t.Errorf("Expected to generate 1 rule shim, but got %d", len(ruleShims))
-	}
-
-	fooRuleShim := ruleShims["foo"]
-	expectedRules := []string{"foo_binary", "foo_library", "foo_test_"}
-
-	if len(fooRuleShim.rules) != 3 {
-		t.Errorf("Expected 3 rules, but got %d", len(fooRuleShim.rules))
-	}
-
-	for i, rule := range fooRuleShim.rules {
-		if rule != expectedRules[i] {
-			t.Errorf("Expected rule shim to contain %s, but got %s", expectedRules[i], rule)
-		}
-	}
-
-	expectedBzl := `load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
-
-def _foo_binary_impl(ctx):
-    return [SoongModuleInfo()]
-
-foo_binary = rule(
-    implementation = _foo_binary_impl,
-    attrs = {
-        "module_name": attr.string(mandatory = True),
-        "module_variant": attr.string(),
-        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
-        "bool_prop": attr.bool(),
-        "int64_prop": attr.int(),
-        "int_prop": attr.int(),
-#         "nested_prop__int_prop": attr.int(),
-#         "nested_prop__bool_prop": attr.bool(),
-#         "nested_prop__string_prop": attr.string(),
-        "string_list_prop": attr.string_list(),
-        "string_prop": attr.string(),
-    },
-)
-
-def _foo_library_impl(ctx):
-    return [SoongModuleInfo()]
-
-foo_library = rule(
-    implementation = _foo_library_impl,
-    attrs = {
-        "module_name": attr.string(mandatory = True),
-        "module_variant": attr.string(),
-        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
-        "bool_prop": attr.bool(),
-        "int64_prop": attr.int(),
-        "int_prop": attr.int(),
-#         "nested_prop__int_prop": attr.int(),
-#         "nested_prop__bool_prop": attr.bool(),
-#         "nested_prop__string_prop": attr.string(),
-        "string_list_prop": attr.string_list(),
-        "string_prop": attr.string(),
-    },
-)
-
-def _foo_test__impl(ctx):
-    return [SoongModuleInfo()]
-
-foo_test_ = rule(
-    implementation = _foo_test__impl,
-    attrs = {
-        "module_name": attr.string(mandatory = True),
-        "module_variant": attr.string(),
-        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
-        "bool_prop": attr.bool(),
-        "int64_prop": attr.int(),
-        "int_prop": attr.int(),
-#         "nested_prop__int_prop": attr.int(),
-#         "nested_prop__bool_prop": attr.bool(),
-#         "nested_prop__string_prop": attr.string(),
-        "string_list_prop": attr.string_list(),
-        "string_prop": attr.string(),
-    },
-)
-`
-
-	if fooRuleShim.content != expectedBzl {
-		t.Errorf(
-			"Expected the generated rule shim bzl to be:\n%s\nbut got:\n%s",
-			expectedBzl,
-			fooRuleShim.content)
-	}
-}
-
-func TestGenerateSoongModuleBzl(t *testing.T) {
-	ruleShims, err := createRuleShims(createPackageFixtures())
-	if err != nil {
-		panic(err)
-	}
-	actualSoongModuleBzl := generateSoongModuleBzl(ruleShims)
-
-	expectedLoad := "load(\"//build/bazel/queryview_rules:foo.bzl\", \"foo_binary\", \"foo_library\", \"foo_test_\")"
-	expectedRuleMap := `soong_module_rule_map = {
-    "foo_binary": foo_binary,
-    "foo_library": foo_library,
-    "foo_test_": foo_test_,
-}`
-	if !strings.Contains(actualSoongModuleBzl, expectedLoad) {
-		t.Errorf(
-			"Generated soong_module.bzl:\n\n%s\n\n"+
-				"Could not find the load statement in the generated soong_module.bzl:\n%s",
-			actualSoongModuleBzl,
-			expectedLoad)
-	}
-
-	if !strings.Contains(actualSoongModuleBzl, expectedRuleMap) {
-		t.Errorf(
-			"Generated soong_module.bzl:\n\n%s\n\n"+
-				"Could not find the module -> rule map in the generated soong_module.bzl:\n%s",
-			actualSoongModuleBzl,
-			expectedRuleMap)
-	}
-}
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index ab789aa..532d8fc 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -255,24 +255,13 @@
 
 // Add class loader context for the given library to the map entry for the given SDK version.
 func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
-	hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
-
-	// If missing dependencies are allowed, the build shouldn't fail when a <uses-library> is
-	// not found. However, this is likely to result is disabling dexpreopt, as it won't be
-	// possible to construct class loader context without on-host and on-device library paths.
-	strict = strict && !ctx.Config().AllowMissingDependencies()
-
-	if hostPath == nil && strict {
-		return fmt.Errorf("unknown build path to <uses-library> \"%s\"", lib)
-	}
+	hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) error {
 
 	devicePath := UnknownInstallLibraryPath
 	if installPath == nil {
 		if android.InList(lib, CompatUsesLibs) || android.InList(lib, OptionalCompatUsesLibs) {
 			// Assume that compatibility libraries are installed in /system/framework.
 			installPath = android.PathForModuleInstall(ctx, "framework", lib+".jar")
-		} else if strict {
-			return fmt.Errorf("unknown install path to <uses-library> \"%s\"", lib)
 		} else {
 			// For some stub libraries the only known thing is the name of their implementation
 			// library, but the library itself is unavailable (missing or part of a prebuilt). In
@@ -310,40 +299,17 @@
 	return nil
 }
 
-// Wrapper around addContext that reports errors.
-func (clcMap ClassLoaderContextMap) addContextOrReportError(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
-	hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
-
-	err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, strict, nestedClcMap)
-	if err != nil {
-		ctx.ModuleErrorf(err.Error())
-	}
-}
-
-// Add class loader context. Fail on unknown build/install paths.
-func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, lib string,
-	hostPath, installPath android.Path) {
-
-	clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, hostPath, installPath, true, nil)
-}
-
-// Add class loader context if the library exists. Don't fail on unknown build/install paths.
-func (clcMap ClassLoaderContextMap) MaybeAddContext(ctx android.ModuleInstallPathContext, lib *string,
-	hostPath, installPath android.Path) {
-
-	if lib != nil {
-		clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, hostPath, installPath, false, nil)
-	}
-}
-
 // Add class loader context for the given SDK version. Don't fail on unknown build/install paths, as
 // libraries with unknown paths still need to be processed by manifest_fixer (which doesn't care
 // about paths). For the subset of libraries that are used in dexpreopt, their build/install paths
 // are validated later before CLC is used (in validateClassLoaderContext).
-func (clcMap ClassLoaderContextMap) AddContextForSdk(ctx android.ModuleInstallPathContext, sdkVer int,
+func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, sdkVer int,
 	lib string, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
 
-	clcMap.addContextOrReportError(ctx, sdkVer, lib, hostPath, installPath, false, nestedClcMap)
+	err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, nestedClcMap)
+	if err != nil {
+		ctx.ModuleErrorf(err.Error())
+	}
 }
 
 // Merge the other class loader context map into this one, do not override existing entries.
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 6b6b162..86f7871 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -18,6 +18,7 @@
 // For class loader context tests involving .bp files, see TestUsesLibraries in java package.
 
 import (
+	"fmt"
 	"reflect"
 	"strings"
 	"testing"
@@ -50,36 +51,30 @@
 
 	m := make(ClassLoaderContextMap)
 
-	m.AddContext(ctx, "a", buildPath(ctx, "a"), installPath(ctx, "a"))
-	m.AddContext(ctx, "b", buildPath(ctx, "b"), installPath(ctx, "b"))
-
-	// "Maybe" variant in the good case: add as usual.
-	c := "c"
-	m.MaybeAddContext(ctx, &c, buildPath(ctx, "c"), installPath(ctx, "c"))
-
-	// "Maybe" variant in the bad case: don't add library with unknown name, keep going.
-	m.MaybeAddContext(ctx, nil, nil, nil)
+	m.AddContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+	m.AddContext(ctx, AnySdkVersion, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
 
 	// Add some libraries with nested subcontexts.
 
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContext(ctx, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"))
-	m1.AddContext(ctx, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"))
+	m1.AddContext(ctx, AnySdkVersion, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"), nil)
+	m1.AddContext(ctx, AnySdkVersion, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"), nil)
 
 	m2 := make(ClassLoaderContextMap)
-	m2.AddContext(ctx, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"))
-	m2.AddContext(ctx, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"))
-	m2.AddContextForSdk(ctx, AnySdkVersion, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
+	m2.AddContext(ctx, AnySdkVersion, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"), nil)
+	m2.AddContext(ctx, AnySdkVersion, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"), nil)
+	m2.AddContext(ctx, AnySdkVersion, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
 
 	m3 := make(ClassLoaderContextMap)
-	m3.AddContext(ctx, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"))
-	m3.AddContext(ctx, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"))
+	m3.AddContext(ctx, AnySdkVersion, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"), nil)
+	m3.AddContext(ctx, AnySdkVersion, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"), nil)
 
-	m.AddContextForSdk(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), m2)
+	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), m2)
 	// When the same library is both in conditional and unconditional context, it should be removed
 	// from conditional context.
-	m.AddContextForSdk(ctx, 42, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
-	m.AddContextForSdk(ctx, AnySdkVersion, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContext(ctx, 42, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContext(ctx, AnySdkVersion, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
 
 	// Merge map with implicit root library that is among toplevel contexts => does nothing.
 	m.AddContextMap(m1, "c")
@@ -88,12 +83,12 @@
 	m.AddContextMap(m3, "m_g")
 
 	// Compatibility libraries with unknown install paths get default paths.
-	m.AddContextForSdk(ctx, 29, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil, nil)
-	m.AddContextForSdk(ctx, 29, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil, nil)
+	m.AddContext(ctx, 29, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil, nil)
+	m.AddContext(ctx, 29, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil, nil)
 
 	// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
 	// needed as a compatibility library if "android.test.runner" is in CLC as well.
-	m.AddContextForSdk(ctx, 30, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil, nil)
+	m.AddContext(ctx, 30, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil, nil)
 
 	valid, validationError := validateClassLoaderContext(m)
 
@@ -160,31 +155,19 @@
 	})
 }
 
-// Test that an unexpected unknown build path causes immediate error.
-func TestCLCUnknownBuildPath(t *testing.T) {
-	ctx := testContext()
-	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "a", nil, nil, true, nil)
-	checkError(t, err, "unknown build path to <uses-library> \"a\"")
-}
-
-// Test that an unexpected unknown install path causes immediate error.
-func TestCLCUnknownInstallPath(t *testing.T) {
-	ctx := testContext()
-	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), nil, true, nil)
-	checkError(t, err, "unknown install path to <uses-library> \"a\"")
-}
-
-func TestCLCMaybeAdd(t *testing.T) {
+// Test that unknown library paths cause a validation error.
+func testCLCUnknownPath(t *testing.T, whichPath string) {
 	ctx := testContext()
 
 	m := make(ClassLoaderContextMap)
-	a := "a"
-	m.MaybeAddContext(ctx, &a, nil, nil)
+	if whichPath == "build" {
+		m.AddContext(ctx, AnySdkVersion, "a", nil, nil, nil)
+	} else {
+		m.AddContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), nil, nil)
+	}
 
 	// The library should be added to <uses-library> tags by the manifest_fixer.
-	t.Run("maybe add", func(t *testing.T) {
+	t.Run("uses libs", func(t *testing.T) {
 		haveUsesLibs := m.UsesLibs()
 		wantUsesLibs := []string{"a"}
 		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
@@ -192,20 +175,28 @@
 		}
 	})
 
-	// But class loader context in such cases should raise an error on validation.
-	t.Run("validate", func(t *testing.T) {
-		_, err := validateClassLoaderContext(m)
-		checkError(t, err, "invalid build path for <uses-library> \"a\"")
-	})
+	// But CLC cannot be constructed: there is a validation error.
+	_, err := validateClassLoaderContext(m)
+	checkError(t, err, fmt.Sprintf("invalid %s path for <uses-library> \"a\"", whichPath))
+}
+
+// Test that unknown build path is an error.
+func TestCLCUnknownBuildPath(t *testing.T) {
+	testCLCUnknownPath(t, "build")
+}
+
+// Test that unknown install path is an error.
+func TestCLCUnknownInstallPath(t *testing.T) {
+	testCLCUnknownPath(t, "install")
 }
 
 // An attempt to add conditional nested subcontext should fail.
 func TestCLCNestedConditional(t *testing.T) {
 	ctx := testContext()
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContextForSdk(ctx, 42, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m1.AddContext(ctx, 42, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
 	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), true, m1)
+	err := m.addContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), m1)
 	checkError(t, err, "nested class loader context shouldn't have conditional part")
 }
 
@@ -214,10 +205,10 @@
 func TestCLCSdkVersionOrder(t *testing.T) {
 	ctx := testContext()
 	m := make(ClassLoaderContextMap)
-	m.AddContextForSdk(ctx, 28, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContextForSdk(ctx, 29, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContextForSdk(ctx, 30, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
-	m.AddContextForSdk(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), nil)
+	m.AddContext(ctx, 28, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, 29, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+	m.AddContext(ctx, 30, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), nil)
 
 	valid, validationError := validateClassLoaderContext(m)
 
diff --git a/go.mod b/go.mod
index 117fa65..7297dea 100644
--- a/go.mod
+++ b/go.mod
@@ -8,4 +8,4 @@
 
 replace github.com/google/blueprint v0.0.0 => ../blueprint
 
-go 1.15.6
+go 1.15
diff --git a/java/app.go b/java/app.go
index 574472c..e6c9a2d 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1226,7 +1226,7 @@
 			if tag, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
 				dep := ctx.OtherModuleName(m)
 				if lib, ok := m.(UsesLibraryDependency); ok {
-					clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep,
+					clcMap.AddContext(ctx, tag.sdkVersion, dep,
 						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{dep})
diff --git a/java/java.go b/java/java.go
index 82b53be..f684a00 100644
--- a/java/java.go
+++ b/java/java.go
@@ -3305,7 +3305,7 @@
 	}
 
 	if implicitSdkLib != nil {
-		clcMap.AddContextForSdk(ctx, dexpreopt.AnySdkVersion, *implicitSdkLib,
+		clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *implicitSdkLib,
 			dep.DexJarBuildPath(), dep.DexJarInstallPath(), dep.ClassLoaderContexts())
 	} else {
 		depName := ctx.OtherModuleName(depModule)
diff --git a/rust/binary.go b/rust/binary.go
index c2d97f3..ca07d07 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -164,3 +164,7 @@
 	}
 	return binary.baseCompiler.stdLinkage(ctx)
 }
+
+func (binary *binaryDecorator) isDependencyRoot() bool {
+	return true
+}
diff --git a/rust/compiler.go b/rust/compiler.go
index ee88a27..bcea6cc 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -236,6 +236,10 @@
 	panic(fmt.Errorf("baseCrater doesn't know how to crate things!"))
 }
 
+func (compiler *baseCompiler) isDependencyRoot() bool {
+	return false
+}
+
 func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...)
 	deps.Dylibs = append(deps.Dylibs, compiler.Properties.Dylibs...)
diff --git a/rust/rust.go b/rust/rust.go
index 1053846..1fa97af 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -106,6 +106,42 @@
 	hideApexVariantFromMake bool
 }
 
+func (mod *Module) Header() bool {
+	//TODO: If Rust libraries provide header variants, this needs to be updated.
+	return false
+}
+
+func (mod *Module) SetPreventInstall() {
+	mod.Properties.PreventInstall = true
+}
+
+// Returns true if the module is "vendor" variant. Usually these modules are installed in /vendor
+func (mod *Module) InVendor() bool {
+	return mod.Properties.ImageVariationPrefix == cc.VendorVariationPrefix
+}
+
+func (mod *Module) SetHideFromMake() {
+	mod.Properties.HideFromMake = true
+}
+
+func (mod *Module) SanitizePropDefined() bool {
+	return false
+}
+
+func (mod *Module) IsDependencyRoot() bool {
+	if mod.compiler != nil {
+		return mod.compiler.isDependencyRoot()
+	}
+	panic("IsDependencyRoot called on a non-compiler Rust module")
+}
+
+func (mod *Module) IsPrebuilt() bool {
+	if _, ok := mod.compiler.(*prebuiltLibraryDecorator); ok {
+		return true
+	}
+	return false
+}
+
 func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
 	switch tag {
 	case "":
@@ -281,6 +317,7 @@
 	SetDisabled()
 
 	stdLinkage(ctx *depsContext) RustLinkage
+	isDependencyRoot() bool
 }
 
 type exportedFlagsProducer interface {
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 8db33a3..18749b5 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -345,15 +345,8 @@
 		depTag := ctx.OtherModuleDependencyTag(dep)
 		switch depTag {
 		case shTestDataBinsTag, shTestDataDeviceBinsTag:
-			if cc, isCc := dep.(*cc.Module); isCc {
-				s.addToDataModules(ctx, cc.OutputFile().Path().Base(), cc.OutputFile().Path())
-				return
-			}
-			property := "data_bins"
-			if depTag == shTestDataDeviceBinsTag {
-				property = "data_device_bins"
-			}
-			ctx.PropertyErrorf(property, "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
+			path := android.OutputFileForModule(ctx, dep, "")
+			s.addToDataModules(ctx, path.Base(), path)
 		case shTestDataLibsTag, shTestDataDeviceLibsTag:
 			if cc, isCc := dep.(*cc.Module); isCc {
 				// Copy to an intermediate output directory to append "lib[64]" to the path,