Merge "Provide an interface for shared paths between Soong and Soong UI."
diff --git a/android/Android.bp b/android/Android.bp
index 4bd272d..f8c1d55 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -23,7 +23,8 @@
"csuite_config.go",
"defaults.go",
"defs.go",
- "depset.go",
+ "depset_generic.go",
+ "depset_paths.go",
"deptag.go",
"expand.go",
"filegroup.go",
diff --git a/android/config.go b/android/config.go
index 9882d55..453e074 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1272,6 +1272,10 @@
return Bool(c.productVariables.Flatten_apex)
}
+func (c *config) CompressedApex() bool {
+ return Bool(c.productVariables.CompressedApex)
+}
+
func (c *config) EnforceSystemCertificate() bool {
return Bool(c.productVariables.EnforceSystemCertificate)
}
diff --git a/android/depset.go b/android/depset.go
deleted file mode 100644
index 60ebcac..0000000
--- a/android/depset.go
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
- transitiveCopy := make([]*DepSet, 0, len(transitive))
-
- for _, dep := range transitive {
- if dep != nil {
- if dep.order != order {
- panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
- order, dep.order))
- }
- transitiveCopy = append(transitiveCopy, dep)
- }
- }
-
- if order == TOPOLOGICAL {
- directCopy = ReversePaths(direct)
- reverseDepSetsInPlace(transitiveCopy)
- } 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)
- }
-
- 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 {
- if d == nil {
- return nil
- }
- 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 reverseDepSetsInPlace(list []*DepSet) {
- for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
- list[i], list[j] = list[j], list[i]
- }
-
-}
diff --git a/android/depset_generic.go b/android/depset_generic.go
new file mode 100644
index 0000000..f00e462
--- /dev/null
+++ b/android/depset_generic.go
@@ -0,0 +1,351 @@
+// 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"
+)
+
+// depSet is designed to be conceptually compatible with Bazel's depsets:
+// https://docs.bazel.build/versions/master/skylark/depsets.html
+
+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))
+ }
+}
+
+// A depSet efficiently stores a slice of an arbitrary type 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 slice for direct contents
+// and the *depSets of dependencies. A depSet is immutable once created.
+//
+// This object uses reflection to remain agnostic to the type it contains. It should be replaced
+// with generics once those exist in Go. Callers should generally use a thin wrapper around depSet
+// that provides type-safe methods like DepSet for Paths.
+type depSet struct {
+ preorder bool
+ reverse bool
+ order DepSetOrder
+ direct interface{}
+ transitive []*depSet
+}
+
+type depSetInterface interface {
+ embeddedDepSet() *depSet
+}
+
+func (d *depSet) embeddedDepSet() *depSet {
+ return d
+}
+
+var _ depSetInterface = (*depSet)(nil)
+
+// newDepSet returns an immutable depSet with the given order, direct and transitive contents.
+// direct must be a slice, but is not type-safe due to the lack of generics in Go. It can be a
+// nil slice, but not a nil interface{}, i.e. []string(nil) but not nil.
+func newDepSet(order DepSetOrder, direct interface{}, transitive interface{}) *depSet {
+ var directCopy interface{}
+ transitiveDepSet := sliceToDepSets(transitive, order)
+
+ if order == TOPOLOGICAL {
+ directCopy = reverseSlice(direct)
+ reverseSliceInPlace(transitiveDepSet)
+ } else {
+ directCopy = copySlice(direct)
+ }
+
+ return &depSet{
+ preorder: order == PREORDER,
+ reverse: order == TOPOLOGICAL,
+ order: order,
+ direct: directCopy,
+ transitive: transitiveDepSet,
+ }
+}
+
+// depSetBuilder is used to create an immutable depSet.
+type depSetBuilder struct {
+ order DepSetOrder
+ direct reflect.Value
+ transitive []*depSet
+}
+
+// newDepSetBuilder returns a depSetBuilder to create an immutable depSet with the given order and
+// type, represented by a slice of type that will be in the depSet.
+func newDepSetBuilder(order DepSetOrder, typ interface{}) *depSetBuilder {
+ empty := reflect.Zero(reflect.TypeOf(typ))
+ return &depSetBuilder{
+ order: order,
+ direct: empty,
+ }
+}
+
+// sliceToDepSets converts a slice of any type that implements depSetInterface (by having a depSet
+// embedded in it) into a []*depSet.
+func sliceToDepSets(in interface{}, order DepSetOrder) []*depSet {
+ slice := reflect.ValueOf(in)
+ length := slice.Len()
+ out := make([]*depSet, length)
+ for i := 0; i < length; i++ {
+ vi := slice.Index(i)
+ depSetIntf, ok := vi.Interface().(depSetInterface)
+ if !ok {
+ panic(fmt.Errorf("element %d is a %s, not a depSetInterface", i, vi.Type()))
+ }
+ depSet := depSetIntf.embeddedDepSet()
+ if depSet.order != order {
+ panic(fmt.Errorf("incompatible order, new depSet is %s but transitive depSet is %s",
+ order, depSet.order))
+ }
+ out[i] = depSet
+ }
+ return out
+}
+
+// DirectSlice adds direct contents to the depSet being built by a depSetBuilder. Newly added direct
+// contents are to the right of any existing direct contents. The argument must be a slice, but
+// is not type-safe due to the lack of generics in Go.
+func (b *depSetBuilder) DirectSlice(direct interface{}) *depSetBuilder {
+ b.direct = reflect.AppendSlice(b.direct, reflect.ValueOf(direct))
+ return b
+}
+
+// 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. The argument must be the same type
+// as the element of the slice passed to newDepSetBuilder, but is not type-safe due to the lack of
+// generics in Go.
+func (b *depSetBuilder) Direct(direct interface{}) *depSetBuilder {
+ b.direct = reflect.Append(b.direct, reflect.ValueOf(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. The argument can
+// be any slice of type that has depSet embedded in it.
+func (b *depSetBuilder) Transitive(transitive interface{}) *depSetBuilder {
+ depSets := sliceToDepSets(transitive, b.order)
+ b.transitive = append(b.transitive, depSets...)
+ 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.Interface(), 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(interface{})) {
+ 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).
+//
+// This method uses a reflection-based implementation to find the unique elements in slice, which
+// is around 3x slower than a concrete implementation. Type-safe wrappers around depSet can
+// provide their own implementation of ToList that calls depSet.toList with a method that
+// uses a concrete implementation.
+func (d *depSet) ToList() interface{} {
+ return d.toList(firstUnique)
+}
+
+// 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). The firstUniqueFunc is used to remove duplicates from the list.
+func (d *depSet) toList(firstUniqueFunc func(interface{}) interface{}) interface{} {
+ if d == nil {
+ return nil
+ }
+ slice := reflect.Zero(reflect.TypeOf(d.direct))
+ d.walk(func(paths interface{}) {
+ slice = reflect.AppendSlice(slice, reflect.ValueOf(paths))
+ })
+ list := slice.Interface()
+ list = firstUniqueFunc(list)
+ if d.reverse {
+ reverseSliceInPlace(list)
+ }
+ return list
+}
+
+// firstUnique returns all unique elements of a slice, keeping the first copy of each. It
+// modifies the slice contents in place, and returns a subslice of the original slice. The
+// argument must be a slice, but is not type-safe due to the lack of reflection in Go.
+//
+// Performance of the reflection-based firstUnique is up to 3x slower than a concrete type
+// version such as FirstUniqueStrings.
+func firstUnique(slice interface{}) interface{} {
+ // 4 was chosen based on Benchmark_firstUnique results.
+ if reflect.ValueOf(slice).Len() > 4 {
+ return firstUniqueMap(slice)
+ }
+ return firstUniqueList(slice)
+}
+
+// firstUniqueList is an implementation of firstUnique using an O(N^2) list comparison to look for
+// duplicates.
+func firstUniqueList(in interface{}) interface{} {
+ writeIndex := 0
+ slice := reflect.ValueOf(in)
+ length := slice.Len()
+outer:
+ for readIndex := 0; readIndex < length; readIndex++ {
+ readValue := slice.Index(readIndex)
+ for compareIndex := 0; compareIndex < writeIndex; compareIndex++ {
+ compareValue := slice.Index(compareIndex)
+ // These two Interface() calls seem to cause an allocation and significantly
+ // slow down this list-based implementation. The map implementation below doesn't
+ // have this issue because reflect.Value.MapIndex takes a Value and appears to be
+ // able to do the map lookup without an allocation.
+ if readValue.Interface() == compareValue.Interface() {
+ // The value at readIndex already exists somewhere in the output region
+ // of the slice before writeIndex, skip it.
+ continue outer
+ }
+ }
+ if readIndex != writeIndex {
+ writeValue := slice.Index(writeIndex)
+ writeValue.Set(readValue)
+ }
+ writeIndex++
+ }
+ return slice.Slice(0, writeIndex).Interface()
+}
+
+var trueValue = reflect.ValueOf(true)
+
+// firstUniqueList is an implementation of firstUnique using an O(N) hash set lookup to look for
+// duplicates.
+func firstUniqueMap(in interface{}) interface{} {
+ writeIndex := 0
+ slice := reflect.ValueOf(in)
+ length := slice.Len()
+ seen := reflect.MakeMapWithSize(reflect.MapOf(slice.Type().Elem(), trueValue.Type()), slice.Len())
+ for readIndex := 0; readIndex < length; readIndex++ {
+ readValue := slice.Index(readIndex)
+ if seen.MapIndex(readValue).IsValid() {
+ continue
+ }
+ seen.SetMapIndex(readValue, trueValue)
+ if readIndex != writeIndex {
+ writeValue := slice.Index(writeIndex)
+ writeValue.Set(readValue)
+ }
+ writeIndex++
+ }
+ return slice.Slice(0, writeIndex).Interface()
+}
+
+// reverseSliceInPlace reverses the elements of a slice in place. The argument must be a slice, but
+// is not type-safe due to the lack of reflection in Go.
+func reverseSliceInPlace(in interface{}) {
+ swapper := reflect.Swapper(in)
+ slice := reflect.ValueOf(in)
+ length := slice.Len()
+ for i, j := 0, length-1; i < j; i, j = i+1, j-1 {
+ swapper(i, j)
+ }
+}
+
+// reverseSlice returns a copy of a slice in reverse order. The argument must be a slice, but is
+// not type-safe due to the lack of reflection in Go.
+func reverseSlice(in interface{}) interface{} {
+ slice := reflect.ValueOf(in)
+ if !slice.IsValid() || slice.IsNil() {
+ return in
+ }
+ if slice.Kind() != reflect.Slice {
+ panic(fmt.Errorf("%t is not a slice", in))
+ }
+ length := slice.Len()
+ if length == 0 {
+ return in
+ }
+ out := reflect.MakeSlice(slice.Type(), length, length)
+ for i := 0; i < length; i++ {
+ out.Index(i).Set(slice.Index(length - 1 - i))
+ }
+ return out.Interface()
+}
+
+// copySlice returns a copy of a slice. The argument must be a slice, but is not type-safe due to
+// the lack of reflection in Go.
+func copySlice(in interface{}) interface{} {
+ slice := reflect.ValueOf(in)
+ if !slice.IsValid() || slice.IsNil() {
+ return in
+ }
+ length := slice.Len()
+ if length == 0 {
+ return in
+ }
+ out := reflect.MakeSlice(slice.Type(), length, length)
+ reflect.Copy(out, slice)
+ return out.Interface()
+}
diff --git a/android/depset_paths.go b/android/depset_paths.go
new file mode 100644
index 0000000..ed561ba
--- /dev/null
+++ b/android/depset_paths.go
@@ -0,0 +1,94 @@
+// 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
+
+// This file implements DepSet, a thin type-safe wrapper around depSet that contains Paths.
+
+// 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 {
+ depSet
+}
+
+// DepSetBuilder is used to create an immutable DepSet.
+type DepSetBuilder struct {
+ depSetBuilder
+}
+
+// NewDepSet returns an immutable DepSet with the given order, direct and transitive contents.
+func NewDepSet(order DepSetOrder, direct Paths, transitive []*DepSet) *DepSet {
+ return &DepSet{*newDepSet(order, direct, transitive)}
+}
+
+// NewDepSetBuilder returns a DepSetBuilder to create an immutable DepSet with the given order.
+func NewDepSetBuilder(order DepSetOrder) *DepSetBuilder {
+ return &DepSetBuilder{*newDepSetBuilder(order, Paths(nil))}
+}
+
+// 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.depSetBuilder.DirectSlice(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.depSetBuilder.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 &DepSet{*b.depSetBuilder.Build()}
+}
+
+// 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 {
+ if d == nil {
+ return nil
+ }
+ return d.toList(func(paths interface{}) interface{} {
+ return FirstUniquePaths(paths.(Paths))
+ }).(Paths)
+}
+
+// ToSortedList returns the direct and transitive contents of a DepSet in lexically sorted order
+// with duplicates removed.
+func (d *DepSet) ToSortedList() Paths {
+ if d == nil {
+ return nil
+ }
+ paths := d.ToList()
+ return SortedUniquePaths(paths)
+}
diff --git a/android/depset_test.go b/android/depset_test.go
index c328127..955ccb0 100644
--- a/android/depset_test.go
+++ b/android/depset_test.go
@@ -17,6 +17,7 @@
import (
"fmt"
"reflect"
+ "strconv"
"strings"
"testing"
)
@@ -108,6 +109,7 @@
name: "builderReuse",
depSet: func(t *testing.T, order DepSetOrder) *DepSet {
assertEquals := func(t *testing.T, w, g Paths) {
+ t.Helper()
if !reflect.DeepEqual(w, g) {
t.Errorf("want %q, got %q", w, g)
}
@@ -302,3 +304,87 @@
})
}
}
+
+func Test_firstUnique(t *testing.T) {
+ f := func(t *testing.T, imp func([]string) []string, in, want []string) {
+ t.Helper()
+ out := imp(in)
+ if !reflect.DeepEqual(out, want) {
+ t.Errorf("incorrect output:")
+ t.Errorf(" input: %#v", in)
+ t.Errorf(" expected: %#v", want)
+ t.Errorf(" got: %#v", out)
+ }
+ }
+
+ for _, testCase := range firstUniqueStringsTestCases {
+ t.Run("list", func(t *testing.T) {
+ f(t, func(s []string) []string {
+ return firstUniqueList(s).([]string)
+ }, testCase.in, testCase.out)
+ })
+ t.Run("map", func(t *testing.T) {
+ f(t, func(s []string) []string {
+ return firstUniqueMap(s).([]string)
+ }, testCase.in, testCase.out)
+ })
+ }
+}
+
+func Benchmark_firstUnique(b *testing.B) {
+ implementations := []struct {
+ name string
+ f func([]string) []string
+ }{
+ {
+ name: "list",
+ f: func(slice []string) []string {
+ return firstUniqueList(slice).([]string)
+ },
+ },
+ {
+ name: "map",
+ f: func(slice []string) []string {
+ return firstUniqueMap(slice).([]string)
+ },
+ },
+ {
+ name: "optimal",
+ f: func(slice []string) []string {
+ return firstUnique(slice).([]string)
+ },
+ },
+ }
+ const maxSize = 1024
+ uniqueStrings := make([]string, maxSize)
+ for i := range uniqueStrings {
+ uniqueStrings[i] = strconv.Itoa(i)
+ }
+ sameString := make([]string, maxSize)
+ for i := range sameString {
+ sameString[i] = uniqueStrings[0]
+ }
+
+ f := func(b *testing.B, imp func([]string) []string, s []string) {
+ for i := 0; i < b.N; i++ {
+ b.ReportAllocs()
+ s = append([]string(nil), s...)
+ imp(s)
+ }
+ }
+
+ for n := 1; n <= maxSize; n <<= 1 {
+ b.Run(strconv.Itoa(n), func(b *testing.B) {
+ for _, implementation := range implementations {
+ b.Run(implementation.name, func(b *testing.B) {
+ b.Run("same", func(b *testing.B) {
+ f(b, implementation.f, sameString[:n])
+ })
+ b.Run("unique", func(b *testing.B) {
+ f(b, implementation.f, uniqueStrings[:n])
+ })
+ })
+ }
+ })
+ }
+}
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 98cb572..bb98ed4 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -178,7 +178,7 @@
srcPropertyName := proptools.PropertyNameForField(srcField)
srcsSupplier := func(ctx BaseModuleContext) []string {
- if !ctx.Module().Enabled() {
+ if !module.Enabled() {
return nil
}
value := srcPropsValue.FieldByIndex(srcFieldIndex)
diff --git a/android/util_test.go b/android/util_test.go
index 25b52ca..fa26c77 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -593,6 +593,10 @@
name: "map",
f: firstUniqueStringsMap,
},
+ {
+ name: "optimal",
+ f: FirstUniqueStrings,
+ },
}
const maxSize = 1024
uniqueStrings := make([]string, maxSize)
diff --git a/android/variable.go b/android/variable.go
index 0df5272..aed145c 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -319,8 +319,9 @@
Ndk_abis *bool `json:",omitempty"`
Exclude_draft_ndk_apis *bool `json:",omitempty"`
- Flatten_apex *bool `json:",omitempty"`
- Aml_abis *bool `json:",omitempty"`
+ Flatten_apex *bool `json:",omitempty"`
+ CompressedApex *bool `json:",omitempty"`
+ Aml_abis *bool `json:",omitempty"`
DexpreoptGlobalConfig *string `json:",omitempty"`
diff --git a/apex/androidmk.go b/apex/androidmk.go
index da38c2a..6c76ad3f 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -360,7 +360,11 @@
fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") // do we need a new class?
fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFile.String())
fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.ToMakePath().String())
- fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+apexType.suffix())
+ stemSuffix := apexType.suffix()
+ if a.isCompressed {
+ stemSuffix = ".capex"
+ }
+ fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+stemSuffix)
fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !a.installable())
// Because apex writes .mk with Custom(), we need to write manually some common properties
diff --git a/apex/apex.go b/apex/apex.go
index 7ab7454..28ae4bd 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -120,6 +120,12 @@
// Default: true.
Installable *bool
+ // Whether this APEX can be compressed or not. Setting this property to false means this
+ // APEX will never be compressed. When set to true, APEX will be compressed if other
+ // conditions, e.g, target device needs to support APEX compression, are also fulfilled.
+ // Default: true.
+ Compressible *bool
+
// For native libraries and binaries, use the vendor variant instead of the core (platform)
// variant. Default is false. DO NOT use this for APEXes that are installed to the system or
// system_ext partition.
@@ -354,6 +360,8 @@
prebuiltFileToDelete string
+ isCompressed bool
+
// Path of API coverage generate file
coverageOutputPath android.ModuleOutPath
}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 0b67ef5..7ffd226 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -346,6 +346,13 @@
}
}
+func ensureListNotEmpty(t *testing.T, result []string) {
+ t.Helper()
+ if len(result) == 0 {
+ t.Errorf("%q is expected to be not empty", result)
+ }
+}
+
// Minimal test
func TestBasicApex(t *testing.T) {
ctx, config := testApex(t, `
@@ -6186,6 +6193,40 @@
`)
}
+func TestCompressedApex(t *testing.T) {
+ ctx, config := testApex(t, `
+ apex {
+ name: "myapex",
+ key: "myapex.key",
+ compressible: true,
+ }
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+ `, func(fs map[string][]byte, config android.Config) {
+ config.TestProductVariables.CompressedApex = proptools.BoolPtr(true)
+ })
+
+ compressRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("compressRule")
+ ensureContains(t, compressRule.Output.String(), "myapex.capex.unsigned")
+
+ signApkRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Description("sign compressedApex")
+ ensureEquals(t, signApkRule.Input.String(), compressRule.Output.String())
+
+ // Make sure output of bundle is .capex
+ ab := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+ ensureContains(t, ab.outputFile.String(), "myapex.capex")
+
+ // Verify android.mk rules
+ data := android.AndroidMkDataForTest(t, config, "", ab)
+ var builder strings.Builder
+ data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
+ androidMk := builder.String()
+ ensureContains(t, androidMk, "LOCAL_MODULE_STEM := myapex.capex\n")
+}
+
func TestPreferredPrebuiltSharedLibDep(t *testing.T) {
ctx, config := testApex(t, `
apex {
diff --git a/apex/builder.go b/apex/builder.go
index 66eaff1..9db8e59 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -66,6 +66,7 @@
pctx.HostBinToolVariable("extract_apks", "extract_apks")
pctx.HostBinToolVariable("make_f2fs", "make_f2fs")
pctx.HostBinToolVariable("sload_f2fs", "sload_f2fs")
+ pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool")
pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh")
}
@@ -738,7 +739,7 @@
////////////////////////////////////////////////////////////////////////////////////
// Step 4: Sign the APEX using signapk
- a.outputFile = android.PathForModuleOut(ctx, a.Name()+suffix)
+ signedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix)
pem, key := a.getCertificateAndPrivateKey(ctx)
rule := java.Signapk
@@ -750,16 +751,47 @@
if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
rule = java.SignapkRE
args["implicits"] = strings.Join(implicits.Strings(), ",")
- args["outCommaList"] = a.outputFile.String()
+ args["outCommaList"] = signedOutputFile.String()
}
ctx.Build(pctx, android.BuildParams{
Rule: rule,
Description: "signapk",
- Output: a.outputFile,
+ Output: signedOutputFile,
Input: unsignedOutputFile,
Implicits: implicits,
Args: args,
})
+ a.outputFile = signedOutputFile
+
+ // Process APEX compression if enabled
+ compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, true)
+ if compressionEnabled && apexType == imageApex {
+ a.isCompressed = true
+ unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex.unsigned")
+
+ compressRule := android.NewRuleBuilder(pctx, ctx)
+ compressRule.Command().
+ Text("rm").
+ FlagWithOutput("-f ", unsignedCompressedOutputFile)
+ compressRule.Command().
+ BuiltTool("apex_compression_tool").
+ Flag("compress").
+ FlagWithArg("--apex_compression_tool ", outHostBinDir+":"+prebuiltSdkToolsBinDir).
+ FlagWithInput("--input ", signedOutputFile).
+ FlagWithOutput("--output ", unsignedCompressedOutputFile)
+ compressRule.Build("compressRule", "Generate unsigned compressed APEX file")
+
+ signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex")
+ ctx.Build(pctx, android.BuildParams{
+ Rule: rule,
+ Description: "sign compressedApex",
+ Output: signedCompressedOutputFile,
+ Input: unsignedCompressedOutputFile,
+ Implicits: implicits,
+ Args: args,
+ })
+ a.outputFile = signedCompressedOutputFile
+ }
// Install to $OUT/soong/{target,host}/.../apex
if a.installable() {
diff --git a/cc/builder.go b/cc/builder.go
index 5545a5b..9cd78d5 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -235,6 +235,7 @@
}, &remoteexec.REParams{
Labels: map[string]string{"type": "abi-dump", "tool": "header-abi-dumper"},
ExecStrategy: "${config.REAbiDumperExecStrategy}",
+ Inputs: []string{"$sAbiLinkerLibs"},
Platform: map[string]string{
remoteexec.PoolKey: "${config.RECXXPool}",
},
diff --git a/java/app_builder.go b/java/app_builder.go
index 69e462c..b53c15a 100644
--- a/java/app_builder.go
+++ b/java/app_builder.go
@@ -32,7 +32,7 @@
var (
Signapk, SignapkRE = remoteexec.StaticRules(pctx, "signapk",
blueprint.RuleParams{
- Command: `$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -Djava.library.path=$$(dirname ${config.SignapkJniLibrary}) ` +
+ Command: `rm -f $out && $reTemplate${config.JavaCmd} ${config.JavaVmFlags} -Djava.library.path=$$(dirname ${config.SignapkJniLibrary}) ` +
`-jar ${config.SignapkCmd} $flags $certificates $in $out`,
CommandDeps: []string{"${config.SignapkCmd}", "${config.SignapkJniLibrary}"},
},
diff --git a/java/app_test.go b/java/app_test.go
index ef5e84d..e13c6b9 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2566,6 +2566,34 @@
}
}
+func TestAndroidAppImport_overridesDisabledAndroidApp(t *testing.T) {
+ ctx, _ := testJava(t, `
+ android_app {
+ name: "foo",
+ srcs: ["a.java"],
+ enabled: false,
+ }
+
+ android_app_import {
+ name: "foo",
+ apk: "prebuilts/apk/app.apk",
+ certificate: "platform",
+ prefer: true,
+ }
+ `)
+
+ variant := ctx.ModuleForTests("prebuilt_foo", "android_common")
+ a := variant.Module().(*AndroidAppImport)
+ // The prebuilt module should still be enabled and active even if the source-based counterpart
+ // is disabled.
+ if !a.prebuilt.UsePrebuilt() {
+ t.Errorf("prebuilt foo module is not active")
+ }
+ if !a.Enabled() {
+ t.Errorf("prebuilt foo module is disabled")
+ }
+}
+
func TestAndroidTestImport(t *testing.T) {
ctx, config := testJava(t, `
android_test_import {
diff --git a/java/builder.go b/java/builder.go
index cd35245..995160d 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -42,7 +42,7 @@
// TODO(b/143658984): goma can't handle the --system argument to javac.
javac, javacRE = remoteexec.MultiCommandStaticRules(pctx, "javac",
blueprint.RuleParams{
- Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
+ Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" "$out" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
`(if [ -s $srcJarDir/list ] || [ -s $out.rsp ] ; then ` +
`${config.SoongJavacWrapper} $javaTemplate${config.JavacCmd} ` +
diff --git a/rust/config/Android.bp b/rust/config/Android.bp
index e0cc4ce..1f0109f 100644
--- a/rust/config/Android.bp
+++ b/rust/config/Android.bp
@@ -13,6 +13,7 @@
"toolchain.go",
"allowed_list.go",
"x86_darwin_host.go",
+ "x86_linux_bionic_host.go",
"x86_linux_host.go",
"x86_device.go",
"x86_64_device.go",
diff --git a/rust/config/x86_linux_bionic_host.go b/rust/config/x86_linux_bionic_host.go
new file mode 100644
index 0000000..b1a2c17
--- /dev/null
+++ b/rust/config/x86_linux_bionic_host.go
@@ -0,0 +1,73 @@
+// Copyright 2020 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.
+
+package config
+
+import (
+ "strings"
+
+ "android/soong/android"
+)
+
+var (
+ LinuxBionicRustFlags = []string{}
+ LinuxBionicRustLinkFlags = []string{
+ "-B${cc_config.ClangBin}",
+ "-fuse-ld=lld",
+ "-Wl,--undefined-version",
+ "-nostdlib",
+ }
+)
+
+func init() {
+ registerToolchainFactory(android.LinuxBionic, android.X86_64, linuxBionicX8664ToolchainFactory)
+
+ pctx.StaticVariable("LinuxBionicToolchainRustFlags", strings.Join(LinuxBionicRustFlags, " "))
+ pctx.StaticVariable("LinuxBionicToolchainLinkFlags", strings.Join(LinuxBionicRustLinkFlags, " "))
+}
+
+type toolchainLinuxBionicX8664 struct {
+ toolchain64Bit
+}
+
+func (toolchainLinuxBionicX8664) Supported() bool {
+ return true
+}
+
+func (toolchainLinuxBionicX8664) Bionic() bool {
+ return true
+}
+
+func (t *toolchainLinuxBionicX8664) Name() string {
+ return "x86_64"
+}
+
+func (t *toolchainLinuxBionicX8664) RustTriple() string {
+ return "x86_64-linux-android"
+}
+
+func (t *toolchainLinuxBionicX8664) ToolchainLinkFlags() string {
+ // Prepend the lld flags from cc_config so we stay in sync with cc
+ return "${cc_config.LinuxBionicLldflags} ${config.LinuxBionicToolchainLinkFlags}"
+}
+
+func (t *toolchainLinuxBionicX8664) ToolchainRustFlags() string {
+ return "${config.LinuxBionicToolchainRustFlags}"
+}
+
+func linuxBionicX8664ToolchainFactory(arch android.Arch) Toolchain {
+ return toolchainLinuxBionicX8664Singleton
+}
+
+var toolchainLinuxBionicX8664Singleton Toolchain = &toolchainLinuxBionicX8664{}
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index 8a5a2b7..d9c2266 100644
--- a/ui/build/bazel.go
+++ b/ui/build/bazel.go
@@ -169,8 +169,11 @@
fmt.Fprintf(bazelEnvStringBuffer, "%s=%s ", k, v)
}
- // Print the full command line (including environment variables) for debugging purposes.
- ctx.Println("Bazel command line: " + bazelEnvStringBuffer.String() + cmd.Cmd.String() + "\n")
+ // Print the implicit command line
+ ctx.Println("Bazel implicit command line: " + strings.Join(cmd.Environment.Environ(), " ") + " " + cmd.Cmd.String() + "\n")
+
+ // Print the explicit command line too
+ ctx.Println("Bazel explicit command line: " + bazelEnvStringBuffer.String() + cmd.Cmd.String() + "\n")
// Execute the build command.
cmd.RunAndStreamOrFatal()
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 3164680..41acc26 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -68,6 +68,7 @@
miniBootstrapDir := filepath.Join(outDir, "soong", ".minibootstrap")
modulePathsDir := filepath.Join(outDir, ".module_paths")
variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
+
// dexpreopt.config is an input to the soong_docs action, which runs the
// soong_build primary builder. However, this file is created from $(shell)
// invocation at Kati parse time, so it's not an explicit output of any
@@ -75,6 +76,9 @@
// treated as an source file.
dexpreoptConfigFilePath := filepath.Join(outDir, "soong", "dexpreopt.config")
+ // out/build_date.txt is considered a "source file"
+ buildDatetimeFilePath := filepath.Join(outDir, "build_date.txt")
+
danglingRules := make(map[string]bool)
scanner := bufio.NewScanner(stdout)
@@ -88,7 +92,8 @@
strings.HasPrefix(line, miniBootstrapDir) ||
strings.HasPrefix(line, modulePathsDir) ||
line == variablesFilePath ||
- line == dexpreoptConfigFilePath {
+ line == dexpreoptConfigFilePath ||
+ line == buildDatetimeFilePath {
// Leaf node is in one of Soong's bootstrap directories, which do not have
// full build rules in the primary build.ninja file.
continue