Merge "Update error message for new policy."
diff --git a/android/bazel.go b/android/bazel.go
index 26e7deb..7123ed2 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -151,6 +151,7 @@
 
 		"prebuilts/sdk":/* recursive = */ false,
 		"prebuilts/sdk/tools":/* recursive = */ false,
+		"prebuilts/r8":/* recursive = */ false,
 		"packages/apps/Music":/* recursive = */ false,
 	}
 
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index c6364af..341d500 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -333,7 +333,7 @@
 	// The actual platform values here may be overridden by configuration
 	// transitions from the buildroot.
 	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_arm"))
+		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_target"))
 	cmdFlags = append(cmdFlags,
 		fmt.Sprintf("--extra_toolchains=%s", "//prebuilts/clang/host/linux-x86:all"))
 	// This should be parameterized on the host OS, but let's restrict to linux
diff --git a/android/paths.go b/android/paths.go
index 99db22f..9c9914e 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -20,6 +20,7 @@
 	"os"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"sort"
 	"strings"
 
@@ -2094,3 +2095,25 @@
 	}
 	return ret
 }
+
+var thirdPartyDirPrefixExceptions = []*regexp.Regexp{
+	regexp.MustCompile("^vendor/[^/]*google[^/]*/"),
+	regexp.MustCompile("^hardware/google/"),
+	regexp.MustCompile("^hardware/interfaces/"),
+	regexp.MustCompile("^hardware/libhardware[^/]*/"),
+	regexp.MustCompile("^hardware/ril/"),
+}
+
+func IsThirdPartyPath(path string) bool {
+	thirdPartyDirPrefixes := []string{"external/", "vendor/", "hardware/"}
+
+	if HasAnyPrefix(path, thirdPartyDirPrefixes) {
+		for _, prefix := range thirdPartyDirPrefixExceptions {
+			if prefix.MatchString(path) {
+				return false
+			}
+		}
+		return true
+	}
+	return false
+}
diff --git a/apex/apex.go b/apex/apex.go
index 149f782..e525aff 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1696,6 +1696,7 @@
 	a.checkUpdatable(ctx)
 	a.checkMinSdkVersion(ctx)
 	a.checkStaticLinkingToStubLibraries(ctx)
+	a.checkStaticExecutables(ctx)
 	if len(a.properties.Tests) > 0 && !a.testApex {
 		ctx.PropertyErrorf("tests", "property allowed only in apex_test module type")
 		return
@@ -2487,6 +2488,41 @@
 	})
 }
 
+// checkStaticExecutable ensures that executables in an APEX are not static.
+func (a *apexBundle) checkStaticExecutables(ctx android.ModuleContext) {
+	// No need to run this for host APEXes
+	if ctx.Host() {
+		return
+	}
+
+	ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
+		if ctx.OtherModuleDependencyTag(module) != executableTag {
+			return
+		}
+
+		if l, ok := module.(cc.LinkableInterface); ok && l.StaticExecutable() {
+			apex := a.ApexVariationName()
+			exec := ctx.OtherModuleName(module)
+			if isStaticExecutableAllowed(apex, exec) {
+				return
+			}
+			ctx.ModuleErrorf("executable %s is static", ctx.OtherModuleName(module))
+		}
+	})
+}
+
+// A small list of exceptions where static executables are allowed in APEXes.
+func isStaticExecutableAllowed(apex string, exec string) bool {
+	m := map[string][]string{
+		"com.android.runtime": []string{
+			"linker",
+			"linkerconfig",
+		},
+	}
+	execNames, ok := m[apex]
+	return ok && android.InList(exec, execNames)
+}
+
 // Collect information for opening IDE project files in java/jdeps.go.
 func (a *apexBundle) IDEInfo(dpInfo *android.IdeInfo) {
 	dpInfo.Deps = append(dpInfo.Deps, a.properties.Java_libs...)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index f58bf6c..41bfcea 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -374,7 +374,6 @@
 			symlinks: ["foo_link_"],
 			symlink_preferred_arch: true,
 			system_shared_libs: [],
-			static_executable: true,
 			stl: "none",
 			apex_available: [ "myapex", "com.android.gki.*" ],
 		}
@@ -2494,7 +2493,6 @@
 			srcs: ["mylib.cpp"],
 			relative_install_path: "foo/bar",
 			system_shared_libs: [],
-			static_executable: true,
 			stl: "none",
 			apex_available: [ "myapex" ],
 		}
@@ -2554,7 +2552,6 @@
 			name: "mybin",
 			relative_install_path: "foo/bar",
 			system_shared_libs: [],
-			static_executable: true,
 			stl: "none",
 			apex_available: [ "myapex" ],
 			native_bridge_supported: true,
@@ -8188,6 +8185,57 @@
 	}
 }
 
+func TestProhibitStaticExecutable(t *testing.T) {
+	testApexError(t, `executable mybin is static`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			binaries: ["mybin"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_binary {
+			name: "mybin",
+			srcs: ["mylib.cpp"],
+			relative_install_path: "foo/bar",
+			static_executable: true,
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
+		}
+	`)
+
+	testApexError(t, `executable mybin.rust is static`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			binaries: ["mybin.rust"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		rust_binary {
+			name: "mybin.rust",
+			srcs: ["foo.rs"],
+			static_executable: true,
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
+	`)
+}
+
 func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
diff --git a/cc/cc_test.go b/cc/cc_test.go
index dd51fe8..84c3a86 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -3955,10 +3955,13 @@
 		`, lib, lib)
 	}
 
-	ctx := PrepareForIntegrationTestWithCc.RunTestWithBp(t, bp)
+	ctx := android.GroupFixturePreparers(
+		PrepareForIntegrationTestWithCc,
+		android.FixtureAddTextFile("external/foo/Android.bp", bp),
+	).RunTest(t)
 	// Use the arm variant instead of the arm64 variant so that it gets headers from
 	// ndk_libandroid_support to test LateStaticLibs.
-	cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/foo.o").Args["cFlags"]
+	cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/external/foo/foo.o").Args["cFlags"]
 
 	var includes []string
 	flags := strings.Split(cflags, " ")
@@ -3981,32 +3984,32 @@
 		"${config.ArmToolchainCflags}",
 		"${config.ArmArmv7ANeonCflags}",
 		"${config.ArmGenericCflags}",
-		"android_arm_export_include_dirs",
-		"lib32_export_include_dirs",
-		"arm_export_include_dirs",
-		"android_export_include_dirs",
-		"linux_export_include_dirs",
-		"export_include_dirs",
-		"android_arm_local_include_dirs",
-		"lib32_local_include_dirs",
-		"arm_local_include_dirs",
-		"android_local_include_dirs",
-		"linux_local_include_dirs",
-		"local_include_dirs",
-		".",
-		"libheader1",
-		"libheader2",
-		"libwhole1",
-		"libwhole2",
-		"libstatic1",
-		"libstatic2",
-		"libshared1",
-		"libshared2",
-		"liblinux",
-		"libandroid",
-		"libarm",
-		"lib32",
-		"libandroid_arm",
+		"external/foo/android_arm_export_include_dirs",
+		"external/foo/lib32_export_include_dirs",
+		"external/foo/arm_export_include_dirs",
+		"external/foo/android_export_include_dirs",
+		"external/foo/linux_export_include_dirs",
+		"external/foo/export_include_dirs",
+		"external/foo/android_arm_local_include_dirs",
+		"external/foo/lib32_local_include_dirs",
+		"external/foo/arm_local_include_dirs",
+		"external/foo/android_local_include_dirs",
+		"external/foo/linux_local_include_dirs",
+		"external/foo/local_include_dirs",
+		"external/foo",
+		"external/foo/libheader1",
+		"external/foo/libheader2",
+		"external/foo/libwhole1",
+		"external/foo/libwhole2",
+		"external/foo/libstatic1",
+		"external/foo/libstatic2",
+		"external/foo/libshared1",
+		"external/foo/libshared2",
+		"external/foo/liblinux",
+		"external/foo/libandroid",
+		"external/foo/libarm",
+		"external/foo/lib32",
+		"external/foo/libandroid_arm",
 		"defaults/cc/common/ndk_libc++_shared",
 		"defaults/cc/common/ndk_libandroid_support",
 		"out/soong/ndk/sysroot/usr/include",
diff --git a/cc/compiler.go b/cc/compiler.go
index 34ac47a..03214c8 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -450,7 +450,7 @@
 		"${config.CommonGlobalCflags}",
 		fmt.Sprintf("${config.%sGlobalCflags}", hod))
 
-	if isThirdParty(modulePath) {
+	if android.IsThirdPartyPath(modulePath) {
 		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "${config.ExternalCflags}")
 	}
 
@@ -675,27 +675,6 @@
 	return transformSourceToObj(ctx, subdir, srcFiles, flags, pathDeps, cFlagsDeps)
 }
 
-var thirdPartyDirPrefixExceptions = []*regexp.Regexp{
-	regexp.MustCompile("^vendor/[^/]*google[^/]*/"),
-	regexp.MustCompile("^hardware/google/"),
-	regexp.MustCompile("^hardware/interfaces/"),
-	regexp.MustCompile("^hardware/libhardware[^/]*/"),
-	regexp.MustCompile("^hardware/ril/"),
-}
-
-func isThirdParty(path string) bool {
-	thirdPartyDirPrefixes := []string{"external/", "vendor/", "hardware/"}
-
-	if android.HasAnyPrefix(path, thirdPartyDirPrefixes) {
-		for _, prefix := range thirdPartyDirPrefixExceptions {
-			if prefix.MatchString(path) {
-				return false
-			}
-		}
-	}
-	return true
-}
-
 // Properties for rust_bindgen related to generating rust bindings.
 // This exists here so these properties can be included in a cc_default
 // which can be used in both cc and rust modules.
diff --git a/cc/compiler_test.go b/cc/compiler_test.go
index c301388..9ae4d18 100644
--- a/cc/compiler_test.go
+++ b/cc/compiler_test.go
@@ -16,27 +16,30 @@
 
 import (
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestIsThirdParty(t *testing.T) {
-	shouldFail := []string{
+	thirdPartyPaths := []string{
 		"external/foo/",
 		"vendor/bar/",
 		"hardware/underwater_jaguar/",
 	}
-	shouldPass := []string{
+	nonThirdPartyPaths := []string{
 		"vendor/google/cts/",
 		"hardware/google/pixel",
 		"hardware/interfaces/camera",
 		"hardware/ril/supa_ril",
+		"bionic/libc",
 	}
-	for _, path := range shouldFail {
-		if !isThirdParty(path) {
+	for _, path := range thirdPartyPaths {
+		if !android.IsThirdPartyPath(path) {
 			t.Errorf("Expected %s to be considered third party", path)
 		}
 	}
-	for _, path := range shouldPass {
-		if isThirdParty(path) {
+	for _, path := range nonThirdPartyPaths {
+		if android.IsThirdPartyPath(path) {
 			t.Errorf("Expected %s to *not* be considered third party", path)
 		}
 	}
diff --git a/cc/rs.go b/cc/rs.go
index ba69f23..fbc86e2 100644
--- a/cc/rs.go
+++ b/cc/rs.go
@@ -36,7 +36,8 @@
 
 var rsCppCmdLine = strings.Replace(`
 ${rsCmd} -o ${outDir} -d ${outDir} -a ${out} -MD -reflect-c++ ${rsFlags} $in &&
-(echo '${out}: \' && cat ${depFiles} | awk 'start { sub(/( \\)?$$/, " \\"); print } /:/ { start=1 }') > ${out}.d &&
+echo '${out}: \' > ${out}.d &&
+for f in ${depFiles}; do cat $${f} | awk 'start { sub(/( \\)?$$/, " \\"); print } /:/ { start=1 }' >> ${out}.d; done &&
 touch $out
 `, "\n", "", -1)
 
diff --git a/genrule/genrule.go b/genrule/genrule.go
index c26b20c..71a8780 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -112,16 +112,17 @@
 	label string
 }
 type generatorProperties struct {
-	// The command to run on one or more input files. Cmd supports substitution of a few variables
+	// The command to run on one or more input files. Cmd supports substitution of a few variables.
 	//
 	// Available variables for substitution:
 	//
-	//  $(location): the path to the first entry in tools or tool_files
-	//  $(location <label>): the path to the tool, tool_file, input or output with name <label>
-	//  $(in): one or more input files
-	//  $(out): a single output file
-	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true
-	//  $(genDir): the sandbox directory for this tool; contains $(out)
+	//  $(location): the path to the first entry in tools or tool_files.
+	//  $(location <label>): the path to the tool, tool_file, input or output with name <label>. Use $(location) if <label> refers to a rule that outputs exactly one file.
+	//  $(locations <label>): the paths to the tools, tool_files, inputs or outputs with name <label>. Use $(locations) if <label> refers to a rule that outputs two or more files.
+	//  $(in): one or more input files.
+	//  $(out): a single output file.
+	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true.
+	//  $(genDir): the sandbox directory for this tool; contains $(out).
 	//  $$: a literal $
 	Cmd *string
 
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 268e797..c50e077 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -419,7 +419,7 @@
 	// local files that are used within user customized droiddoc options.
 	Droiddoc_option_files []string
 
-	// additional droiddoc options
+	// additional droiddoc options.
 	// Available variables for substitution:
 	//
 	//  $(location <label>): the path to the droiddoc_option_files with name <label>
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
index 4d3d8d8..72525c4 100644
--- a/mk2rbc/cmd/mk2rbc.go
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -21,13 +21,16 @@
 package main
 
 import (
+	"bufio"
 	"flag"
 	"fmt"
 	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"regexp"
 	"runtime/debug"
+	"runtime/pprof"
 	"sort"
 	"strings"
 	"time"
@@ -52,6 +55,7 @@
 	outputTop             = flag.String("outdir", "", "write output files into this directory hierarchy")
 	launcher              = flag.String("launcher", "", "generated launcher path. If set, the non-flag argument is _product_name_")
 	printProductConfigMap = flag.Bool("print_product_config_map", false, "print product config map and exit")
+	cpuProfile            = flag.String("cpu_profile", "", "write cpu profile to file")
 	traceCalls            = flag.Bool("trace_calls", false, "trace function calls")
 )
 
@@ -76,6 +80,7 @@
 var backupSuffix string
 var tracedVariables []string
 var errorLogger = errorsByType{data: make(map[string]datum)}
+var makefileFinder = &LinuxMakefileFinder{}
 
 func main() {
 	flag.Usage = func() {
@@ -122,6 +127,14 @@
 		tracedVariables = strings.Split(*traceVar, ",")
 	}
 
+	if *cpuProfile != "" {
+		f, err := os.Create(*cpuProfile)
+		if err != nil {
+			quit(err)
+		}
+		pprof.StartCPUProfile(f)
+		defer pprof.StopCPUProfile()
+	}
 	// Find out global variables
 	getConfigVariables()
 	getSoongVariables()
@@ -292,6 +305,8 @@
 		TracedVariables:    tracedVariables,
 		TraceCalls:         *traceCalls,
 		WarnPartialSuccess: *warn,
+		SourceFS:           os.DirFS(*rootDir),
+		MakefileFinder:     makefileFinder,
 	}
 	if *errstat {
 		mk2starRequest.ErrorLogger = errorLogger
@@ -494,3 +509,39 @@
 	}
 	return res, len(sorted)
 }
+
+type LinuxMakefileFinder struct {
+	cachedRoot      string
+	cachedMakefiles []string
+}
+
+func (l *LinuxMakefileFinder) Find(root string) []string {
+	if l.cachedMakefiles != nil && l.cachedRoot == root {
+		return l.cachedMakefiles
+	}
+	l.cachedRoot = root
+	l.cachedMakefiles = make([]string, 0)
+
+	// Return all *.mk files but not in hidden directories.
+
+	// NOTE(asmundak): as it turns out, even the WalkDir (which is an _optimized_ directory tree walker)
+	// is about twice slower than running `find` command (14s vs 6s on the internal Android source tree).
+	common_args := []string{"!", "-type", "d", "-name", "*.mk", "!", "-path", "*/.*/*"}
+	if root != "" {
+		common_args = append([]string{root}, common_args...)
+	}
+	cmd := exec.Command("/usr/bin/find", common_args...)
+	stdout, err := cmd.StdoutPipe()
+	if err == nil {
+		err = cmd.Start()
+	}
+	if err != nil {
+		panic(fmt.Errorf("cannot get the output from %s: %s", cmd, err))
+	}
+	scanner := bufio.NewScanner(stdout)
+	for scanner.Scan() {
+		l.cachedMakefiles = append(l.cachedMakefiles, strings.TrimPrefix(scanner.Text(), "./"))
+	}
+	stdout.Close()
+	return l.cachedMakefiles
+}
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index b06ed90..0bb8b95 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -240,22 +240,21 @@
 }
 
 func (eq *eqExpr) emit(gctx *generationContext) {
-	// Are we checking that a variable is empty?
-	var varRef *variableRefExpr
-	if s, ok := maybeString(eq.left); ok && s == "" {
-		varRef, ok = eq.right.(*variableRefExpr)
-	} else if s, ok := maybeString(eq.right); ok && s == "" {
-		varRef, ok = eq.left.(*variableRefExpr)
-	}
-	if varRef != nil {
-		// Yes.
+	emitSimple := func(expr starlarkExpr) {
 		if eq.isEq {
 			gctx.write("not ")
 		}
-		varRef.emit(gctx)
-		return
+		expr.emit(gctx)
 	}
+	// Are we checking that a variable is empty?
+	if isEmptyString(eq.left) {
+		emitSimple(eq.right)
+		return
+	} else if isEmptyString(eq.right) {
+		emitSimple(eq.left)
+		return
 
+	}
 	// General case
 	eq.left.emit(gctx)
 	if eq.isEq {
@@ -517,6 +516,7 @@
 }
 
 func (cx *callExpr) emit(gctx *generationContext) {
+	sep := ""
 	if cx.object != nil {
 		gctx.write("(")
 		cx.object.emit(gctx)
@@ -531,8 +531,14 @@
 			panic(fmt.Errorf("callExpr for %q should not be there", cx.name))
 		}
 		gctx.write(kf.runtimeName, "(")
+		if kf.hiddenArg == hiddenArgGlobal {
+			gctx.write("g")
+			sep = ", "
+		} else if kf.hiddenArg == hiddenArgConfig {
+			gctx.write("cfg")
+			sep = ", "
+		}
 	}
-	sep := ""
 	for _, arg := range cx.args {
 		gctx.write(sep)
 		arg.emit(gctx)
@@ -578,3 +584,8 @@
 	}
 	return expr
 }
+
+func isEmptyString(expr starlarkExpr) bool {
+	x, ok := expr.(*stringLiteralExpr)
+	return ok && x.literal == ""
+}
diff --git a/mk2rbc/find_mockfs.go b/mk2rbc/find_mockfs.go
new file mode 100644
index 0000000..73eff07
--- /dev/null
+++ b/mk2rbc/find_mockfs.go
@@ -0,0 +1,121 @@
+package mk2rbc
+
+import (
+	"io/fs"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+// Mock FS. Maps a directory name to an array of entries.
+// An entry implements fs.DirEntry, fs.FIleInfo and fs.File interface
+type FindMockFS struct {
+	dirs map[string][]myFileInfo
+}
+
+func (m FindMockFS) locate(name string) (myFileInfo, bool) {
+	if name == "." {
+		return myFileInfo{".", true}, true
+	}
+	dir := filepath.Dir(name)
+	base := filepath.Base(name)
+	if entries, ok := m.dirs[dir]; ok {
+		for _, e := range entries {
+			if e.name == base {
+				return e, true
+			}
+		}
+	}
+	return myFileInfo{}, false
+}
+
+func (m FindMockFS) create(name string, isDir bool) {
+	dir := filepath.Dir(name)
+	m.dirs[dir] = append(m.dirs[dir], myFileInfo{filepath.Base(name), isDir})
+}
+
+func (m FindMockFS) Stat(name string) (fs.FileInfo, error) {
+	if fi, ok := m.locate(name); ok {
+		return fi, nil
+	}
+	return nil, os.ErrNotExist
+}
+
+type myFileInfo struct {
+	name  string
+	isDir bool
+}
+
+func (m myFileInfo) Info() (fs.FileInfo, error) {
+	panic("implement me")
+}
+
+func (m myFileInfo) Size() int64 {
+	panic("implement me")
+}
+
+func (m myFileInfo) Mode() fs.FileMode {
+	panic("implement me")
+}
+
+func (m myFileInfo) ModTime() time.Time {
+	panic("implement me")
+}
+
+func (m myFileInfo) Sys() interface{} {
+	return nil
+}
+
+func (m myFileInfo) Stat() (fs.FileInfo, error) {
+	return m, nil
+}
+
+func (m myFileInfo) Read(bytes []byte) (int, error) {
+	panic("implement me")
+}
+
+func (m myFileInfo) Close() error {
+	panic("implement me")
+}
+
+func (m myFileInfo) Name() string {
+	return m.name
+}
+
+func (m myFileInfo) IsDir() bool {
+	return m.isDir
+}
+
+func (m myFileInfo) Type() fs.FileMode {
+	return m.Mode()
+}
+
+func (m FindMockFS) Open(name string) (fs.File, error) {
+	panic("implement me")
+}
+
+func (m FindMockFS) ReadDir(name string) ([]fs.DirEntry, error) {
+	if d, ok := m.dirs[name]; ok {
+		var res []fs.DirEntry
+		for _, e := range d {
+			res = append(res, e)
+		}
+		return res, nil
+	}
+	return nil, os.ErrNotExist
+}
+
+func NewFindMockFS(files []string) FindMockFS {
+	myfs := FindMockFS{make(map[string][]myFileInfo)}
+	for _, f := range files {
+		isDir := false
+		for f != "." {
+			if _, ok := myfs.locate(f); !ok {
+				myfs.create(f, isDir)
+			}
+			isDir = true
+			f = filepath.Dir(f)
+		}
+	}
+	return myfs
+}
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index 86e647d..7ceac41 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -27,6 +27,7 @@
 	"bytes"
 	"fmt"
 	"io"
+	"io/fs"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -59,8 +60,10 @@
 const (
 	// Phony makefile functions, they are eventually rewritten
 	// according to knownFunctions map
-	fileExistsPhony     = "$file_exists"
-	wildcardExistsPhony = "$wildcard_exists"
+	addSoongNamespace      = "add_soong_config_namespace"
+	addSoongConfigVarValue = "add_soong_config_var_value"
+	fileExistsPhony        = "$file_exists"
+	wildcardExistsPhony    = "$wildcard_exists"
 )
 
 const (
@@ -74,48 +77,58 @@
 	// something else.
 	runtimeName string
 	returnType  starlarkType
+	hiddenArg   hiddenArgType
 }{
-	fileExistsPhony:                       {baseName + ".file_exists", starlarkTypeBool},
-	wildcardExistsPhony:                   {baseName + ".file_wildcard_exists", starlarkTypeBool},
-	"add-to-product-copy-files-if-exists": {baseName + ".copy_if_exists", starlarkTypeList},
-	"addprefix":                           {baseName + ".addprefix", starlarkTypeList},
-	"addsuffix":                           {baseName + ".addsuffix", starlarkTypeList},
-	"enforce-product-packages-exist":      {baseName + ".enforce_product_packages_exist", starlarkTypeVoid},
-	"error":                               {baseName + ".mkerror", starlarkTypeVoid},
-	"findstring":                          {"!findstring", starlarkTypeInt},
-	"find-copy-subdir-files":              {baseName + ".find_and_copy", starlarkTypeList},
-	"find-word-in-list":                   {"!find-word-in-list", starlarkTypeUnknown}, // internal macro
-	"filter":                              {baseName + ".filter", starlarkTypeList},
-	"filter-out":                          {baseName + ".filter_out", starlarkTypeList},
-	"get-vendor-board-platforms":          {"!get-vendor-board-platforms", starlarkTypeList}, // internal macro, used by is-board-platform, etc.
-	"info":                                {baseName + ".mkinfo", starlarkTypeVoid},
-	"is-android-codename":                 {"!is-android-codename", starlarkTypeBool},         // unused by product config
-	"is-android-codename-in-list":         {"!is-android-codename-in-list", starlarkTypeBool}, // unused by product config
-	"is-board-platform":                   {"!is-board-platform", starlarkTypeBool},
-	"is-board-platform-in-list":           {"!is-board-platform-in-list", starlarkTypeBool},
-	"is-chipset-in-board-platform":        {"!is-chipset-in-board-platform", starlarkTypeUnknown},     // unused by product config
-	"is-chipset-prefix-in-board-platform": {"!is-chipset-prefix-in-board-platform", starlarkTypeBool}, // unused by product config
-	"is-not-board-platform":               {"!is-not-board-platform", starlarkTypeBool},               // defined but never used
-	"is-platform-sdk-version-at-least":    {"!is-platform-sdk-version-at-least", starlarkTypeBool},    // unused by product config
-	"is-product-in-list":                  {"!is-product-in-list", starlarkTypeBool},
-	"is-vendor-board-platform":            {"!is-vendor-board-platform", starlarkTypeBool},
-	callLoadAlways:                        {"!inherit-product", starlarkTypeVoid},
-	callLoadIf:                            {"!inherit-product-if-exists", starlarkTypeVoid},
-	"match-prefix":                        {"!match-prefix", starlarkTypeUnknown},       // internal macro
-	"match-word":                          {"!match-word", starlarkTypeUnknown},         // internal macro
-	"match-word-in-list":                  {"!match-word-in-list", starlarkTypeUnknown}, // internal macro
-	"patsubst":                            {baseName + ".mkpatsubst", starlarkTypeString},
-	"produce_copy_files":                  {baseName + ".produce_copy_files", starlarkTypeList},
-	"require-artifacts-in-path":           {baseName + ".require_artifacts_in_path", starlarkTypeVoid},
-	"require-artifacts-in-path-relaxed":   {baseName + ".require_artifacts_in_path_relaxed", starlarkTypeVoid},
+	"abspath":                             {baseName + ".abspath", starlarkTypeString, hiddenArgNone},
+	fileExistsPhony:                       {baseName + ".file_exists", starlarkTypeBool, hiddenArgNone},
+	wildcardExistsPhony:                   {baseName + ".file_wildcard_exists", starlarkTypeBool, hiddenArgNone},
+	addSoongNamespace:                     {baseName + ".add_soong_config_namespace", starlarkTypeVoid, hiddenArgGlobal},
+	addSoongConfigVarValue:                {baseName + ".add_soong_config_var_value", starlarkTypeVoid, hiddenArgGlobal},
+	"add-to-product-copy-files-if-exists": {baseName + ".copy_if_exists", starlarkTypeList, hiddenArgNone},
+	"addprefix":                           {baseName + ".addprefix", starlarkTypeList, hiddenArgNone},
+	"addsuffix":                           {baseName + ".addsuffix", starlarkTypeList, hiddenArgNone},
+	"copy-files":                          {baseName + ".copy_files", starlarkTypeList, hiddenArgNone},
+	"dir":                                 {baseName + ".dir", starlarkTypeList, hiddenArgNone},
+	"enforce-product-packages-exist":      {baseName + ".enforce_product_packages_exist", starlarkTypeVoid, hiddenArgNone},
+	"error":                               {baseName + ".mkerror", starlarkTypeVoid, hiddenArgNone},
+	"findstring":                          {"!findstring", starlarkTypeInt, hiddenArgNone},
+	"find-copy-subdir-files":              {baseName + ".find_and_copy", starlarkTypeList, hiddenArgNone},
+	"find-word-in-list":                   {"!find-word-in-list", starlarkTypeUnknown, hiddenArgNone}, // internal macro
+	"filter":                              {baseName + ".filter", starlarkTypeList, hiddenArgNone},
+	"filter-out":                          {baseName + ".filter_out", starlarkTypeList, hiddenArgNone},
+	"firstword":                           {"!firstword", starlarkTypeString, hiddenArgNone},
+	"get-vendor-board-platforms":          {"!get-vendor-board-platforms", starlarkTypeList, hiddenArgNone}, // internal macro, used by is-board-platform, etc.
+	"info":                                {baseName + ".mkinfo", starlarkTypeVoid, hiddenArgNone},
+	"is-android-codename":                 {"!is-android-codename", starlarkTypeBool, hiddenArgNone},         // unused by product config
+	"is-android-codename-in-list":         {"!is-android-codename-in-list", starlarkTypeBool, hiddenArgNone}, // unused by product config
+	"is-board-platform":                   {"!is-board-platform", starlarkTypeBool, hiddenArgNone},
+	"is-board-platform-in-list":           {"!is-board-platform-in-list", starlarkTypeBool, hiddenArgNone},
+	"is-chipset-in-board-platform":        {"!is-chipset-in-board-platform", starlarkTypeUnknown, hiddenArgNone},     // unused by product config
+	"is-chipset-prefix-in-board-platform": {"!is-chipset-prefix-in-board-platform", starlarkTypeBool, hiddenArgNone}, // unused by product config
+	"is-not-board-platform":               {"!is-not-board-platform", starlarkTypeBool, hiddenArgNone},               // defined but never used
+	"is-platform-sdk-version-at-least":    {"!is-platform-sdk-version-at-least", starlarkTypeBool, hiddenArgNone},    // unused by product config
+	"is-product-in-list":                  {"!is-product-in-list", starlarkTypeBool, hiddenArgNone},
+	"is-vendor-board-platform":            {"!is-vendor-board-platform", starlarkTypeBool, hiddenArgNone},
+	callLoadAlways:                        {"!inherit-product", starlarkTypeVoid, hiddenArgNone},
+	callLoadIf:                            {"!inherit-product-if-exists", starlarkTypeVoid, hiddenArgNone},
+	"lastword":                            {"!lastword", starlarkTypeString, hiddenArgNone},
+	"match-prefix":                        {"!match-prefix", starlarkTypeUnknown, hiddenArgNone},       // internal macro
+	"match-word":                          {"!match-word", starlarkTypeUnknown, hiddenArgNone},         // internal macro
+	"match-word-in-list":                  {"!match-word-in-list", starlarkTypeUnknown, hiddenArgNone}, // internal macro
+	"notdir":                              {baseName + ".notdir", starlarkTypeString, hiddenArgNone},
+	"my-dir":                              {"!my-dir", starlarkTypeString, hiddenArgNone},
+	"patsubst":                            {baseName + ".mkpatsubst", starlarkTypeString, hiddenArgNone},
+	"produce_copy_files":                  {baseName + ".produce_copy_files", starlarkTypeList, hiddenArgNone},
+	"require-artifacts-in-path":           {baseName + ".require_artifacts_in_path", starlarkTypeVoid, hiddenArgNone},
+	"require-artifacts-in-path-relaxed":   {baseName + ".require_artifacts_in_path_relaxed", starlarkTypeVoid, hiddenArgNone},
 	// TODO(asmundak): remove it once all calls are removed from configuration makefiles. see b/183161002
-	"shell":      {baseName + ".shell", starlarkTypeString},
-	"strip":      {baseName + ".mkstrip", starlarkTypeString},
-	"tb-modules": {"!tb-modules", starlarkTypeUnknown}, // defined in hardware/amlogic/tb_modules/tb_detect.mk, unused
-	"subst":      {baseName + ".mksubst", starlarkTypeString},
-	"warning":    {baseName + ".mkwarning", starlarkTypeVoid},
-	"word":       {baseName + "!word", starlarkTypeString},
-	"wildcard":   {baseName + ".expand_wildcard", starlarkTypeList},
+	"shell":      {baseName + ".shell", starlarkTypeString, hiddenArgNone},
+	"strip":      {baseName + ".mkstrip", starlarkTypeString, hiddenArgNone},
+	"tb-modules": {"!tb-modules", starlarkTypeUnknown, hiddenArgNone}, // defined in hardware/amlogic/tb_modules/tb_detect.mk, unused
+	"subst":      {baseName + ".mksubst", starlarkTypeString, hiddenArgNone},
+	"warning":    {baseName + ".mkwarning", starlarkTypeVoid, hiddenArgNone},
+	"word":       {baseName + "!word", starlarkTypeString, hiddenArgNone},
+	"wildcard":   {baseName + ".expand_wildcard", starlarkTypeList, hiddenArgNone},
 }
 
 var builtinFuncRex = regexp.MustCompile(
@@ -136,6 +149,8 @@
 	TracedVariables    []string // trace assignment to these variables
 	TraceCalls         bool
 	WarnPartialSuccess bool
+	SourceFS           fs.FS
+	MakefileFinder     MakefileFinder
 }
 
 // An error sink allowing to gather error statistics.
@@ -149,7 +164,8 @@
 func moduleNameForFile(mkFile string) string {
 	base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
 	// TODO(asmundak): what else can be in the product file names?
-	return strings.ReplaceAll(base, "-", "_")
+	return strings.NewReplacer("-", "_", ".", "_").Replace(base)
+
 }
 
 func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
@@ -241,7 +257,7 @@
 			sc.moduleLocalName = m
 			continue
 		}
-		if !sc.loadAlways {
+		if sc.optional {
 			uri += "|init"
 		}
 		gctx.newLine()
@@ -342,11 +358,13 @@
 	moduleName         string
 	mkPos              scanner.Position
 	nodes              []starlarkNode
-	inherited          []*inheritedModule
+	inherited          []*moduleInfo
 	hasErrors          bool
 	topDir             string
 	traceCalls         bool // print enter/exit each init function
 	warnPartialSuccess bool
+	sourceFS           fs.FS
+	makefileFinder     MakefileFinder
 }
 
 func (ss *StarlarkScript) newNode(node starlarkNode) {
@@ -379,13 +397,16 @@
 	receiver         nodeReceiver // receptacle for the generated starlarkNode's
 	receiverStack    []nodeReceiver
 	outputDir        string
+	dependentModules map[string]*moduleInfo
+	soongNamespaces  map[string]map[string]bool
 }
 
 func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
+	topdir, _ := filepath.Split(filepath.Join(ss.topDir, "foo"))
 	predefined := []struct{ name, value string }{
 		{"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
 		{"LOCAL_PATH", filepath.Dir(ss.mkFile)},
-		{"TOPDIR", ss.topDir},
+		{"TOPDIR", topdir},
 		// TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
 		{"TARGET_COPY_OUT_SYSTEM", "system"},
 		{"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
@@ -428,6 +449,8 @@
 		moduleNameCount:  make(map[string]int),
 		builtinMakeVars:  map[string]starlarkExpr{},
 		variables:        make(map[string]variable),
+		dependentModules: make(map[string]*moduleInfo),
+		soongNamespaces:  make(map[string]map[string]bool),
 	}
 	ctx.pushVarAssignments()
 	for _, item := range predefined {
@@ -506,6 +529,12 @@
 		return
 	}
 	name := a.Name.Strings[0]
+	const soongNsPrefix = "SOONG_CONFIG_"
+	// Soong confuguration
+	if strings.HasPrefix(name, soongNsPrefix) {
+		ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
+		return
+	}
 	lhs := ctx.addVariable(name)
 	if lhs == nil {
 		ctx.errorf(a, "unknown variable %s", name)
@@ -569,6 +598,88 @@
 	ctx.receiver.newNode(asgn)
 }
 
+func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) {
+	val := ctx.parseMakeString(asgn, asgn.Value)
+	if xBad, ok := val.(*badExpr); ok {
+		ctx.wrapBadExpr(xBad)
+		return
+	}
+	val, _ = val.eval(ctx.builtinMakeVars)
+
+	// Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
+	// variables instead of via add_soong_config_namespace + add_soong_config_var_value.
+	// Try to divine the call from the assignment as follows:
+	if name == "NAMESPACES" {
+		// Upon seeng
+		//      SOONG_CONFIG_NAMESPACES += foo
+		//    remember that there is a namespace `foo` and act as we saw
+		//      $(call add_soong_config_namespace,foo)
+		s, ok := maybeString(val)
+		if !ok {
+			ctx.errorf(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")
+			return
+		}
+		for _, ns := range strings.Fields(s) {
+			ctx.addSoongNamespace(ns)
+			ctx.receiver.newNode(&exprNode{&callExpr{
+				name:       addSoongNamespace,
+				args:       []starlarkExpr{&stringLiteralExpr{ns}},
+				returnType: starlarkTypeVoid,
+			}})
+		}
+	} else {
+		// Upon seeing
+		//      SOONG_CONFIG_x_y = v
+		// find a namespace called `x` and act as if we encountered
+		//      $(call add_config_var_value(x,y,v)
+		// or check that `x_y` is a namespace, and then add the RHS of this assignment as variables in
+		// it.
+		// Emit an error in the ambiguous situation (namespaces `foo_bar` with a variable `baz`
+		// and `foo` with a variable `bar_baz`.
+		namespaceName := ""
+		if ctx.hasSoongNamespace(name) {
+			namespaceName = name
+		}
+		var varName string
+		for pos, ch := range name {
+			if !(ch == '_' && ctx.hasSoongNamespace(name[0:pos])) {
+				continue
+			}
+			if namespaceName != "" {
+				ctx.errorf(asgn, "ambiguous soong namespace (may be either `%s` or  `%s`)", namespaceName, name[0:pos])
+				return
+			}
+			namespaceName = name[0:pos]
+			varName = name[pos+1:]
+		}
+		if namespaceName == "" {
+			ctx.errorf(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")
+			return
+		}
+		if varName == "" {
+			// Remember variables in this namespace
+			s, ok := maybeString(val)
+			if !ok {
+				ctx.errorf(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")
+				return
+			}
+			ctx.updateSoongNamespace(asgn.Type != "+=", namespaceName, strings.Fields(s))
+			return
+		}
+
+		// Finally, handle assignment to a namespace variable
+		if !ctx.hasNamespaceVar(namespaceName, varName) {
+			ctx.errorf(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)
+			return
+		}
+		ctx.receiver.newNode(&exprNode{&callExpr{
+			name:       addSoongConfigVarValue,
+			args:       []starlarkExpr{&stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
+			returnType: starlarkTypeVoid,
+		}})
+	}
+}
+
 func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) *concatExpr {
 	xConcat := &concatExpr{}
 	var xItemList *listExpr
@@ -619,16 +730,12 @@
 	return xConcat
 }
 
-func (ctx *parseContext) newInheritedModule(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) *inheritedModule {
-	var path string
-	x, _ := pathExpr.eval(ctx.builtinMakeVars)
-	s, ok := x.(*stringLiteralExpr)
-	if !ok {
-		ctx.errorf(v, "inherit-product/include argument is too complex")
-		return nil
+func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
+	modulePath := ctx.loadedModulePath(path)
+	if mi, ok := ctx.dependentModules[modulePath]; ok {
+		mi.optional = mi.optional || optional
+		return mi
 	}
-
-	path = s.literal
 	moduleName := moduleNameForFile(path)
 	moduleLocalName := "_" + moduleName
 	n, found := ctx.moduleNameCount[moduleName]
@@ -636,27 +743,130 @@
 		moduleLocalName += fmt.Sprintf("%d", n)
 	}
 	ctx.moduleNameCount[moduleName] = n + 1
-	ln := &inheritedModule{
-		path:            ctx.loadedModulePath(path),
+	mi := &moduleInfo{
+		path:            modulePath,
 		originalPath:    path,
-		moduleName:      moduleName,
 		moduleLocalName: moduleLocalName,
-		loadAlways:      loadAlways,
+		optional:        optional,
 	}
-	ctx.script.inherited = append(ctx.script.inherited, ln)
-	return ln
+	ctx.dependentModules[modulePath] = mi
+	ctx.script.inherited = append(ctx.script.inherited, mi)
+	return mi
+}
+
+func (ctx *parseContext) handleSubConfig(
+	v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule)) {
+	pathExpr, _ = pathExpr.eval(ctx.builtinMakeVars)
+
+	// In a simple case, the name of a module to inherit/include is known statically.
+	if path, ok := maybeString(pathExpr); ok {
+		if strings.Contains(path, "*") {
+			if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
+				for _, p := range paths {
+					processModule(inheritedStaticModule{ctx.newDependentModule(p, !loadAlways), loadAlways})
+				}
+			} else {
+				ctx.errorf(v, "cannot glob wildcard argument")
+			}
+		} else {
+			processModule(inheritedStaticModule{ctx.newDependentModule(path, !loadAlways), loadAlways})
+		}
+		return
+	}
+
+	// If module path references variables (e.g., $(v1)/foo/$(v2)/device-config.mk), find all the paths in the
+	// source tree that may be a match and the corresponding variable values. For instance, if the source tree
+	// contains vendor1/foo/abc/dev.mk and vendor2/foo/def/dev.mk, the first one will be inherited when
+	// (v1, v2) == ('vendor1', 'abc'), and the second one when (v1, v2) == ('vendor2', 'def').
+	// We then emit the code that loads all of them, e.g.:
+	//    load("//vendor1/foo/abc:dev.rbc", _dev1_init="init")
+	//    load("//vendor2/foo/def/dev.rbc", _dev2_init="init")
+	// And then inherit it as follows:
+	//    _e = {
+	//       "vendor1/foo/abc/dev.mk": ("vendor1/foo/abc/dev", _dev1_init),
+	//       "vendor2/foo/def/dev.mk": ("vendor2/foo/def/dev", _dev_init2) }.get("%s/foo/%s/dev.mk" % (v1, v2))
+	//    if _e:
+	//       rblf.inherit(handle, _e[0], _e[1])
+	//
+	var matchingPaths []string
+	varPath, ok := pathExpr.(*interpolateExpr)
+	if !ok {
+		ctx.errorf(v, "inherit-product/include argument is too complex")
+		return
+	}
+
+	pathPattern := []string{varPath.chunks[0]}
+	for _, chunk := range varPath.chunks[1:] {
+		if chunk != "" {
+			pathPattern = append(pathPattern, chunk)
+		}
+	}
+	if pathPattern[0] != "" {
+		matchingPaths = ctx.findMatchingPaths(pathPattern)
+	} else {
+		// Heuristics -- if pattern starts from top, restrict it to the directories where
+		// we know inherit-product uses dynamically calculated path. Restrict it even further
+		// for certain path which would yield too many useless matches
+		if len(varPath.chunks) == 2 && varPath.chunks[1] == "/BoardConfigVendor.mk" {
+			pathPattern[0] = "vendor/google_devices"
+			matchingPaths = ctx.findMatchingPaths(pathPattern)
+		} else {
+			for _, t := range []string{"vendor/qcom", "vendor/google_devices"} {
+				pathPattern[0] = t
+				matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
+			}
+		}
+	}
+	// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
+	const maxMatchingFiles = 150
+	if len(matchingPaths) > maxMatchingFiles {
+		ctx.errorf(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)
+		return
+	}
+	res := inheritedDynamicModule{*varPath, []*moduleInfo{}, loadAlways}
+	for _, p := range matchingPaths {
+		// A product configuration files discovered dynamically may attempt to inherit
+		// from another one which does not exist in this source tree. Prevent load errors
+		// by always loading the dynamic files as optional.
+		res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
+	}
+	processModule(res)
+}
+
+func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
+	files := ctx.script.makefileFinder.Find(ctx.script.topDir)
+	if len(pattern) == 0 {
+		return files
+	}
+
+	// Create regular expression from the pattern
+	s_regexp := "^" + regexp.QuoteMeta(pattern[0])
+	for _, s := range pattern[1:] {
+		s_regexp += ".*" + regexp.QuoteMeta(s)
+	}
+	s_regexp += "$"
+	rex := regexp.MustCompile(s_regexp)
+
+	// Now match
+	var res []string
+	for _, p := range files {
+		if rex.MatchString(p) {
+			res = append(res, p)
+		}
+	}
+	return res
 }
 
 func (ctx *parseContext) handleInheritModule(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
-	if im := ctx.newInheritedModule(v, pathExpr, loadAlways); im != nil {
+	ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
 		ctx.receiver.newNode(&inheritNode{im})
-	}
+	})
 }
 
 func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
-	if ln := ctx.newInheritedModule(v, pathExpr, loadAlways); ln != nil {
-		ctx.receiver.newNode(&includeNode{ln})
-	}
+	ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
+		ctx.receiver.newNode(&includeNode{im})
+	})
 }
 
 func (ctx *parseContext) handleVariable(v *mkparser.Variable) {
@@ -938,14 +1148,14 @@
 func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive,
 	filterFuncCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
 	// We handle:
-	// *  ifeq/ifneq (,$(filter v1 v2 ..., $(VAR)) becomes if VAR not in/in ["v1", "v2", ...]
-	// *  ifeq/ifneq (,$(filter $(VAR), v1 v2 ...) becomes if VAR not in/in ["v1", "v2", ...]
+	// *  ifeq/ifneq (,$(filter v1 v2 ..., EXPR) becomes if EXPR not in/in ["v1", "v2", ...]
+	// *  ifeq/ifneq (,$(filter EXPR, v1 v2 ...) becomes if EXPR not in/in ["v1", "v2", ...]
 	// *  ifeq/ifneq ($(VAR),$(filter $(VAR), v1 v2 ...) becomes if VAR in/not in ["v1", "v2"]
 	// TODO(Asmundak): check the last case works for filter-out, too.
 	xPattern := filterFuncCall.args[0]
 	xText := filterFuncCall.args[1]
 	var xInList *stringLiteralExpr
-	var xVar starlarkExpr
+	var expr starlarkExpr
 	var ok bool
 	switch x := xValue.(type) {
 	case *stringLiteralExpr:
@@ -955,34 +1165,42 @@
 		// Either pattern or text should be const, and the
 		// non-const one should be varRefExpr
 		if xInList, ok = xPattern.(*stringLiteralExpr); ok {
-			xVar = xText
+			expr = xText
 		} else if xInList, ok = xText.(*stringLiteralExpr); ok {
-			xVar = xPattern
+			expr = xPattern
+		} else {
+			return &callExpr{
+				object:     nil,
+				name:       filterFuncCall.name,
+				args:       filterFuncCall.args,
+				returnType: starlarkTypeBool,
+			}
 		}
 	case *variableRefExpr:
 		if v, ok := xPattern.(*variableRefExpr); ok {
 			if xInList, ok = xText.(*stringLiteralExpr); ok && v.ref.name() == x.ref.name() {
 				// ifeq/ifneq ($(VAR),$(filter $(VAR), v1 v2 ...), flip negate,
 				// it's the opposite to what is done when comparing to empty.
-				xVar = xPattern
+				expr = xPattern
 				negate = !negate
 			}
 		}
 	}
-	if xVar != nil && xInList != nil {
-		if _, ok := xVar.(*variableRefExpr); ok {
-			slExpr := newStringListExpr(strings.Fields(xInList.literal))
-			// Generate simpler code for the common cases:
-			if xVar.typ() == starlarkTypeList {
-				if len(slExpr.items) == 1 {
-					// Checking that a string belongs to list
-					return &inExpr{isNot: negate, list: xVar, expr: slExpr.items[0]}
-				} else {
-					// TODO(asmundak):
-					panic("TBD")
-				}
+	if expr != nil && xInList != nil {
+		slExpr := newStringListExpr(strings.Fields(xInList.literal))
+		// Generate simpler code for the common cases:
+		if expr.typ() == starlarkTypeList {
+			if len(slExpr.items) == 1 {
+				// Checking that a string belongs to list
+				return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]}
+			} else {
+				// TODO(asmundak):
+				panic("TBD")
 			}
-			return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: xVar}
+		} else if len(slExpr.items) == 1 {
+			return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate}
+		} else {
+			return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr}
 		}
 	}
 	return ctx.newBadExpr(cond, "filter arguments are too complex: %s", cond.Dump())
@@ -990,7 +1208,7 @@
 
 func (ctx *parseContext) parseCompareWildcardFuncResult(directive *mkparser.Directive,
 	xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
-	if x, ok := xValue.(*stringLiteralExpr); !ok || x.literal != "" {
+	if !isEmptyString(xValue) {
 		return ctx.newBadExpr(directive, "wildcard result can be compared only to empty: %s", xValue)
 	}
 	callFunc := wildcardExistsPhony
@@ -1006,19 +1224,19 @@
 
 func (ctx *parseContext) parseCheckFindstringFuncResult(directive *mkparser.Directive,
 	xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
-	if x, ok := xValue.(*stringLiteralExpr); !ok || x.literal != "" {
-		return ctx.newBadExpr(directive, "findstring result can be compared only to empty: %s", xValue)
+	if isEmptyString(xValue) {
+		return &eqExpr{
+			left: &callExpr{
+				object:     xCall.args[1],
+				name:       "find",
+				args:       []starlarkExpr{xCall.args[0]},
+				returnType: starlarkTypeInt,
+			},
+			right: &intLiteralExpr{-1},
+			isEq:  !negate,
+		}
 	}
-	return &eqExpr{
-		left: &callExpr{
-			object:     xCall.args[1],
-			name:       "find",
-			args:       []starlarkExpr{xCall.args[0]},
-			returnType: starlarkTypeInt,
-		},
-		right: &intLiteralExpr{-1},
-		isEq:  !negate,
-	}
+	return ctx.newBadExpr(directive, "findstring result can be compared only to empty: %s", xValue)
 }
 
 func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
@@ -1083,9 +1301,10 @@
 		}
 		expr.name = words[0].Dump()
 		if len(words) < 2 {
-			return expr
+			args = &mkparser.MakeString{}
+		} else {
+			args = words[1]
 		}
-		args = words[1]
 	}
 	if kf, found := knownFunctions[expr.name]; found {
 		expr.returnType = kf.returnType
@@ -1095,6 +1314,10 @@
 	switch expr.name {
 	case "word":
 		return ctx.parseWordFunc(node, args)
+	case "firstword", "lastword":
+		return ctx.parseFirstOrLastwordFunc(node, expr.name, args)
+	case "my-dir":
+		return &variableRefExpr{ctx.addVariable("LOCAL_PATH"), true}
 	case "subst", "patsubst":
 		return ctx.parseSubstFunc(node, expr.name, args)
 	default:
@@ -1165,6 +1388,24 @@
 	return indexExpr{array, &intLiteralExpr{int(index - 1)}}
 }
 
+func (ctx *parseContext) parseFirstOrLastwordFunc(node mkparser.Node, name string, args *mkparser.MakeString) starlarkExpr {
+	arg := ctx.parseMakeString(node, args)
+	if bad, ok := arg.(*badExpr); ok {
+		return bad
+	}
+	index := &intLiteralExpr{0}
+	if name == "lastword" {
+		if v, ok := arg.(*variableRefExpr); ok && v.ref.name() == "MAKEFILE_LIST" {
+			return &stringLiteralExpr{ctx.script.mkFile}
+		}
+		index.literal = -1
+	}
+	if arg.typ() == starlarkTypeList {
+		return &indexExpr{arg, index}
+	}
+	return &indexExpr{&callExpr{object: arg, name: "split", returnType: starlarkTypeList}, index}
+}
+
 func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
 	if mk.Const() {
 		return &stringLiteralExpr{mk.Dump()}
@@ -1272,11 +1513,44 @@
 	return filepath.Join(ctx.outputDir, loadedModuleDir, loadedModuleName)
 }
 
+func (ctx *parseContext) addSoongNamespace(ns string) {
+	if _, ok := ctx.soongNamespaces[ns]; ok {
+		return
+	}
+	ctx.soongNamespaces[ns] = make(map[string]bool)
+}
+
+func (ctx *parseContext) hasSoongNamespace(name string) bool {
+	_, ok := ctx.soongNamespaces[name]
+	return ok
+}
+
+func (ctx *parseContext) updateSoongNamespace(replace bool, namespaceName string, varNames []string) {
+	ctx.addSoongNamespace(namespaceName)
+	vars := ctx.soongNamespaces[namespaceName]
+	if replace {
+		vars = make(map[string]bool)
+		ctx.soongNamespaces[namespaceName] = vars
+	}
+	for _, v := range varNames {
+		vars[v] = true
+	}
+}
+
+func (ctx *parseContext) hasNamespaceVar(namespaceName string, varName string) bool {
+	vars, ok := ctx.soongNamespaces[namespaceName]
+	if ok {
+		_, ok = vars[varName]
+	}
+	return ok
+}
+
 func (ss *StarlarkScript) String() string {
 	return NewGenerateContext(ss).emit()
 }
 
 func (ss *StarlarkScript) SubConfigFiles() []string {
+
 	var subs []string
 	for _, src := range ss.inherited {
 		subs = append(subs, src.originalPath)
@@ -1314,6 +1588,8 @@
 		topDir:             req.RootDir,
 		traceCalls:         req.TraceCalls,
 		warnPartialSuccess: req.WarnPartialSuccess,
+		sourceFS:           req.SourceFS,
+		makefileFinder:     req.MakefileFinder,
 	}
 	ctx := newParseContext(starScript, nodes)
 	ctx.outputSuffix = req.OutputSuffix
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 240d0b8..a14c7a4 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -16,6 +16,8 @@
 
 import (
 	"bytes"
+	"io/fs"
+	"path/filepath"
 	"strings"
 	"testing"
 )
@@ -100,10 +102,13 @@
 		desc:   "Unknown function",
 		mkname: "product.mk",
 		in: `
-PRODUCT_NAME := $(call foo, bar)
+PRODUCT_NAME := $(call foo1, bar)
+PRODUCT_NAME := $(call foo0)
 `,
-		expected: `# MK2RBC TRANSLATION ERROR: cannot handle invoking foo
-# PRODUCT_NAME := $(call foo, bar)
+		expected: `# MK2RBC TRANSLATION ERROR: cannot handle invoking foo1
+# PRODUCT_NAME := $(call foo1, bar)
+# MK2RBC TRANSLATION ERROR: cannot handle invoking foo0
+# PRODUCT_NAME := $(call foo0)
 load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
@@ -130,7 +135,7 @@
     rblf.inherit(handle, "part", _part_init)
   else:
     # Comment
-    rblf.inherit(handle, "./part", _part_init)
+    rblf.inherit(handle, "part", _part_init)
 `,
 	},
 	{
@@ -144,7 +149,7 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if _part_init != None:
+  if _part_init:
     rblf.inherit(handle, "part", _part_init)
 `,
 	},
@@ -160,7 +165,7 @@
 endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
-load(":part.star", _part_init = "init")
+load(":part.star|init", _part_init = "init")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
@@ -176,8 +181,7 @@
 		desc:   "Synonymous inherited configurations",
 		mkname: "path/product.mk",
 		in: `
-$(call inherit-product, foo/font.mk)
-$(call inherit-product, bar/font.mk)
+$(call inherit-product, */font.mk)
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 load("//foo:font.star", _font_init = "init")
@@ -254,6 +258,8 @@
 		in: `
 ifdef PRODUCT_NAME
 # Comment
+else
+  TARGET_COPY_OUT_VENDOR := foo
 endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
@@ -263,6 +269,10 @@
   if g.get("PRODUCT_NAME") != None:
     # Comment
     pass
+  else:
+    # MK2RBC TRANSLATION ERROR: cannot set predefined variable TARGET_COPY_OUT_VENDOR to "foo", its value should be "||VENDOR-PATH-PH||"
+    pass
+  rblf.warning("product.mk", "partially successful conversion")
 `,
 	},
 	{
@@ -342,6 +352,8 @@
 endif
 ifeq ($(TARGET_BUILD_VARIANT), $(filter $(TARGET_BUILD_VARIANT), userdebug eng))
 endif
+ifneq (,$(filter true, $(v1)$(v2)))
+endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
@@ -349,12 +361,14 @@
   cfg = rblf.cfg(handle)
   if g["TARGET_BUILD_VARIANT"] not in ["userdebug", "eng"]:
     pass
-  if g["TARGET_BUILD_VARIANT"] in ["userdebug"]:
+  if g["TARGET_BUILD_VARIANT"] == "userdebug":
     pass
   if "plaf" in g.get("PLATFORM_LIST", []):
     pass
   if g["TARGET_BUILD_VARIANT"] in ["userdebug", "eng"]:
     pass
+  if "%s%s" % (_v1, _v2) == "true":
+    pass
 `,
 	},
 	{
@@ -385,11 +399,26 @@
 def init(g, handle):
   cfg = rblf.cfg(handle)
   if g["TARGET_PRODUCT"] not in ["yukawa_gms", "yukawa_sei510_gms"]:
-    if g["TARGET_PRODUCT"] in ["yukawa_gms"]:
+    if g["TARGET_PRODUCT"] == "yukawa_gms":
       pass
 `,
 	},
 	{
+		desc:   "filter $(V1), $(V2)",
+		mkname: "product.mk",
+		in: `
+ifneq (, $(filter $(PRODUCT_LIST), $(TARGET_PRODUCT)))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if rblf.filter(g.get("PRODUCT_LIST", ""), g["TARGET_PRODUCT"]):
+    pass
+`,
+	},
+	{
 		desc:   "ifeq",
 		mkname: "product.mk",
 		in: `
@@ -629,7 +658,17 @@
 PRODUCT_COPY_FILES := $(addsuffix .sff, a b c)
 PRODUCT_NAME := $(word 1, $(subst ., ,$(TARGET_BOARD_PLATFORM)))
 $(info $(patsubst %.pub,%,$(PRODUCT_ADB_KEYS)))
-
+$(info $(dir foo/bar))
+$(info $(firstword $(PRODUCT_COPY_FILES)))
+$(info $(lastword $(PRODUCT_COPY_FILES)))
+$(info $(dir $(lastword $(MAKEFILE_LIST))))
+$(info $(dir $(lastword $(PRODUCT_COPY_FILES))))
+$(info $(dir $(lastword $(foobar))))
+$(info $(abspath foo/bar))
+$(info $(notdir foo/bar))
+$(call add_soong_config_namespace,snsconfig)
+$(call add_soong_config_var_value,snsconfig,imagetype,odm_image)
+PRODUCT_COPY_FILES := $(call copy-files,$(wildcard foo*.mk),etc)
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
@@ -639,6 +678,17 @@
   cfg["PRODUCT_COPY_FILES"] = rblf.addsuffix(".sff", "a b c")
   cfg["PRODUCT_NAME"] = ((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " ")).split()[0]
   rblf.mkinfo("product.mk", rblf.mkpatsubst("%.pub", "%", g.get("PRODUCT_ADB_KEYS", "")))
+  rblf.mkinfo("product.mk", rblf.dir("foo/bar"))
+  rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][0])
+  rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][-1])
+  rblf.mkinfo("product.mk", rblf.dir("product.mk"))
+  rblf.mkinfo("product.mk", rblf.dir(cfg["PRODUCT_COPY_FILES"][-1]))
+  rblf.mkinfo("product.mk", rblf.dir((_foobar).split()[-1]))
+  rblf.mkinfo("product.mk", rblf.abspath("foo/bar"))
+  rblf.mkinfo("product.mk", rblf.notdir("foo/bar"))
+  rblf.add_soong_config_namespace(g, "snsconfig")
+  rblf.add_soong_config_var_value(g, "snsconfig", "imagetype", "odm_image")
+  cfg["PRODUCT_COPY_FILES"] = rblf.copy_files(rblf.expand_wildcard("foo*.mk"), "etc")
 `,
 	},
 	{
@@ -714,6 +764,25 @@
 `,
 	},
 	{
+		desc:   "soong namespace assignments",
+		mkname: "product.mk",
+		in: `
+SOONG_CONFIG_NAMESPACES += cvd
+SOONG_CONFIG_cvd += launch_configs
+SOONG_CONFIG_cvd_launch_configs += cvd_config_auto.json
+SOONG_CONFIG_cvd += grub_config
+SOONG_CONFIG_cvd_grub_config += grub.cfg
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.add_soong_config_namespace(g, "cvd")
+  rblf.add_soong_config_var_value(g, "cvd", "launch_configs", "cvd_config_auto.json")
+  rblf.add_soong_config_var_value(g, "cvd", "grub_config", "grub.cfg")
+`,
+	},
+	{
 		desc:   "string split",
 		mkname: "product.mk",
 		in: `
@@ -779,7 +848,7 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if rblf.mkstrip(g.get("TARGET_VENDOR", "")) != "":
+  if rblf.mkstrip(g.get("TARGET_VENDOR", "")):
     pass
 `,
 	},
@@ -823,6 +892,30 @@
     g["V3"] = g["PRODUCT_ADB_KEYS"]
 `,
 	},
+	{
+		desc:   "Dynamic inherit path",
+		mkname: "product.mk",
+		in: `
+MY_PATH=foo
+$(call inherit-product,vendor/$(MY_PATH)/cfg.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
+load("//vendor/bar/baz:cfg.star|init", _cfg1_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_PATH"] = "foo"
+  _entry = {
+    "vendor/foo1/cfg.mk": ("_cfg", _cfg_init),
+    "vendor/bar/baz/cfg.mk": ("_cfg1", _cfg1_init),
+  }.get("vendor/%s/cfg.mk" % g["MY_PATH"])
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("cannot")
+  rblf.inherit(handle, _varmod, _varmod_init)
+`,
+	},
 }
 
 var known_variables = []struct {
@@ -846,10 +939,47 @@
 	{"PLATFORM_LIST", VarClassSoong, starlarkTypeList}, // TODO(asmundak): make it local instead of soong
 }
 
+type testMakefileFinder struct {
+	fs    fs.FS
+	root  string
+	files []string
+}
+
+func (t *testMakefileFinder) Find(root string) []string {
+	if t.files != nil || root == t.root {
+		return t.files
+	}
+	t.files = make([]string, 0)
+	fs.WalkDir(t.fs, root, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+		if d.IsDir() {
+			base := filepath.Base(path)
+			if base[0] == '.' && len(base) > 1 {
+				return fs.SkipDir
+			}
+			return nil
+		}
+		if strings.HasSuffix(path, ".mk") {
+			t.files = append(t.files, path)
+		}
+		return nil
+	})
+	return t.files
+}
+
 func TestGood(t *testing.T) {
 	for _, v := range known_variables {
 		KnownVariables.NewVariable(v.name, v.class, v.starlarkType)
 	}
+	fs := NewFindMockFS([]string{
+		"vendor/foo1/cfg.mk",
+		"vendor/bar/baz/cfg.mk",
+		"part.mk",
+		"foo/font.mk",
+		"bar/font.mk",
+	})
 	for _, test := range testCases {
 		t.Run(test.desc,
 			func(t *testing.T) {
@@ -859,6 +989,8 @@
 					RootDir:            ".",
 					OutputSuffix:       ".star",
 					WarnPartialSuccess: true,
+					SourceFS:           fs,
+					MakefileFinder:     &testMakefileFinder{fs: fs},
 				})
 				if err != nil {
 					t.Error(err)
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
index d4b4222..8efe207 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -42,24 +42,85 @@
 	}
 }
 
-type inheritedModule struct {
+type moduleInfo struct {
 	path            string // Converted Starlark file path
 	originalPath    string // Makefile file path
-	moduleName      string
 	moduleLocalName string
-	loadAlways      bool
+	optional        bool
 }
 
-func (im inheritedModule) name() string {
-	return MakePath2ModuleName(im.originalPath)
-}
-
-func (im inheritedModule) entryName() string {
+func (im moduleInfo) entryName() string {
 	return im.moduleLocalName + "_init"
 }
 
+type inheritedModule interface {
+	name() string
+	entryName() string
+	emitSelect(gctx *generationContext)
+	isLoadAlways() bool
+}
+
+type inheritedStaticModule struct {
+	*moduleInfo
+	loadAlways bool
+}
+
+func (im inheritedStaticModule) name() string {
+	return fmt.Sprintf("%q", MakePath2ModuleName(im.originalPath))
+}
+
+func (im inheritedStaticModule) emitSelect(_ *generationContext) {
+}
+
+func (im inheritedStaticModule) isLoadAlways() bool {
+	return im.loadAlways
+}
+
+type inheritedDynamicModule struct {
+	path             interpolateExpr
+	candidateModules []*moduleInfo
+	loadAlways       bool
+}
+
+func (i inheritedDynamicModule) name() string {
+	return "_varmod"
+}
+
+func (i inheritedDynamicModule) entryName() string {
+	return i.name() + "_init"
+}
+
+func (i inheritedDynamicModule) emitSelect(gctx *generationContext) {
+	gctx.newLine()
+	gctx.writef("_entry = {")
+	gctx.indentLevel++
+	for _, mi := range i.candidateModules {
+		gctx.newLine()
+		gctx.writef(`"%s": (%q, %s),`, mi.originalPath, mi.moduleLocalName, mi.entryName())
+	}
+	gctx.indentLevel--
+	gctx.newLine()
+	gctx.write("}.get(")
+	i.path.emit(gctx)
+	gctx.write(")")
+	gctx.newLine()
+	gctx.writef("(%s, %s) = _entry if _entry else (None, None)", i.name(), i.entryName())
+	if i.loadAlways {
+		gctx.newLine()
+		gctx.writef("if not %s:", i.entryName())
+		gctx.indentLevel++
+		gctx.newLine()
+		gctx.write(`rblf.mkerror("cannot")`)
+		gctx.indentLevel--
+	}
+}
+
+func (i inheritedDynamicModule) isLoadAlways() bool {
+	return i.loadAlways
+}
+
 type inheritNode struct {
-	*inheritedModule
+	module inheritedModule
 }
 
 func (inn *inheritNode) emit(gctx *generationContext) {
@@ -68,32 +129,40 @@
 	// Conditional case:
 	//    if <module>_init != None:
 	//      same as above
+	inn.module.emitSelect(gctx)
+
+	name := inn.module.name()
+	entry := inn.module.entryName()
 	gctx.newLine()
-	if inn.loadAlways {
-		gctx.writef("%s(handle, %q, %s)", cfnInherit, inn.name(), inn.entryName())
+	if inn.module.isLoadAlways() {
+		gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
 		return
 	}
-	gctx.writef("if %s != None:", inn.entryName())
+
+	gctx.writef("if %s:", entry)
 	gctx.indentLevel++
 	gctx.newLine()
-	gctx.writef("%s(handle, %q, %s)", cfnInherit, inn.name(), inn.entryName())
+	gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
 	gctx.indentLevel--
 }
 
 type includeNode struct {
-	*inheritedModule
+	module inheritedModule
 }
 
 func (inn *includeNode) emit(gctx *generationContext) {
+	inn.module.emitSelect(gctx)
+	entry := inn.module.entryName()
 	gctx.newLine()
-	if inn.loadAlways {
-		gctx.writef("%s(g, handle)", inn.entryName())
+	if inn.module.isLoadAlways() {
+		gctx.writef("%s(g, handle)", entry)
 		return
 	}
-	gctx.writef("if %s != None:", inn.entryName())
+
+	gctx.writef("if %s != None:", entry)
 	gctx.indentLevel++
 	gctx.newLine()
-	gctx.writef("%s(g, handle)", inn.entryName())
+	gctx.writef("%s(g, handle)", entry)
 	gctx.indentLevel--
 }
 
diff --git a/mk2rbc/types.go b/mk2rbc/types.go
index 1625464..ebd52d8 100644
--- a/mk2rbc/types.go
+++ b/mk2rbc/types.go
@@ -31,6 +31,16 @@
 	starlarkTypeVoid    starlarkType = iota
 )
 
+type hiddenArgType int
+
+const (
+	// Some functions have an implicitly emitted first argument, which may be
+	// a global ('g') or configuration ('cfg') variable.
+	hiddenArgNone   hiddenArgType = iota
+	hiddenArgGlobal hiddenArgType = iota
+	hiddenArgConfig hiddenArgType = iota
+)
+
 type varClass int
 
 const (
@@ -58,3 +68,8 @@
 func (s ScopeBase) SetFunc(_ string, _ func([]string) []string) {
 	panic("implement me")
 }
+
+// Used to find all makefiles in the source tree
+type MakefileFinder interface {
+	Find(root string) []string
+}
diff --git a/mk2rbc/variable.go b/mk2rbc/variable.go
index a650453..88d63c9 100644
--- a/mk2rbc/variable.go
+++ b/mk2rbc/variable.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"os"
 	"strings"
 )
 
@@ -222,15 +221,18 @@
 	pv.value.emit(gctx)
 }
 
-func (pv predefinedVariable) emitSet(_ *generationContext, asgn *assignmentNode) {
+func (pv predefinedVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
 	if expectedValue, ok1 := maybeString(pv.value); ok1 {
 		actualValue, ok2 := maybeString(asgn.value)
 		if ok2 {
 			if actualValue == expectedValue {
 				return
 			}
-			fmt.Fprintf(os.Stderr, "cannot set predefined variable %s to %q, its value should be %q",
+			gctx.writef("# MK2RBC TRANSLATION ERROR: cannot set predefined variable %s to %q, its value should be %q",
 				pv.name(), actualValue, expectedValue)
+			gctx.newLine()
+			gctx.write("pass")
+			gctx.starScript.hasErrors = true
 			return
 		}
 	}
diff --git a/rust/builder.go b/rust/builder.go
index 6c44166..a5b3ab9 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -332,8 +332,11 @@
 	rustdocFlags = append(rustdocFlags, makeLibFlags(deps)...)
 	docTimestampFile := android.PathForModuleOut(ctx, "rustdoc.timestamp")
 
-	// Silence warnings about renamed lints
-	rustdocFlags = append(rustdocFlags, " -A renamed_and_removed_lints")
+	// Silence warnings about renamed lints for third-party crates
+	modulePath := android.PathForModuleSrc(ctx).String()
+	if android.IsThirdPartyPath(modulePath) {
+		rustdocFlags = append(rustdocFlags, " -A renamed_and_removed_lints")
+	}
 
 	// Yes, the same out directory is used simultaneously by all rustdoc builds.
 	// This is what cargo does. The docs for individual crates get generated to
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index ca110a2..e527aea 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -6,7 +6,6 @@
 	// for an example.
 	// TODO(b/160223496): enable rustfmt globally.
 	RustAllowedPaths = []string{
-		"bionic/libc",
 		"device/google/cuttlefish",
 		"external/adhd",
 		"external/crosvm",
@@ -24,6 +23,7 @@
 		"system/extras/profcollectd",
 		"system/extras/simpleperf",
 		"system/hardware/interfaces/keystore2",
+		"system/librustutils",
 		"system/logging/rust",
 		"system/security",
 		"system/tools/aidl",
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 3d14d51..a4ba4bd 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -47,6 +47,9 @@
 	"-C llvm-args=-sanitizer-coverage-trace-geps",
 	"-C llvm-args=-sanitizer-coverage-prune-blocks=0",
 
+	// See https://github.com/rust-fuzz/cargo-fuzz/pull/193
+	"-C link-dead-code",
+
 	// Sancov breaks with lto
 	// TODO: Remove when https://bugs.llvm.org/show_bug.cgi?id=41734 is resolved and sancov works with LTO
 	"-C lto=no",