Merge "Package robolectric tests for tradefed"
diff --git a/OWNERS b/OWNERS
index dbb491d..8355d10 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,9 +6,8 @@
 per-file * = patricearruda@google.com
 per-file * = paulduffin@google.com
 
-per-file ndk_*.go, *gen_stub_libs.py = danalbert@google.com
+per-file ndk_*.go = danalbert@google.com
 per-file clang.go,global.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com
 per-file tidy.go = srhines@google.com, chh@google.com
 per-file lto.go,pgo.go = srhines@google.com, pirama@google.com, yikong@google.com
 per-file docs/map_files.md = danalbert@google.com, enh@google.com, jiyong@google.com
-per-file *ndk_api_coverage_parser.py = sophiez@google.com
\ No newline at end of file
diff --git a/android/Android.bp b/android/Android.bp
index 2682279..6ddcc14 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -19,6 +19,7 @@
         "csuite_config.go",
         "defaults.go",
         "defs.go",
+        "depset.go",
         "expand.go",
         "filegroup.go",
         "hooks.go",
@@ -62,6 +63,7 @@
         "arch_test.go",
         "config_test.go",
         "csuite_config_test.go",
+        "depset_test.go",
         "expand_test.go",
         "module_test.go",
         "mutator_test.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index 045cb59..dfc68c4 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -107,6 +107,25 @@
 	a.EntryMap[name] = []string{path.String()}
 }
 
+func (a *AndroidMkEntries) SetOptionalPath(name string, path OptionalPath) {
+	if path.Valid() {
+		a.SetPath(name, path.Path())
+	}
+}
+
+func (a *AndroidMkEntries) AddPath(name string, path Path) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	a.EntryMap[name] = append(a.EntryMap[name], path.String())
+}
+
+func (a *AndroidMkEntries) AddOptionalPath(name string, path OptionalPath) {
+	if path.Valid() {
+		a.AddPath(name, path.Path())
+	}
+}
+
 func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
 	if flag {
 		if _, ok := a.EntryMap[name]; !ok {
diff --git a/android/config.go b/android/config.go
index 7073a49..d680b65 100644
--- a/android/config.go
+++ b/android/config.go
@@ -875,12 +875,12 @@
 	enforceList := c.productVariables.EnforceRROTargets
 	// TODO(b/150820813) Some modules depend on static overlay, remove this after eliminating the dependency.
 	exemptedList := c.productVariables.EnforceRROExemptedTargets
-	if exemptedList != nil {
+	if len(exemptedList) > 0 {
 		if InList(name, exemptedList) {
 			return false
 		}
 	}
-	if enforceList != nil {
+	if len(enforceList) > 0 {
 		if InList("*", enforceList) {
 			return true
 		}
@@ -891,7 +891,7 @@
 
 func (c *config) EnforceRROExcludedOverlay(path string) bool {
 	excluded := c.productVariables.EnforceRROExcludedOverlays
-	if excluded != nil {
+	if len(excluded) > 0 {
 		return HasAnyPrefix(path, excluded)
 	}
 	return false
@@ -1059,7 +1059,7 @@
 		HasAnyPrefix(path, c.config.productVariables.JavaCoveragePaths) {
 		coverage = true
 	}
-	if coverage && c.config.productVariables.JavaCoverageExcludePaths != nil {
+	if coverage && len(c.config.productVariables.JavaCoverageExcludePaths) > 0 {
 		if HasAnyPrefix(path, c.config.productVariables.JavaCoverageExcludePaths) {
 			coverage = false
 		}
@@ -1088,12 +1088,12 @@
 // NativeCoveragePaths represents any path.
 func (c *deviceConfig) NativeCoverageEnabledForPath(path string) bool {
 	coverage := false
-	if c.config.productVariables.NativeCoveragePaths != nil {
+	if len(c.config.productVariables.NativeCoveragePaths) > 0 {
 		if InList("*", c.config.productVariables.NativeCoveragePaths) || HasAnyPrefix(path, c.config.productVariables.NativeCoveragePaths) {
 			coverage = true
 		}
 	}
-	if coverage && c.config.productVariables.NativeCoverageExcludePaths != nil {
+	if coverage && len(c.config.productVariables.NativeCoverageExcludePaths) > 0 {
 		if HasAnyPrefix(path, c.config.productVariables.NativeCoverageExcludePaths) {
 			coverage = false
 		}
@@ -1164,21 +1164,21 @@
 }
 
 func (c *config) IntegerOverflowDisabledForPath(path string) bool {
-	if c.productVariables.IntegerOverflowExcludePaths == nil {
+	if len(c.productVariables.IntegerOverflowExcludePaths) == 0 {
 		return false
 	}
 	return HasAnyPrefix(path, c.productVariables.IntegerOverflowExcludePaths)
 }
 
 func (c *config) CFIDisabledForPath(path string) bool {
-	if c.productVariables.CFIExcludePaths == nil {
+	if len(c.productVariables.CFIExcludePaths) == 0 {
 		return false
 	}
 	return HasAnyPrefix(path, c.productVariables.CFIExcludePaths)
 }
 
 func (c *config) CFIEnabledForPath(path string) bool {
-	if c.productVariables.CFIIncludePaths == nil {
+	if len(c.productVariables.CFIIncludePaths) == 0 {
 		return false
 	}
 	return HasAnyPrefix(path, c.productVariables.CFIIncludePaths)
diff --git a/android/depset.go b/android/depset.go
new file mode 100644
index 0000000..f707094
--- /dev/null
+++ b/android/depset.go
@@ -0,0 +1,190 @@
+// 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 android
+
+import "fmt"
+
+// DepSet is designed to be conceptually compatible with Bazel's depsets:
+// https://docs.bazel.build/versions/master/skylark/depsets.html
+
+// A DepSet efficiently stores Paths from transitive dependencies without copying. It is stored
+// as a DAG of DepSet nodes, each of which has some direct contents and a list of dependency
+// DepSet nodes.
+//
+// A DepSet has an order that will be used to walk the DAG when ToList() is called.  The order
+// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
+// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
+// elements of children are listed after all of their parents (unless there are duplicate direct
+// elements in the DepSet or any of its transitive dependencies, in which case the ordering of the
+// duplicated element is not guaranteed).
+//
+// A DepSet is created by NewDepSet or NewDepSetBuilder.Build from the Paths for direct contents
+// and the *DepSets of dependencies. A DepSet is immutable once created.
+type DepSet struct {
+	preorder   bool
+	reverse    bool
+	order      DepSetOrder
+	direct     Paths
+	transitive []*DepSet
+}
+
+// DepSetBuilder is used to create an immutable DepSet.
+type DepSetBuilder struct {
+	order      DepSetOrder
+	direct     Paths
+	transitive []*DepSet
+}
+
+type DepSetOrder int
+
+const (
+	PREORDER DepSetOrder = iota
+	POSTORDER
+	TOPOLOGICAL
+)
+
+func (o DepSetOrder) String() string {
+	switch o {
+	case PREORDER:
+		return "PREORDER"
+	case POSTORDER:
+		return "POSTORDER"
+	case TOPOLOGICAL:
+		return "TOPOLOGICAL"
+	default:
+		panic(fmt.Errorf("Invalid DepSetOrder %d", o))
+	}
+}
+
+// NewDepSet returns an immutable DepSet with the given order, direct and transitive contents.
+func NewDepSet(order DepSetOrder, direct Paths, transitive []*DepSet) *DepSet {
+	var directCopy Paths
+	var transitiveCopy []*DepSet
+	if order == TOPOLOGICAL {
+		directCopy = ReversePaths(direct)
+		transitiveCopy = reverseDepSets(transitive)
+	} else {
+		// Use copy instead of append(nil, ...) to make a slice that is exactly the size of the input
+		// slice.  The DepSet is immutable, there is no need for additional capacity.
+		directCopy = make(Paths, len(direct))
+		copy(directCopy, direct)
+		transitiveCopy = make([]*DepSet, len(transitive))
+		copy(transitiveCopy, transitive)
+	}
+
+	for _, dep := range transitive {
+		if dep.order != order {
+			panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
+				order, dep.order))
+		}
+	}
+
+	return &DepSet{
+		preorder:   order == PREORDER,
+		reverse:    order == TOPOLOGICAL,
+		order:      order,
+		direct:     directCopy,
+		transitive: transitiveCopy,
+	}
+}
+
+// NewDepSetBuilder returns a DepSetBuilder to create an immutable DepSet with the given order.
+func NewDepSetBuilder(order DepSetOrder) *DepSetBuilder {
+	return &DepSetBuilder{order: order}
+}
+
+// Direct adds direct contents to the DepSet being built by a DepSetBuilder. Newly added direct
+// contents are to the right of any existing direct contents.
+func (b *DepSetBuilder) Direct(direct ...Path) *DepSetBuilder {
+	b.direct = append(b.direct, direct...)
+	return b
+}
+
+// Transitive adds transitive contents to the DepSet being built by a DepSetBuilder. Newly added
+// transitive contents are to the right of any existing transitive contents.
+func (b *DepSetBuilder) Transitive(transitive ...*DepSet) *DepSetBuilder {
+	b.transitive = append(b.transitive, transitive...)
+	return b
+}
+
+// Returns the DepSet being built by this DepSetBuilder.  The DepSetBuilder retains its contents
+// for creating more DepSets.
+func (b *DepSetBuilder) Build() *DepSet {
+	return NewDepSet(b.order, b.direct, b.transitive)
+}
+
+// walk calls the visit method in depth-first order on a DepSet, preordered if d.preorder is set,
+// otherwise postordered.
+func (d *DepSet) walk(visit func(Paths)) {
+	visited := make(map[*DepSet]bool)
+
+	var dfs func(d *DepSet)
+	dfs = func(d *DepSet) {
+		visited[d] = true
+		if d.preorder {
+			visit(d.direct)
+		}
+		for _, dep := range d.transitive {
+			if !visited[dep] {
+				dfs(dep)
+			}
+		}
+
+		if !d.preorder {
+			visit(d.direct)
+		}
+	}
+
+	dfs(d)
+}
+
+// ToList returns the DepSet flattened to a list.  The order in the list is based on the order
+// of the DepSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
+// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
+// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
+// its transitive dependencies, in which case the ordering of the duplicated element is not
+// guaranteed).
+func (d *DepSet) ToList() Paths {
+	var list Paths
+	d.walk(func(paths Paths) {
+		list = append(list, paths...)
+	})
+	list = FirstUniquePaths(list)
+	if d.reverse {
+		reversePathsInPlace(list)
+	}
+	return list
+}
+
+// ToSortedList returns the direct and transitive contents of a DepSet in lexically sorted order
+// with duplicates removed.
+func (d *DepSet) ToSortedList() Paths {
+	list := d.ToList()
+	return SortedUniquePaths(list)
+}
+
+func reversePathsInPlace(list Paths) {
+	for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
+		list[i], list[j] = list[j], list[i]
+	}
+}
+
+func reverseDepSets(list []*DepSet) []*DepSet {
+	ret := make([]*DepSet, len(list))
+	for i := range list {
+		ret[i] = list[len(list)-1-i]
+	}
+	return ret
+}
diff --git a/android/depset_test.go b/android/depset_test.go
new file mode 100644
index 0000000..c328127
--- /dev/null
+++ b/android/depset_test.go
@@ -0,0 +1,304 @@
+// 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 android
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+func ExampleDepSet_ToList_postordered() {
+	a := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("a")).Build()
+	b := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("b")).Transitive(a).Build()
+	c := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("c")).Transitive(a).Build()
+	d := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("d")).Transitive(b, c).Build()
+
+	fmt.Println(d.ToList().Strings())
+	// Output: [a b c d]
+}
+
+func ExampleDepSet_ToList_preordered() {
+	a := NewDepSetBuilder(PREORDER).Direct(PathForTesting("a")).Build()
+	b := NewDepSetBuilder(PREORDER).Direct(PathForTesting("b")).Transitive(a).Build()
+	c := NewDepSetBuilder(PREORDER).Direct(PathForTesting("c")).Transitive(a).Build()
+	d := NewDepSetBuilder(PREORDER).Direct(PathForTesting("d")).Transitive(b, c).Build()
+
+	fmt.Println(d.ToList().Strings())
+	// Output: [d b a c]
+}
+
+func ExampleDepSet_ToList_topological() {
+	a := NewDepSetBuilder(TOPOLOGICAL).Direct(PathForTesting("a")).Build()
+	b := NewDepSetBuilder(TOPOLOGICAL).Direct(PathForTesting("b")).Transitive(a).Build()
+	c := NewDepSetBuilder(TOPOLOGICAL).Direct(PathForTesting("c")).Transitive(a).Build()
+	d := NewDepSetBuilder(TOPOLOGICAL).Direct(PathForTesting("d")).Transitive(b, c).Build()
+
+	fmt.Println(d.ToList().Strings())
+	// Output: [d b c a]
+}
+
+func ExampleDepSet_ToSortedList() {
+	a := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("a")).Build()
+	b := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("b")).Transitive(a).Build()
+	c := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("c")).Transitive(a).Build()
+	d := NewDepSetBuilder(POSTORDER).Direct(PathForTesting("d")).Transitive(b, c).Build()
+
+	fmt.Println(d.ToSortedList().Strings())
+	// Output: [a b c d]
+}
+
+// Tests based on Bazel's ExpanderTestBase.java to ensure compatibility
+// https://github.com/bazelbuild/bazel/blob/master/src/test/java/com/google/devtools/build/lib/collect/nestedset/ExpanderTestBase.java
+func TestDepSet(t *testing.T) {
+	a := PathForTesting("a")
+	b := PathForTesting("b")
+	c := PathForTesting("c")
+	c2 := PathForTesting("c2")
+	d := PathForTesting("d")
+	e := PathForTesting("e")
+
+	tests := []struct {
+		name                             string
+		depSet                           func(t *testing.T, order DepSetOrder) *DepSet
+		postorder, preorder, topological []string
+	}{
+		{
+			name: "simple",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				return NewDepSet(order, Paths{c, a, b}, nil)
+			},
+			postorder:   []string{"c", "a", "b"},
+			preorder:    []string{"c", "a", "b"},
+			topological: []string{"c", "a", "b"},
+		},
+		{
+			name: "simpleNoDuplicates",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				return NewDepSet(order, Paths{c, a, a, a, b}, nil)
+			},
+			postorder:   []string{"c", "a", "b"},
+			preorder:    []string{"c", "a", "b"},
+			topological: []string{"c", "a", "b"},
+		},
+		{
+			name: "nesting",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				subset := NewDepSet(order, Paths{c, a, e}, nil)
+				return NewDepSet(order, Paths{b, d}, []*DepSet{subset})
+			},
+			postorder:   []string{"c", "a", "e", "b", "d"},
+			preorder:    []string{"b", "d", "c", "a", "e"},
+			topological: []string{"b", "d", "c", "a", "e"},
+		},
+		{
+			name: "builderReuse",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				assertEquals := func(t *testing.T, w, g Paths) {
+					if !reflect.DeepEqual(w, g) {
+						t.Errorf("want %q, got %q", w, g)
+					}
+				}
+				builder := NewDepSetBuilder(order)
+				assertEquals(t, nil, builder.Build().ToList())
+
+				builder.Direct(b)
+				assertEquals(t, Paths{b}, builder.Build().ToList())
+
+				builder.Direct(d)
+				assertEquals(t, Paths{b, d}, builder.Build().ToList())
+
+				child := NewDepSetBuilder(order).Direct(c, a, e).Build()
+				builder.Transitive(child)
+				return builder.Build()
+			},
+			postorder:   []string{"c", "a", "e", "b", "d"},
+			preorder:    []string{"b", "d", "c", "a", "e"},
+			topological: []string{"b", "d", "c", "a", "e"},
+		},
+		{
+			name: "builderChaining",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				return NewDepSetBuilder(order).Direct(b).Direct(d).
+					Transitive(NewDepSetBuilder(order).Direct(c, a, e).Build()).Build()
+			},
+			postorder:   []string{"c", "a", "e", "b", "d"},
+			preorder:    []string{"b", "d", "c", "a", "e"},
+			topological: []string{"b", "d", "c", "a", "e"},
+		},
+		{
+			name: "transitiveDepsHandledSeparately",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				subset := NewDepSetBuilder(order).Direct(c, a, e).Build()
+				builder := NewDepSetBuilder(order)
+				// The fact that we add the transitive subset between the Direct(b) and Direct(d)
+				// calls should not change the result.
+				builder.Direct(b)
+				builder.Transitive(subset)
+				builder.Direct(d)
+				return builder.Build()
+			},
+			postorder:   []string{"c", "a", "e", "b", "d"},
+			preorder:    []string{"b", "d", "c", "a", "e"},
+			topological: []string{"b", "d", "c", "a", "e"},
+		},
+		{
+			name: "nestingNoDuplicates",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				subset := NewDepSetBuilder(order).Direct(c, a, e).Build()
+				return NewDepSetBuilder(order).Direct(b, d, e).Transitive(subset).Build()
+			},
+			postorder:   []string{"c", "a", "e", "b", "d"},
+			preorder:    []string{"b", "d", "e", "c", "a"},
+			topological: []string{"b", "d", "c", "a", "e"},
+		},
+		{
+			name: "chain",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				c := NewDepSetBuilder(order).Direct(c).Build()
+				b := NewDepSetBuilder(order).Direct(b).Transitive(c).Build()
+				a := NewDepSetBuilder(order).Direct(a).Transitive(b).Build()
+
+				return a
+			},
+			postorder:   []string{"c", "b", "a"},
+			preorder:    []string{"a", "b", "c"},
+			topological: []string{"a", "b", "c"},
+		},
+		{
+			name: "diamond",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				d := NewDepSetBuilder(order).Direct(d).Build()
+				c := NewDepSetBuilder(order).Direct(c).Transitive(d).Build()
+				b := NewDepSetBuilder(order).Direct(b).Transitive(d).Build()
+				a := NewDepSetBuilder(order).Direct(a).Transitive(b).Transitive(c).Build()
+
+				return a
+			},
+			postorder:   []string{"d", "b", "c", "a"},
+			preorder:    []string{"a", "b", "d", "c"},
+			topological: []string{"a", "b", "c", "d"},
+		},
+		{
+			name: "extendedDiamond",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				d := NewDepSetBuilder(order).Direct(d).Build()
+				e := NewDepSetBuilder(order).Direct(e).Build()
+				b := NewDepSetBuilder(order).Direct(b).Transitive(d).Transitive(e).Build()
+				c := NewDepSetBuilder(order).Direct(c).Transitive(e).Transitive(d).Build()
+				a := NewDepSetBuilder(order).Direct(a).Transitive(b).Transitive(c).Build()
+				return a
+			},
+			postorder:   []string{"d", "e", "b", "c", "a"},
+			preorder:    []string{"a", "b", "d", "e", "c"},
+			topological: []string{"a", "b", "c", "e", "d"},
+		},
+		{
+			name: "extendedDiamondRightArm",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				d := NewDepSetBuilder(order).Direct(d).Build()
+				e := NewDepSetBuilder(order).Direct(e).Build()
+				b := NewDepSetBuilder(order).Direct(b).Transitive(d).Transitive(e).Build()
+				c2 := NewDepSetBuilder(order).Direct(c2).Transitive(e).Transitive(d).Build()
+				c := NewDepSetBuilder(order).Direct(c).Transitive(c2).Build()
+				a := NewDepSetBuilder(order).Direct(a).Transitive(b).Transitive(c).Build()
+				return a
+			},
+			postorder:   []string{"d", "e", "b", "c2", "c", "a"},
+			preorder:    []string{"a", "b", "d", "e", "c", "c2"},
+			topological: []string{"a", "b", "c", "c2", "e", "d"},
+		},
+		{
+			name: "orderConflict",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				child1 := NewDepSetBuilder(order).Direct(a, b).Build()
+				child2 := NewDepSetBuilder(order).Direct(b, a).Build()
+				parent := NewDepSetBuilder(order).Transitive(child1).Transitive(child2).Build()
+				return parent
+			},
+			postorder:   []string{"a", "b"},
+			preorder:    []string{"a", "b"},
+			topological: []string{"b", "a"},
+		},
+		{
+			name: "orderConflictNested",
+			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
+				a := NewDepSetBuilder(order).Direct(a).Build()
+				b := NewDepSetBuilder(order).Direct(b).Build()
+				child1 := NewDepSetBuilder(order).Transitive(a).Transitive(b).Build()
+				child2 := NewDepSetBuilder(order).Transitive(b).Transitive(a).Build()
+				parent := NewDepSetBuilder(order).Transitive(child1).Transitive(child2).Build()
+				return parent
+			},
+			postorder:   []string{"a", "b"},
+			preorder:    []string{"a", "b"},
+			topological: []string{"b", "a"},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Run("postorder", func(t *testing.T) {
+				depSet := tt.depSet(t, POSTORDER)
+				if g, w := depSet.ToList().Strings(), tt.postorder; !reflect.DeepEqual(g, w) {
+					t.Errorf("expected ToList() = %q, got %q", w, g)
+				}
+			})
+			t.Run("preorder", func(t *testing.T) {
+				depSet := tt.depSet(t, PREORDER)
+				if g, w := depSet.ToList().Strings(), tt.preorder; !reflect.DeepEqual(g, w) {
+					t.Errorf("expected ToList() = %q, got %q", w, g)
+				}
+			})
+			t.Run("topological", func(t *testing.T) {
+				depSet := tt.depSet(t, TOPOLOGICAL)
+				if g, w := depSet.ToList().Strings(), tt.topological; !reflect.DeepEqual(g, w) {
+					t.Errorf("expected ToList() = %q, got %q", w, g)
+				}
+			})
+		})
+	}
+}
+
+func TestDepSetInvalidOrder(t *testing.T) {
+	orders := []DepSetOrder{POSTORDER, PREORDER, TOPOLOGICAL}
+
+	run := func(t *testing.T, order1, order2 DepSetOrder) {
+		defer func() {
+			if r := recover(); r != nil {
+				if err, ok := r.(error); !ok {
+					t.Fatalf("expected panic error, got %v", err)
+				} else if !strings.Contains(err.Error(), "incompatible order") {
+					t.Fatalf("expected incompatible order error, got %v", err)
+				}
+			}
+		}()
+		NewDepSet(order1, nil, []*DepSet{NewDepSet(order2, nil, nil)})
+		t.Fatal("expected panic")
+	}
+
+	for _, order1 := range orders {
+		t.Run(order1.String(), func(t *testing.T) {
+			for _, order2 := range orders {
+				t.Run(order2.String(), func(t *testing.T) {
+					if order1 != order2 {
+						run(t, order1, order2)
+					}
+				})
+			}
+		})
+	}
+}
diff --git a/android/module.go b/android/module.go
index 06079ca..2062a4d 100644
--- a/android/module.go
+++ b/android/module.go
@@ -50,6 +50,8 @@
 	Implicit        Path
 	Implicits       Paths
 	OrderOnly       Paths
+	Validation      Path
+	Validations     Paths
 	Default         bool
 	Args            map[string]string
 }
@@ -228,6 +230,16 @@
 	// For more information, see Module.GenerateBuildActions within Blueprint's module_ctx.go
 	GenerateAndroidBuildActions(ModuleContext)
 
+	// Add dependencies to the components of a module, i.e. modules that are created
+	// by the module and which are considered to be part of the creating module.
+	//
+	// This is called before prebuilts are renamed so as to allow a dependency to be
+	// added directly to a prebuilt child module instead of depending on a source module
+	// and relying on prebuilt processing to switch to the prebuilt module if preferred.
+	//
+	// A dependency on a prebuilt must include the "prebuilt_" prefix.
+	ComponentDepsMutator(ctx BottomUpMutatorContext)
+
 	DepsMutator(BottomUpMutatorContext)
 
 	base() *ModuleBase
@@ -769,6 +781,8 @@
 	prefer32 func(ctx BaseModuleContext, base *ModuleBase, class OsClass) bool
 }
 
+func (m *ModuleBase) ComponentDepsMutator(BottomUpMutatorContext) {}
+
 func (m *ModuleBase) DepsMutator(BottomUpMutatorContext) {}
 
 func (m *ModuleBase) AddProperties(props ...interface{}) {
@@ -1549,6 +1563,7 @@
 		Inputs:          params.Inputs.Strings(),
 		Implicits:       params.Implicits.Strings(),
 		OrderOnly:       params.OrderOnly.Strings(),
+		Validations:     params.Validations.Strings(),
 		Args:            params.Args,
 		Optional:        !params.Default,
 	}
@@ -1568,13 +1583,17 @@
 	if params.Implicit != nil {
 		bparams.Implicits = append(bparams.Implicits, params.Implicit.String())
 	}
+	if params.Validation != nil {
+		bparams.Validations = append(bparams.Validations, params.Validation.String())
+	}
 
 	bparams.Outputs = proptools.NinjaEscapeList(bparams.Outputs)
 	bparams.ImplicitOutputs = proptools.NinjaEscapeList(bparams.ImplicitOutputs)
 	bparams.Inputs = proptools.NinjaEscapeList(bparams.Inputs)
 	bparams.Implicits = proptools.NinjaEscapeList(bparams.Implicits)
 	bparams.OrderOnly = proptools.NinjaEscapeList(bparams.OrderOnly)
-	bparams.Depfile = proptools.NinjaEscapeList([]string{bparams.Depfile})[0]
+	bparams.Validations = proptools.NinjaEscapeList(bparams.Validations)
+	bparams.Depfile = proptools.NinjaEscape(bparams.Depfile)
 
 	return bparams
 }
@@ -2099,15 +2118,6 @@
 	m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
 }
 
-func findStringInSlice(str string, slice []string) int {
-	for i, s := range slice {
-		if s == str {
-			return i
-		}
-	}
-	return -1
-}
-
 // SrcIsModule decodes module references in the format ":name" into the module name, or empty string if the input
 // was not a module reference.
 func SrcIsModule(s string) (module string) {
diff --git a/android/mutator.go b/android/mutator.go
index 77d5f43..b70c4ff 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -115,6 +115,18 @@
 	// a DefaultableHook.
 	RegisterDefaultsPreArchMutators,
 
+	// Add dependencies on any components so that any component references can be
+	// resolved within the deps mutator.
+	//
+	// Must be run after defaults so it can be used to create dependencies on the
+	// component modules that are creating in a DefaultableHook.
+	//
+	// Must be run before RegisterPrebuiltsPreArchMutators, i.e. before prebuilts are
+	// renamed. That is so that if a module creates components using a prebuilt module
+	// type that any dependencies (which must use prebuilt_ prefixes) are resolved to
+	// the prebuilt module and not the source module.
+	RegisterComponentsMutator,
+
 	// Create an association between prebuilt modules and their corresponding source
 	// modules (if any).
 	//
@@ -202,6 +214,7 @@
 	AddFarVariationDependencies([]blueprint.Variation, blueprint.DependencyTag, ...string)
 	AddInterVariantDependency(tag blueprint.DependencyTag, from, to blueprint.Module)
 	ReplaceDependencies(string)
+	ReplaceDependenciesIf(string, blueprint.ReplaceDependencyPredicate)
 	AliasVariation(variationName string)
 }
 
@@ -252,8 +265,21 @@
 	return mutator
 }
 
+func RegisterComponentsMutator(ctx RegisterMutatorsContext) {
+	ctx.BottomUp("component-deps", componentDepsMutator).Parallel()
+}
+
+// A special mutator that runs just prior to the deps mutator to allow the dependencies
+// on component modules to be added so that they can depend directly on a prebuilt
+// module.
+func componentDepsMutator(ctx BottomUpMutatorContext) {
+	if m := ctx.Module(); m.Enabled() {
+		m.ComponentDepsMutator(ctx)
+	}
+}
+
 func depsMutator(ctx BottomUpMutatorContext) {
-	if m, ok := ctx.Module().(Module); ok && m.Enabled() {
+	if m := ctx.Module(); m.Enabled() {
 		m.DepsMutator(ctx)
 	}
 }
@@ -403,6 +429,10 @@
 	b.bp.ReplaceDependencies(name)
 }
 
+func (b *bottomUpMutatorContext) ReplaceDependenciesIf(name string, predicate blueprint.ReplaceDependencyPredicate) {
+	b.bp.ReplaceDependenciesIf(name, predicate)
+}
+
 func (b *bottomUpMutatorContext) AliasVariation(variationName string) {
 	b.bp.AliasVariation(variationName)
 }
diff --git a/android/paths.go b/android/paths.go
index 066baf2..20ff55e 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -380,6 +380,18 @@
 }
 
 func expandOneSrcPath(ctx ModuleContext, s string, expandedExcludes []string) (Paths, error) {
+	excludePaths := func(paths Paths) Paths {
+		if len(expandedExcludes) == 0 {
+			return paths
+		}
+		remainder := make(Paths, 0, len(paths))
+		for _, p := range paths {
+			if !InList(p.String(), expandedExcludes) {
+				remainder = append(remainder, p)
+			}
+		}
+		return remainder
+	}
 	if m, t := SrcIsModuleWithTag(s); m != "" {
 		module := ctx.GetDirectDepWithTag(m, sourceOrOutputDepTag(t))
 		if module == nil {
@@ -390,20 +402,11 @@
 			if err != nil {
 				return nil, fmt.Errorf("path dependency %q: %s", s, err)
 			}
-			return outputFiles, nil
+			return excludePaths(outputFiles), nil
 		} else if t != "" {
 			return nil, fmt.Errorf("path dependency %q is not an output file producing module", s)
 		} else if srcProducer, ok := module.(SourceFileProducer); ok {
-			moduleSrcs := srcProducer.Srcs()
-			for _, e := range expandedExcludes {
-				for j := 0; j < len(moduleSrcs); j++ {
-					if moduleSrcs[j].String() == e {
-						moduleSrcs = append(moduleSrcs[:j], moduleSrcs[j+1:]...)
-						j--
-					}
-				}
-			}
-			return moduleSrcs, nil
+			return excludePaths(srcProducer.Srcs()), nil
 		} else {
 			return nil, fmt.Errorf("path dependency %q is not a source file producing module", s)
 		}
@@ -418,8 +421,7 @@
 			reportPathErrorf(ctx, "module source path %q does not exist", p)
 		}
 
-		j := findStringInSlice(p.String(), expandedExcludes)
-		if j >= 0 {
+		if InList(p.String(), expandedExcludes) {
 			return nil, nil
 		}
 		return Paths{p}, nil
@@ -484,6 +486,10 @@
 	return ret
 }
 
+func CopyOfPaths(paths Paths) Paths {
+	return append(Paths(nil), paths...)
+}
+
 // FirstUniquePaths returns all unique elements of a Paths, keeping the first copy of each.  It
 // modifies the Paths slice contents in place, and returns a subslice of the original slice.
 func FirstUniquePaths(list Paths) Paths {
@@ -494,7 +500,8 @@
 	return firstUniquePathsList(list)
 }
 
-// SortedUniquePaths returns what its name says
+// SortedUniquePaths returns all unique elements of a Paths in sorted order.  It modifies the
+// Paths slice contents in place, and returns a subslice of the original slice.
 func SortedUniquePaths(list Paths) Paths {
 	unique := FirstUniquePaths(list)
 	sort.Slice(unique, func(i, j int) bool {
diff --git a/android/paths_test.go b/android/paths_test.go
index 9b45d3f..a9cd22b 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -1091,6 +1091,21 @@
 			rels: []string{"gen/c"},
 		},
 		{
+			name: "output file provider with exclude",
+			bp: `
+			test {
+				name: "foo",
+				srcs: [":b", ":c"],
+				exclude_srcs: [":c"]
+			}
+			output_file_provider {
+				name: "c",
+				outs: ["gen/c"],
+			}`,
+			srcs: []string{buildDir + "/.intermediates/ofp/b/gen/b"},
+			rels: []string{"gen/b"},
+		},
+		{
 			name: "special characters glob",
 			bp: `
 			test {
diff --git a/android/prebuilt.go b/android/prebuilt.go
index a29ec91..269ad5d 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -30,6 +30,16 @@
 	ctx.PostDepsMutators(RegisterPrebuiltsPostDepsMutators)
 }
 
+// Marks a dependency tag as possibly preventing a reference to a source from being
+// replaced with the prebuilt.
+type ReplaceSourceWithPrebuilt interface {
+	blueprint.DependencyTag
+
+	// Return true if the dependency defined by this tag should be replaced with the
+	// prebuilt.
+	ReplaceSourceWithPrebuilt() bool
+}
+
 type prebuiltDependencyTag struct {
 	blueprint.BaseDependencyTag
 }
@@ -215,7 +225,7 @@
 // PrebuiltSourceDepsMutator adds dependencies to the prebuilt module from the
 // corresponding source module, if one exists for the same variant.
 func PrebuiltSourceDepsMutator(ctx BottomUpMutatorContext) {
-	if m, ok := ctx.Module().(PrebuiltInterface); ok && m.Prebuilt() != nil {
+	if m, ok := ctx.Module().(PrebuiltInterface); ok && m.Enabled() && m.Prebuilt() != nil {
 		p := m.Prebuilt()
 		if !p.properties.PrebuiltRenamedToSource {
 			name := m.base().BaseModuleName()
@@ -260,7 +270,13 @@
 		name := m.base().BaseModuleName()
 		if p.properties.UsePrebuilt {
 			if p.properties.SourceExists {
-				ctx.ReplaceDependencies(name)
+				ctx.ReplaceDependenciesIf(name, func(from blueprint.Module, tag blueprint.DependencyTag, to blueprint.Module) bool {
+					if t, ok := tag.(ReplaceSourceWithPrebuilt); ok {
+						return t.ReplaceSourceWithPrebuilt()
+					}
+
+					return true
+				})
 			}
 		} else {
 			m.SkipInstall()
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 8029b85..6c3cd9e 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -22,9 +22,10 @@
 )
 
 var prebuiltsTests = []struct {
-	name     string
-	modules  string
-	prebuilt []OsClass
+	name      string
+	replaceBp bool // modules is added to default bp boilerplate if false.
+	modules   string
+	prebuilt  []OsType
 }{
 	{
 		name: "no prebuilt",
@@ -42,7 +43,7 @@
 				prefer: false,
 				srcs: ["prebuilt_file"],
 			}`,
-		prebuilt: []OsClass{Device, Host},
+		prebuilt: []OsType{Android, BuildOs},
 	},
 	{
 		name: "no source prebuilt preferred",
@@ -52,7 +53,7 @@
 				prefer: true,
 				srcs: ["prebuilt_file"],
 			}`,
-		prebuilt: []OsClass{Device, Host},
+		prebuilt: []OsType{Android, BuildOs},
 	},
 	{
 		name: "prebuilt not preferred",
@@ -80,7 +81,7 @@
 				prefer: true,
 				srcs: ["prebuilt_file"],
 			}`,
-		prebuilt: []OsClass{Device, Host},
+		prebuilt: []OsType{Android, BuildOs},
 	},
 	{
 		name: "prebuilt no file not preferred",
@@ -120,7 +121,7 @@
 				prefer: true,
 				srcs: [":fg"],
 			}`,
-		prebuilt: []OsClass{Device, Host},
+		prebuilt: []OsType{Android, BuildOs},
 	},
 	{
 		name: "prebuilt module for device only",
@@ -135,7 +136,7 @@
 				prefer: true,
 				srcs: ["prebuilt_file"],
 			}`,
-		prebuilt: []OsClass{Device},
+		prebuilt: []OsType{Android},
 	},
 	{
 		name: "prebuilt file for host only",
@@ -153,7 +154,7 @@
 					},
 				},
 			}`,
-		prebuilt: []OsClass{Host},
+		prebuilt: []OsType{BuildOs},
 	},
 	{
 		name: "prebuilt override not preferred",
@@ -191,7 +192,72 @@
 				prefer: true,
 				srcs: ["prebuilt_file"],
 			}`,
-		prebuilt: []OsClass{Device, Host},
+		prebuilt: []OsType{Android, BuildOs},
+	},
+	{
+		name:      "prebuilt including default-disabled OS",
+		replaceBp: true,
+		modules: `
+			source {
+				name: "foo",
+				deps: [":bar"],
+				target: {
+					windows: {
+						enabled: true,
+					},
+				},
+			}
+
+			source {
+				name: "bar",
+				target: {
+					windows: {
+						enabled: true,
+					},
+				},
+			}
+
+			prebuilt {
+				name: "bar",
+				prefer: true,
+				srcs: ["prebuilt_file"],
+				target: {
+					windows: {
+						enabled: true,
+					},
+				},
+			}`,
+		prebuilt: []OsType{Android, BuildOs, Windows},
+	},
+	{
+		name:      "fall back to source for default-disabled OS",
+		replaceBp: true,
+		modules: `
+			source {
+				name: "foo",
+				deps: [":bar"],
+				target: {
+					windows: {
+						enabled: true,
+					},
+				},
+			}
+
+			source {
+				name: "bar",
+				target: {
+					windows: {
+						enabled: true,
+					},
+				},
+			}
+
+			prebuilt {
+				name: "bar",
+				prefer: true,
+				srcs: ["prebuilt_file"],
+			}`,
+		prebuilt: []OsType{Android, BuildOs},
 	},
 }
 
@@ -203,14 +269,25 @@
 
 	for _, test := range prebuiltsTests {
 		t.Run(test.name, func(t *testing.T) {
-			bp := `
-				source {
-					name: "foo",
-					deps: [":bar"],
-				}
-				` + test.modules
+			bp := test.modules
+			if !test.replaceBp {
+				bp = bp + `
+					source {
+						name: "foo",
+						deps: [":bar"],
+					}`
+			}
 			config := TestArchConfig(buildDir, nil, bp, fs)
 
+			// Add windows to the target list to test the logic when a variant is
+			// disabled by default.
+			if !Windows.DefaultDisabled {
+				t.Errorf("windows is assumed to be disabled by default")
+			}
+			config.config.Targets[Windows] = []Target{
+				{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", ""},
+			}
+
 			ctx := NewTestArchContext()
 			registerTestPrebuiltBuildComponents(ctx)
 			ctx.RegisterModuleType("filegroup", FileGroupFactory)
@@ -223,7 +300,7 @@
 
 			for _, variant := range ctx.ModuleVariantsForTests("foo") {
 				foo := ctx.ModuleForTests("foo", variant)
-				t.Run(foo.Module().Target().Os.Class.String(), func(t *testing.T) {
+				t.Run(foo.Module().Target().Os.String(), func(t *testing.T) {
 					var dependsOnSourceModule, dependsOnPrebuiltModule bool
 					ctx.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
 						if _, ok := m.(*sourceModule); ok {
@@ -237,26 +314,38 @@
 						}
 					})
 
+					moduleIsDisabled := !foo.Module().Enabled()
 					deps := foo.Module().(*sourceModule).deps
-					if deps == nil || len(deps) != 1 {
-						t.Errorf("deps does not have single path, but is %v", deps)
+					if moduleIsDisabled {
+						if len(deps) > 0 {
+							t.Errorf("disabled module got deps: %v", deps)
+						}
+					} else {
+						if len(deps) != 1 {
+							t.Errorf("deps does not have single path, but is %v", deps)
+						}
 					}
+
 					var usingSourceFile, usingPrebuiltFile bool
-					if deps[0].String() == "source_file" {
+					if len(deps) > 0 && deps[0].String() == "source_file" {
 						usingSourceFile = true
 					}
-					if deps[0].String() == "prebuilt_file" {
+					if len(deps) > 0 && deps[0].String() == "prebuilt_file" {
 						usingPrebuiltFile = true
 					}
 
 					prebuilt := false
 					for _, os := range test.prebuilt {
-						if os == foo.Module().Target().Os.Class {
+						if os == foo.Module().Target().Os {
 							prebuilt = true
 						}
 					}
 
 					if prebuilt {
+						if moduleIsDisabled {
+							t.Errorf("dependent module for prebuilt is disabled")
+						}
+
 						if !dependsOnPrebuiltModule {
 							t.Errorf("doesn't depend on prebuilt module")
 						}
@@ -270,7 +359,7 @@
 						if usingSourceFile {
 							t.Errorf("using source_file")
 						}
-					} else {
+					} else if !moduleIsDisabled {
 						if dependsOnPrebuiltModule {
 							t.Errorf("depends on prebuilt module")
 						}
diff --git a/android/sdk.go b/android/sdk.go
index e823106..2c38f56 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -266,6 +266,9 @@
 	SdkMemberType() SdkMemberType
 }
 
+var _ SdkMemberTypeDependencyTag = (*sdkMemberDependencyTag)(nil)
+var _ ReplaceSourceWithPrebuilt = (*sdkMemberDependencyTag)(nil)
+
 type sdkMemberDependencyTag struct {
 	blueprint.BaseDependencyTag
 	memberType SdkMemberType
@@ -275,6 +278,12 @@
 	return t.memberType
 }
 
+// Prevent dependencies from the sdk/module_exports onto their members from being
+// replaced with a preferred prebuilt.
+func (t *sdkMemberDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
+}
+
 func DependencyTagForSdkMemberType(memberType SdkMemberType) SdkMemberTypeDependencyTag {
 	return &sdkMemberDependencyTag{memberType: memberType}
 }
@@ -457,8 +466,7 @@
 
 // Base structure for all implementations of SdkMemberProperties.
 //
-// Contains common properties that apply across many different member types. These
-// are not affected by the optimization to extract common values.
+// Contains common properties that apply across many different member types.
 type SdkMemberPropertiesBase struct {
 	// The number of unique os types supported by the member variants.
 	//
@@ -480,9 +488,7 @@
 	Os OsType `sdk:"keep"`
 
 	// The setting to use for the compile_multilib property.
-	//
-	// This property is set after optimization so there is no point in trying to optimize it.
-	Compile_multilib string `sdk:"keep"`
+	Compile_multilib string `android:"arch_variant"`
 }
 
 // The os prefix to use for any file paths in the sdk.
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 7595238..b9bcc3a 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -105,9 +105,10 @@
 		fmt.Fprintln(w, "LOCAL_MODULE :=", moduleName)
 		// /apex/<apex_name>/{lib|framework|...}
 		pathWhenActivated := filepath.Join("$(PRODUCT_OUT)", "apex", apexName, fi.installDir)
+		var modulePath string
 		if apexType == flattenedApex {
 			// /system/apex/<name>/{lib|framework|...}
-			modulePath := filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.installDir)
+			modulePath = filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.installDir)
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", modulePath)
 			if a.primaryApexType && !symbolFilesNotNeeded {
 				fmt.Fprintln(w, "LOCAL_SOONG_SYMBOL_PATH :=", pathWhenActivated)
@@ -131,6 +132,7 @@
 				fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", strings.Join(fi.module.NoticeFiles().Strings(), " "))
 			}
 		} else {
+			modulePath = pathWhenActivated
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", pathWhenActivated)
 
 			// For non-flattend APEXes, the merged notice file is attached to the APEX itself.
@@ -193,8 +195,13 @@
 			// we need to remove the suffix from LOCAL_MODULE_STEM, otherwise
 			// we will have foo.apk.apk
 			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.Stem(), ".apk"))
-			if app, ok := fi.module.(*java.AndroidApp); ok && len(app.JniCoverageOutputs()) > 0 {
-				fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", strings.Join(app.JniCoverageOutputs().Strings(), " "))
+			if app, ok := fi.module.(*java.AndroidApp); ok {
+				if jniCoverageOutputs := app.JniCoverageOutputs(); len(jniCoverageOutputs) > 0 {
+					fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", strings.Join(jniCoverageOutputs.Strings(), " "))
+				}
+				if jniLibSymbols := app.JNISymbolsInstalls(modulePath); len(jniLibSymbols) > 0 {
+					fmt.Fprintln(w, "LOCAL_SOONG_JNI_LIBS_SYMBOLS :=", jniLibSymbols.String())
+				}
 			}
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_app_prebuilt.mk")
 		case appSet:
@@ -203,6 +210,7 @@
 				panic(fmt.Sprintf("Expected %s to be AndroidAppSet", fi.module))
 			}
 			fmt.Fprintln(w, "LOCAL_APK_SET_MASTER_FILE :=", as.MasterFile())
+			fmt.Fprintln(w, "LOCAL_APKCERTS_FILE :=", as.APKCertsFile().String())
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_android_app_set.mk")
 		case nativeSharedLib, nativeExecutable, nativeTest:
 			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.Stem())
diff --git a/apex/apex.go b/apex/apex.go
index d0c1a09..045ae82 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1750,9 +1750,15 @@
 			return false
 		}
 
+		dt := ctx.OtherModuleDependencyTag(child)
+
+		if _, ok := dt.(android.ExcludeFromApexContentsTag); ok {
+			return false
+		}
+
 		// Check for the direct dependencies that contribute to the payload
-		if dt, ok := ctx.OtherModuleDependencyTag(child).(dependencyTag); ok {
-			if dt.payload {
+		if adt, ok := dt.(dependencyTag); ok {
+			if adt.payload {
 				return do(ctx, parent, am, false /* externalDep */)
 			}
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
@@ -2106,7 +2112,7 @@
 			case android.PrebuiltDepTag:
 				// If the prebuilt is force disabled, remember to delete the prebuilt file
 				// that might have been installed in the previous builds
-				if prebuilt, ok := child.(*Prebuilt); ok && prebuilt.isForceDisabled() {
+				if prebuilt, ok := child.(prebuilt); ok && prebuilt.isForceDisabled() {
 					a.prebuiltFileToDelete = prebuilt.InstallFilename()
 				}
 			}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index befb814..f064338 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -233,6 +233,7 @@
 	ctx.RegisterModuleType("apex_set", apexSetFactory)
 
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	ctx.PreArchMutators(android.RegisterComponentsMutator)
 	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
 
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
@@ -5790,6 +5791,41 @@
 	}
 }
 
+func TestNonPreferredPrebuiltDependency(t *testing.T) {
+	_, _ = testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			stubs: {
+				versions: ["10000"],
+			},
+			apex_available: ["myapex"],
+		}
+
+		cc_prebuilt_library_shared {
+			name: "mylib",
+			prefer: false,
+			srcs: ["prebuilt.so"],
+			stubs: {
+				versions: ["10000"],
+			},
+			apex_available: ["myapex"],
+		}
+	`)
+}
+
 func TestMain(m *testing.M) {
 	run := func() int {
 		setUp()
diff --git a/apex/builder.go b/apex/builder.go
index 5fb9a5f..49e4642 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -390,7 +390,7 @@
 		} else {
 			if fi.class == appSet {
 				copyCommands = append(copyCommands,
-					fmt.Sprintf("unzip -q -d %s %s", destPathDir, fi.builtFile.String()))
+					fmt.Sprintf("unzip -qDD -d %s %s", destPathDir, fi.builtFile.String()))
 			} else {
 				copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath)
 			}
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index bf574dc..d459f87 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -21,6 +21,7 @@
 
 	"android/soong/android"
 	"android/soong/java"
+
 	"github.com/google/blueprint"
 
 	"github.com/google/blueprint/proptools"
@@ -39,9 +40,55 @@
 		"abis", "allow-prereleased", "sdk-version")
 )
 
+type prebuilt interface {
+	isForceDisabled() bool
+	InstallFilename() string
+}
+
+type prebuiltCommon struct {
+	prebuilt   android.Prebuilt
+	properties prebuiltCommonProperties
+}
+
+type prebuiltCommonProperties struct {
+	ForceDisable bool `blueprint:"mutated"`
+}
+
+func (p *prebuiltCommon) Prebuilt() *android.Prebuilt {
+	return &p.prebuilt
+}
+
+func (p *prebuiltCommon) isForceDisabled() bool {
+	return p.properties.ForceDisable
+}
+
+func (p *prebuiltCommon) checkForceDisable(ctx android.ModuleContext) bool {
+	// If the device is configured to use flattened APEX, force disable the prebuilt because
+	// the prebuilt is a non-flattened one.
+	forceDisable := ctx.Config().FlattenApex()
+
+	// Force disable the prebuilts when we are doing unbundled build. We do unbundled build
+	// to build the prebuilts themselves.
+	forceDisable = forceDisable || ctx.Config().UnbundledBuild()
+
+	// Force disable the prebuilts when coverage is enabled.
+	forceDisable = forceDisable || ctx.DeviceConfig().NativeCoverageEnabled()
+	forceDisable = forceDisable || ctx.Config().IsEnvTrue("EMMA_INSTRUMENT")
+
+	// b/137216042 don't use prebuilts when address sanitizer is on
+	forceDisable = forceDisable || android.InList("address", ctx.Config().SanitizeDevice()) ||
+		android.InList("hwaddress", ctx.Config().SanitizeDevice())
+
+	if forceDisable && p.prebuilt.SourceExists() {
+		p.properties.ForceDisable = true
+		return true
+	}
+	return false
+}
+
 type Prebuilt struct {
 	android.ModuleBase
-	prebuilt android.Prebuilt
+	prebuiltCommon
 
 	properties PrebuiltProperties
 
@@ -57,8 +104,7 @@
 
 type PrebuiltProperties struct {
 	// the path to the prebuilt .apex file to import.
-	Source       string `blueprint:"mutated"`
-	ForceDisable bool   `blueprint:"mutated"`
+	Source string `blueprint:"mutated"`
 
 	Src  *string
 	Arch struct {
@@ -93,10 +139,6 @@
 	return p.properties.Installable == nil || proptools.Bool(p.properties.Installable)
 }
 
-func (p *Prebuilt) isForceDisabled() bool {
-	return p.properties.ForceDisable
-}
-
 func (p *Prebuilt) OutputFiles(tag string) (android.Paths, error) {
 	switch tag {
 	case "":
@@ -110,12 +152,8 @@
 	return proptools.StringDefault(p.properties.Filename, p.BaseModuleName()+imageApexSuffix)
 }
 
-func (p *Prebuilt) Prebuilt() *android.Prebuilt {
-	return &p.prebuilt
-}
-
 func (p *Prebuilt) Name() string {
-	return p.prebuilt.Name(p.ModuleBase.Name())
+	return p.prebuiltCommon.prebuilt.Name(p.ModuleBase.Name())
 }
 
 // prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex.
@@ -128,27 +166,6 @@
 }
 
 func (p *Prebuilt) DepsMutator(ctx android.BottomUpMutatorContext) {
-	// If the device is configured to use flattened APEX, force disable the prebuilt because
-	// the prebuilt is a non-flattened one.
-	forceDisable := ctx.Config().FlattenApex()
-
-	// Force disable the prebuilts when we are doing unbundled build. We do unbundled build
-	// to build the prebuilts themselves.
-	forceDisable = forceDisable || ctx.Config().UnbundledBuild()
-
-	// Force disable the prebuilts when coverage is enabled.
-	forceDisable = forceDisable || ctx.DeviceConfig().NativeCoverageEnabled()
-	forceDisable = forceDisable || ctx.Config().IsEnvTrue("EMMA_INSTRUMENT")
-
-	// b/137216042 don't use prebuilts when address sanitizer is on
-	forceDisable = forceDisable || android.InList("address", ctx.Config().SanitizeDevice()) ||
-		android.InList("hwaddress", ctx.Config().SanitizeDevice())
-
-	if forceDisable && p.prebuilt.SourceExists() {
-		p.properties.ForceDisable = true
-		return
-	}
-
 	// This is called before prebuilt_select and prebuilt_postdeps mutators
 	// The mutators requires that src to be set correctly for each arch so that
 	// arch variants are disabled when src is not provided for the arch.
@@ -177,10 +194,6 @@
 }
 
 func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	if p.properties.ForceDisable {
-		return
-	}
-
 	// TODO(jungjw): Check the key validity.
 	p.inputApex = p.Prebuilt().SingleSourcePath(ctx)
 	p.installDir = android.PathForModuleInstall(ctx, "apex")
@@ -194,6 +207,12 @@
 		Input:  p.inputApex,
 		Output: p.outputApex,
 	})
+
+	if p.prebuiltCommon.checkForceDisable(ctx) {
+		p.SkipInstall()
+		return
+	}
+
 	if p.installable() {
 		ctx.InstallFile(p.installDir, p.installFilename, p.inputApex)
 	}
@@ -227,7 +246,7 @@
 
 type ApexSet struct {
 	android.ModuleBase
-	prebuilt android.Prebuilt
+	prebuiltCommon
 
 	properties ApexSetProperties
 
@@ -270,12 +289,8 @@
 	return proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+imageApexSuffix)
 }
 
-func (a *ApexSet) Prebuilt() *android.Prebuilt {
-	return &a.prebuilt
-}
-
 func (a *ApexSet) Name() string {
-	return a.prebuilt.Name(a.ModuleBase.Name())
+	return a.prebuiltCommon.prebuilt.Name(a.ModuleBase.Name())
 }
 
 func (a *ApexSet) Overrides() []string {
@@ -297,7 +312,7 @@
 		ctx.ModuleErrorf("filename should end in %s for apex_set", imageApexSuffix)
 	}
 
-	apexSet := a.prebuilt.SingleSourcePath(ctx)
+	apexSet := a.prebuiltCommon.prebuilt.SingleSourcePath(ctx)
 	a.outputApex = android.PathForModuleOut(ctx, a.installFilename)
 	ctx.Build(pctx,
 		android.BuildParams{
@@ -311,6 +326,12 @@
 				"sdk-version":       ctx.Config().PlatformSdkVersion(),
 			},
 		})
+
+	if a.prebuiltCommon.checkForceDisable(ctx) {
+		a.SkipInstall()
+		return
+	}
+
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	if a.installable() {
 		ctx.InstallFile(a.installDir, a.installFilename, a.outputApex)
diff --git a/cc/binary_sdk_member.go b/cc/binary_sdk_member.go
index 372a72e..51d8b4e 100644
--- a/cc/binary_sdk_member.go
+++ b/cc/binary_sdk_member.go
@@ -140,10 +140,6 @@
 }
 
 func (p *nativeBinaryInfoProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
-	if p.Compile_multilib != "" {
-		propertySet.AddProperty("compile_multilib", p.Compile_multilib)
-	}
-
 	builder := ctx.SnapshotBuilder()
 	if p.outputFile != nil {
 		propertySet.AddProperty("srcs", []string{nativeBinaryPathFor(*p)})
diff --git a/cc/config/x86_darwin_host.go b/cc/config/x86_darwin_host.go
index 8eb79e3..81c907d 100644
--- a/cc/config/x86_darwin_host.go
+++ b/cc/config/x86_darwin_host.go
@@ -66,6 +66,7 @@
 		"10.13",
 		"10.14",
 		"10.15",
+		"11.0",
 	}
 
 	darwinAvailableLibraries = append(
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index 9c54399..4b9eb30 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -234,7 +234,7 @@
 	for _, propertyInfo := range includeDirProperties {
 		// Calculate the base directory in the snapshot into which the files will be copied.
 		// lib.ArchType is "" for common properties.
-		targetDir := filepath.Join(libInfo.archType, propertyInfo.snapshotDir)
+		targetDir := filepath.Join(libInfo.OsPrefix(), libInfo.archType, propertyInfo.snapshotDir)
 
 		propertyName := propertyInfo.propertyName
 
diff --git a/cc/llndk_library.go b/cc/llndk_library.go
index 7ff20f4..71c9204 100644
--- a/cc/llndk_library.go
+++ b/cc/llndk_library.go
@@ -225,6 +225,8 @@
 func llndkHeadersFactory() android.Module {
 	module, library := NewLibrary(android.DeviceSupported)
 	library.HeaderOnly()
+	module.stl = nil
+	module.sanitize = nil
 
 	decorator := &llndkHeadersDecorator{
 		libraryDecorator: library,
diff --git a/cc/ndk_api_coverage_parser/.gitignore b/cc/ndk_api_coverage_parser/.gitignore
new file mode 100644
index 0000000..fd94eac
--- /dev/null
+++ b/cc/ndk_api_coverage_parser/.gitignore
@@ -0,0 +1,140 @@
+# From https://github.com/github/gitignore/blob/master/Python.gitignore
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
diff --git a/cc/scriptlib/Android.bp b/cc/ndk_api_coverage_parser/Android.bp
similarity index 73%
rename from cc/scriptlib/Android.bp
rename to cc/ndk_api_coverage_parser/Android.bp
index ff9a2f0..8d9827c 100644
--- a/cc/scriptlib/Android.bp
+++ b/cc/ndk_api_coverage_parser/Android.bp
@@ -14,19 +14,33 @@
 // limitations under the License.
 //
 
+python_library_host {
+    name: "ndk_api_coverage_parser_lib",
+    pkg_path: "ndk_api_coverage_parser",
+    srcs: [
+        "__init__.py",
+    ],
+}
+
 python_test_host {
     name: "test_ndk_api_coverage_parser",
     main: "test_ndk_api_coverage_parser.py",
     srcs: [
         "test_ndk_api_coverage_parser.py",
     ],
+    libs: [
+        "ndk_api_coverage_parser_lib",
+        "symbolfile",
+    ],
 }
 
 python_binary_host {
     name: "ndk_api_coverage_parser",
-    main: "ndk_api_coverage_parser.py",
+    main: "__init__.py",
     srcs: [
-        "gen_stub_libs.py",
-        "ndk_api_coverage_parser.py",
+        "__init__.py",
+    ],
+    libs: [
+        "symbolfile",
     ],
 }
diff --git a/cc/ndk_api_coverage_parser/OWNERS b/cc/ndk_api_coverage_parser/OWNERS
new file mode 100644
index 0000000..a90c48c
--- /dev/null
+++ b/cc/ndk_api_coverage_parser/OWNERS
@@ -0,0 +1 @@
+sophiez@google.com
diff --git a/cc/scriptlib/ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/__init__.py
similarity index 96%
rename from cc/scriptlib/ndk_api_coverage_parser.py
rename to cc/ndk_api_coverage_parser/__init__.py
index d74035b..7817c78 100755
--- a/cc/scriptlib/ndk_api_coverage_parser.py
+++ b/cc/ndk_api_coverage_parser/__init__.py
@@ -21,7 +21,7 @@
 import sys
 
 from xml.etree.ElementTree import Element, SubElement, tostring
-from gen_stub_libs import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser
+from symbolfile import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser
 
 
 ROOT_ELEMENT_TAG = 'ndk-library'
diff --git a/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
new file mode 100644
index 0000000..3ec14c1
--- /dev/null
+++ b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Tests for ndk_api_coverage_parser.py."""
+import io
+import textwrap
+import unittest
+
+from xml.etree.ElementTree import fromstring
+from symbolfile import FUTURE_API_LEVEL, SymbolFileParser
+import ndk_api_coverage_parser as nparser
+
+
+# pylint: disable=missing-docstring
+
+
+# https://stackoverflow.com/a/24349916/632035
+def etree_equal(elem1, elem2):
+    """Returns true if the two XML elements are equal.
+
+    xml.etree.ElementTree's comparison operator cares about the ordering of
+    elements and attributes, but they are stored in an unordered dict so the
+    ordering is not deterministic.
+
+    lxml is apparently API compatible with xml and does use an OrderedDict, but
+    we don't have it in the tree.
+    """
+    if elem1.tag != elem2.tag:
+        return False
+    if elem1.text != elem2.text:
+        return False
+    if elem1.tail != elem2.tail:
+        return False
+    if elem1.attrib != elem2.attrib:
+        return False
+    if len(elem1) != len(elem2):
+        return False
+    return all(etree_equal(c1, c2) for c1, c2 in zip(elem1, elem2))
+
+
+class ApiCoverageSymbolFileParserTest(unittest.TestCase):
+    def test_parse(self):
+        input_file = io.StringIO(textwrap.dedent(u"""\
+            LIBLOG { # introduced-arm64=24 introduced-x86=24 introduced-x86_64=24
+              global:
+                android_name_to_log_id; # apex llndk introduced=23
+                android_log_id_to_name; # llndk arm
+                __android_log_assert; # introduced-x86=23
+                __android_log_buf_print; # var
+                __android_log_buf_write;
+              local:
+                *;
+            };
+            
+            LIBLOG_PLATFORM {
+                android_fdtrack; # llndk
+                android_net; # introduced=23
+            };
+            
+            LIBLOG_FOO { # var
+                android_var;
+            };
+        """))
+        parser = SymbolFileParser(input_file, {}, "", FUTURE_API_LEVEL, True, True)
+        generator = nparser.XmlGenerator(io.StringIO())
+        result = generator.convertToXml(parser.parse())
+        expected = fromstring('<ndk-library><symbol apex="True" arch="" introduced="23" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_name_to_log_id" /><symbol arch="arm" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_log_id_to_name" /><symbol arch="" introduced-arm64="24" introduced-x86="23" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_assert" /><symbol arch="" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_buf_write" /><symbol arch="" is_deprecated="False" is_platform="True" llndk="True" name="android_fdtrack" /><symbol arch="" introduced="23" is_deprecated="False" is_platform="True" name="android_net" /></ndk-library>')
+        self.assertTrue(etree_equal(expected, result))
+
+
+def main():
+    suite = unittest.TestLoader().loadTestsFromName(__name__)
+    unittest.TextTestRunner(verbosity=3).run(suite)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 6299b00..4578fd3 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -26,17 +26,16 @@
 )
 
 func init() {
+	pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen")
 	pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser")
 }
 
 var (
-	toolPath = pctx.SourcePathVariable("toolPath", "build/soong/cc/scriptlib/gen_stub_libs.py")
-
 	genStubSrc = pctx.AndroidStaticRule("genStubSrc",
 		blueprint.RuleParams{
-			Command: "$toolPath --arch $arch --api $apiLevel --api-map " +
-				"$apiMap $flags $in $out",
-			CommandDeps: []string{"$toolPath"},
+			Command: "$ndkStubGenerator --arch $arch --api $apiLevel " +
+				"--api-map $apiMap $flags $in $out",
+			CommandDeps: []string{"$ndkStubGenerator"},
 		}, "arch", "apiLevel", "apiMap", "flags")
 
 	parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule",
@@ -78,9 +77,9 @@
 	// https://github.com/android-ndk/ndk/issues/265.
 	Unversioned_until *string
 
-	// Private property for use by the mutator that splits per-API level.
-	// can be one of <number:sdk_version> or <codename> or "current"
-	// passed to "gen_stub_libs.py" as it is
+	// Private property for use by the mutator that splits per-API level. Can be
+	// one of <number:sdk_version> or <codename> or "current" passed to
+	// "ndkstubgen.py" as it is
 	ApiLevel string `blueprint:"mutated"`
 
 	// True if this API is not yet ready to be shipped in the NDK. It will be
diff --git a/cc/ndkstubgen/.gitignore b/cc/ndkstubgen/.gitignore
new file mode 100644
index 0000000..fd94eac
--- /dev/null
+++ b/cc/ndkstubgen/.gitignore
@@ -0,0 +1,140 @@
+# From https://github.com/github/gitignore/blob/master/Python.gitignore
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
diff --git a/cc/scriptlib/Android.bp b/cc/ndkstubgen/Android.bp
similarity index 63%
copy from cc/scriptlib/Android.bp
copy to cc/ndkstubgen/Android.bp
index ff9a2f0..85dfaee 100644
--- a/cc/scriptlib/Android.bp
+++ b/cc/ndkstubgen/Android.bp
@@ -14,19 +14,35 @@
 // limitations under the License.
 //
 
-python_test_host {
-    name: "test_ndk_api_coverage_parser",
-    main: "test_ndk_api_coverage_parser.py",
+python_binary_host {
+    name: "ndkstubgen",
+    pkg_path: "ndkstubgen",
+    main: "__init__.py",
     srcs: [
-        "test_ndk_api_coverage_parser.py",
+        "__init__.py",
+    ],
+    libs: [
+        "symbolfile",
     ],
 }
 
-python_binary_host {
-    name: "ndk_api_coverage_parser",
-    main: "ndk_api_coverage_parser.py",
+python_library_host {
+    name: "ndkstubgenlib",
+    pkg_path: "ndkstubgen",
     srcs: [
-        "gen_stub_libs.py",
-        "ndk_api_coverage_parser.py",
+        "__init__.py",
+    ],
+    libs: [
+        "symbolfile",
+    ],
+}
+
+python_test_host {
+    name: "test_ndkstubgen",
+    srcs: [
+        "test_ndkstubgen.py",
+    ],
+    libs: [
+        "ndkstubgenlib",
     ],
 }
diff --git a/cc/ndkstubgen/OWNERS b/cc/ndkstubgen/OWNERS
new file mode 100644
index 0000000..f0d8733
--- /dev/null
+++ b/cc/ndkstubgen/OWNERS
@@ -0,0 +1 @@
+danalbert@google.com
diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py
new file mode 100755
index 0000000..2f4326a
--- /dev/null
+++ b/cc/ndkstubgen/__init__.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Generates source for stub shared libraries for the NDK."""
+import argparse
+import json
+import logging
+import os
+import sys
+
+import symbolfile
+
+
+class Generator:
+    """Output generator that writes stub source files and version scripts."""
+    def __init__(self, src_file, version_script, arch, api, llndk, apex):
+        self.src_file = src_file
+        self.version_script = version_script
+        self.arch = arch
+        self.api = api
+        self.llndk = llndk
+        self.apex = apex
+
+    def write(self, versions):
+        """Writes all symbol data to the output files."""
+        for version in versions:
+            self.write_version(version)
+
+    def write_version(self, version):
+        """Writes a single version block's data to the output files."""
+        if symbolfile.should_omit_version(version, self.arch, self.api,
+                                          self.llndk, self.apex):
+            return
+
+        section_versioned = symbolfile.symbol_versioned_in_api(
+            version.tags, self.api)
+        version_empty = True
+        pruned_symbols = []
+        for symbol in version.symbols:
+            if symbolfile.should_omit_symbol(symbol, self.arch, self.api,
+                                             self.llndk, self.apex):
+                continue
+
+            if symbolfile.symbol_versioned_in_api(symbol.tags, self.api):
+                version_empty = False
+            pruned_symbols.append(symbol)
+
+        if len(pruned_symbols) > 0:
+            if not version_empty and section_versioned:
+                self.version_script.write(version.name + ' {\n')
+                self.version_script.write('    global:\n')
+            for symbol in pruned_symbols:
+                emit_version = symbolfile.symbol_versioned_in_api(
+                    symbol.tags, self.api)
+                if section_versioned and emit_version:
+                    self.version_script.write('        ' + symbol.name + ';\n')
+
+                weak = ''
+                if 'weak' in symbol.tags:
+                    weak = '__attribute__((weak)) '
+
+                if 'var' in symbol.tags:
+                    self.src_file.write('{}int {} = 0;\n'.format(
+                        weak, symbol.name))
+                else:
+                    self.src_file.write('{}void {}() {{}}\n'.format(
+                        weak, symbol.name))
+
+            if not version_empty and section_versioned:
+                base = '' if version.base is None else ' ' + version.base
+                self.version_script.write('}' + base + ';\n')
+
+
+def parse_args():
+    """Parses and returns command line arguments."""
+    parser = argparse.ArgumentParser()
+
+    parser.add_argument('-v', '--verbose', action='count', default=0)
+
+    parser.add_argument(
+        '--api', required=True, help='API level being targeted.')
+    parser.add_argument(
+        '--arch', choices=symbolfile.ALL_ARCHITECTURES, required=True,
+        help='Architecture being targeted.')
+    parser.add_argument(
+        '--llndk', action='store_true', help='Use the LLNDK variant.')
+    parser.add_argument(
+        '--apex', action='store_true', help='Use the APEX variant.')
+
+    parser.add_argument(
+        '--api-map', type=os.path.realpath, required=True,
+        help='Path to the API level map JSON file.')
+
+    parser.add_argument(
+        'symbol_file', type=os.path.realpath, help='Path to symbol file.')
+    parser.add_argument(
+        'stub_src', type=os.path.realpath,
+        help='Path to output stub source file.')
+    parser.add_argument(
+        'version_script', type=os.path.realpath,
+        help='Path to output version script.')
+
+    return parser.parse_args()
+
+
+def main():
+    """Program entry point."""
+    args = parse_args()
+
+    with open(args.api_map) as map_file:
+        api_map = json.load(map_file)
+    api = symbolfile.decode_api_level(args.api, api_map)
+
+    verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
+    verbosity = args.verbose
+    if verbosity > 2:
+        verbosity = 2
+    logging.basicConfig(level=verbose_map[verbosity])
+
+    with open(args.symbol_file) as symbol_file:
+        try:
+            versions = symbolfile.SymbolFileParser(symbol_file, api_map,
+                                                   args.arch, api, args.llndk,
+                                                   args.apex).parse()
+        except symbolfile.MultiplyDefinedSymbolError as ex:
+            sys.exit('{}: error: {}'.format(args.symbol_file, ex))
+
+    with open(args.stub_src, 'w') as src_file:
+        with open(args.version_script, 'w') as version_file:
+            generator = Generator(src_file, version_file, args.arch, api,
+                                  args.llndk, args.apex)
+            generator.write(versions)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
new file mode 100755
index 0000000..70bcf78
--- /dev/null
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -0,0 +1,378 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Tests for ndkstubgen.py."""
+import io
+import textwrap
+import unittest
+
+import ndkstubgen
+import symbolfile
+
+
+# pylint: disable=missing-docstring
+
+
+class GeneratorTest(unittest.TestCase):
+    def test_omit_version(self):
+        # Thorough testing of the cases involved here is handled by
+        # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
+                                         False, False)
+
+        version = symbolfile.Version('VERSION_PRIVATE', None, [], [
+            symbolfile.Symbol('foo', []),
+        ])
+        generator.write_version(version)
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
+        version = symbolfile.Version('VERSION', None, ['x86'], [
+            symbolfile.Symbol('foo', []),
+        ])
+        generator.write_version(version)
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
+        version = symbolfile.Version('VERSION', None, ['introduced=14'], [
+            symbolfile.Symbol('foo', []),
+        ])
+        generator.write_version(version)
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
+    def test_omit_symbol(self):
+        # Thorough testing of the cases involved here is handled by
+        # SymbolPresenceTest.
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
+                                         False, False)
+
+        version = symbolfile.Version('VERSION_1', None, [], [
+            symbolfile.Symbol('foo', ['x86']),
+        ])
+        generator.write_version(version)
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
+        version = symbolfile.Version('VERSION_1', None, [], [
+            symbolfile.Symbol('foo', ['introduced=14']),
+        ])
+        generator.write_version(version)
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
+        version = symbolfile.Version('VERSION_1', None, [], [
+            symbolfile.Symbol('foo', ['llndk']),
+        ])
+        generator.write_version(version)
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
+        version = symbolfile.Version('VERSION_1', None, [], [
+            symbolfile.Symbol('foo', ['apex']),
+        ])
+        generator.write_version(version)
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
+    def test_write(self):
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
+                                         False, False)
+
+        versions = [
+            symbolfile.Version('VERSION_1', None, [], [
+                symbolfile.Symbol('foo', []),
+                symbolfile.Symbol('bar', ['var']),
+                symbolfile.Symbol('woodly', ['weak']),
+                symbolfile.Symbol('doodly', ['weak', 'var']),
+            ]),
+            symbolfile.Version('VERSION_2', 'VERSION_1', [], [
+                symbolfile.Symbol('baz', []),
+            ]),
+            symbolfile.Version('VERSION_3', 'VERSION_1', [], [
+                symbolfile.Symbol('qux', ['versioned=14']),
+            ]),
+        ]
+
+        generator.write(versions)
+        expected_src = textwrap.dedent("""\
+            void foo() {}
+            int bar = 0;
+            __attribute__((weak)) void woodly() {}
+            __attribute__((weak)) int doodly = 0;
+            void baz() {}
+            void qux() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo;
+                    bar;
+                    woodly;
+                    doodly;
+            };
+            VERSION_2 {
+                global:
+                    baz;
+            } VERSION_1;
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
+
+class IntegrationTest(unittest.TestCase):
+    def test_integration(self):
+        api_map = {
+            'O': 9000,
+            'P': 9001,
+        }
+
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo; # var
+                    bar; # x86
+                    fizz; # introduced=O
+                    buzz; # introduced=P
+                local:
+                    *;
+            };
+
+            VERSION_2 { # arm
+                baz; # introduced=9
+                qux; # versioned=14
+            } VERSION_1;
+
+            VERSION_3 { # introduced=14
+                woodly;
+                doodly; # var
+            } VERSION_2;
+
+            VERSION_4 { # versioned=9
+                wibble;
+                wizzes; # llndk
+                waggle; # apex
+            } VERSION_2;
+
+            VERSION_5 { # versioned=14
+                wobble;
+            } VERSION_4;
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9,
+                                             False, False)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
+                                         False, False)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            int foo = 0;
+            void baz() {}
+            void qux() {}
+            void wibble() {}
+            void wobble() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo;
+            };
+            VERSION_2 {
+                global:
+                    baz;
+            } VERSION_1;
+            VERSION_4 {
+                global:
+                    wibble;
+            } VERSION_2;
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
+    def test_integration_future_api(self):
+        api_map = {
+            'O': 9000,
+            'P': 9001,
+            'Q': 9002,
+        }
+
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo; # introduced=O
+                    bar; # introduced=P
+                    baz; # introduced=Q
+                local:
+                    *;
+            };
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9001,
+                                             False, False)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9001,
+                                         False, False)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            void foo() {}
+            void bar() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo;
+                    bar;
+            };
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
+    def test_multiple_definition(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo;
+                    foo;
+                    bar;
+                    baz;
+                    qux; # arm
+                local:
+                    *;
+            };
+
+            VERSION_2 {
+                global:
+                    bar;
+                    qux; # arm64
+            } VERSION_1;
+
+            VERSION_PRIVATE {
+                global:
+                    baz;
+            } VERSION_2;
+
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False,
+                                             False)
+
+        with self.assertRaises(
+                symbolfile.MultiplyDefinedSymbolError) as ex_context:
+            parser.parse()
+        self.assertEqual(['bar', 'foo'],
+                         ex_context.exception.multiply_defined_symbols)
+
+    def test_integration_with_apex(self):
+        api_map = {
+            'O': 9000,
+            'P': 9001,
+        }
+
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo; # var
+                    bar; # x86
+                    fizz; # introduced=O
+                    buzz; # introduced=P
+                local:
+                    *;
+            };
+
+            VERSION_2 { # arm
+                baz; # introduced=9
+                qux; # versioned=14
+            } VERSION_1;
+
+            VERSION_3 { # introduced=14
+                woodly;
+                doodly; # var
+            } VERSION_2;
+
+            VERSION_4 { # versioned=9
+                wibble;
+                wizzes; # llndk
+                waggle; # apex
+                bubble; # apex llndk
+                duddle; # llndk apex
+            } VERSION_2;
+
+            VERSION_5 { # versioned=14
+                wobble;
+            } VERSION_4;
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9,
+                                             False, True)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
+                                         False, True)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            int foo = 0;
+            void baz() {}
+            void qux() {}
+            void wibble() {}
+            void waggle() {}
+            void bubble() {}
+            void duddle() {}
+            void wobble() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo;
+            };
+            VERSION_2 {
+                global:
+                    baz;
+            } VERSION_1;
+            VERSION_4 {
+                global:
+                    wibble;
+                    waggle;
+                    bubble;
+                    duddle;
+            } VERSION_2;
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
+def main():
+    suite = unittest.TestLoader().loadTestsFromName(__name__)
+    unittest.TextTestRunner(verbosity=3).run(suite)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/cc/pylintrc b/cc/pylintrc
index ed49dd7..2032d4e 100644
--- a/cc/pylintrc
+++ b/cc/pylintrc
@@ -1,280 +1,11 @@
-[MASTER]
-
-# Specify a configuration file.
-#rcfile=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Profiled execution.
-profile=no
-
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
-ignore=CVS
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# List of plugins (as comma separated values of python modules names) to load,
-# usually to register additional checkers.
-load-plugins=
-
-
 [MESSAGES CONTROL]
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time. See also the "--disable" option for examples.
-#enable=
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once).You can also use "--disable=all" to
-# disable everything first and then reenable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use"--disable=all --enable=classes
-# --disable=W"
 disable=design,fixme
 
-
-[REPORTS]
-
-# Set the output format. Available formats are text, parseable, colorized, msvs
-# (visual studio) and html. You can also give a reporter class, eg
-# mypackage.mymodule.MyReporterClass.
-output-format=text
-
-# Put messages in a separate file for each module / package specified on the
-# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]".
-files-output=no
-
-# Tells whether to display a full report or only the messages
-reports=yes
-
-# Python expression which should return a note less than 10 (10 is the highest
-# note). You have access to the variables errors warning, statement which
-# respectively contain the number of errors / warnings messages and the total
-# number of statements analyzed. This is used by the global evaluation report
-# (RP0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-
-# Add a comment according to your evaluation note. This is used by the global
-# evaluation report (RP0004).
-comment=no
-
-# Template used to display messages. This is a python new-style format string
-# used to format the message information. See doc for all details
-#msg-template=
-
-
 [BASIC]
-
-# Required attributes for module, separated by a comma
-required-attributes=
-
-# List of builtins function names that should not be used, separated by a comma
-bad-functions=map,filter,apply,input
-
-# Regular expression which should only match correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression which should only match correct module level names
-const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Regular expression which should only match correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression which should only match correct function names
-function-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct instance attribute names
-attr-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct argument names
-argument-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct variable names
-variable-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct attribute names in class
-# bodies
-class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Regular expression which should only match correct list comprehension /
-# generator expression variable names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Good variable names which should always be accepted, separated by a comma
 good-names=i,j,k,ex,Run,_
 
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=__.*__
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
-
-
-[TYPECHECK]
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# List of classes names for which member attributes should not be checked
-# (useful for classes with attributes dynamically set).
-ignored-classes=SQLObject
-
-# When zope mode is activated, add a predefined set of Zope acquired attributes
-# to generated-members.
-zope=no
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E0201 when accessed. Python regular
-# expressions are accepted.
-generated-members=REQUEST,acl_users,aq_parent
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
-
-
 [SIMILARITIES]
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-# Ignore comments when computing similarities.
-ignore-comments=yes
-
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
-
-# Ignore imports when computing similarities.
-ignore-imports=no
-
+ignore-imports=yes
 
 [VARIABLES]
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching the beginning of the name of dummy variables
-# (i.e. not used).
 dummy-variables-rgx=_|dummy
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-
-[FORMAT]
-
-# Maximum number of characters on a single line.
-max-line-length=80
-
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
-
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
-
-# List of optional constructs for which whitespace checking is disabled
-no-space-check=trailing-comma,dict-separator
-
-# Maximum number of lines in a module
-max-module-lines=1000
-
-# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
-# tab).
-indent-string='    '
-
-
-[IMPORTS]
-
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=regsub,TERMIOS,Bastion,rexec
-
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled)
-import-graph=
-
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled)
-ext-import-graph=
-
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled)
-int-import-graph=
-
-
-[DESIGN]
-
-# Maximum number of arguments for function / method
-max-args=5
-
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore
-ignored-argument-names=_.*
-
-# Maximum number of locals for function / method body
-max-locals=15
-
-# Maximum number of return / yield for function / method body
-max-returns=6
-
-# Maximum number of branch for function / method body
-max-branches=12
-
-# Maximum number of statements in function / method body
-max-statements=50
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=7
-
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-
-[CLASSES]
-
-# List of interface methods to ignore, separated by a comma. This is used for
-# instance to not check methods defines in Zope's Interface base class.
-ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
-
-
-[EXCEPTIONS]
-
-# Exceptions that will emit a warning when being caught. Defaults to
-# "Exception"
-overgeneral-exceptions=Exception
diff --git a/cc/scriptlib/__init__.py b/cc/scriptlib/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/cc/scriptlib/__init__.py
+++ /dev/null
diff --git a/cc/scriptlib/test_gen_stub_libs.py b/cc/scriptlib/test_gen_stub_libs.py
deleted file mode 100755
index 0b45e71..0000000
--- a/cc/scriptlib/test_gen_stub_libs.py
+++ /dev/null
@@ -1,807 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Tests for gen_stub_libs.py."""
-import io
-import textwrap
-import unittest
-
-import gen_stub_libs as gsl
-
-
-# pylint: disable=missing-docstring
-
-
-class DecodeApiLevelTest(unittest.TestCase):
-    def test_decode_api_level(self):
-        self.assertEqual(9, gsl.decode_api_level('9', {}))
-        self.assertEqual(9000, gsl.decode_api_level('O', {'O': 9000}))
-
-        with self.assertRaises(KeyError):
-            gsl.decode_api_level('O', {})
-
-
-class TagsTest(unittest.TestCase):
-    def test_get_tags_no_tags(self):
-        self.assertEqual([], gsl.get_tags(''))
-        self.assertEqual([], gsl.get_tags('foo bar baz'))
-
-    def test_get_tags(self):
-        self.assertEqual(['foo', 'bar'], gsl.get_tags('# foo bar'))
-        self.assertEqual(['bar', 'baz'], gsl.get_tags('foo # bar baz'))
-
-    def test_split_tag(self):
-        self.assertTupleEqual(('foo', 'bar'), gsl.split_tag('foo=bar'))
-        self.assertTupleEqual(('foo', 'bar=baz'), gsl.split_tag('foo=bar=baz'))
-        with self.assertRaises(ValueError):
-            gsl.split_tag('foo')
-
-    def test_get_tag_value(self):
-        self.assertEqual('bar', gsl.get_tag_value('foo=bar'))
-        self.assertEqual('bar=baz', gsl.get_tag_value('foo=bar=baz'))
-        with self.assertRaises(ValueError):
-            gsl.get_tag_value('foo')
-
-    def test_is_api_level_tag(self):
-        self.assertTrue(gsl.is_api_level_tag('introduced=24'))
-        self.assertTrue(gsl.is_api_level_tag('introduced-arm=24'))
-        self.assertTrue(gsl.is_api_level_tag('versioned=24'))
-
-        # Shouldn't try to process things that aren't a key/value tag.
-        self.assertFalse(gsl.is_api_level_tag('arm'))
-        self.assertFalse(gsl.is_api_level_tag('introduced'))
-        self.assertFalse(gsl.is_api_level_tag('versioned'))
-
-        # We don't support arch specific `versioned` tags.
-        self.assertFalse(gsl.is_api_level_tag('versioned-arm=24'))
-
-    def test_decode_api_level_tags(self):
-        api_map = {
-            'O': 9000,
-            'P': 9001,
-        }
-
-        tags = [
-            'introduced=9',
-            'introduced-arm=14',
-            'versioned=16',
-            'arm',
-            'introduced=O',
-            'introduced=P',
-        ]
-        expected_tags = [
-            'introduced=9',
-            'introduced-arm=14',
-            'versioned=16',
-            'arm',
-            'introduced=9000',
-            'introduced=9001',
-        ]
-        self.assertListEqual(
-            expected_tags, gsl.decode_api_level_tags(tags, api_map))
-
-        with self.assertRaises(gsl.ParseError):
-            gsl.decode_api_level_tags(['introduced=O'], {})
-
-
-class PrivateVersionTest(unittest.TestCase):
-    def test_version_is_private(self):
-        self.assertFalse(gsl.version_is_private('foo'))
-        self.assertFalse(gsl.version_is_private('PRIVATE'))
-        self.assertFalse(gsl.version_is_private('PLATFORM'))
-        self.assertFalse(gsl.version_is_private('foo_private'))
-        self.assertFalse(gsl.version_is_private('foo_platform'))
-        self.assertFalse(gsl.version_is_private('foo_PRIVATE_'))
-        self.assertFalse(gsl.version_is_private('foo_PLATFORM_'))
-
-        self.assertTrue(gsl.version_is_private('foo_PRIVATE'))
-        self.assertTrue(gsl.version_is_private('foo_PLATFORM'))
-
-
-class SymbolPresenceTest(unittest.TestCase):
-    def test_symbol_in_arch(self):
-        self.assertTrue(gsl.symbol_in_arch([], 'arm'))
-        self.assertTrue(gsl.symbol_in_arch(['arm'], 'arm'))
-
-        self.assertFalse(gsl.symbol_in_arch(['x86'], 'arm'))
-
-    def test_symbol_in_api(self):
-        self.assertTrue(gsl.symbol_in_api([], 'arm', 9))
-        self.assertTrue(gsl.symbol_in_api(['introduced=9'], 'arm', 9))
-        self.assertTrue(gsl.symbol_in_api(['introduced=9'], 'arm', 14))
-        self.assertTrue(gsl.symbol_in_api(['introduced-arm=9'], 'arm', 14))
-        self.assertTrue(gsl.symbol_in_api(['introduced-arm=9'], 'arm', 14))
-        self.assertTrue(gsl.symbol_in_api(['introduced-x86=14'], 'arm', 9))
-        self.assertTrue(gsl.symbol_in_api(
-            ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14))
-        self.assertTrue(gsl.symbol_in_api(
-            ['introduced=9', 'introduced-x86=21'], 'arm', 14))
-        self.assertTrue(gsl.symbol_in_api(
-            ['introduced=21', 'introduced-arm=9'], 'arm', 14))
-        self.assertTrue(gsl.symbol_in_api(
-            ['future'], 'arm', gsl.FUTURE_API_LEVEL))
-
-        self.assertFalse(gsl.symbol_in_api(['introduced=14'], 'arm', 9))
-        self.assertFalse(gsl.symbol_in_api(['introduced-arm=14'], 'arm', 9))
-        self.assertFalse(gsl.symbol_in_api(['future'], 'arm', 9))
-        self.assertFalse(gsl.symbol_in_api(
-            ['introduced=9', 'future'], 'arm', 14))
-        self.assertFalse(gsl.symbol_in_api(
-            ['introduced-arm=9', 'future'], 'arm', 14))
-        self.assertFalse(gsl.symbol_in_api(
-            ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14))
-        self.assertFalse(gsl.symbol_in_api(
-            ['introduced=9', 'introduced-arm=21'], 'arm', 14))
-        self.assertFalse(gsl.symbol_in_api(
-            ['introduced=21', 'introduced-x86=9'], 'arm', 14))
-
-        # Interesting edge case: this symbol should be omitted from the
-        # library, but this call should still return true because none of the
-        # tags indiciate that it's not present in this API level.
-        self.assertTrue(gsl.symbol_in_api(['x86'], 'arm', 9))
-
-    def test_verioned_in_api(self):
-        self.assertTrue(gsl.symbol_versioned_in_api([], 9))
-        self.assertTrue(gsl.symbol_versioned_in_api(['versioned=9'], 9))
-        self.assertTrue(gsl.symbol_versioned_in_api(['versioned=9'], 14))
-
-        self.assertFalse(gsl.symbol_versioned_in_api(['versioned=14'], 9))
-
-
-class OmitVersionTest(unittest.TestCase):
-    def test_omit_private(self):
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, [], []), 'arm', 9, False, False))
-
-        self.assertTrue(
-            gsl.should_omit_version(
-                gsl.Version('foo_PRIVATE', None, [], []), 'arm', 9, False, False))
-        self.assertTrue(
-            gsl.should_omit_version(
-                gsl.Version('foo_PLATFORM', None, [], []), 'arm', 9, False, False))
-
-        self.assertTrue(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['platform-only'], []), 'arm', 9,
-                False, False))
-
-    def test_omit_llndk(self):
-        self.assertTrue(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['llndk'], []), 'arm', 9, False, False))
-
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, [], []), 'arm', 9, True, False))
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['llndk'], []), 'arm', 9, True, False))
-
-    def test_omit_apex(self):
-        self.assertTrue(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['apex'], []), 'arm', 9, False, False))
-
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, [], []), 'arm', 9, False, True))
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['apex'], []), 'arm', 9, False, True))
-
-    def test_omit_arch(self):
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, [], []), 'arm', 9, False, False))
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['arm'], []), 'arm', 9, False, False))
-
-        self.assertTrue(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['x86'], []), 'arm', 9, False, False))
-
-    def test_omit_api(self):
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, [], []), 'arm', 9, False, False))
-        self.assertFalse(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['introduced=9'], []), 'arm', 9,
-                False, False))
-
-        self.assertTrue(
-            gsl.should_omit_version(
-                gsl.Version('foo', None, ['introduced=14'], []), 'arm', 9,
-                False, False))
-
-
-class OmitSymbolTest(unittest.TestCase):
-    def test_omit_llndk(self):
-        self.assertTrue(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['llndk']), 'arm', 9, False, False))
-
-        self.assertFalse(
-            gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, True, False))
-        self.assertFalse(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['llndk']), 'arm', 9, True, False))
-
-    def test_omit_apex(self):
-        self.assertTrue(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['apex']), 'arm', 9, False, False))
-
-        self.assertFalse(
-            gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, True))
-        self.assertFalse(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['apex']), 'arm', 9, False, True))
-
-    def test_omit_arch(self):
-        self.assertFalse(
-            gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, False))
-        self.assertFalse(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['arm']), 'arm', 9, False, False))
-
-        self.assertTrue(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['x86']), 'arm', 9, False, False))
-
-    def test_omit_api(self):
-        self.assertFalse(
-            gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, False))
-        self.assertFalse(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['introduced=9']), 'arm', 9, False, False))
-
-        self.assertTrue(
-            gsl.should_omit_symbol(
-                gsl.Symbol('foo', ['introduced=14']), 'arm', 9, False, False))
-
-
-class SymbolFileParseTest(unittest.TestCase):
-    def test_next_line(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            foo
-
-            bar
-            # baz
-            qux
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-        self.assertIsNone(parser.current_line)
-
-        self.assertEqual('foo', parser.next_line().strip())
-        self.assertEqual('foo', parser.current_line.strip())
-
-        self.assertEqual('bar', parser.next_line().strip())
-        self.assertEqual('bar', parser.current_line.strip())
-
-        self.assertEqual('qux', parser.next_line().strip())
-        self.assertEqual('qux', parser.current_line.strip())
-
-        self.assertEqual('', parser.next_line())
-        self.assertEqual('', parser.current_line)
-
-    def test_parse_version(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 { # foo bar
-                baz;
-                qux; # woodly doodly
-            };
-
-            VERSION_2 {
-            } VERSION_1; # asdf
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-
-        parser.next_line()
-        version = parser.parse_version()
-        self.assertEqual('VERSION_1', version.name)
-        self.assertIsNone(version.base)
-        self.assertEqual(['foo', 'bar'], version.tags)
-
-        expected_symbols = [
-            gsl.Symbol('baz', []),
-            gsl.Symbol('qux', ['woodly', 'doodly']),
-        ]
-        self.assertEqual(expected_symbols, version.symbols)
-
-        parser.next_line()
-        version = parser.parse_version()
-        self.assertEqual('VERSION_2', version.name)
-        self.assertEqual('VERSION_1', version.base)
-        self.assertEqual([], version.tags)
-
-    def test_parse_version_eof(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-        parser.next_line()
-        with self.assertRaises(gsl.ParseError):
-            parser.parse_version()
-
-    def test_unknown_scope_label(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                foo:
-            }
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-        parser.next_line()
-        with self.assertRaises(gsl.ParseError):
-            parser.parse_version()
-
-    def test_parse_symbol(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            foo;
-            bar; # baz qux
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-
-        parser.next_line()
-        symbol = parser.parse_symbol()
-        self.assertEqual('foo', symbol.name)
-        self.assertEqual([], symbol.tags)
-
-        parser.next_line()
-        symbol = parser.parse_symbol()
-        self.assertEqual('bar', symbol.name)
-        self.assertEqual(['baz', 'qux'], symbol.tags)
-
-    def test_wildcard_symbol_global(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                *;
-            };
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-        parser.next_line()
-        with self.assertRaises(gsl.ParseError):
-            parser.parse_version()
-
-    def test_wildcard_symbol_local(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                local:
-                    *;
-            };
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-        parser.next_line()
-        version = parser.parse_version()
-        self.assertEqual([], version.symbols)
-
-    def test_missing_semicolon(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                foo
-            };
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-        parser.next_line()
-        with self.assertRaises(gsl.ParseError):
-            parser.parse_version()
-
-    def test_parse_fails_invalid_input(self):
-        with self.assertRaises(gsl.ParseError):
-            input_file = io.StringIO('foo')
-            parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-            parser.parse()
-
-    def test_parse(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                local:
-                    hidden1;
-                global:
-                    foo;
-                    bar; # baz
-            };
-
-            VERSION_2 { # wasd
-                # Implicit global scope.
-                    woodly;
-                    doodly; # asdf
-                local:
-                    qwerty;
-            } VERSION_1;
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-        versions = parser.parse()
-
-        expected = [
-            gsl.Version('VERSION_1', None, [], [
-                gsl.Symbol('foo', []),
-                gsl.Symbol('bar', ['baz']),
-            ]),
-            gsl.Version('VERSION_2', 'VERSION_1', ['wasd'], [
-                gsl.Symbol('woodly', []),
-                gsl.Symbol('doodly', ['asdf']),
-            ]),
-        ]
-
-        self.assertEqual(expected, versions)
-
-    def test_parse_llndk_apex_symbol(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                foo;
-                bar; # llndk
-                baz; # llndk apex
-                qux; # apex
-            };
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, True)
-
-        parser.next_line()
-        version = parser.parse_version()
-        self.assertEqual('VERSION_1', version.name)
-        self.assertIsNone(version.base)
-
-        expected_symbols = [
-            gsl.Symbol('foo', []),
-            gsl.Symbol('bar', ['llndk']),
-            gsl.Symbol('baz', ['llndk', 'apex']),
-            gsl.Symbol('qux', ['apex']),
-        ]
-        self.assertEqual(expected_symbols, version.symbols)
-
-
-class GeneratorTest(unittest.TestCase):
-    def test_omit_version(self):
-        # Thorough testing of the cases involved here is handled by
-        # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
-        src_file = io.StringIO()
-        version_file = io.StringIO()
-        generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False)
-
-        version = gsl.Version('VERSION_PRIVATE', None, [], [
-            gsl.Symbol('foo', []),
-        ])
-        generator.write_version(version)
-        self.assertEqual('', src_file.getvalue())
-        self.assertEqual('', version_file.getvalue())
-
-        version = gsl.Version('VERSION', None, ['x86'], [
-            gsl.Symbol('foo', []),
-        ])
-        generator.write_version(version)
-        self.assertEqual('', src_file.getvalue())
-        self.assertEqual('', version_file.getvalue())
-
-        version = gsl.Version('VERSION', None, ['introduced=14'], [
-            gsl.Symbol('foo', []),
-        ])
-        generator.write_version(version)
-        self.assertEqual('', src_file.getvalue())
-        self.assertEqual('', version_file.getvalue())
-
-    def test_omit_symbol(self):
-        # Thorough testing of the cases involved here is handled by
-        # SymbolPresenceTest.
-        src_file = io.StringIO()
-        version_file = io.StringIO()
-        generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False)
-
-        version = gsl.Version('VERSION_1', None, [], [
-            gsl.Symbol('foo', ['x86']),
-        ])
-        generator.write_version(version)
-        self.assertEqual('', src_file.getvalue())
-        self.assertEqual('', version_file.getvalue())
-
-        version = gsl.Version('VERSION_1', None, [], [
-            gsl.Symbol('foo', ['introduced=14']),
-        ])
-        generator.write_version(version)
-        self.assertEqual('', src_file.getvalue())
-        self.assertEqual('', version_file.getvalue())
-
-        version = gsl.Version('VERSION_1', None, [], [
-            gsl.Symbol('foo', ['llndk']),
-        ])
-        generator.write_version(version)
-        self.assertEqual('', src_file.getvalue())
-        self.assertEqual('', version_file.getvalue())
-
-        version = gsl.Version('VERSION_1', None, [], [
-            gsl.Symbol('foo', ['apex']),
-        ])
-        generator.write_version(version)
-        self.assertEqual('', src_file.getvalue())
-        self.assertEqual('', version_file.getvalue())
-
-    def test_write(self):
-        src_file = io.StringIO()
-        version_file = io.StringIO()
-        generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False)
-
-        versions = [
-            gsl.Version('VERSION_1', None, [], [
-                gsl.Symbol('foo', []),
-                gsl.Symbol('bar', ['var']),
-                gsl.Symbol('woodly', ['weak']),
-                gsl.Symbol('doodly', ['weak', 'var']),
-            ]),
-            gsl.Version('VERSION_2', 'VERSION_1', [], [
-                gsl.Symbol('baz', []),
-            ]),
-            gsl.Version('VERSION_3', 'VERSION_1', [], [
-                gsl.Symbol('qux', ['versioned=14']),
-            ]),
-        ]
-
-        generator.write(versions)
-        expected_src = textwrap.dedent("""\
-            void foo() {}
-            int bar = 0;
-            __attribute__((weak)) void woodly() {}
-            __attribute__((weak)) int doodly = 0;
-            void baz() {}
-            void qux() {}
-        """)
-        self.assertEqual(expected_src, src_file.getvalue())
-
-        expected_version = textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo;
-                    bar;
-                    woodly;
-                    doodly;
-            };
-            VERSION_2 {
-                global:
-                    baz;
-            } VERSION_1;
-        """)
-        self.assertEqual(expected_version, version_file.getvalue())
-
-
-class IntegrationTest(unittest.TestCase):
-    def test_integration(self):
-        api_map = {
-            'O': 9000,
-            'P': 9001,
-        }
-
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo; # var
-                    bar; # x86
-                    fizz; # introduced=O
-                    buzz; # introduced=P
-                local:
-                    *;
-            };
-
-            VERSION_2 { # arm
-                baz; # introduced=9
-                qux; # versioned=14
-            } VERSION_1;
-
-            VERSION_3 { # introduced=14
-                woodly;
-                doodly; # var
-            } VERSION_2;
-
-            VERSION_4 { # versioned=9
-                wibble;
-                wizzes; # llndk
-                waggle; # apex
-            } VERSION_2;
-
-            VERSION_5 { # versioned=14
-                wobble;
-            } VERSION_4;
-        """))
-        parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False, False)
-        versions = parser.parse()
-
-        src_file = io.StringIO()
-        version_file = io.StringIO()
-        generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False)
-        generator.write(versions)
-
-        expected_src = textwrap.dedent("""\
-            int foo = 0;
-            void baz() {}
-            void qux() {}
-            void wibble() {}
-            void wobble() {}
-        """)
-        self.assertEqual(expected_src, src_file.getvalue())
-
-        expected_version = textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo;
-            };
-            VERSION_2 {
-                global:
-                    baz;
-            } VERSION_1;
-            VERSION_4 {
-                global:
-                    wibble;
-            } VERSION_2;
-        """)
-        self.assertEqual(expected_version, version_file.getvalue())
-
-    def test_integration_future_api(self):
-        api_map = {
-            'O': 9000,
-            'P': 9001,
-            'Q': 9002,
-        }
-
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo; # introduced=O
-                    bar; # introduced=P
-                    baz; # introduced=Q
-                local:
-                    *;
-            };
-        """))
-        parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9001, False, False)
-        versions = parser.parse()
-
-        src_file = io.StringIO()
-        version_file = io.StringIO()
-        generator = gsl.Generator(src_file, version_file, 'arm', 9001, False, False)
-        generator.write(versions)
-
-        expected_src = textwrap.dedent("""\
-            void foo() {}
-            void bar() {}
-        """)
-        self.assertEqual(expected_src, src_file.getvalue())
-
-        expected_version = textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo;
-                    bar;
-            };
-        """)
-        self.assertEqual(expected_version, version_file.getvalue())
-
-    def test_multiple_definition(self):
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo;
-                    foo;
-                    bar;
-                    baz;
-                    qux; # arm
-                local:
-                    *;
-            };
-
-            VERSION_2 {
-                global:
-                    bar;
-                    qux; # arm64
-            } VERSION_1;
-
-            VERSION_PRIVATE {
-                global:
-                    baz;
-            } VERSION_2;
-
-        """))
-        parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
-
-        with self.assertRaises(gsl.MultiplyDefinedSymbolError) as cm:
-            parser.parse()
-        self.assertEquals(['bar', 'foo'],
-                          cm.exception.multiply_defined_symbols)
-
-    def test_integration_with_apex(self):
-        api_map = {
-            'O': 9000,
-            'P': 9001,
-        }
-
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo; # var
-                    bar; # x86
-                    fizz; # introduced=O
-                    buzz; # introduced=P
-                local:
-                    *;
-            };
-
-            VERSION_2 { # arm
-                baz; # introduced=9
-                qux; # versioned=14
-            } VERSION_1;
-
-            VERSION_3 { # introduced=14
-                woodly;
-                doodly; # var
-            } VERSION_2;
-
-            VERSION_4 { # versioned=9
-                wibble;
-                wizzes; # llndk
-                waggle; # apex
-                bubble; # apex llndk
-                duddle; # llndk apex
-            } VERSION_2;
-
-            VERSION_5 { # versioned=14
-                wobble;
-            } VERSION_4;
-        """))
-        parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False, True)
-        versions = parser.parse()
-
-        src_file = io.StringIO()
-        version_file = io.StringIO()
-        generator = gsl.Generator(src_file, version_file, 'arm', 9, False, True)
-        generator.write(versions)
-
-        expected_src = textwrap.dedent("""\
-            int foo = 0;
-            void baz() {}
-            void qux() {}
-            void wibble() {}
-            void waggle() {}
-            void bubble() {}
-            void duddle() {}
-            void wobble() {}
-        """)
-        self.assertEqual(expected_src, src_file.getvalue())
-
-        expected_version = textwrap.dedent("""\
-            VERSION_1 {
-                global:
-                    foo;
-            };
-            VERSION_2 {
-                global:
-                    baz;
-            } VERSION_1;
-            VERSION_4 {
-                global:
-                    wibble;
-                    waggle;
-                    bubble;
-                    duddle;
-            } VERSION_2;
-        """)
-        self.assertEqual(expected_version, version_file.getvalue())
-
-def main():
-    suite = unittest.TestLoader().loadTestsFromName(__name__)
-    unittest.TextTestRunner(verbosity=3).run(suite)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/cc/scriptlib/test_ndk_api_coverage_parser.py b/cc/scriptlib/test_ndk_api_coverage_parser.py
deleted file mode 100644
index a3c9b70..0000000
--- a/cc/scriptlib/test_ndk_api_coverage_parser.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Tests for ndk_api_coverage_parser.py."""
-import io
-import textwrap
-import unittest
-
-from xml.etree.ElementTree import tostring
-from gen_stub_libs import FUTURE_API_LEVEL, SymbolFileParser
-import ndk_api_coverage_parser as nparser
-
-
-# pylint: disable=missing-docstring
-
-
-class ApiCoverageSymbolFileParserTest(unittest.TestCase):
-    def test_parse(self):
-        input_file = io.StringIO(textwrap.dedent(u"""\
-            LIBLOG { # introduced-arm64=24 introduced-x86=24 introduced-x86_64=24
-              global:
-                android_name_to_log_id; # apex llndk introduced=23
-                android_log_id_to_name; # llndk arm
-                __android_log_assert; # introduced-x86=23
-                __android_log_buf_print; # var
-                __android_log_buf_write;
-              local:
-                *;
-            };
-            
-            LIBLOG_PLATFORM {
-                android_fdtrack; # llndk
-                android_net; # introduced=23
-            };
-            
-            LIBLOG_FOO { # var
-                android_var;
-            };
-        """))
-        parser = SymbolFileParser(input_file, {}, "", FUTURE_API_LEVEL, True, True)
-        generator = nparser.XmlGenerator(io.StringIO())
-        result = tostring(generator.convertToXml(parser.parse())).decode()
-        expected = '<ndk-library><symbol apex="True" arch="" introduced="23" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_name_to_log_id" /><symbol arch="arm" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_log_id_to_name" /><symbol arch="" introduced-arm64="24" introduced-x86="23" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_assert" /><symbol arch="" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_buf_write" /><symbol arch="" is_deprecated="False" is_platform="True" llndk="True" name="android_fdtrack" /><symbol arch="" introduced="23" is_deprecated="False" is_platform="True" name="android_net" /></ndk-library>'
-        self.assertEqual(expected, result)
-
-
-def main():
-    suite = unittest.TestLoader().loadTestsFromName(__name__)
-    unittest.TextTestRunner(verbosity=3).run(suite)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/cc/symbolfile/.gitignore b/cc/symbolfile/.gitignore
new file mode 100644
index 0000000..fd94eac
--- /dev/null
+++ b/cc/symbolfile/.gitignore
@@ -0,0 +1,140 @@
+# From https://github.com/github/gitignore/blob/master/Python.gitignore
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
diff --git a/cc/scriptlib/Android.bp b/cc/symbolfile/Android.bp
similarity index 69%
copy from cc/scriptlib/Android.bp
copy to cc/symbolfile/Android.bp
index ff9a2f0..5b43309 100644
--- a/cc/scriptlib/Android.bp
+++ b/cc/symbolfile/Android.bp
@@ -14,19 +14,20 @@
 // limitations under the License.
 //
 
-python_test_host {
-    name: "test_ndk_api_coverage_parser",
-    main: "test_ndk_api_coverage_parser.py",
+python_library_host {
+    name: "symbolfile",
+    pkg_path: "symbolfile",
     srcs: [
-        "test_ndk_api_coverage_parser.py",
+        "__init__.py",
     ],
 }
 
-python_binary_host {
-    name: "ndk_api_coverage_parser",
-    main: "ndk_api_coverage_parser.py",
+python_test_host {
+    name: "test_symbolfile",
     srcs: [
-        "gen_stub_libs.py",
-        "ndk_api_coverage_parser.py",
+        "test_symbolfile.py",
+    ],
+    libs: [
+        "symbolfile",
     ],
 }
diff --git a/cc/symbolfile/OWNERS b/cc/symbolfile/OWNERS
new file mode 100644
index 0000000..f0d8733
--- /dev/null
+++ b/cc/symbolfile/OWNERS
@@ -0,0 +1 @@
+danalbert@google.com
diff --git a/cc/scriptlib/gen_stub_libs.py b/cc/symbolfile/__init__.py
old mode 100755
new mode 100644
similarity index 72%
rename from cc/scriptlib/gen_stub_libs.py
rename to cc/symbolfile/__init__.py
index d61dfbb..faa3823
--- a/cc/scriptlib/gen_stub_libs.py
+++ b/cc/symbolfile/__init__.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 #
 # Copyright (C) 2016 The Android Open Source Project
 #
@@ -14,13 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-"""Generates source for stub shared libraries for the NDK."""
-import argparse
-import json
+"""Parser for Android's version script information."""
 import logging
-import os
 import re
-import sys
 
 
 ALL_ARCHITECTURES = (
@@ -57,6 +52,24 @@
     return False
 
 
+def decode_api_level(api, api_map):
+    """Decodes the API level argument into the API level number.
+
+    For the average case, this just decodes the integer value from the string,
+    but for unreleased APIs we need to translate from the API codename (like
+    "O") to the future API level for that codename.
+    """
+    try:
+        return int(api)
+    except ValueError:
+        pass
+
+    if api == "current":
+        return FUTURE_API_LEVEL
+
+    return api_map[api]
+
+
 def decode_api_level_tags(tags, api_map):
     """Decodes API level code names in a list of tags.
 
@@ -118,7 +131,8 @@
     if 'platform-only' in version.tags:
         return True
 
-    no_llndk_no_apex = 'llndk' not in version.tags and 'apex' not in version.tags
+    no_llndk_no_apex = ('llndk' not in version.tags
+                        and 'apex' not in version.tags)
     keep = no_llndk_no_apex or \
            ('llndk' in version.tags and llndk) or \
            ('apex' in version.tags and apex)
@@ -205,7 +219,6 @@
 
 class ParseError(RuntimeError):
     """An error that occurred while parsing a symbol file."""
-    pass
 
 
 class MultiplyDefinedSymbolError(RuntimeError):
@@ -217,7 +230,7 @@
         self.multiply_defined_symbols = multiply_defined_symbols
 
 
-class Version(object):
+class Version:
     """A version block of a symbol file."""
     def __init__(self, name, base, tags, symbols):
         self.name = name
@@ -237,7 +250,7 @@
         return True
 
 
-class Symbol(object):
+class Symbol:
     """A symbol definition from a symbol file."""
     def __init__(self, name, tags):
         self.name = name
@@ -247,7 +260,7 @@
         return self.name == other.name and set(self.tags) == set(other.tags)
 
 
-class SymbolFileParser(object):
+class SymbolFileParser:
     """Parses NDK symbol files."""
     def __init__(self, input_file, api_map, arch, api, llndk, apex):
         self.input_file = input_file
@@ -283,11 +296,13 @@
         symbol_names = set()
         multiply_defined_symbols = set()
         for version in versions:
-            if should_omit_version(version, self.arch, self.api, self.llndk, self.apex):
+            if should_omit_version(version, self.arch, self.api, self.llndk,
+                                   self.apex):
                 continue
 
             for symbol in version.symbols:
-                if should_omit_symbol(symbol, self.arch, self.api, self.llndk, self.apex):
+                if should_omit_symbol(symbol, self.arch, self.api, self.llndk,
+                                      self.apex):
                     continue
 
                 if symbol.name in symbol_names:
@@ -367,141 +382,3 @@
                 break
         self.current_line = line
         return self.current_line
-
-
-class Generator(object):
-    """Output generator that writes stub source files and version scripts."""
-    def __init__(self, src_file, version_script, arch, api, llndk, apex):
-        self.src_file = src_file
-        self.version_script = version_script
-        self.arch = arch
-        self.api = api
-        self.llndk = llndk
-        self.apex = apex
-
-    def write(self, versions):
-        """Writes all symbol data to the output files."""
-        for version in versions:
-            self.write_version(version)
-
-    def write_version(self, version):
-        """Writes a single version block's data to the output files."""
-        if should_omit_version(version, self.arch, self.api, self.llndk, self.apex):
-            return
-
-        section_versioned = symbol_versioned_in_api(version.tags, self.api)
-        version_empty = True
-        pruned_symbols = []
-        for symbol in version.symbols:
-            if should_omit_symbol(symbol, self.arch, self.api, self.llndk, self.apex):
-                continue
-
-            if symbol_versioned_in_api(symbol.tags, self.api):
-                version_empty = False
-            pruned_symbols.append(symbol)
-
-        if len(pruned_symbols) > 0:
-            if not version_empty and section_versioned:
-                self.version_script.write(version.name + ' {\n')
-                self.version_script.write('    global:\n')
-            for symbol in pruned_symbols:
-                emit_version = symbol_versioned_in_api(symbol.tags, self.api)
-                if section_versioned and emit_version:
-                    self.version_script.write('        ' + symbol.name + ';\n')
-
-                weak = ''
-                if 'weak' in symbol.tags:
-                    weak = '__attribute__((weak)) '
-
-                if 'var' in symbol.tags:
-                    self.src_file.write('{}int {} = 0;\n'.format(
-                        weak, symbol.name))
-                else:
-                    self.src_file.write('{}void {}() {{}}\n'.format(
-                        weak, symbol.name))
-
-            if not version_empty and section_versioned:
-                base = '' if version.base is None else ' ' + version.base
-                self.version_script.write('}' + base + ';\n')
-
-
-def decode_api_level(api, api_map):
-    """Decodes the API level argument into the API level number.
-
-    For the average case, this just decodes the integer value from the string,
-    but for unreleased APIs we need to translate from the API codename (like
-    "O") to the future API level for that codename.
-    """
-    try:
-        return int(api)
-    except ValueError:
-        pass
-
-    if api == "current":
-        return FUTURE_API_LEVEL
-
-    return api_map[api]
-
-
-def parse_args():
-    """Parses and returns command line arguments."""
-    parser = argparse.ArgumentParser()
-
-    parser.add_argument('-v', '--verbose', action='count', default=0)
-
-    parser.add_argument(
-        '--api', required=True, help='API level being targeted.')
-    parser.add_argument(
-        '--arch', choices=ALL_ARCHITECTURES, required=True,
-        help='Architecture being targeted.')
-    parser.add_argument(
-        '--llndk', action='store_true', help='Use the LLNDK variant.')
-    parser.add_argument(
-        '--apex', action='store_true', help='Use the APEX variant.')
-
-    parser.add_argument(
-        '--api-map', type=os.path.realpath, required=True,
-        help='Path to the API level map JSON file.')
-
-    parser.add_argument(
-        'symbol_file', type=os.path.realpath, help='Path to symbol file.')
-    parser.add_argument(
-        'stub_src', type=os.path.realpath,
-        help='Path to output stub source file.')
-    parser.add_argument(
-        'version_script', type=os.path.realpath,
-        help='Path to output version script.')
-
-    return parser.parse_args()
-
-
-def main():
-    """Program entry point."""
-    args = parse_args()
-
-    with open(args.api_map) as map_file:
-        api_map = json.load(map_file)
-    api = decode_api_level(args.api, api_map)
-
-    verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
-    verbosity = args.verbose
-    if verbosity > 2:
-        verbosity = 2
-    logging.basicConfig(level=verbose_map[verbosity])
-
-    with open(args.symbol_file) as symbol_file:
-        try:
-            versions = SymbolFileParser(symbol_file, api_map, args.arch, api,
-                                        args.llndk, args.apex).parse()
-        except MultiplyDefinedSymbolError as ex:
-            sys.exit('{}: error: {}'.format(args.symbol_file, ex))
-
-    with open(args.stub_src, 'w') as src_file:
-        with open(args.version_script, 'w') as version_file:
-            generator = Generator(src_file, version_file, args.arch, api,
-                                  args.llndk, args.apex)
-            generator.write(versions)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
new file mode 100644
index 0000000..c91131f
--- /dev/null
+++ b/cc/symbolfile/test_symbolfile.py
@@ -0,0 +1,493 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Tests for symbolfile."""
+import io
+import textwrap
+import unittest
+
+import symbolfile
+
+# pylint: disable=missing-docstring
+
+
+class DecodeApiLevelTest(unittest.TestCase):
+    def test_decode_api_level(self):
+        self.assertEqual(9, symbolfile.decode_api_level('9', {}))
+        self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000}))
+
+        with self.assertRaises(KeyError):
+            symbolfile.decode_api_level('O', {})
+
+
+class TagsTest(unittest.TestCase):
+    def test_get_tags_no_tags(self):
+        self.assertEqual([], symbolfile.get_tags(''))
+        self.assertEqual([], symbolfile.get_tags('foo bar baz'))
+
+    def test_get_tags(self):
+        self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar'))
+        self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz'))
+
+    def test_split_tag(self):
+        self.assertTupleEqual(('foo', 'bar'), symbolfile.split_tag('foo=bar'))
+        self.assertTupleEqual(('foo', 'bar=baz'), symbolfile.split_tag('foo=bar=baz'))
+        with self.assertRaises(ValueError):
+            symbolfile.split_tag('foo')
+
+    def test_get_tag_value(self):
+        self.assertEqual('bar', symbolfile.get_tag_value('foo=bar'))
+        self.assertEqual('bar=baz', symbolfile.get_tag_value('foo=bar=baz'))
+        with self.assertRaises(ValueError):
+            symbolfile.get_tag_value('foo')
+
+    def test_is_api_level_tag(self):
+        self.assertTrue(symbolfile.is_api_level_tag('introduced=24'))
+        self.assertTrue(symbolfile.is_api_level_tag('introduced-arm=24'))
+        self.assertTrue(symbolfile.is_api_level_tag('versioned=24'))
+
+        # Shouldn't try to process things that aren't a key/value tag.
+        self.assertFalse(symbolfile.is_api_level_tag('arm'))
+        self.assertFalse(symbolfile.is_api_level_tag('introduced'))
+        self.assertFalse(symbolfile.is_api_level_tag('versioned'))
+
+        # We don't support arch specific `versioned` tags.
+        self.assertFalse(symbolfile.is_api_level_tag('versioned-arm=24'))
+
+    def test_decode_api_level_tags(self):
+        api_map = {
+            'O': 9000,
+            'P': 9001,
+        }
+
+        tags = [
+            'introduced=9',
+            'introduced-arm=14',
+            'versioned=16',
+            'arm',
+            'introduced=O',
+            'introduced=P',
+        ]
+        expected_tags = [
+            'introduced=9',
+            'introduced-arm=14',
+            'versioned=16',
+            'arm',
+            'introduced=9000',
+            'introduced=9001',
+        ]
+        self.assertListEqual(
+            expected_tags, symbolfile.decode_api_level_tags(tags, api_map))
+
+        with self.assertRaises(symbolfile.ParseError):
+            symbolfile.decode_api_level_tags(['introduced=O'], {})
+
+
+class PrivateVersionTest(unittest.TestCase):
+    def test_version_is_private(self):
+        self.assertFalse(symbolfile.version_is_private('foo'))
+        self.assertFalse(symbolfile.version_is_private('PRIVATE'))
+        self.assertFalse(symbolfile.version_is_private('PLATFORM'))
+        self.assertFalse(symbolfile.version_is_private('foo_private'))
+        self.assertFalse(symbolfile.version_is_private('foo_platform'))
+        self.assertFalse(symbolfile.version_is_private('foo_PRIVATE_'))
+        self.assertFalse(symbolfile.version_is_private('foo_PLATFORM_'))
+
+        self.assertTrue(symbolfile.version_is_private('foo_PRIVATE'))
+        self.assertTrue(symbolfile.version_is_private('foo_PLATFORM'))
+
+
+class SymbolPresenceTest(unittest.TestCase):
+    def test_symbol_in_arch(self):
+        self.assertTrue(symbolfile.symbol_in_arch([], 'arm'))
+        self.assertTrue(symbolfile.symbol_in_arch(['arm'], 'arm'))
+
+        self.assertFalse(symbolfile.symbol_in_arch(['x86'], 'arm'))
+
+    def test_symbol_in_api(self):
+        self.assertTrue(symbolfile.symbol_in_api([], 'arm', 9))
+        self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 9))
+        self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 14))
+        self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14))
+        self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14))
+        self.assertTrue(symbolfile.symbol_in_api(['introduced-x86=14'], 'arm', 9))
+        self.assertTrue(symbolfile.symbol_in_api(
+            ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14))
+        self.assertTrue(symbolfile.symbol_in_api(
+            ['introduced=9', 'introduced-x86=21'], 'arm', 14))
+        self.assertTrue(symbolfile.symbol_in_api(
+            ['introduced=21', 'introduced-arm=9'], 'arm', 14))
+        self.assertTrue(symbolfile.symbol_in_api(
+            ['future'], 'arm', symbolfile.FUTURE_API_LEVEL))
+
+        self.assertFalse(symbolfile.symbol_in_api(['introduced=14'], 'arm', 9))
+        self.assertFalse(symbolfile.symbol_in_api(['introduced-arm=14'], 'arm', 9))
+        self.assertFalse(symbolfile.symbol_in_api(['future'], 'arm', 9))
+        self.assertFalse(symbolfile.symbol_in_api(
+            ['introduced=9', 'future'], 'arm', 14))
+        self.assertFalse(symbolfile.symbol_in_api(
+            ['introduced-arm=9', 'future'], 'arm', 14))
+        self.assertFalse(symbolfile.symbol_in_api(
+            ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14))
+        self.assertFalse(symbolfile.symbol_in_api(
+            ['introduced=9', 'introduced-arm=21'], 'arm', 14))
+        self.assertFalse(symbolfile.symbol_in_api(
+            ['introduced=21', 'introduced-x86=9'], 'arm', 14))
+
+        # Interesting edge case: this symbol should be omitted from the
+        # library, but this call should still return true because none of the
+        # tags indiciate that it's not present in this API level.
+        self.assertTrue(symbolfile.symbol_in_api(['x86'], 'arm', 9))
+
+    def test_verioned_in_api(self):
+        self.assertTrue(symbolfile.symbol_versioned_in_api([], 9))
+        self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 9))
+        self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 14))
+
+        self.assertFalse(symbolfile.symbol_versioned_in_api(['versioned=14'], 9))
+
+
+class OmitVersionTest(unittest.TestCase):
+    def test_omit_private(self):
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
+                False))
+
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo_PRIVATE', None, [], []), 'arm', 9,
+                False, False))
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo_PLATFORM', None, [], []), 'arm', 9,
+                False, False))
+
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['platform-only'], []), 'arm',
+                9, False, False))
+
+    def test_omit_llndk(self):
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9,
+                False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [], []), 'arm', 9, True,
+                False))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, True,
+                False))
+
+    def test_omit_apex(self):
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False,
+                False))
+
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
+                True))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False,
+                True))
+
+    def test_omit_arch(self):
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
+                False))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['arm'], []), 'arm', 9, False,
+                False))
+
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['x86'], []), 'arm', 9, False,
+                False))
+
+    def test_omit_api(self):
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
+                False))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['introduced=9'], []), 'arm',
+                9, False, False))
+
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, ['introduced=14'], []), 'arm',
+                9, False, False))
+
+
+class OmitSymbolTest(unittest.TestCase):
+    def test_omit_llndk(self):
+        self.assertTrue(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']),
+                                          'arm', 9, False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
+                                          9, True, False))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']),
+                                          'arm', 9, True, False))
+
+    def test_omit_apex(self):
+        self.assertTrue(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']),
+                                          'arm', 9, False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
+                                          9, False, True))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']),
+                                          'arm', 9, False, True))
+
+    def test_omit_arch(self):
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
+                                          9, False, False))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['arm']),
+                                          'arm', 9, False, False))
+
+        self.assertTrue(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['x86']),
+                                          'arm', 9, False, False))
+
+    def test_omit_api(self):
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
+                                          9, False, False))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', ['introduced=9']), 'arm', 9, False,
+                False))
+
+        self.assertTrue(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', ['introduced=14']), 'arm', 9, False,
+                False))
+
+
+class SymbolFileParseTest(unittest.TestCase):
+    def test_next_line(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            foo
+
+            bar
+            # baz
+            qux
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        self.assertIsNone(parser.current_line)
+
+        self.assertEqual('foo', parser.next_line().strip())
+        self.assertEqual('foo', parser.current_line.strip())
+
+        self.assertEqual('bar', parser.next_line().strip())
+        self.assertEqual('bar', parser.current_line.strip())
+
+        self.assertEqual('qux', parser.next_line().strip())
+        self.assertEqual('qux', parser.current_line.strip())
+
+        self.assertEqual('', parser.next_line())
+        self.assertEqual('', parser.current_line)
+
+    def test_parse_version(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 { # foo bar
+                baz;
+                qux; # woodly doodly
+            };
+
+            VERSION_2 {
+            } VERSION_1; # asdf
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+
+        parser.next_line()
+        version = parser.parse_version()
+        self.assertEqual('VERSION_1', version.name)
+        self.assertIsNone(version.base)
+        self.assertEqual(['foo', 'bar'], version.tags)
+
+        expected_symbols = [
+            symbolfile.Symbol('baz', []),
+            symbolfile.Symbol('qux', ['woodly', 'doodly']),
+        ]
+        self.assertEqual(expected_symbols, version.symbols)
+
+        parser.next_line()
+        version = parser.parse_version()
+        self.assertEqual('VERSION_2', version.name)
+        self.assertEqual('VERSION_1', version.base)
+        self.assertEqual([], version.tags)
+
+    def test_parse_version_eof(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser.next_line()
+        with self.assertRaises(symbolfile.ParseError):
+            parser.parse_version()
+
+    def test_unknown_scope_label(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                foo:
+            }
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser.next_line()
+        with self.assertRaises(symbolfile.ParseError):
+            parser.parse_version()
+
+    def test_parse_symbol(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            foo;
+            bar; # baz qux
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+
+        parser.next_line()
+        symbol = parser.parse_symbol()
+        self.assertEqual('foo', symbol.name)
+        self.assertEqual([], symbol.tags)
+
+        parser.next_line()
+        symbol = parser.parse_symbol()
+        self.assertEqual('bar', symbol.name)
+        self.assertEqual(['baz', 'qux'], symbol.tags)
+
+    def test_wildcard_symbol_global(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                *;
+            };
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser.next_line()
+        with self.assertRaises(symbolfile.ParseError):
+            parser.parse_version()
+
+    def test_wildcard_symbol_local(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                local:
+                    *;
+            };
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser.next_line()
+        version = parser.parse_version()
+        self.assertEqual([], version.symbols)
+
+    def test_missing_semicolon(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                foo
+            };
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser.next_line()
+        with self.assertRaises(symbolfile.ParseError):
+            parser.parse_version()
+
+    def test_parse_fails_invalid_input(self):
+        with self.assertRaises(symbolfile.ParseError):
+            input_file = io.StringIO('foo')
+            parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16,
+                                                 False, False)
+            parser.parse()
+
+    def test_parse(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                local:
+                    hidden1;
+                global:
+                    foo;
+                    bar; # baz
+            };
+
+            VERSION_2 { # wasd
+                # Implicit global scope.
+                    woodly;
+                    doodly; # asdf
+                local:
+                    qwerty;
+            } VERSION_1;
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        versions = parser.parse()
+
+        expected = [
+            symbolfile.Version('VERSION_1', None, [], [
+                symbolfile.Symbol('foo', []),
+                symbolfile.Symbol('bar', ['baz']),
+            ]),
+            symbolfile.Version('VERSION_2', 'VERSION_1', ['wasd'], [
+                symbolfile.Symbol('woodly', []),
+                symbolfile.Symbol('doodly', ['asdf']),
+            ]),
+        ]
+
+        self.assertEqual(expected, versions)
+
+    def test_parse_llndk_apex_symbol(self):
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                foo;
+                bar; # llndk
+                baz; # llndk apex
+                qux; # apex
+            };
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, True)
+
+        parser.next_line()
+        version = parser.parse_version()
+        self.assertEqual('VERSION_1', version.name)
+        self.assertIsNone(version.base)
+
+        expected_symbols = [
+            symbolfile.Symbol('foo', []),
+            symbolfile.Symbol('bar', ['llndk']),
+            symbolfile.Symbol('baz', ['llndk', 'apex']),
+            symbolfile.Symbol('qux', ['apex']),
+        ]
+        self.assertEqual(expected_symbols, version.symbols)
+
+
+def main():
+    suite = unittest.TestLoader().loadTestsFromName(__name__)
+    unittest.TextTestRunner(verbosity=3).run(suite)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/cc/testing.go b/cc/testing.go
index a106d46..4d0b28b 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -30,6 +30,7 @@
 	ctx.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
 	ctx.RegisterModuleType("llndk_library", LlndkLibraryFactory)
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
+	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_object", NdkPrebuiltObjectFactory)
 	ctx.RegisterModuleType("ndk_library", NdkLibraryFactory)
@@ -39,6 +40,7 @@
 	ret := `
 		toolchain_library {
 			name: "libatomic",
+			defaults: ["linux_bionic_supported"],
 			vendor_available: true,
 			recovery_available: true,
 			native_bridge_supported: true,
@@ -92,6 +94,7 @@
 
 		toolchain_library {
 			name: "libclang_rt.builtins-x86_64-android",
+			defaults: ["linux_bionic_supported"],
 			vendor_available: true,
 			recovery_available: true,
 			native_bridge_supported: true,
@@ -121,6 +124,7 @@
 
 		toolchain_library {
 			name: "libclang_rt.fuzzer-x86_64-android",
+			defaults: ["linux_bionic_supported"],
 			vendor_available: true,
 			recovery_available: true,
 			src: "",
@@ -144,6 +148,7 @@
 
 		toolchain_library {
 			name: "libgcc",
+			defaults: ["linux_bionic_supported"],
 			vendor_available: true,
 			recovery_available: true,
 			src: "",
@@ -151,6 +156,7 @@
 
 		toolchain_library {
 			name: "libgcc_stripped",
+			defaults: ["linux_bionic_supported"],
 			vendor_available: true,
 			recovery_available: true,
 			sdk_version: "current",
@@ -159,6 +165,7 @@
 
 		cc_library {
 			name: "libc",
+			defaults: ["linux_bionic_supported"],
 			no_libcrt: true,
 			nocrt: true,
 			stl: "none",
@@ -175,6 +182,7 @@
 		}
 		cc_library {
 			name: "libm",
+			defaults: ["linux_bionic_supported"],
 			no_libcrt: true,
 			nocrt: true,
 			stl: "none",
@@ -234,6 +242,7 @@
 
 		cc_library {
 			name: "libdl",
+			defaults: ["linux_bionic_supported"],
 			no_libcrt: true,
 			nocrt: true,
 			stl: "none",
@@ -326,6 +335,7 @@
 
 		cc_defaults {
 			name: "crt_defaults",
+			defaults: ["linux_bionic_supported"],
 			recovery_available: true,
 			vendor_available: true,
 			native_bridge_supported: true,
@@ -437,6 +447,7 @@
 		}
 	`
 
+	supportLinuxBionic := false
 	for _, os := range oses {
 		if os == android.Fuchsia {
 			ret += `
@@ -465,7 +476,59 @@
 		}
 		`
 		}
+		if os == android.LinuxBionic {
+			supportLinuxBionic = true
+			ret += `
+				cc_binary {
+					name: "linker",
+					defaults: ["linux_bionic_supported"],
+					recovery_available: true,
+					stl: "none",
+					nocrt: true,
+					static_executable: true,
+					native_coverage: false,
+					system_shared_libs: [],
+				}
+
+				cc_genrule {
+					name: "host_bionic_linker_flags",
+					host_supported: true,
+					device_supported: false,
+					target: {
+						host: {
+							enabled: false,
+						},
+						linux_bionic: {
+							enabled: true,
+						},
+					},
+					out: ["linker.flags"],
+				}
+
+				cc_defaults {
+					name: "linux_bionic_supported",
+					host_supported: true,
+					target: {
+						host: {
+							enabled: false,
+						},
+						linux_bionic: {
+							enabled: true,
+						},
+					},
+				}
+			`
+		}
 	}
+
+	if !supportLinuxBionic {
+		ret += `
+			cc_defaults {
+				name: "linux_bionic_supported",
+			}
+		`
+	}
+
 	return ret
 }
 
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index c965107..e485c60 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -117,7 +117,7 @@
 // Command is the type of soong_ui execution. Only one type of
 // execution is specified. The args are specific to the command.
 func main() {
-	buildStartedMilli := time.Now().UnixNano() / int64(time.Millisecond)
+	buildStarted := time.Now()
 
 	c, args := getCommand(os.Args)
 	if c == nil {
@@ -138,6 +138,7 @@
 	defer trace.Close()
 
 	met := metrics.New()
+	met.SetBuildDateTime(buildStarted)
 
 	stat := &status.Status{}
 	defer stat.Finish()
@@ -171,7 +172,7 @@
 	buildErrorFile := filepath.Join(logsDir, c.logsPrefix+"build_error")
 	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
 	soongMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_metrics")
-	defer build.UploadMetrics(buildCtx, config, c.forceDumbOutput, buildStartedMilli, buildErrorFile, rbeMetricsFile, soongMetricsFile)
+	defer build.UploadMetrics(buildCtx, config, c.forceDumbOutput, buildStarted, buildErrorFile, rbeMetricsFile, soongMetricsFile)
 
 	os.MkdirAll(logsDir, 0777)
 	log.SetOutput(filepath.Join(logsDir, c.logsPrefix+"soong.log"))
@@ -187,6 +188,7 @@
 		config.Parallel(), config.RemoteParallel(), config.HighmemParallel())
 
 	defer met.Dump(soongMetricsFile)
+	defer build.DumpRBEMetrics(buildCtx, config, rbeMetricsFile)
 
 	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
 		if !strings.HasSuffix(start, "N") {
diff --git a/docs/perf.md b/docs/perf.md
index 538adff..86a27b4 100644
--- a/docs/perf.md
+++ b/docs/perf.md
@@ -12,6 +12,41 @@
 
 ![trace example](./trace_example.png)
 
+### Critical path
+
+soong_ui logs the wall time of the longest dependency chain compared to the
+elapsed wall time in `$OUT_DIR/soong.log`.  For example:
+```
+critical path took 3m10s
+elapsed time 5m16s
+perfect parallelism ratio 60%
+critical path:
+    0:00 build out/target/product/generic_arm64/obj/FAKE/sepolicy_neverallows_intermediates/policy_2.conf
+    0:04 build out/target/product/generic_arm64/obj/FAKE/sepolicy_neverallows_intermediates/sepolicy_neverallows
+    0:13 build out/target/product/generic_arm64/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil
+    0:01 build out/target/product/generic_arm64/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil
+    0:02 build out/target/product/generic_arm64/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil
+    0:16 build out/target/product/generic_arm64/obj/ETC/sepolicy_intermediates/sepolicy
+    0:00 build out/target/product/generic_arm64/obj/ETC/plat_seapp_contexts_intermediates/plat_seapp_contexts
+    0:00 Install: out/target/product/generic_arm64/system/etc/selinux/plat_seapp_contexts
+    0:02 build out/target/product/generic_arm64/obj/NOTICE.txt
+    0:00 build out/target/product/generic_arm64/obj/NOTICE.xml.gz
+    0:00 build out/target/product/generic_arm64/system/etc/NOTICE.xml.gz
+    0:01 Installed file list: out/target/product/generic_arm64/installed-files.txt
+    1:00 Target system fs image: out/target/product/generic_arm64/obj/PACKAGING/systemimage_intermediates/system.img
+    0:01 Install system fs image: out/target/product/generic_arm64/system.img
+    0:01 Target vbmeta image: out/target/product/generic_arm64/vbmeta.img
+    1:26 Package target files: out/target/product/generic_arm64/obj/PACKAGING/target_files_intermediates/aosp_arm64-target_files-6663974.zip
+    0:01 Package: out/target/product/generic_arm64/aosp_arm64-img-6663974.zip
+    0:01 Dist: /buildbot/dist_dirs/aosp-master-linux-aosp_arm64-userdebug/6663974/aosp_arm64-img-6663974.zip
+```
+
+If the elapsed time is much longer than the critical path then additional
+parallelism on the build machine will improve total build times.  If there are
+long individual times listed in the critical path then improving build times
+for those steps or adjusting dependencies so that those steps can run earlier
+in the build graph will improve total build times.
+
 ### Soong
 
 Soong can be traced and profiled using the standard Go tools. It understands
diff --git a/java/aar.go b/java/aar.go
index 500788f..ad9b5e7 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -641,7 +641,7 @@
 var unzipAAR = pctx.AndroidStaticRule("unzipAAR",
 	blueprint.RuleParams{
 		Command: `rm -rf $outDir && mkdir -p $outDir && ` +
-			`unzip -qo -d $outDir $in && rm -rf $outDir/res && touch $out`,
+			`unzip -qoDD -d $outDir $in && rm -rf $outDir/res && touch $out`,
 	},
 	"outDir")
 
diff --git a/java/androidmk.go b/java/androidmk.go
index 618e15d..03994bf 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -131,6 +131,10 @@
 						entries.SetPath("LOCAL_SOONG_PROGUARD_DICT", library.proguardDictionary)
 					}
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem())
+
+					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveHTMLZip)
+					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveTextZip)
+					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveXMLZip)
 				},
 			},
 		}
@@ -370,9 +374,15 @@
 				entries.SetString("LOCAL_CERTIFICATE", app.certificate.AndroidMkString())
 				entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", app.getOverriddenPackages()...)
 
-				for _, jniLib := range app.installJniLibs {
-					entries.AddStrings("LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), jniLib.name)
+				if app.embeddedJniLibs {
+					jniSymbols := app.JNISymbolsInstalls(app.installPathForJNISymbols.String())
+					entries.SetString("LOCAL_SOONG_JNI_LIBS_SYMBOLS", jniSymbols.String())
+				} else {
+					for _, jniLib := range app.jniLibs {
+						entries.AddStrings("LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), jniLib.name)
+					}
 				}
+
 				if len(app.jniCoverageOutputs) > 0 {
 					entries.AddStrings("LOCAL_PREBUILT_COVERAGE_ARCHIVE", app.jniCoverageOutputs.Strings()...)
 				}
@@ -383,6 +393,10 @@
 					install := app.onDeviceDir + "/" + extra.Base()
 					entries.AddStrings("LOCAL_SOONG_BUILT_INSTALLED", extra.String()+":"+install)
 				}
+
+				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveHTMLZip)
+				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveTextZip)
+				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveXMLZip)
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
diff --git a/java/app.go b/java/app.go
index 98bce94..4031cfe 100755
--- a/java/app.go
+++ b/java/app.go
@@ -106,6 +106,10 @@
 	return as.masterFile
 }
 
+func (as *AndroidAppSet) APKCertsFile() android.Path {
+	return as.apkcertsFile
+}
+
 var TargetCpuAbi = map[string]string{
 	"arm":    "ARMEABI_V7A",
 	"arm64":  "ARM64_V8A",
@@ -288,8 +292,10 @@
 
 	overridableAppProperties overridableAppProperties
 
-	installJniLibs     []jniLib
-	jniCoverageOutputs android.Paths
+	jniLibs                  []jniLib
+	installPathForJNISymbols android.Path
+	embeddedJniLibs          bool
+	jniCoverageOutputs       android.Paths
 
 	bundleFile android.Path
 
@@ -566,8 +572,7 @@
 	a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, a.proguardOptionsFile)
 }
 
-func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
-
+func (a *AndroidApp) installPath(ctx android.ModuleContext) android.InstallPath {
 	var installDir string
 	if ctx.ModuleName() == "framework-res" {
 		// framework-res.apk is installed as system/framework/framework-res.apk
@@ -577,7 +582,12 @@
 	} else {
 		installDir = filepath.Join("app", a.installApkName)
 	}
-	a.dexpreopter.installPath = android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
+
+	return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
+}
+
+func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
+	a.dexpreopter.installPath = a.installPath(ctx)
 	if a.deviceProperties.Uncompress_dex == nil {
 		// If the value was not force-set by the user, use reasonable default based on the module.
 		a.deviceProperties.Uncompress_dex = proptools.BoolPtr(a.shouldUncompressDex(ctx))
@@ -599,8 +609,10 @@
 func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext) android.WritablePath {
 	var jniJarFile android.WritablePath
 	if len(jniLibs) > 0 {
+		a.jniLibs = jniLibs
 		if a.shouldEmbedJnis(ctx) {
 			jniJarFile = android.PathForModuleOut(ctx, "jnilibs.zip")
+			a.installPathForJNISymbols = a.installPath(ctx).ToMakePath()
 			TransformJniLibsToJar(ctx, jniJarFile, jniLibs, a.useEmbeddedNativeLibs(ctx))
 			for _, jni := range jniLibs {
 				if jni.coverageFile.Valid() {
@@ -618,13 +630,25 @@
 					}
 				}
 			}
-		} else {
-			a.installJniLibs = jniLibs
+			a.embeddedJniLibs = true
 		}
 	}
 	return jniJarFile
 }
 
+func (a *AndroidApp) JNISymbolsInstalls(installPath string) android.RuleBuilderInstalls {
+	var jniSymbols android.RuleBuilderInstalls
+	for _, jniLib := range a.jniLibs {
+		if jniLib.unstrippedFile != nil {
+			jniSymbols = append(jniSymbols, android.RuleBuilderInstall{
+				From: jniLib.unstrippedFile,
+				To:   filepath.Join(installPath, targetToJniDir(jniLib.target), jniLib.unstrippedFile.Base()),
+			})
+		}
+	}
+	return jniSymbols
+}
+
 func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext) {
 	// Collect NOTICE files from all dependencies.
 	seenModules := make(map[android.Module]bool)
@@ -752,6 +776,7 @@
 	a.linter.mergedManifest = a.aapt.mergedManifestFile
 	a.linter.manifest = a.aapt.manifestPath
 	a.linter.resources = a.aapt.resourceFiles
+	a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps()
 
 	dexJarFile := a.dexBuildActions(ctx)
 
@@ -840,10 +865,11 @@
 
 				if lib.Valid() {
 					jniLibs = append(jniLibs, jniLib{
-						name:         ctx.OtherModuleName(module),
-						path:         path,
-						target:       module.Target(),
-						coverageFile: dep.CoverageOutputFile(),
+						name:           ctx.OtherModuleName(module),
+						path:           path,
+						target:         module.Target(),
+						coverageFile:   dep.CoverageOutputFile(),
+						unstrippedFile: dep.UnstrippedOutputFile(),
 					})
 				} else {
 					ctx.ModuleErrorf("dependency %q missing output file", otherName)
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 99bfb6d..190a052 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -112,13 +112,20 @@
 	// local files that are used within user customized droiddoc options.
 	Arg_files []string `android:"path"`
 
-	// user customized droiddoc args.
+	// user customized droiddoc args. Deprecated, use flags instead.
 	// Available variables for substitution:
 	//
 	//  $(location <label>): the path to the arg_files with name <label>
 	//  $$: a literal $
 	Args *string
 
+	// user customized droiddoc args. Not compatible with property args.
+	// Available variables for substitution:
+	//
+	//  $(location <label>): the path to the arg_files with name <label>
+	//  $$: a literal $
+	Flags []string
+
 	// names of the output files used in args that will be generated
 	Out []string
 
@@ -382,7 +389,7 @@
 	argFiles    android.Paths
 	implicits   android.Paths
 
-	args string
+	args []string
 
 	docZip      android.WritablePath
 	stubsSrcJar android.WritablePath
@@ -619,8 +626,8 @@
 	}
 	srcFiles = filterHtml(srcFiles)
 
-	flags := j.collectAidlFlags(ctx, deps)
-	srcFiles = j.genSources(ctx, srcFiles, flags)
+	aidlFlags := j.collectAidlFlags(ctx, deps)
+	srcFiles = j.genSources(ctx, srcFiles, aidlFlags)
 
 	// srcs may depend on some genrule output.
 	j.srcJars = srcFiles.FilterByExt(".srcjar")
@@ -649,24 +656,38 @@
 		}
 	}
 
-	var err error
-	j.args, err = android.Expand(String(j.properties.Args), func(name string) (string, error) {
-		if strings.HasPrefix(name, "location ") {
-			label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
-			if paths, ok := argFilesMap[label]; ok {
-				return paths, nil
-			} else {
-				return "", fmt.Errorf("unknown location label %q, expecting one of %q",
-					label, strings.Join(argFileLabels, ", "))
-			}
-		} else if name == "genDir" {
-			return android.PathForModuleGen(ctx).String(), nil
-		}
-		return "", fmt.Errorf("unknown variable '$(%s)'", name)
-	})
+	var argsPropertyName string
+	flags := make([]string, 0)
+	if j.properties.Args != nil && j.properties.Flags != nil {
+		ctx.PropertyErrorf("args", "flags is set. Cannot set args")
+	} else if args := proptools.String(j.properties.Args); args != "" {
+		flags = append(flags, args)
+		argsPropertyName = "args"
+	} else {
+		flags = append(flags, j.properties.Flags...)
+		argsPropertyName = "flags"
+	}
 
-	if err != nil {
-		ctx.PropertyErrorf("args", "%s", err.Error())
+	for _, flag := range flags {
+		args, err := android.Expand(flag, func(name string) (string, error) {
+			if strings.HasPrefix(name, "location ") {
+				label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
+				if paths, ok := argFilesMap[label]; ok {
+					return paths, nil
+				} else {
+					return "", fmt.Errorf("unknown location label %q, expecting one of %q",
+						label, strings.Join(argFileLabels, ", "))
+				}
+			} else if name == "genDir" {
+				return android.PathForModuleGen(ctx).String(), nil
+			}
+			return "", fmt.Errorf("unknown variable '$(%s)'", name)
+		})
+
+		if err != nil {
+			ctx.PropertyErrorf(argsPropertyName, "%s", err.Error())
+		}
+		j.args = append(j.args, args)
 	}
 
 	return deps
@@ -1010,7 +1031,7 @@
 
 	d.stubsFlags(ctx, cmd, stubsDir)
 
-	cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles)
+	cmd.Flag(strings.Join(d.Javadoc.args, " ")).Implicits(d.Javadoc.argFiles)
 
 	if d.properties.Compat_config != nil {
 		compatConfig := android.PathForModuleSrc(ctx, String(d.properties.Compat_config))
@@ -1327,7 +1348,7 @@
 		cmd.Flag("--include-annotations")
 
 		validatingNullability :=
-			strings.Contains(d.Javadoc.args, "--validate-nullability-from-merged-stubs") ||
+			android.InList("--validate-nullability-from-merged-stubs", d.Javadoc.args) ||
 				String(d.properties.Validate_nullability_from_list) != ""
 
 		migratingNullability := String(d.properties.Previous_api) != ""
@@ -1498,7 +1519,9 @@
 	cmd.Flag("--no-banner").
 		Flag("--color").
 		Flag("--quiet").
-		Flag("--format=v2")
+		Flag("--format=v2").
+		FlagWithArg("--repeat-errors-max ", "10").
+		FlagWithArg("--hide ", "UnresolvedImport")
 
 	return cmd
 }
@@ -1539,14 +1562,14 @@
 	d.apiLevelsAnnotationsFlags(ctx, cmd)
 	d.apiToXmlFlags(ctx, cmd)
 
-	if strings.Contains(d.Javadoc.args, "--generate-documentation") {
+	if android.InList("--generate-documentation", d.Javadoc.args) {
 		// Currently Metalava have the ability to invoke Javadoc in a seperate process.
 		// Pass "-nodocs" to suppress the Javadoc invocation when Metalava receives
 		// "--generate-documentation" arg. This is not needed when Metalava removes this feature.
-		d.Javadoc.args = d.Javadoc.args + " -nodocs "
+		d.Javadoc.args = append(d.Javadoc.args, "-nodocs")
 	}
 
-	cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles)
+	cmd.Flag(strings.Join(d.Javadoc.args, " ")).Implicits(d.Javadoc.argFiles)
 	for _, o := range d.Javadoc.properties.Out {
 		cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
 	}
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 130b634..b5a0217 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -146,7 +146,7 @@
 
 var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", blueprint.RuleParams{
 	Command: `rm -rf $tmpDir && mkdir -p $tmpDir && mkdir $tmpDir/dex-input && mkdir $tmpDir/dex-output &&
-		unzip -o -q $in 'classes*.dex' -d $tmpDir/dex-input &&
+		unzip -qoDD $in 'classes*.dex' -d $tmpDir/dex-input &&
 		for INPUT_DEX in $$(find $tmpDir/dex-input -maxdepth 1 -name 'classes*.dex' | sort); do
 		  echo "--input-dex=$${INPUT_DEX}";
 		  echo "--output-dex=$tmpDir/dex-output/$$(basename $${INPUT_DEX})";
diff --git a/java/java.go b/java/java.go
index 46ef98b..367b09c 100644
--- a/java/java.go
+++ b/java/java.go
@@ -635,10 +635,11 @@
 }
 
 type jniLib struct {
-	name         string
-	path         android.Path
-	target       android.Target
-	coverageFile android.OptionalPath
+	name           string
+	path           android.Path
+	target         android.Target
+	coverageFile   android.OptionalPath
+	unstrippedFile android.Path
 }
 
 func (j *Module) shouldInstrument(ctx android.BaseModuleContext) bool {
diff --git a/java/java_test.go b/java/java_test.go
index def42db..db3f187 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -25,7 +25,6 @@
 	"strings"
 	"testing"
 
-	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -86,6 +85,7 @@
 	RegisterStubsBuildComponents(ctx)
 	RegisterSdkLibraryBuildComponents(ctx)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	ctx.PreArchMutators(android.RegisterComponentsMutator)
 
 	RegisterPrebuiltApisBuildComponents(ctx)
 
@@ -172,20 +172,6 @@
 	}
 }
 
-func checkModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) {
-	t.Helper()
-	module := ctx.ModuleForTests(name, variant).Module()
-	deps := []string{}
-	ctx.VisitDirectDeps(module, func(m blueprint.Module) {
-		deps = append(deps, m.Name())
-	})
-	sort.Strings(deps)
-
-	if actual := deps; !reflect.DeepEqual(expected, actual) {
-		t.Errorf("expected %#q, found %#q", expected, actual)
-	}
-}
-
 func TestJavaLinkType(t *testing.T) {
 	testJava(t, `
 		java_library {
@@ -646,7 +632,7 @@
 		}
 	}
 
-	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
 		`prebuilt_sdklib.stubs`,
 		`prebuilt_sdklib.stubs.source.test`,
 		`prebuilt_sdklib.stubs.system`,
@@ -674,7 +660,7 @@
 		}
 		`)
 
-	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
 		`dex2oatd`,
 		`prebuilt_sdklib`,
 		`sdklib.impl`,
@@ -683,12 +669,12 @@
 		`sdklib.xml`,
 	})
 
-	checkModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
+	CheckModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
 		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
 		// dependency is added after prebuilts may have been renamed and so has to use
 		// the renamed name.
-		`sdklib.stubs`,
 		`sdklib.xml`,
 	})
 }
@@ -714,17 +700,16 @@
 		}
 		`)
 
-	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
 		`dex2oatd`,
 		`prebuilt_sdklib`,
-		// This should be sdklib.stubs but is switched to the prebuilt because it is preferred.
-		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
+		`sdklib.stubs`,
 		`sdklib.stubs.source`,
 		`sdklib.xml`,
 	})
 
-	checkModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
+	CheckModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
 		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
 		`sdklib.xml`,
@@ -1112,7 +1097,7 @@
 		    ],
 		    proofread_file: "libcore-proofread.txt",
 		    todo_file: "libcore-docs-todo.html",
-		    args: "-offlinemode -title \"libcore\"",
+		    flags: ["-offlinemode -title \"libcore\""],
 		}
 		`,
 		map[string][]byte{
@@ -1139,6 +1124,42 @@
 	}
 }
 
+func TestDroiddocArgsAndFlagsCausesError(t *testing.T) {
+	testJavaError(t, "flags is set. Cannot set args", `
+		droiddoc_exported_dir {
+		    name: "droiddoc-templates-sdk",
+		    path: ".",
+		}
+		filegroup {
+		    name: "bar-doc-aidl-srcs",
+		    srcs: ["bar-doc/IBar.aidl"],
+		    path: "bar-doc",
+		}
+		droiddoc {
+		    name: "bar-doc",
+		    srcs: [
+		        "bar-doc/a.java",
+		        "bar-doc/IFoo.aidl",
+		        ":bar-doc-aidl-srcs",
+		    ],
+		    exclude_srcs: [
+		        "bar-doc/b.java"
+		    ],
+		    custom_template: "droiddoc-templates-sdk",
+		    hdf: [
+		        "android.whichdoc offline",
+		    ],
+		    knowntags: [
+		        "bar-doc/known_oj_tags.txt",
+		    ],
+		    proofread_file: "libcore-proofread.txt",
+		    todo_file: "libcore-docs-todo.html",
+		    flags: ["-offlinemode -title \"libcore\""],
+		    args: "-offlinemode -title \"libcore\"",
+		}
+		`)
+}
+
 func TestDroidstubsWithSystemModules(t *testing.T) {
 	ctx, _ := testJava(t, `
 		droidstubs {
@@ -1491,7 +1512,7 @@
 		}
 		`)
 
-	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
 		`dex2oatd`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
diff --git a/java/lint.go b/java/lint.go
index b73d6a5..20a7dc4 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -67,12 +67,32 @@
 	kotlinLanguageLevel string
 	outputs             lintOutputs
 	properties          LintProperties
+
+	buildModuleReportZip bool
 }
 
 type lintOutputs struct {
 	html android.ModuleOutPath
 	text android.ModuleOutPath
 	xml  android.ModuleOutPath
+
+	transitiveHTML *android.DepSet
+	transitiveText *android.DepSet
+	transitiveXML  *android.DepSet
+
+	transitiveHTMLZip android.OptionalPath
+	transitiveTextZip android.OptionalPath
+	transitiveXMLZip  android.OptionalPath
+}
+
+type lintOutputIntf interface {
+	lintOutputs() *lintOutputs
+}
+
+var _ lintOutputIntf = (*linter)(nil)
+
+func (l *linter) lintOutputs() *lintOutputs {
+	return &l.outputs
 }
 
 func (l *linter) enabled() bool {
@@ -213,27 +233,49 @@
 
 	projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
 
-	l.outputs.html = android.PathForModuleOut(ctx, "lint-report.html")
-	l.outputs.text = android.PathForModuleOut(ctx, "lint-report.txt")
-	l.outputs.xml = android.PathForModuleOut(ctx, "lint-report.xml")
+	html := android.PathForModuleOut(ctx, "lint-report.html")
+	text := android.PathForModuleOut(ctx, "lint-report.txt")
+	xml := android.PathForModuleOut(ctx, "lint-report.xml")
+
+	htmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(html)
+	textDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(text)
+	xmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(xml)
+
+	ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
+		if depLint, ok := dep.(lintOutputIntf); ok {
+			depLintOutputs := depLint.lintOutputs()
+			htmlDeps.Transitive(depLintOutputs.transitiveHTML)
+			textDeps.Transitive(depLintOutputs.transitiveText)
+			xmlDeps.Transitive(depLintOutputs.transitiveXML)
+		}
+	})
 
 	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
 
+	var annotationsZipPath, apiVersionsXMLPath android.Path
+	if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
+		annotationsZipPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/annotations.zip")
+		apiVersionsXMLPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/api-versions.xml")
+	} else {
+		annotationsZipPath = copiedAnnotationsZipPath(ctx)
+		apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx)
+	}
+
 	rule.Command().
 		Text("(").
 		Flag("JAVA_OPTS=-Xmx2048m").
 		FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
-		FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath(ctx)).
-		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXmlPath(ctx)).
+		FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
+		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath).
 		Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
 		Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
 		Flag("--quiet").
 		FlagWithInput("--project ", projectXML).
 		FlagWithInput("--config ", lintXML).
-		FlagWithOutput("--html ", l.outputs.html).
-		FlagWithOutput("--text ", l.outputs.text).
-		FlagWithOutput("--xml ", l.outputs.xml).
+		FlagWithOutput("--html ", html).
+		FlagWithOutput("--text ", text).
+		FlagWithOutput("--xml ", xml).
 		FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
 		FlagWithArg("--java-language-level ", l.javaLanguageLevel).
 		FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
@@ -241,23 +283,37 @@
 		Flag("--exitcode").
 		Flags(l.properties.Lint.Flags).
 		Implicits(deps).
-		Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
+		Text("|| (").Text("cat").Input(text).Text("; exit 7)").
 		Text(")")
 
 	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
 
 	rule.Build(pctx, ctx, "lint", "lint")
-}
 
-func (l *linter) lintOutputs() *lintOutputs {
-	return &l.outputs
-}
+	l.outputs = lintOutputs{
+		html: html,
+		text: text,
+		xml:  xml,
 
-type lintOutputIntf interface {
-	lintOutputs() *lintOutputs
-}
+		transitiveHTML: htmlDeps.Build(),
+		transitiveText: textDeps.Build(),
+		transitiveXML:  xmlDeps.Build(),
+	}
 
-var _ lintOutputIntf = (*linter)(nil)
+	if l.buildModuleReportZip {
+		htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
+		l.outputs.transitiveHTMLZip = android.OptionalPathForPath(htmlZip)
+		lintZip(ctx, l.outputs.transitiveHTML.ToSortedList(), htmlZip)
+
+		textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
+		l.outputs.transitiveTextZip = android.OptionalPathForPath(textZip)
+		lintZip(ctx, l.outputs.transitiveText.ToSortedList(), textZip)
+
+		xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
+		l.outputs.transitiveXMLZip = android.OptionalPathForPath(xmlZip)
+		lintZip(ctx, l.outputs.transitiveXML.ToSortedList(), xmlZip)
+	}
+}
 
 type lintSingleton struct {
 	htmlZip android.WritablePath
@@ -271,7 +327,7 @@
 }
 
 func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
-	if ctx.Config().UnbundledBuild() {
+	if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
 		return
 	}
 
@@ -297,25 +353,29 @@
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Cp,
 		Input:  android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
-		Output: annotationsZipPath(ctx),
+		Output: copiedAnnotationsZipPath(ctx),
 	})
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Cp,
 		Input:  android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
-		Output: apiVersionsXmlPath(ctx),
+		Output: copiedAPIVersionsXmlPath(ctx),
 	})
 }
 
-func annotationsZipPath(ctx android.PathContext) android.WritablePath {
+func copiedAnnotationsZipPath(ctx android.PathContext) android.WritablePath {
 	return android.PathForOutput(ctx, "lint", "annotations.zip")
 }
 
-func apiVersionsXmlPath(ctx android.PathContext) android.WritablePath {
+func copiedAPIVersionsXmlPath(ctx android.PathContext) android.WritablePath {
 	return android.PathForOutput(ctx, "lint", "api_versions.xml")
 }
 
 func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
+	if ctx.Config().UnbundledBuild() {
+		return
+	}
+
 	var outputs []*lintOutputs
 	var dirs []string
 	ctx.VisitAllModules(func(m android.Module) {
@@ -343,18 +403,7 @@
 			paths = append(paths, get(output))
 		}
 
-		sort.Slice(paths, func(i, j int) bool {
-			return paths[i].String() < paths[j].String()
-		})
-
-		rule := android.NewRuleBuilder()
-
-		rule.Command().BuiltTool(ctx, "soong_zip").
-			FlagWithOutput("-o ", outputPath).
-			FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
-			FlagWithRspFileInputList("-l ", paths)
-
-		rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
+		lintZip(ctx, paths, outputPath)
 	}
 
 	l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
@@ -370,7 +419,9 @@
 }
 
 func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
+	if !ctx.Config().UnbundledBuild() {
+		ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
+	}
 }
 
 var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
@@ -379,3 +430,20 @@
 	android.RegisterSingletonType("lint",
 		func() android.Singleton { return &lintSingleton{} })
 }
+
+func lintZip(ctx android.BuilderContext, paths android.Paths, outputPath android.WritablePath) {
+	paths = android.SortedUniquePaths(android.CopyOfPaths(paths))
+
+	sort.Slice(paths, func(i, j int) bool {
+		return paths[i].String() < paths[j].String()
+	})
+
+	rule := android.NewRuleBuilder()
+
+	rule.Command().BuiltTool(ctx, "soong_zip").
+		FlagWithOutput("-o ", outputPath).
+		FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
+		FlagWithRspFileInputList("-l ", paths)
+
+	rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
+}
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index 999c72f..b10e6c7 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -83,8 +83,7 @@
 	}{}
 	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, module, scope, apiver))
 	props.Jars = append(props.Jars, path)
-	// TODO(hansson): change to scope after migration is done.
-	props.Sdk_version = proptools.StringPtr("current")
+	props.Sdk_version = proptools.StringPtr(scope)
 	props.Installable = proptools.BoolPtr(false)
 
 	mctx.CreateModule(ImportFactory, &props)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 8f8f8ce..58e05e5 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -70,6 +70,12 @@
 	}
 }
 
+var _ android.ReplaceSourceWithPrebuilt = (*scopeDependencyTag)(nil)
+
+func (tag scopeDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
+}
+
 // Provides information about an api scope, e.g. public, system, test.
 type apiScope struct {
 	// The name of the api scope, e.g. public, system, test
@@ -137,6 +143,7 @@
 	droidstubsArgsForGeneratingApi []string
 
 	// True if the stubs source and api can be created by the same metalava invocation.
+	// TODO(b/146727827) Now that metalava supports "API hierarchy", do we still need it?
 	createStubsSourceAndApiTogether bool
 
 	// Whether the api scope can be treated as unstable, and should skip compat checks.
@@ -278,6 +285,7 @@
 		sdkVersion:    "module_current",
 		droidstubsArgs: []string{
 			"--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
+			"--show-for-stub-purposes-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
 		},
 	})
 	apiScopeSystemServer = initApiScope(&apiScope{
@@ -973,7 +981,8 @@
 
 var implLibraryTag = sdkLibraryComponentTag{name: "impl-library"}
 
-func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
+// Add the dependencies on the child modules in the component deps mutator.
+func (module *SdkLibrary) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
 	for _, apiScope := range module.getGeneratedApiScopes(ctx) {
 		// Add dependencies to the stubs library
 		ctx.AddVariationDependencies(nil, apiScope.stubsTag, module.stubsLibraryModuleName(apiScope))
@@ -998,7 +1007,12 @@
 			// Add dependency to the rule for generating the xml permissions file
 			ctx.AddDependency(module, xmlPermissionsFileTag, module.xmlPermissionsModuleName())
 		}
+	}
+}
 
+// Add other dependencies as normal.
+func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
+	if module.requiresRuntimeImplementationLibrary() {
 		// Only add the deps for the library if it is actually going to be built.
 		module.Library.deps(ctx)
 	}
@@ -1874,20 +1888,26 @@
 	props.Prefer = proptools.BoolPtr(module.prebuilt.Prefer())
 }
 
-func (module *SdkLibraryImport) DepsMutator(ctx android.BottomUpMutatorContext) {
+// Add the dependencies on the child module in the component deps mutator so that it
+// creates references to the prebuilt and not the source modules.
+func (module *SdkLibraryImport) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
 	for apiScope, scopeProperties := range module.scopeProperties {
 		if len(scopeProperties.Jars) == 0 {
 			continue
 		}
 
 		// Add dependencies to the prebuilt stubs library
-		ctx.AddVariationDependencies(nil, apiScope.stubsTag, module.stubsLibraryModuleName(apiScope))
+		ctx.AddVariationDependencies(nil, apiScope.stubsTag, "prebuilt_"+module.stubsLibraryModuleName(apiScope))
 
 		if len(scopeProperties.Stub_srcs) > 0 {
 			// Add dependencies to the prebuilt stubs source library
-			ctx.AddVariationDependencies(nil, apiScope.stubsSourceTag, module.stubsSourceModuleName(apiScope))
+			ctx.AddVariationDependencies(nil, apiScope.stubsSourceTag, "prebuilt_"+module.stubsSourceModuleName(apiScope))
 		}
 	}
+}
+
+// Add other dependencies as normal.
+func (module *SdkLibraryImport) DepsMutator(ctx android.BottomUpMutatorContext) {
 
 	implName := module.implLibraryModuleName()
 	if ctx.OtherModuleExists(implName) {
diff --git a/java/testing.go b/java/testing.go
index f5688e6..94f054e 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -16,9 +16,13 @@
 
 import (
 	"fmt"
+	"reflect"
+	"sort"
+	"testing"
 
 	"android/soong/android"
 	"android/soong/cc"
+	"github.com/google/blueprint"
 )
 
 func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) android.Config {
@@ -216,3 +220,17 @@
 
 	return bp
 }
+
+func CheckModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) {
+	t.Helper()
+	module := ctx.ModuleForTests(name, variant).Module()
+	deps := []string{}
+	ctx.VisitDirectDeps(module, func(m blueprint.Module) {
+		deps = append(deps, m.Name())
+	})
+	sort.Strings(deps)
+
+	if actual := deps; !reflect.DeepEqual(expected, actual) {
+		t.Errorf("expected %#q, found %#q", expected, actual)
+	}
+}
diff --git a/rust/binary.go b/rust/binary.go
index a1cd410..9fc52cd 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -106,7 +106,8 @@
 func (binary *binaryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
 	fileName := binary.getStem(ctx) + ctx.toolchain().ExecutableSuffix()
 
-	srcPath := srcPathFromModuleSrcs(ctx, binary.baseCompiler.Properties.Srcs)
+	srcPath, paths := srcPathFromModuleSrcs(ctx, binary.baseCompiler.Properties.Srcs)
+	deps.SrcDeps = paths
 
 	outputFile := android.PathForModuleOut(ctx, fileName)
 	binary.unstrippedOutputFile = outputFile
diff --git a/rust/builder.go b/rust/builder.go
index 16d7306..7f94bb5 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -166,6 +166,7 @@
 	implicits = append(implicits, rustLibsToPaths(deps.ProcMacros)...)
 	implicits = append(implicits, deps.StaticLibs...)
 	implicits = append(implicits, deps.SharedLibs...)
+	implicits = append(implicits, deps.SrcDeps...)
 	if deps.CrtBegin.Valid() {
 		implicits = append(implicits, deps.CrtBegin.Path(), deps.CrtEnd.Path())
 	}
diff --git a/rust/compiler.go b/rust/compiler.go
index 92a3b07..c20179b 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -253,10 +253,24 @@
 	return String(compiler.Properties.Relative_install_path)
 }
 
-func srcPathFromModuleSrcs(ctx ModuleContext, srcs []string) android.Path {
-	srcPaths := android.PathsForModuleSrc(ctx, srcs)
-	if len(srcPaths) != 1 {
-		ctx.PropertyErrorf("srcs", "srcs can only contain one path for rust modules")
+func srcPathFromModuleSrcs(ctx ModuleContext, srcs []string) (android.Path, android.Paths) {
+	// The srcs can contain strings with prefix ":".
+	// They are dependent modules of this module, with android.SourceDepTag.
+	// They are not the main source file compiled by rustc.
+	numSrcs := 0
+	srcIndex := 0
+	for i, s := range srcs {
+		if android.SrcIsModule(s) == "" {
+			numSrcs++
+			srcIndex = i
+		}
 	}
-	return srcPaths[0]
+	if numSrcs != 1 {
+		ctx.PropertyErrorf("srcs", "srcs can only contain one path for a rust file")
+	}
+	if srcIndex != 0 {
+		ctx.PropertyErrorf("srcs", "main source file must be the first in srcs")
+	}
+	paths := android.PathsForModuleSrc(ctx, srcs)
+	return paths[srcIndex], paths
 }
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index bcde757..58ca52a 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -43,7 +43,7 @@
 // Test that we reject multiple source files.
 func TestEnforceSingleSourceFile(t *testing.T) {
 
-	singleSrcError := "srcs can only contain one path for rust modules"
+	singleSrcError := "srcs can only contain one path for a rust file"
 
 	// Test libraries
 	testRustError(t, singleSrcError, `
diff --git a/rust/config/global.go b/rust/config/global.go
index e1b1775..2020f46 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -54,6 +54,9 @@
 		"-Wl,--pack-dyn-relocs=android+relr",
 		"-Wl,--no-undefined",
 		"-Wl,--hash-style=gnu",
+
+		"-B${ccConfig.ClangBin}",
+		"-fuse-ld=lld",
 	}
 )
 
@@ -80,7 +83,7 @@
 
 	pctx.ImportAs("ccConfig", "android/soong/cc/config")
 	pctx.StaticVariable("RustLinker", "${ccConfig.ClangBin}/clang++")
-	pctx.StaticVariable("RustLinkerArgs", "-B ${ccConfig.ClangBin} -fuse-ld=lld")
+	pctx.StaticVariable("RustLinkerArgs", "")
 
 	pctx.StaticVariable("DeviceGlobalLinkFlags", strings.Join(deviceGlobalLinkFlags, " "))
 
diff --git a/rust/config/x86_darwin_host.go b/rust/config/x86_darwin_host.go
index 4c16693..4104400 100644
--- a/rust/config/x86_darwin_host.go
+++ b/rust/config/x86_darwin_host.go
@@ -21,8 +21,10 @@
 )
 
 var (
-	DarwinRustFlags      = []string{}
-	DarwinRustLinkFlags  = []string{}
+	DarwinRustFlags     = []string{}
+	DarwinRustLinkFlags = []string{
+		"-B${ccConfig.MacToolPath}",
+	}
 	darwinX8664Rustflags = []string{}
 	darwinX8664Linkflags = []string{}
 )
@@ -66,6 +68,10 @@
 	return ".dylib"
 }
 
+func (t *toolchainDarwin) DylibSuffix() string {
+	return ".rustlib.dylib"
+}
+
 func (t *toolchainDarwin) ProcMacroSuffix() string {
 	return ".dylib"
 }
diff --git a/rust/config/x86_linux_host.go b/rust/config/x86_linux_host.go
index 5376e5b..acc99e1 100644
--- a/rust/config/x86_linux_host.go
+++ b/rust/config/x86_linux_host.go
@@ -21,8 +21,11 @@
 )
 
 var (
-	LinuxRustFlags      = []string{}
-	LinuxRustLinkFlags  = []string{}
+	LinuxRustFlags     = []string{}
+	LinuxRustLinkFlags = []string{
+		"-B${ccConfig.ClangBin}",
+		"-fuse-ld=lld",
+	}
 	linuxX86Rustflags   = []string{}
 	linuxX86Linkflags   = []string{}
 	linuxX8664Rustflags = []string{}
diff --git a/rust/library.go b/rust/library.go
index 8b8e797..d718eb8 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -368,7 +368,8 @@
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
 	var outputFile android.WritablePath
 
-	srcPath := srcPathFromModuleSrcs(ctx, library.baseCompiler.Properties.Srcs)
+	srcPath, paths := srcPathFromModuleSrcs(ctx, library.baseCompiler.Properties.Srcs)
+	deps.SrcDeps = paths
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 67d649d..3b4f40a 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -95,7 +95,8 @@
 func (prebuilt *prebuiltLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
 	prebuilt.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
 
-	srcPath := srcPathFromModuleSrcs(ctx, prebuilt.prebuiltSrcs())
+	srcPath, paths := srcPathFromModuleSrcs(ctx, prebuilt.prebuiltSrcs())
+	deps.SrcDeps = paths
 
 	prebuilt.unstrippedOutputFile = srcPath
 
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 2719161..49dbd8d 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -65,7 +65,8 @@
 	fileName := procMacro.getStem(ctx) + ctx.toolchain().ProcMacroSuffix()
 	outputFile := android.PathForModuleOut(ctx, fileName)
 
-	srcPath := srcPathFromModuleSrcs(ctx, procMacro.baseCompiler.Properties.Srcs)
+	srcPath, paths := srcPathFromModuleSrcs(ctx, procMacro.baseCompiler.Properties.Srcs)
+	deps.SrcDeps = paths
 
 	procMacro.unstrippedOutputFile = outputFile
 
diff --git a/rust/rust.go b/rust/rust.go
index 72301a7..7a98c64 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -239,6 +239,9 @@
 
 	CrtBegin android.OptionalPath
 	CrtEnd   android.OptionalPath
+
+	// Paths to generated source files
+	SrcDeps android.Paths
 }
 
 type RustLibraries []RustLibrary
@@ -843,6 +846,7 @@
 	// Dedup exported flags from dependencies
 	depPaths.linkDirs = android.FirstUniqueStrings(depPaths.linkDirs)
 	depPaths.depFlags = android.FirstUniqueStrings(depPaths.depFlags)
+	depPaths.SrcDeps = android.FirstUniquePaths(depPaths.SrcDeps)
 
 	return depPaths
 }
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 08bc8ca..e803925 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -61,6 +61,7 @@
 		"foo.rs":     nil,
 		"foo.c":      nil,
 		"src/bar.rs": nil,
+		"src/any.h":  nil,
 		"liby.so":    nil,
 		"libz.so":    nil,
 	}
@@ -181,7 +182,7 @@
 		}
 		rust_library_host_rlib {
 			name: "librlib",
-			srcs: ["foo.rs"],
+			srcs: ["foo.rs", ":my_generator"],
 			crate_name: "rlib",
 		}
 		rust_proc_macro {
@@ -189,17 +190,38 @@
 			srcs: ["foo.rs"],
 			crate_name: "pm",
 		}
+		genrule {
+			name: "my_generator",
+			tools: ["any_rust_binary"],
+			cmd: "$(location) -o $(out) $(in)",
+			srcs: ["src/any.h"],
+			out: ["src/any.rs"],
+		}
 		rust_binary_host {
-			name: "fizz-buzz",
+			name: "fizz-buzz-dep",
 			dylibs: ["libdylib"],
 			rlibs: ["librlib"],
 			proc_macros: ["libpm"],
 			static_libs: ["libstatic"],
 			shared_libs: ["libshared"],
-			srcs: ["foo.rs"],
+			srcs: [
+				"foo.rs",
+				":my_generator",
+			],
 		}
 	`)
-	module := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
+	module := ctx.ModuleForTests("fizz-buzz-dep", "linux_glibc_x86_64").Module().(*Module)
+	rlibmodule := ctx.ModuleForTests("librlib", "linux_glibc_x86_64_rlib").Module().(*Module)
+
+	srcs := module.compiler.(*binaryDecorator).baseCompiler.Properties.Srcs
+	if len(srcs) != 2 || !android.InList(":my_generator", srcs) {
+		t.Errorf("missing module dependency in fizz-buzz)")
+	}
+
+	srcs = rlibmodule.compiler.(*libraryDecorator).baseCompiler.Properties.Srcs
+	if len(srcs) != 2 || !android.InList(":my_generator", srcs) {
+		t.Errorf("missing module dependency in rlib")
+	}
 
 	// Since dependencies are added to AndroidMk* properties, we can check these to see if they've been picked up.
 	if !android.InList("libdylib", module.Properties.AndroidMkDylibs) {
diff --git a/rust/testing.go b/rust/testing.go
index 3d583e1..430b40b 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -17,6 +17,7 @@
 import (
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/genrule"
 )
 
 func GatherRequiredDepsForTest() string {
@@ -77,6 +78,7 @@
 func CreateTestContext() *android.TestContext {
 	ctx := android.NewTestArchContext()
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
+	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
 	ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
 	ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
 	ctx.RegisterModuleType("rust_test", RustTestFactory)
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 123fe70..497f14b 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -21,20 +21,20 @@
 	"android/soong/cc"
 )
 
+var ccTestFs = map[string][]byte{
+	"Test.cpp":                      nil,
+	"include/Test.h":                nil,
+	"include-android/AndroidTest.h": nil,
+	"include-host/HostTest.h":       nil,
+	"arm64/include/Arm64Test.h":     nil,
+	"libfoo.so":                     nil,
+	"aidl/foo/bar/Test.aidl":        nil,
+	"some/where/stubslib.map.txt":   nil,
+}
+
 func testSdkWithCc(t *testing.T, bp string) *testSdkResult {
 	t.Helper()
-
-	fs := map[string][]byte{
-		"Test.cpp":                      nil,
-		"include/Test.h":                nil,
-		"include-android/AndroidTest.h": nil,
-		"include-host/HostTest.h":       nil,
-		"arm64/include/Arm64Test.h":     nil,
-		"libfoo.so":                     nil,
-		"aidl/foo/bar/Test.aidl":        nil,
-		"some/where/stubslib.map.txt":   nil,
-	}
-	return testSdkWithFs(t, bp, fs)
+	return testSdkWithFs(t, bp, ccTestFs)
 }
 
 // Contains tests for SDK members provided by the cc package.
@@ -69,6 +69,76 @@
 	ensureListContains(t, inputs, arm64Output.String())
 }
 
+func TestSdkCompileMultilibOverride(t *testing.T) {
+	result := testSdkWithCc(t, `
+		sdk {
+			name: "mysdk",
+			device_supported: false,
+			host_supported: true,
+			native_shared_libs: ["sdkmember"],
+			compile_multilib: "64",
+		}
+
+		cc_library_shared {
+			name: "sdkmember",
+			device_supported: false,
+			host_supported: true,
+			srcs: ["Test.cpp"],
+			stl: "none",
+			compile_multilib: "64",
+		}
+	`)
+
+	result.CheckSnapshot("mysdk", "",
+		checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+cc_prebuilt_library_shared {
+    name: "mysdk_sdkmember@current",
+    sdk_member_name: "sdkmember",
+    device_supported: false,
+    host_supported: true,
+    installable: false,
+    stl: "none",
+    compile_multilib: "64",
+    arch: {
+        x86_64: {
+            srcs: ["x86_64/lib/sdkmember.so"],
+        },
+    },
+}
+
+cc_prebuilt_library_shared {
+    name: "sdkmember",
+    prefer: false,
+    device_supported: false,
+    host_supported: true,
+    stl: "none",
+    compile_multilib: "64",
+    arch: {
+        x86_64: {
+            srcs: ["x86_64/lib/sdkmember.so"],
+        },
+    },
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    device_supported: false,
+    host_supported: true,
+    native_shared_libs: ["mysdk_sdkmember@current"],
+    target: {
+        linux_glibc: {
+            compile_multilib: "64",
+        },
+    },
+}
+`),
+		checkAllCopyRules(`
+.intermediates/sdkmember/linux_glibc_x86_64_shared/sdkmember.so -> x86_64/lib/sdkmember.so
+`))
+}
+
 func TestBasicSdkWithCc(t *testing.T) {
 	result := testSdkWithCc(t, `
 		sdk {
@@ -79,6 +149,8 @@
 		cc_library_shared {
 			name: "sdkmember",
 			system_shared_libs: [],
+			stl: "none",
+			apex_available: ["mysdkapex"],
 		}
 
 		sdk_snapshot {
@@ -152,6 +224,13 @@
 			key: "myapex.key",
 			certificate: ":myapex.cert",
 		}
+
+		apex {
+			name: "mysdkapex",
+			native_shared_libs: ["sdkmember"],
+			key: "myapex.key",
+			certificate: ":myapex.cert",
+		}
 	`)
 
 	sdkMemberV1 := result.ModuleForTests("sdkmember_mysdk_1", "android_arm64_armv8-a_shared_myapex").Rule("toc").Output
@@ -240,6 +319,7 @@
     name: "mysdk_crtobj@current",
     sdk_member_name: "crtobj",
     stl: "none",
+    compile_multilib: "both",
     arch: {
         arm64: {
             srcs: ["arm64/lib/crtobj.o"],
@@ -254,6 +334,7 @@
     name: "crtobj",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     arch: {
         arm64: {
             srcs: ["arm64/lib/crtobj.o"],
@@ -347,6 +428,7 @@
     sdk_member_name: "mynativelib",
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -363,6 +445,7 @@
     name: "mynativelib",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -450,9 +533,6 @@
 }
 
 func TestMultipleHostOsTypesSnapshotWithCcBinary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -561,9 +641,6 @@
 // Test that we support the necessary flags for the linker binary, which is
 // special in several ways.
 func TestSnapshotWithCcStaticNocrtBinary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "mymodule_exports",
@@ -596,9 +673,9 @@
     host_supported: true,
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     static_executable: true,
     nocrt: true,
-    compile_multilib: "both",
     arch: {
         x86_64: {
             srcs: ["x86_64/bin/linker"],
@@ -615,9 +692,9 @@
     device_supported: false,
     host_supported: true,
     stl: "none",
+    compile_multilib: "both",
     static_executable: true,
     nocrt: true,
-    compile_multilib: "both",
     arch: {
         x86_64: {
             srcs: ["x86_64/bin/linker"],
@@ -677,6 +754,7 @@
     ],
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -698,6 +776,7 @@
         "apex2",
     ],
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -799,6 +878,7 @@
     sdk_member_name: "mynativelib",
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     shared_libs: [
         "mysdk_myothernativelib@current",
         "libc",
@@ -817,6 +897,7 @@
     name: "mynativelib",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     shared_libs: [
         "myothernativelib",
         "libc",
@@ -836,6 +917,7 @@
     sdk_member_name: "myothernativelib",
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     system_shared_libs: ["libm"],
     arch: {
         arm64: {
@@ -851,6 +933,7 @@
     name: "myothernativelib",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     system_shared_libs: ["libm"],
     arch: {
         arm64: {
@@ -867,6 +950,7 @@
     sdk_member_name: "mysystemnativelib",
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     arch: {
         arm64: {
             srcs: ["arm64/lib/mysystemnativelib.so"],
@@ -881,6 +965,7 @@
     name: "mysystemnativelib",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     arch: {
         arm64: {
             srcs: ["arm64/lib/mysystemnativelib.so"],
@@ -912,9 +997,6 @@
 }
 
 func TestHostSnapshotWithCcSharedLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -952,6 +1034,7 @@
     installable: false,
     sdk_version: "minimum",
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         x86_64: {
@@ -972,6 +1055,7 @@
     host_supported: true,
     sdk_version: "minimum",
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         x86_64: {
@@ -1007,9 +1091,6 @@
 }
 
 func TestMultipleHostOsTypesSnapshotWithCcSharedLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1051,12 +1132,18 @@
     installable: false,
     stl: "none",
     target: {
+        linux_glibc: {
+            compile_multilib: "both",
+        },
         linux_glibc_x86_64: {
             srcs: ["linux_glibc/x86_64/lib/mynativelib.so"],
         },
         linux_glibc_x86: {
             srcs: ["linux_glibc/x86/lib/mynativelib.so"],
         },
+        windows: {
+            compile_multilib: "64",
+        },
         windows_x86_64: {
             srcs: ["windows/x86_64/lib/mynativelib.dll"],
         },
@@ -1070,12 +1157,18 @@
     host_supported: true,
     stl: "none",
     target: {
+        linux_glibc: {
+            compile_multilib: "both",
+        },
         linux_glibc_x86_64: {
             srcs: ["linux_glibc/x86_64/lib/mynativelib.so"],
         },
         linux_glibc_x86: {
             srcs: ["linux_glibc/x86/lib/mynativelib.so"],
         },
+        windows: {
+            compile_multilib: "64",
+        },
         windows_x86_64: {
             srcs: ["windows/x86_64/lib/mynativelib.dll"],
         },
@@ -1132,6 +1225,7 @@
     sdk_member_name: "mynativelib",
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -1149,6 +1243,7 @@
     name: "mynativelib",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -1182,9 +1277,6 @@
 }
 
 func TestHostSnapshotWithCcStaticLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -1220,6 +1312,7 @@
     host_supported: true,
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         x86_64: {
@@ -1239,6 +1332,7 @@
     device_supported: false,
     host_supported: true,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         x86_64: {
@@ -1299,6 +1393,7 @@
     sdk_member_name: "mynativelib",
     installable: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -1324,6 +1419,7 @@
     name: "mynativelib",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
         arm64: {
@@ -1360,9 +1456,6 @@
 }
 
 func TestHostSnapshotWithMultiLib64(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -1403,6 +1496,7 @@
     host_supported: true,
     installable: false,
     stl: "none",
+    compile_multilib: "64",
     export_include_dirs: ["include/include"],
     arch: {
         x86_64: {
@@ -1418,6 +1512,7 @@
     device_supported: false,
     host_supported: true,
     stl: "none",
+    compile_multilib: "64",
     export_include_dirs: ["include/include"],
     arch: {
         x86_64: {
@@ -1470,6 +1565,7 @@
     name: "mysdk_mynativeheaders@current",
     sdk_member_name: "mynativeheaders",
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
 }
 
@@ -1477,6 +1573,7 @@
     name: "mynativeheaders",
     prefer: false,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
 }
 
@@ -1492,9 +1589,6 @@
 }
 
 func TestHostSnapshotWithCcHeadersLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1522,6 +1616,7 @@
     device_supported: false,
     host_supported: true,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
 }
 
@@ -1531,6 +1626,7 @@
     device_supported: false,
     host_supported: true,
     stl: "none",
+    compile_multilib: "both",
     export_include_dirs: ["include/include"],
 }
 
@@ -1548,9 +1644,6 @@
 }
 
 func TestDeviceAndHostSnapshotWithCcHeadersLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1583,13 +1676,14 @@
     sdk_member_name: "mynativeheaders",
     host_supported: true,
     stl: "none",
-    export_system_include_dirs: ["include/include"],
+    compile_multilib: "both",
+    export_system_include_dirs: ["common_os/include/include"],
     target: {
         android: {
-            export_include_dirs: ["include/include-android"],
+            export_include_dirs: ["android/include/include-android"],
         },
         linux_glibc: {
-            export_include_dirs: ["include/include-host"],
+            export_include_dirs: ["linux_glibc/include/include-host"],
         },
     },
 }
@@ -1599,13 +1693,14 @@
     prefer: false,
     host_supported: true,
     stl: "none",
-    export_system_include_dirs: ["include/include"],
+    compile_multilib: "both",
+    export_system_include_dirs: ["common_os/include/include"],
     target: {
         android: {
-            export_include_dirs: ["include/include-android"],
+            export_include_dirs: ["android/include/include-android"],
         },
         linux_glibc: {
-            export_include_dirs: ["include/include-host"],
+            export_include_dirs: ["linux_glibc/include/include-host"],
         },
     },
 }
@@ -1617,17 +1712,14 @@
 }
 `),
 		checkAllCopyRules(`
-include/Test.h -> include/include/Test.h
-include-android/AndroidTest.h -> include/include-android/AndroidTest.h
-include-host/HostTest.h -> include/include-host/HostTest.h
+include/Test.h -> common_os/include/include/Test.h
+include-android/AndroidTest.h -> android/include/include-android/AndroidTest.h
+include-host/HostTest.h -> linux_glibc/include/include-host/HostTest.h
 `),
 	)
 }
 
 func TestSystemSharedLibPropagation(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1658,6 +1750,7 @@
     name: "mysdk_sslnil@current",
     sdk_member_name: "sslnil",
     installable: false,
+    compile_multilib: "both",
     arch: {
         arm64: {
             srcs: ["arm64/lib/sslnil.so"],
@@ -1671,6 +1764,7 @@
 cc_prebuilt_library_shared {
     name: "sslnil",
     prefer: false,
+    compile_multilib: "both",
     arch: {
         arm64: {
             srcs: ["arm64/lib/sslnil.so"],
@@ -1685,6 +1779,7 @@
     name: "mysdk_sslempty@current",
     sdk_member_name: "sslempty",
     installable: false,
+    compile_multilib: "both",
     system_shared_libs: [],
     arch: {
         arm64: {
@@ -1699,6 +1794,7 @@
 cc_prebuilt_library_shared {
     name: "sslempty",
     prefer: false,
+    compile_multilib: "both",
     system_shared_libs: [],
     arch: {
         arm64: {
@@ -1714,6 +1810,7 @@
     name: "mysdk_sslnonempty@current",
     sdk_member_name: "sslnonempty",
     installable: false,
+    compile_multilib: "both",
     system_shared_libs: ["mysdk_sslnil@current"],
     arch: {
         arm64: {
@@ -1728,6 +1825,7 @@
 cc_prebuilt_library_shared {
     name: "sslnonempty",
     prefer: false,
+    compile_multilib: "both",
     system_shared_libs: ["sslnil"],
     arch: {
         arm64: {
@@ -1776,6 +1874,7 @@
     sdk_member_name: "sslvariants",
     host_supported: true,
     installable: false,
+    compile_multilib: "both",
     target: {
         android: {
             system_shared_libs: [],
@@ -1799,6 +1898,7 @@
     name: "sslvariants",
     prefer: false,
     host_supported: true,
+    compile_multilib: "both",
     target: {
         android: {
             system_shared_libs: [],
@@ -1855,6 +1955,7 @@
     name: "mysdk_stubslib@current",
     sdk_member_name: "stubslib",
     installable: false,
+    compile_multilib: "both",
     stubs: {
         versions: ["3"],
     },
@@ -1871,6 +1972,7 @@
 cc_prebuilt_library_shared {
     name: "stubslib",
     prefer: false,
+    compile_multilib: "both",
     stubs: {
         versions: ["3"],
     },
@@ -1892,9 +1994,6 @@
 }
 
 func TestDeviceAndHostSnapshotWithStubsLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1927,6 +2026,7 @@
     sdk_member_name: "stubslib",
     host_supported: true,
     installable: false,
+    compile_multilib: "both",
     stubs: {
         versions: ["3"],
     },
@@ -1950,6 +2050,7 @@
     name: "stubslib",
     prefer: false,
     host_supported: true,
+    compile_multilib: "both",
     stubs: {
         versions: ["3"],
     },
@@ -1978,9 +2079,6 @@
 }
 
 func TestUniqueHostSoname(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -2005,6 +2103,7 @@
     host_supported: true,
     installable: false,
     unique_host_soname: true,
+    compile_multilib: "both",
     target: {
         android_arm64: {
             srcs: ["android/arm64/lib/mylib.so"],
@@ -2026,6 +2125,7 @@
     prefer: false,
     host_supported: true,
     unique_host_soname: true,
+    compile_multilib: "both",
     target: {
         android_arm64: {
             srcs: ["android/arm64/lib/mylib.so"],
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 7496b20..aee04a1 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -16,6 +16,8 @@
 
 import (
 	"testing"
+
+	"android/soong/java"
 )
 
 func testSdkWithJava(t *testing.T, bp string) *testSdkResult {
@@ -26,6 +28,9 @@
 		"resource.test":          nil,
 		"aidl/foo/bar/Test.aidl": nil,
 
+		// For java_import
+		"prebuilt.jar": nil,
+
 		// For java_sdk_library
 		"api/current.txt":                                   nil,
 		"api/removed.txt":                                   nil,
@@ -85,6 +90,52 @@
 
 // Contains tests for SDK members provided by the java package.
 
+func TestSdkDependsOnSourceEvenWhenPrebuiltPreferred(t *testing.T) {
+	result := testSdkWithJava(t, `
+		sdk {
+			name: "mysdk",
+			java_header_libs: ["sdkmember"],
+		}
+
+		java_library {
+			name: "sdkmember",
+			srcs: ["Test.java"],
+			system_modules: "none",
+			sdk_version: "none",
+		}
+
+		java_import {
+			name: "sdkmember",
+			prefer: true,
+			jars: ["prebuilt.jar"],
+		}
+	`)
+
+	// Make sure that the mysdk module depends on "sdkmember" and not "prebuilt_sdkmember".
+	java.CheckModuleDependencies(t, result.ctx, "mysdk", "android_common", []string{"sdkmember"})
+
+	result.CheckSnapshot("mysdk", "",
+		checkAndroidBpContents(`// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_sdkmember@current",
+    sdk_member_name: "sdkmember",
+    jars: ["java/sdkmember.jar"],
+}
+
+java_import {
+    name: "sdkmember",
+    prefer: false,
+    jars: ["java/sdkmember.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    java_header_libs: ["mysdk_sdkmember@current"],
+}
+`))
+}
+
 func TestBasicSdkWithJavaLibrary(t *testing.T) {
 	result := testSdkWithJava(t, `
 		sdk {
@@ -214,9 +265,6 @@
 }
 
 func TestHostSnapshotWithJavaHeaderLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithJava(t, `
 		sdk {
 			name: "mysdk",
@@ -274,9 +322,6 @@
 }
 
 func TestDeviceAndHostSnapshotWithJavaHeaderLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithJava(t, `
 		sdk {
 			name: "mysdk",
@@ -390,9 +435,6 @@
 }
 
 func TestHostSnapshotWithJavaImplLibrary(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithJava(t, `
 		module_exports {
 			name: "myexports",
@@ -497,9 +539,6 @@
 }
 
 func TestHostSnapshotWithJavaTest(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithJava(t, `
 		module_exports {
 			name: "myexports",
@@ -641,9 +680,6 @@
 }
 
 func TestHostSnapshotWithDroidstubs(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithDroidstubs(t, `
 		module_exports {
 			name: "myexports",
@@ -784,9 +820,6 @@
 }
 
 func TestHostSnapshotWithJavaSystemModules(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithJava(t, `
 		sdk {
 			name: "mysdk",
@@ -862,9 +895,6 @@
 }
 
 func TestDeviceAndHostSnapshotWithOsSpecificMembers(t *testing.T) {
-	// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-	SkipIfNotLinux(t)
-
 	result := testSdkWithJava(t, `
 		module_exports {
 			name: "myexports",
diff --git a/sdk/sdk.go b/sdk/sdk.go
index b9b8199..3e76008 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -218,7 +218,7 @@
 			Compile_multilib *string
 		}
 		p := &props{Compile_multilib: proptools.StringPtr("both")}
-		ctx.AppendProperties(p)
+		ctx.PrependProperties(p)
 	})
 	return s
 }
@@ -330,6 +330,11 @@
 	blueprint.BaseDependencyTag
 }
 
+// Mark this tag so dependencies that use it are excluded from APEX contents.
+func (t dependencyTag) ExcludeFromApexContents() {}
+
+var _ android.ExcludeFromApexContentsTag = dependencyTag{}
+
 // For dependencies from an in-development version of an SDK member to frozen versions of the same member
 // e.g. libfoo -> libfoo.mysdk.11 and libfoo.mysdk.12
 type sdkMemberVersionedDepTag struct {
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 56be741..ef62b79 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -15,6 +15,9 @@
 package sdk
 
 import (
+	"android/soong/android"
+	"log"
+	"os"
 	"testing"
 
 	"github.com/google/blueprint/proptools"
@@ -22,6 +25,12 @@
 
 // Needed in an _test.go file in this package to ensure tests run correctly, particularly in IDE.
 func TestMain(m *testing.M) {
+	if android.BuildOs != android.Linux {
+		// b/145598135 - Generating host snapshots for anything other than linux is not supported.
+		log.Printf("Skipping as sdk snapshot generation is only supported on %s not %s", android.Linux, android.BuildOs)
+		os.Exit(0)
+	}
+
 	runTestWithBuildDir(m)
 }
 
diff --git a/sdk/testing.go b/sdk/testing.go
index 4361754..34ea8f0 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -29,7 +29,9 @@
 	"android/soong/java"
 )
 
-func testSdkContext(bp string, fs map[string][]byte) (*android.TestContext, android.Config) {
+func testSdkContext(bp string, fs map[string][]byte, extraOsTypes []android.OsType) (*android.TestContext, android.Config) {
+	extraOsTypes = append(extraOsTypes, android.Android, android.Windows)
+
 	bp = bp + `
 		apex_key {
 			name: "myapex.key",
@@ -41,17 +43,18 @@
 			name: "myapex.cert",
 			certificate: "myapex",
 		}
-	` + cc.GatherRequiredDepsForTest(android.Android, android.Windows)
+	` + cc.GatherRequiredDepsForTest(extraOsTypes...)
 
 	mockFS := map[string][]byte{
-		"build/make/target/product/security":         nil,
-		"apex_manifest.json":                         nil,
-		"system/sepolicy/apex/myapex-file_contexts":  nil,
-		"system/sepolicy/apex/myapex2-file_contexts": nil,
-		"myapex.avbpubkey":                           nil,
-		"myapex.pem":                                 nil,
-		"myapex.x509.pem":                            nil,
-		"myapex.pk8":                                 nil,
+		"build/make/target/product/security":           nil,
+		"apex_manifest.json":                           nil,
+		"system/sepolicy/apex/myapex-file_contexts":    nil,
+		"system/sepolicy/apex/myapex2-file_contexts":   nil,
+		"system/sepolicy/apex/mysdkapex-file_contexts": nil,
+		"myapex.avbpubkey":                             nil,
+		"myapex.pem":                                   nil,
+		"myapex.x509.pem":                              nil,
+		"myapex.pk8":                                   nil,
 	}
 
 	cc.GatherRequiredFilesForTest(mockFS)
@@ -68,6 +71,15 @@
 		{android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", ""},
 	}
 
+	for _, extraOsType := range extraOsTypes {
+		switch extraOsType {
+		case android.LinuxBionic:
+			config.Targets[android.LinuxBionic] = []android.Target{
+				{android.LinuxBionic, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", ""},
+			}
+		}
+	}
+
 	ctx := android.NewTestArchContext()
 
 	// Enable androidmk support.
@@ -84,6 +96,7 @@
 	android.RegisterPackageBuildComponents(ctx)
 	ctx.PreArchMutators(android.RegisterVisibilityRuleChecker)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	ctx.PreArchMutators(android.RegisterComponentsMutator)
 	ctx.PreArchMutators(android.RegisterVisibilityRuleGatherer)
 	ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer)
 
@@ -115,9 +128,8 @@
 	return ctx, config
 }
 
-func testSdkWithFs(t *testing.T, bp string, fs map[string][]byte) *testSdkResult {
+func runTests(t *testing.T, ctx *android.TestContext, config android.Config) *testSdkResult {
 	t.Helper()
-	ctx, config := testSdkContext(bp, fs)
 	_, errs := ctx.ParseBlueprintsFiles(".")
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
@@ -129,9 +141,15 @@
 	}
 }
 
+func testSdkWithFs(t *testing.T, bp string, fs map[string][]byte) *testSdkResult {
+	t.Helper()
+	ctx, config := testSdkContext(bp, fs, nil)
+	return runTests(t, ctx, config)
+}
+
 func testSdkError(t *testing.T, pattern, bp string) {
 	t.Helper()
-	ctx, config := testSdkContext(bp, nil)
+	ctx, config := testSdkContext(bp, nil, nil)
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	if len(errs) > 0 {
 		android.FailIfNoMatchingErrors(t, pattern, errs)
@@ -420,10 +438,3 @@
 
 	os.Exit(run())
 }
-
-func SkipIfNotLinux(t *testing.T) {
-	t.Helper()
-	if android.BuildOs != android.Linux {
-		t.Skipf("Skipping as sdk snapshot generation is only supported on %s not %s", android.Linux, android.BuildOs)
-	}
-}
diff --git a/sdk/update.go b/sdk/update.go
index 8241151..b8d73c6 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -794,6 +794,17 @@
 	return !ok
 }
 
+// Add the properties from the given SdkMemberProperties to the blueprint
+// property set. This handles common properties in SdkMemberPropertiesBase and
+// calls the member-specific AddToPropertySet for the rest.
+func addSdkMemberPropertiesToSet(ctx *memberContext, memberProperties android.SdkMemberProperties, targetPropertySet android.BpPropertySet) {
+	if memberProperties.Base().Compile_multilib != "" {
+		targetPropertySet.AddProperty("compile_multilib", memberProperties.Base().Compile_multilib)
+	}
+
+	memberProperties.AddToPropertySet(ctx, targetPropertySet)
+}
+
 type sdkMemberRef struct {
 	memberType android.SdkMemberType
 	variant    android.SdkAware
@@ -911,7 +922,7 @@
 
 	if commonVariants, ok := variantsByArchName["common"]; ok {
 		if len(osTypeVariants) != 1 {
-			panic("Expected to only have 1 variant when arch type is common but found " + string(len(osTypeVariants)))
+			panic(fmt.Errorf("Expected to only have 1 variant when arch type is common but found %d", len(osTypeVariants)))
 		}
 
 		// A common arch type only has one variant and its properties should be treated
@@ -1009,7 +1020,7 @@
 	}
 
 	// Add the os specific but arch independent properties to the module.
-	osInfo.Properties.AddToPropertySet(ctx, osPropertySet)
+	addSdkMemberPropertiesToSet(ctx, osInfo.Properties, osPropertySet)
 
 	// Add arch (and possibly os) specific sections for each set of arch (and possibly
 	// os) specific properties.
@@ -1111,11 +1122,11 @@
 func (archInfo *archTypeSpecificInfo) addToPropertySet(ctx *memberContext, archPropertySet android.BpPropertySet, archOsPrefix string) {
 	archTypeName := archInfo.archType.Name
 	archTypePropertySet := archPropertySet.AddPropertySet(archOsPrefix + archTypeName)
-	archInfo.Properties.AddToPropertySet(ctx, archTypePropertySet)
+	addSdkMemberPropertiesToSet(ctx, archInfo.Properties, archTypePropertySet)
 
 	for _, linkInfo := range archInfo.linkInfos {
 		linkPropertySet := archTypePropertySet.AddPropertySet(linkInfo.linkType)
-		linkInfo.Properties.AddToPropertySet(ctx, linkPropertySet)
+		addSdkMemberPropertiesToSet(ctx, linkInfo.Properties, linkPropertySet)
 	}
 }
 
@@ -1221,7 +1232,7 @@
 	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, commonProperties, osSpecificPropertiesContainers)
 
 	// Add the common properties to the module.
-	commonProperties.AddToPropertySet(ctx, bpModule)
+	addSdkMemberPropertiesToSet(ctx, commonProperties, bpModule)
 
 	// Create a target property set into which target specific properties can be
 	// added.
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 0a0bb16..4ef2721 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -63,6 +63,7 @@
         "cleanbuild_test.go",
         "config_test.go",
         "environment_test.go",
+        "rbe_test.go",
         "upload_test.go",
         "util_test.go",
         "proc_sync_test.go",
diff --git a/ui/build/config.go b/ui/build/config.go
index c4bbad7..ba477e6 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -184,7 +184,8 @@
 	// Tell python not to spam the source tree with .pyc files.
 	ret.environ.Set("PYTHONDONTWRITEBYTECODE", "1")
 
-	ret.environ.Set("TMPDIR", absPath(ctx, ret.TempDir()))
+	tmpDir := absPath(ctx, ret.TempDir())
+	ret.environ.Set("TMPDIR", tmpDir)
 
 	// Always set ASAN_SYMBOLIZER_PATH so that ASAN-based tools can symbolize any crashes
 	symbolizerPath := filepath.Join("prebuilts/clang/host", ret.HostPrebuiltTag(),
@@ -256,11 +257,14 @@
 		ret.buildDateTime = strconv.FormatInt(time.Now().Unix(), 10)
 	}
 
-	if ctx.Metrics != nil {
-		ctx.Metrics.SetBuildDateTime(ret.buildDateTime)
-	}
 	ret.environ.Set("BUILD_DATETIME_FILE", buildDateTimeFile)
 
+	if ret.UseRBE() {
+		for k, v := range getRBEVars(ctx, tmpDir) {
+			ret.environ.Set(k, v)
+		}
+	}
+
 	return Config{ret}
 }
 
@@ -804,6 +808,15 @@
 	return true
 }
 
+func (c *configImpl) RBEStatsOutputDir() string {
+	for _, f := range []string{"RBE_output_dir", "FLAG_output_dir"} {
+		if v, ok := c.environ.Get(f); ok {
+			return v
+		}
+	}
+	return ""
+}
+
 func (c *configImpl) UseRemoteBuild() bool {
 	return c.UseGoma() || c.UseRBE()
 }
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index ceea4bf..fd3b7ab 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -15,14 +15,47 @@
 package build
 
 import (
+	"fmt"
+	"math/rand"
+	"os"
 	"path/filepath"
+	"time"
 
 	"android/soong/ui/metrics"
 )
 
-const bootstrapCmd = "bootstrap"
-const rbeLeastNProcs = 2500
-const rbeLeastNFiles = 16000
+const (
+	rbeLeastNProcs = 2500
+	rbeLeastNFiles = 16000
+
+	// prebuilt RBE binaries
+	bootstrapCmd = "bootstrap"
+
+	// RBE metrics proto buffer file
+	rbeMetricsPBFilename = "rbe_metrics.pb"
+)
+
+func rbeCommand(ctx Context, config Config, rbeCmd string) string {
+	var cmdPath string
+	if rbeDir, ok := config.Environment().Get("RBE_DIR"); ok {
+		cmdPath = filepath.Join(rbeDir, rbeCmd)
+	} else if home, ok := config.Environment().Get("HOME"); ok {
+		cmdPath = filepath.Join(home, "rbe", rbeCmd)
+	} else {
+		ctx.Fatalf("rbe command path not found")
+	}
+
+	if _, err := os.Stat(cmdPath); err != nil && os.IsNotExist(err) {
+		ctx.Fatalf("rbe command %q not found", rbeCmd)
+	}
+
+	return cmdPath
+}
+
+func getRBEVars(ctx Context, tmpDir string) map[string]string {
+	rand.Seed(time.Now().UnixNano())
+	return map[string]string{"RBE_server_address": fmt.Sprintf("unix://%v/reproxy_%v.sock", tmpDir, rand.Intn(1000))}
+}
 
 func startRBE(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunSetupTool, "rbe_bootstrap")
@@ -35,18 +68,50 @@
 		ctx.Fatalf("max open files is insufficient: %d; want >= %d.\n", n, rbeLeastNFiles)
 	}
 
-	var rbeBootstrap string
-	if rbeDir, ok := config.Environment().Get("RBE_DIR"); ok {
-		rbeBootstrap = filepath.Join(rbeDir, bootstrapCmd)
-	} else if home, ok := config.Environment().Get("HOME"); ok {
-		rbeBootstrap = filepath.Join(home, "rbe", bootstrapCmd)
-	} else {
-		ctx.Fatalln("rbe bootstrap not found")
-	}
-
-	cmd := Command(ctx, config, "boostrap", rbeBootstrap)
+	cmd := Command(ctx, config, "startRBE bootstrap", rbeCommand(ctx, config, bootstrapCmd))
 
 	if output, err := cmd.CombinedOutput(); err != nil {
 		ctx.Fatalf("rbe bootstrap failed with: %v\n%s\n", err, output)
 	}
 }
+
+func stopRBE(ctx Context, config Config) {
+	cmd := Command(ctx, config, "stopRBE bootstrap", rbeCommand(ctx, config, bootstrapCmd), "-shutdown")
+	if output, err := cmd.CombinedOutput(); err != nil {
+		ctx.Fatalf("rbe bootstrap with shutdown failed with: %v\n%s\n", err, output)
+	}
+}
+
+// DumpRBEMetrics creates a metrics protobuf file containing RBE related metrics.
+// The protobuf file is created if RBE is enabled and the proxy service has
+// started. The proxy service is shutdown in order to dump the RBE metrics to the
+// protobuf file.
+func DumpRBEMetrics(ctx Context, config Config, filename string) {
+	ctx.BeginTrace(metrics.RunShutdownTool, "dump_rbe_metrics")
+	defer ctx.EndTrace()
+
+	// Remove the previous metrics file in case there is a failure or RBE has been
+	// disable for this run.
+	os.Remove(filename)
+
+	// If RBE is not enabled then there are no metrics to generate.
+	// If RBE does not require to start, the RBE proxy maybe started
+	// manually for debugging purpose and can generate the metrics
+	// afterwards.
+	if !config.StartRBE() {
+		return
+	}
+
+	outputDir := config.RBEStatsOutputDir()
+	if outputDir == "" {
+		ctx.Fatal("RBE output dir variable not defined. Aborting metrics dumping.")
+	}
+	metricsFile := filepath.Join(outputDir, rbeMetricsPBFilename)
+
+	// Stop the proxy first in order to generate the RBE metrics protobuf file.
+	stopRBE(ctx, config)
+
+	if _, err := copyFile(metricsFile, filename); err != nil {
+		ctx.Fatalf("failed to copy %q to %q: %v\n", metricsFile, filename, err)
+	}
+}
diff --git a/ui/build/rbe_test.go b/ui/build/rbe_test.go
new file mode 100644
index 0000000..2c4995b
--- /dev/null
+++ b/ui/build/rbe_test.go
@@ -0,0 +1,142 @@
+// 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 build
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"android/soong/ui/logger"
+)
+
+func TestDumpRBEMetrics(t *testing.T) {
+	ctx := testContext()
+	tests := []struct {
+		description string
+		env         []string
+		generated   bool
+	}{{
+		description: "RBE disabled",
+		env: []string{
+			"NOSTART_RBE=true",
+		},
+	}, {
+		description: "rbe metrics generated",
+		env: []string{
+			"USE_RBE=true",
+		},
+		generated: true,
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			tmpDir := t.TempDir()
+
+			rbeBootstrapCmd := filepath.Join(tmpDir, bootstrapCmd)
+			if err := ioutil.WriteFile(rbeBootstrapCmd, []byte(rbeBootstrapProgram), 0755); err != nil {
+				t.Fatalf("failed to create a fake bootstrap command file %s: %v", rbeBootstrapCmd, err)
+			}
+
+			env := Environment(tt.env)
+			env.Set("OUT_DIR", tmpDir)
+			env.Set("RBE_DIR", tmpDir)
+			env.Set("RBE_output_dir", t.TempDir())
+			config := Config{&configImpl{
+				environ: &env,
+			}}
+
+			rbeMetricsFilename := filepath.Join(tmpDir, rbeMetricsPBFilename)
+			DumpRBEMetrics(ctx, config, rbeMetricsFilename)
+
+			// Validate that the rbe metrics file exists if RBE is enabled.
+			if _, err := os.Stat(rbeMetricsFilename); err == nil {
+				if !tt.generated {
+					t.Errorf("got true, want false for rbe metrics file %s to exist.", rbeMetricsFilename)
+				}
+			} else if os.IsNotExist(err) {
+				if tt.generated {
+					t.Errorf("got false, want true for rbe metrics file %s to exist.", rbeMetricsFilename)
+				}
+			} else {
+				t.Errorf("unknown error found on checking %s exists: %v", rbeMetricsFilename, err)
+			}
+		})
+	}
+}
+
+func TestDumpRBEMetricsErrors(t *testing.T) {
+	ctx := testContext()
+	tests := []struct {
+		description         string
+		rbeOutputDirDefined bool
+		bootstrapProgram    string
+		expectedErr         string
+	}{{
+		description:      "output_dir not defined",
+		bootstrapProgram: rbeBootstrapProgram,
+		expectedErr:      "RBE output dir variable not defined",
+	}, {
+		description:         "stopRBE failed",
+		rbeOutputDirDefined: true,
+		bootstrapProgram:    "#!/bin/bash\nexit 1",
+		expectedErr:         "shutdown failed",
+	}, {
+		description:         "failed to copy metrics file",
+		rbeOutputDirDefined: true,
+		bootstrapProgram:    "#!/bin/bash",
+		expectedErr:         "failed to copy",
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			defer logger.Recover(func(err error) {
+				got := err.Error()
+				if !strings.Contains(got, tt.expectedErr) {
+					t.Errorf("got %q, want %q to be contained in error", got, tt.expectedErr)
+				}
+			})
+
+			tmpDir := t.TempDir()
+
+			rbeBootstrapCmd := filepath.Join(tmpDir, bootstrapCmd)
+			if err := ioutil.WriteFile(rbeBootstrapCmd, []byte(tt.bootstrapProgram), 0755); err != nil {
+				t.Fatalf("failed to create a fake bootstrap command file %s: %v", rbeBootstrapCmd, err)
+			}
+
+			env := &Environment{}
+			env.Set("USE_RBE", "true")
+			env.Set("OUT_DIR", tmpDir)
+			env.Set("RBE_DIR", tmpDir)
+
+			if tt.rbeOutputDirDefined {
+				env.Set("RBE_output_dir", t.TempDir())
+			}
+
+			config := Config{&configImpl{
+				environ: env,
+			}}
+
+			rbeMetricsFilename := filepath.Join(tmpDir, rbeMetricsPBFilename)
+			DumpRBEMetrics(ctx, config, rbeMetricsFilename)
+			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
+		})
+	}
+}
+
+var rbeBootstrapProgram = fmt.Sprintf("#!/bin/bash\necho 1 > $RBE_output_dir/%s", rbeMetricsPBFilename)
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 749acb3..fb21430 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -38,7 +38,7 @@
 		ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
 		defer ctx.EndTrace()
 
-		cmd := Command(ctx, config, "blueprint bootstrap", "build/blueprint/bootstrap.bash", "-t")
+		cmd := Command(ctx, config, "blueprint bootstrap", "build/blueprint/bootstrap.bash", "-t", "-n")
 		cmd.Environment.Set("BLUEPRINTDIR", "./build/blueprint")
 		cmd.Environment.Set("BOOTSTRAP", "./build/blueprint/bootstrap.bash")
 		cmd.Environment.Set("BUILDDIR", config.SoongOutDir())
diff --git a/ui/build/upload.go b/ui/build/upload.go
index 3a23a80..1cc2e94 100644
--- a/ui/build/upload.go
+++ b/ui/build/upload.go
@@ -44,7 +44,7 @@
 // environment variable. The metrics files are copied to a temporary directory
 // and the uploader is then executed in the background to allow the user to continue
 // working.
-func UploadMetrics(ctx Context, config Config, forceDumbOutput bool, buildStartedMilli int64, files ...string) {
+func UploadMetrics(ctx Context, config Config, forceDumbOutput bool, buildStarted time.Time, files ...string) {
 	ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
 	defer ctx.EndTrace()
 
@@ -86,7 +86,7 @@
 	// For platform builds, the branch and target name is hardcoded to specific
 	// values for later extraction of the metrics in the data metrics pipeline.
 	data, err := proto.Marshal(&upload_proto.Upload{
-		CreationTimestampMs:   proto.Uint64(uint64(buildStartedMilli)),
+		CreationTimestampMs:   proto.Uint64(uint64(buildStarted.UnixNano() / int64(time.Millisecond))),
 		CompletionTimestampMs: proto.Uint64(uint64(time.Now().UnixNano() / int64(time.Millisecond))),
 		BranchName:            proto.String("developer-metrics"),
 		TargetName:            proto.String("platform-build-systems-metrics"),
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
index c812730..dccf156 100644
--- a/ui/build/upload_test.go
+++ b/ui/build/upload_test.go
@@ -97,7 +97,7 @@
 				buildDateTime: strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
 			}}
 
-			UploadMetrics(ctx, config, false, 1591031903, metricsFiles...)
+			UploadMetrics(ctx, config, false, time.Now(), metricsFiles...)
 		})
 	}
 }
@@ -151,7 +151,7 @@
 					"OUT_DIR=/bad",
 				}}}
 
-			UploadMetrics(ctx, config, true, 1591031903, metricsFile)
+			UploadMetrics(ctx, config, true, time.Now(), metricsFile)
 			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
 		})
 	}
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index 3596e10..8188a69 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -25,6 +25,9 @@
         "metrics.go",
         "time.go",
     ],
+    testSrcs: [
+        "time_test.go",
+    ],
 }
 
 bootstrap_go_package {
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index 3e76d37..2b5c4c3 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -17,7 +17,7 @@
 import (
 	"io/ioutil"
 	"os"
-	"strconv"
+	"time"
 
 	"github.com/golang/protobuf/proto"
 
@@ -25,12 +25,13 @@
 )
 
 const (
-	RunSetupTool = "setup"
-	RunKati      = "kati"
-	RunSoong     = "soong"
-	PrimaryNinja = "ninja"
-	TestRun      = "test"
-	Total        = "total"
+	PrimaryNinja    = "ninja"
+	RunKati         = "kati"
+	RunSetupTool    = "setup"
+	RunShutdownTool = "shutdown"
+	RunSoong        = "soong"
+	TestRun         = "test"
+	Total           = "total"
 )
 
 type Metrics struct {
@@ -130,14 +131,8 @@
 	}
 }
 
-func (m *Metrics) SetBuildDateTime(date_time string) {
-	if date_time != "" {
-		date_time_timestamp, err := strconv.ParseInt(date_time, 10, 64)
-		if err != nil {
-			panic(err)
-		}
-		m.metrics.BuildDateTimestamp = &date_time_timestamp
-	}
+func (m *Metrics) SetBuildDateTime(buildTimestamp time.Time) {
+	m.metrics.BuildDateTimestamp = proto.Int64(buildTimestamp.UnixNano() / int64(time.Second))
 }
 
 // exports the output to the file at outputPath
diff --git a/ui/metrics/time.go b/ui/metrics/time.go
index b8baf16..4016563 100644
--- a/ui/metrics/time.go
+++ b/ui/metrics/time.go
@@ -19,13 +19,18 @@
 
 	"android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/tracer"
+	"github.com/golang/protobuf/proto"
 )
 
+// for testing purpose only
+var _now = now
+
 type timeEvent struct {
 	desc string
 	name string
 
-	atNanos uint64 // timestamp measured in nanoseconds since the reference date
+	// the time that the event started to occur.
+	start time.Time
 }
 
 type TimeTracer interface {
@@ -39,33 +44,26 @@
 
 var _ TimeTracer = &timeTracerImpl{}
 
-func (t *timeTracerImpl) now() uint64 {
-	return uint64(time.Now().UnixNano())
+func now() time.Time {
+	return time.Now()
 }
 
-func (t *timeTracerImpl) Begin(name, desc string, thread tracer.Thread) {
-	t.beginAt(name, desc, t.now())
+func (t *timeTracerImpl) Begin(name, desc string, _ tracer.Thread) {
+	t.activeEvents = append(t.activeEvents, timeEvent{name: name, desc: desc, start: _now()})
 }
 
-func (t *timeTracerImpl) beginAt(name, desc string, atNanos uint64) {
-	t.activeEvents = append(t.activeEvents, timeEvent{name: name, desc: desc, atNanos: atNanos})
-}
-
-func (t *timeTracerImpl) End(thread tracer.Thread) soong_metrics_proto.PerfInfo {
-	return t.endAt(t.now())
-}
-
-func (t *timeTracerImpl) endAt(atNanos uint64) soong_metrics_proto.PerfInfo {
+func (t *timeTracerImpl) End(tracer.Thread) soong_metrics_proto.PerfInfo {
 	if len(t.activeEvents) < 1 {
 		panic("Internal error: No pending events for endAt to end!")
 	}
 	lastEvent := t.activeEvents[len(t.activeEvents)-1]
 	t.activeEvents = t.activeEvents[:len(t.activeEvents)-1]
-	realTime := atNanos - lastEvent.atNanos
+	realTime := uint64(_now().Sub(lastEvent.start).Nanoseconds())
 
 	return soong_metrics_proto.PerfInfo{
-		Desc:      &lastEvent.desc,
-		Name:      &lastEvent.name,
-		StartTime: &lastEvent.atNanos,
-		RealTime:  &realTime}
+		Desc:      proto.String(lastEvent.desc),
+		Name:      proto.String(lastEvent.name),
+		StartTime: proto.Uint64(uint64(lastEvent.start.UnixNano())),
+		RealTime:  proto.Uint64(realTime),
+	}
 }
diff --git a/ui/metrics/time_test.go b/ui/metrics/time_test.go
new file mode 100644
index 0000000..d73080a
--- /dev/null
+++ b/ui/metrics/time_test.go
@@ -0,0 +1,42 @@
+// 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 metrics
+
+import (
+	"testing"
+	"time"
+
+	"android/soong/ui/tracer"
+)
+
+func TestEnd(t *testing.T) {
+	startTime := time.Date(2020, time.July, 13, 13, 0, 0, 0, time.UTC)
+	dur := time.Nanosecond * 10
+	initialNow := _now
+	_now = func() time.Time { return startTime.Add(dur) }
+	defer func() { _now = initialNow }()
+
+	timeTracer := &timeTracerImpl{}
+	timeTracer.activeEvents = append(timeTracer.activeEvents, timeEvent{
+		desc:  "test",
+		name:  "test",
+		start: startTime,
+	})
+
+	perf := timeTracer.End(tracer.Thread(0))
+	if perf.GetRealTime() != uint64(dur.Nanoseconds()) {
+		t.Errorf("got %d, want %d nanoseconds for event duration", perf.GetRealTime(), dur.Nanoseconds())
+	}
+}