bef8688e45
This reduces the extract_phonys (now deduplicate_order_only_deps) event's time from ~1.9s to ~1.5s on aosp-master, and from ~5.3s to ~4.6s on internal master. It does so by making keyForPhonyCandidate be based on a hash instead of joining all the deps together. Having a hash allows us to also use it as the name of the phony target, which simplifies the code a little. Bug: None (original cl introducing extractPhonys also didn't have a bug) Test: go tests Change-Id: I2ff6e4614f19ccbfe99112ea7ae1ea33cd1df21b
1274 lines
32 KiB
Go
1274 lines
32 KiB
Go
// Copyright 2014 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 blueprint
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/blueprint/parser"
|
|
)
|
|
|
|
type Walker interface {
|
|
Walk() bool
|
|
}
|
|
|
|
func walkDependencyGraph(ctx *Context, topModule *moduleInfo, allowDuplicates bool) (string, string) {
|
|
var outputDown string
|
|
var outputUp string
|
|
ctx.walkDeps(topModule, allowDuplicates,
|
|
func(dep depInfo, parent *moduleInfo) bool {
|
|
outputDown += ctx.ModuleName(dep.module.logicModule)
|
|
if tag, ok := dep.tag.(walkerDepsTag); ok {
|
|
if !tag.follow {
|
|
return false
|
|
}
|
|
}
|
|
if dep.module.logicModule.(Walker).Walk() {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
},
|
|
func(dep depInfo, parent *moduleInfo) {
|
|
outputUp += ctx.ModuleName(dep.module.logicModule)
|
|
})
|
|
return outputDown, outputUp
|
|
}
|
|
|
|
type depsProvider interface {
|
|
Deps() []string
|
|
IgnoreDeps() []string
|
|
}
|
|
|
|
type fooModule struct {
|
|
SimpleName
|
|
properties struct {
|
|
Deps []string
|
|
Ignored_deps []string
|
|
Foo string
|
|
}
|
|
}
|
|
|
|
func newFooModule() (Module, []interface{}) {
|
|
m := &fooModule{}
|
|
return m, []interface{}{&m.properties, &m.SimpleName.Properties}
|
|
}
|
|
|
|
func (f *fooModule) GenerateBuildActions(ModuleContext) {
|
|
}
|
|
|
|
func (f *fooModule) Deps() []string {
|
|
return f.properties.Deps
|
|
}
|
|
|
|
func (f *fooModule) IgnoreDeps() []string {
|
|
return f.properties.Ignored_deps
|
|
}
|
|
|
|
func (f *fooModule) Foo() string {
|
|
return f.properties.Foo
|
|
}
|
|
|
|
func (f *fooModule) Walk() bool {
|
|
return true
|
|
}
|
|
|
|
type barModule struct {
|
|
SimpleName
|
|
properties struct {
|
|
Deps []string
|
|
Ignored_deps []string
|
|
Bar bool
|
|
}
|
|
}
|
|
|
|
func newBarModule() (Module, []interface{}) {
|
|
m := &barModule{}
|
|
return m, []interface{}{&m.properties, &m.SimpleName.Properties}
|
|
}
|
|
|
|
func (b *barModule) Deps() []string {
|
|
return b.properties.Deps
|
|
}
|
|
|
|
func (b *barModule) IgnoreDeps() []string {
|
|
return b.properties.Ignored_deps
|
|
}
|
|
|
|
func (b *barModule) GenerateBuildActions(ModuleContext) {
|
|
}
|
|
|
|
func (b *barModule) Bar() bool {
|
|
return b.properties.Bar
|
|
}
|
|
|
|
func (b *barModule) Walk() bool {
|
|
return false
|
|
}
|
|
|
|
type walkerDepsTag struct {
|
|
BaseDependencyTag
|
|
// True if the dependency should be followed, false otherwise.
|
|
follow bool
|
|
}
|
|
|
|
func depsMutator(mctx BottomUpMutatorContext) {
|
|
if m, ok := mctx.Module().(depsProvider); ok {
|
|
mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: false}, m.IgnoreDeps()...)
|
|
mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: true}, m.Deps()...)
|
|
}
|
|
}
|
|
|
|
func TestContextParse(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.RegisterModuleType("foo_module", newFooModule)
|
|
ctx.RegisterModuleType("bar_module", newBarModule)
|
|
|
|
r := bytes.NewBufferString(`
|
|
foo_module {
|
|
name: "MyFooModule",
|
|
deps: ["MyBarModule"],
|
|
}
|
|
|
|
bar_module {
|
|
name: "MyBarModule",
|
|
}
|
|
`)
|
|
|
|
_, _, errs := ctx.parseOne(".", "Blueprint", r, parser.NewScope(nil), nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected parse errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
_, errs = ctx.ResolveDependencies(nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected dep errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
}
|
|
|
|
// > |===B---D - represents a non-walkable edge
|
|
// > A = represents a walkable edge
|
|
// > |===C===E---G
|
|
// > | | A should not be visited because it's the root node.
|
|
// > |===F===| B, D and E should not be walked.
|
|
func TestWalkDeps(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.MockFileSystem(map[string][]byte{
|
|
"Android.bp": []byte(`
|
|
foo_module {
|
|
name: "A",
|
|
deps: ["B", "C"],
|
|
}
|
|
|
|
bar_module {
|
|
name: "B",
|
|
deps: ["D"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "C",
|
|
deps: ["E", "F"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "D",
|
|
}
|
|
|
|
bar_module {
|
|
name: "E",
|
|
deps: ["G"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "F",
|
|
deps: ["G"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "G",
|
|
}
|
|
`),
|
|
})
|
|
|
|
ctx.RegisterModuleType("foo_module", newFooModule)
|
|
ctx.RegisterModuleType("bar_module", newBarModule)
|
|
ctx.RegisterBottomUpMutator("deps", depsMutator)
|
|
_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected parse errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
_, errs = ctx.ResolveDependencies(nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected dep errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
|
|
outputDown, outputUp := walkDependencyGraph(ctx, topModule, false)
|
|
if outputDown != "BCEFG" {
|
|
t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown)
|
|
}
|
|
if outputUp != "BEGFC" {
|
|
t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BEGFC", outputUp)
|
|
}
|
|
}
|
|
|
|
// > |===B---D - represents a non-walkable edge
|
|
// > A = represents a walkable edge
|
|
// > |===C===E===\ A should not be visited because it's the root node.
|
|
// > | | B, D should not be walked.
|
|
// > |===F===G===H G should be visited multiple times
|
|
// > \===/ H should only be visited once
|
|
func TestWalkDepsDuplicates(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.MockFileSystem(map[string][]byte{
|
|
"Android.bp": []byte(`
|
|
foo_module {
|
|
name: "A",
|
|
deps: ["B", "C"],
|
|
}
|
|
|
|
bar_module {
|
|
name: "B",
|
|
deps: ["D"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "C",
|
|
deps: ["E", "F"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "D",
|
|
}
|
|
|
|
foo_module {
|
|
name: "E",
|
|
deps: ["G"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "F",
|
|
deps: ["G", "G"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "G",
|
|
deps: ["H"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "H",
|
|
}
|
|
`),
|
|
})
|
|
|
|
ctx.RegisterModuleType("foo_module", newFooModule)
|
|
ctx.RegisterModuleType("bar_module", newBarModule)
|
|
ctx.RegisterBottomUpMutator("deps", depsMutator)
|
|
_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected parse errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
_, errs = ctx.ResolveDependencies(nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected dep errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
|
|
outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
|
|
if outputDown != "BCEGHFGG" {
|
|
t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown)
|
|
}
|
|
if outputUp != "BHGEGGFC" {
|
|
t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BHGEGGFC", outputUp)
|
|
}
|
|
}
|
|
|
|
// > - represents a non-walkable edge
|
|
// > A = represents a walkable edge
|
|
// > |===B-------\ A should not be visited because it's the root node.
|
|
// > | | B -> D should not be walked.
|
|
// > |===C===D===E B -> C -> D -> E should be walked
|
|
func TestWalkDepsDuplicates_IgnoreFirstPath(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.MockFileSystem(map[string][]byte{
|
|
"Android.bp": []byte(`
|
|
foo_module {
|
|
name: "A",
|
|
deps: ["B"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "B",
|
|
deps: ["C"],
|
|
ignored_deps: ["D"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "C",
|
|
deps: ["D"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "D",
|
|
deps: ["E"],
|
|
}
|
|
|
|
foo_module {
|
|
name: "E",
|
|
}
|
|
`),
|
|
})
|
|
|
|
ctx.RegisterModuleType("foo_module", newFooModule)
|
|
ctx.RegisterModuleType("bar_module", newBarModule)
|
|
ctx.RegisterBottomUpMutator("deps", depsMutator)
|
|
_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected parse errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
_, errs = ctx.ResolveDependencies(nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected dep errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
|
|
outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
|
|
expectedDown := "BDCDE"
|
|
if outputDown != expectedDown {
|
|
t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: %s", outputDown, expectedDown)
|
|
}
|
|
expectedUp := "DEDCB"
|
|
if outputUp != expectedUp {
|
|
t.Errorf("unexpected walkDeps behaviour: %s\nup should be: %s", outputUp, expectedUp)
|
|
}
|
|
}
|
|
|
|
func TestCreateModule(t *testing.T) {
|
|
ctx := newContext()
|
|
ctx.MockFileSystem(map[string][]byte{
|
|
"Android.bp": []byte(`
|
|
foo_module {
|
|
name: "A",
|
|
deps: ["B", "C"],
|
|
}
|
|
`),
|
|
})
|
|
|
|
ctx.RegisterTopDownMutator("create", createTestMutator)
|
|
ctx.RegisterBottomUpMutator("deps", depsMutator)
|
|
|
|
ctx.RegisterModuleType("foo_module", newFooModule)
|
|
ctx.RegisterModuleType("bar_module", newBarModule)
|
|
_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected parse errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
_, errs = ctx.ResolveDependencies(nil)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected dep errors:")
|
|
for _, err := range errs {
|
|
t.Errorf(" %s", err)
|
|
}
|
|
t.FailNow()
|
|
}
|
|
|
|
a := ctx.moduleGroupFromName("A", nil).modules.firstModule().logicModule.(*fooModule)
|
|
b := ctx.moduleGroupFromName("B", nil).modules.firstModule().logicModule.(*barModule)
|
|
c := ctx.moduleGroupFromName("C", nil).modules.firstModule().logicModule.(*barModule)
|
|
d := ctx.moduleGroupFromName("D", nil).modules.firstModule().logicModule.(*fooModule)
|
|
|
|
checkDeps := func(m Module, expected string) {
|
|
var deps []string
|
|
ctx.VisitDirectDeps(m, func(m Module) {
|
|
deps = append(deps, ctx.ModuleName(m))
|
|
})
|
|
got := strings.Join(deps, ",")
|
|
if got != expected {
|
|
t.Errorf("unexpected %q dependencies, got %q expected %q",
|
|
ctx.ModuleName(m), got, expected)
|
|
}
|
|
}
|
|
|
|
checkDeps(a, "B,C")
|
|
checkDeps(b, "D")
|
|
checkDeps(c, "D")
|
|
checkDeps(d, "")
|
|
}
|
|
|
|
func createTestMutator(ctx TopDownMutatorContext) {
|
|
type props struct {
|
|
Name string
|
|
Deps []string
|
|
}
|
|
|
|
ctx.CreateModule(newBarModule, "new_bar", &props{
|
|
Name: "B",
|
|
Deps: []string{"D"},
|
|
})
|
|
|
|
ctx.CreateModule(newBarModule, "new_bar", &props{
|
|
Name: "C",
|
|
Deps: []string{"D"},
|
|
})
|
|
|
|
ctx.CreateModule(newFooModule, "new_foo", &props{
|
|
Name: "D",
|
|
})
|
|
}
|
|
|
|
func TestWalkFileOrder(t *testing.T) {
|
|
// Run the test once to see how long it normally takes
|
|
start := time.Now()
|
|
doTestWalkFileOrder(t, time.Duration(0))
|
|
duration := time.Since(start)
|
|
|
|
// Run the test again, but put enough of a sleep into each visitor to detect ordering
|
|
// problems if they exist
|
|
doTestWalkFileOrder(t, duration)
|
|
}
|
|
|
|
// test that WalkBlueprintsFiles calls asyncVisitor in the right order
|
|
func doTestWalkFileOrder(t *testing.T, sleepDuration time.Duration) {
|
|
// setup mock context
|
|
ctx := newContext()
|
|
mockFiles := map[string][]byte{
|
|
"Android.bp": []byte(`
|
|
sample_module {
|
|
name: "a",
|
|
}
|
|
`),
|
|
"dir1/Android.bp": []byte(`
|
|
sample_module {
|
|
name: "b",
|
|
}
|
|
`),
|
|
"dir1/dir2/Android.bp": []byte(`
|
|
sample_module {
|
|
name: "c",
|
|
}
|
|
`),
|
|
}
|
|
ctx.MockFileSystem(mockFiles)
|
|
|
|
// prepare to monitor the visit order
|
|
visitOrder := []string{}
|
|
visitLock := sync.Mutex{}
|
|
correctVisitOrder := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"}
|
|
|
|
// sleep longer when processing the earlier files
|
|
chooseSleepDuration := func(fileName string) (duration time.Duration) {
|
|
duration = time.Duration(0)
|
|
for i := len(correctVisitOrder) - 1; i >= 0; i-- {
|
|
if fileName == correctVisitOrder[i] {
|
|
return duration
|
|
}
|
|
duration = duration + sleepDuration
|
|
}
|
|
panic("unrecognized file name " + fileName)
|
|
}
|
|
|
|
visitor := func(file *parser.File) {
|
|
time.Sleep(chooseSleepDuration(file.Name))
|
|
visitLock.Lock()
|
|
defer visitLock.Unlock()
|
|
visitOrder = append(visitOrder, file.Name)
|
|
}
|
|
keys := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"}
|
|
|
|
// visit the blueprints files
|
|
ctx.WalkBlueprintsFiles(".", keys, visitor)
|
|
|
|
// check the order
|
|
if !reflect.DeepEqual(visitOrder, correctVisitOrder) {
|
|
t.Errorf("Incorrect visit order; expected %v, got %v", correctVisitOrder, visitOrder)
|
|
}
|
|
}
|
|
|
|
// test that WalkBlueprintsFiles reports syntax errors
|
|
func TestWalkingWithSyntaxError(t *testing.T) {
|
|
// setup mock context
|
|
ctx := newContext()
|
|
mockFiles := map[string][]byte{
|
|
"Android.bp": []byte(`
|
|
sample_module {
|
|
name: "a" "b",
|
|
}
|
|
`),
|
|
"dir1/Android.bp": []byte(`
|
|
sample_module {
|
|
name: "b",
|
|
`),
|
|
"dir1/dir2/Android.bp": []byte(`
|
|
sample_module {
|
|
name: "c",
|
|
}
|
|
`),
|
|
}
|
|
ctx.MockFileSystem(mockFiles)
|
|
|
|
keys := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"}
|
|
|
|
// visit the blueprints files
|
|
_, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {})
|
|
|
|
expectedErrs := []error{
|
|
errors.New(`Android.bp:3:18: expected "}", found String`),
|
|
errors.New(`dir1/Android.bp:4:3: expected "}", found EOF`),
|
|
}
|
|
if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
|
|
t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
|
|
}
|
|
|
|
}
|
|
|
|
func TestParseFailsForModuleWithoutName(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.MockFileSystem(map[string][]byte{
|
|
"Android.bp": []byte(`
|
|
foo_module {
|
|
name: "A",
|
|
}
|
|
|
|
bar_module {
|
|
deps: ["A"],
|
|
}
|
|
`),
|
|
})
|
|
ctx.RegisterModuleType("foo_module", newFooModule)
|
|
ctx.RegisterModuleType("bar_module", newBarModule)
|
|
|
|
_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
|
|
|
|
expectedErrs := []error{
|
|
errors.New(`Android.bp:6:4: property 'name' is missing from a module`),
|
|
}
|
|
if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
|
|
t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
|
|
}
|
|
}
|
|
|
|
func Test_findVariant(t *testing.T) {
|
|
module := &moduleInfo{
|
|
variant: variant{
|
|
name: "normal_local",
|
|
variations: variationMap{
|
|
"normal": "normal",
|
|
"local": "local",
|
|
},
|
|
dependencyVariations: variationMap{
|
|
"normal": "normal",
|
|
},
|
|
},
|
|
}
|
|
|
|
type alias struct {
|
|
variant variant
|
|
target int
|
|
}
|
|
|
|
makeDependencyGroup := func(in ...interface{}) *moduleGroup {
|
|
group := &moduleGroup{
|
|
name: "dep",
|
|
}
|
|
for _, x := range in {
|
|
switch m := x.(type) {
|
|
case *moduleInfo:
|
|
m.group = group
|
|
group.modules = append(group.modules, m)
|
|
case alias:
|
|
// aliases may need to target modules that haven't been processed
|
|
// yet, put an empty alias in for now.
|
|
group.modules = append(group.modules, nil)
|
|
default:
|
|
t.Fatalf("unexpected type %T", x)
|
|
}
|
|
}
|
|
|
|
for i, x := range in {
|
|
switch m := x.(type) {
|
|
case *moduleInfo:
|
|
// already added in the first pass
|
|
case alias:
|
|
group.modules[i] = &moduleAlias{
|
|
variant: m.variant,
|
|
target: group.modules[m.target].moduleOrAliasTarget(),
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected type %T", x)
|
|
}
|
|
}
|
|
|
|
return group
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
possibleDeps *moduleGroup
|
|
variations []Variation
|
|
far bool
|
|
reverse bool
|
|
want string
|
|
}{
|
|
{
|
|
name: "AddVariationDependencies(nil)",
|
|
// A dependency that matches the non-local variations of the module
|
|
possibleDeps: makeDependencyGroup(
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "normal",
|
|
variations: variationMap{
|
|
"normal": "normal",
|
|
},
|
|
},
|
|
},
|
|
),
|
|
variations: nil,
|
|
far: false,
|
|
reverse: false,
|
|
want: "normal",
|
|
},
|
|
{
|
|
name: "AddVariationDependencies(nil) to alias",
|
|
// A dependency with an alias that matches the non-local variations of the module
|
|
possibleDeps: makeDependencyGroup(
|
|
alias{
|
|
variant: variant{
|
|
name: "normal",
|
|
variations: variationMap{
|
|
"normal": "normal",
|
|
},
|
|
},
|
|
target: 1,
|
|
},
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "normal_a",
|
|
variations: variationMap{
|
|
"normal": "normal",
|
|
"a": "a",
|
|
},
|
|
},
|
|
},
|
|
),
|
|
variations: nil,
|
|
far: false,
|
|
reverse: false,
|
|
want: "normal_a",
|
|
},
|
|
{
|
|
name: "AddVariationDependencies(a)",
|
|
// A dependency with local variations
|
|
possibleDeps: makeDependencyGroup(
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "normal_a",
|
|
variations: variationMap{
|
|
"normal": "normal",
|
|
"a": "a",
|
|
},
|
|
},
|
|
},
|
|
),
|
|
variations: []Variation{{"a", "a"}},
|
|
far: false,
|
|
reverse: false,
|
|
want: "normal_a",
|
|
},
|
|
{
|
|
name: "AddFarVariationDependencies(far)",
|
|
// A dependency with far variations
|
|
possibleDeps: makeDependencyGroup(
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "",
|
|
variations: nil,
|
|
},
|
|
},
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "far",
|
|
variations: variationMap{
|
|
"far": "far",
|
|
},
|
|
},
|
|
},
|
|
),
|
|
variations: []Variation{{"far", "far"}},
|
|
far: true,
|
|
reverse: false,
|
|
want: "far",
|
|
},
|
|
{
|
|
name: "AddFarVariationDependencies(far) to alias",
|
|
// A dependency with far variations and aliases
|
|
possibleDeps: makeDependencyGroup(
|
|
alias{
|
|
variant: variant{
|
|
name: "far",
|
|
variations: variationMap{
|
|
"far": "far",
|
|
},
|
|
},
|
|
target: 2,
|
|
},
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "far_a",
|
|
variations: variationMap{
|
|
"far": "far",
|
|
"a": "a",
|
|
},
|
|
},
|
|
},
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "far_b",
|
|
variations: variationMap{
|
|
"far": "far",
|
|
"b": "b",
|
|
},
|
|
},
|
|
},
|
|
),
|
|
variations: []Variation{{"far", "far"}},
|
|
far: true,
|
|
reverse: false,
|
|
want: "far_b",
|
|
},
|
|
{
|
|
name: "AddFarVariationDependencies(far, b) to missing",
|
|
// A dependency with far variations and aliases
|
|
possibleDeps: makeDependencyGroup(
|
|
alias{
|
|
variant: variant{
|
|
name: "far",
|
|
variations: variationMap{
|
|
"far": "far",
|
|
},
|
|
},
|
|
target: 1,
|
|
},
|
|
&moduleInfo{
|
|
variant: variant{
|
|
name: "far_a",
|
|
variations: variationMap{
|
|
"far": "far",
|
|
"a": "a",
|
|
},
|
|
},
|
|
},
|
|
),
|
|
variations: []Variation{{"far", "far"}, {"a", "b"}},
|
|
far: true,
|
|
reverse: false,
|
|
want: "nil",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, _ := findVariant(module, tt.possibleDeps, tt.variations, tt.far, tt.reverse)
|
|
if g, w := got == nil, tt.want == "nil"; g != w {
|
|
t.Fatalf("findVariant() got = %v, want %v", got, tt.want)
|
|
}
|
|
if got != nil {
|
|
if g, w := got.String(), fmt.Sprintf("module %q variant %q", "dep", tt.want); g != w {
|
|
t.Errorf("findVariant() got = %v, want %v", g, w)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_parallelVisit(t *testing.T) {
|
|
addDep := func(from, to *moduleInfo) {
|
|
from.directDeps = append(from.directDeps, depInfo{to, nil})
|
|
from.forwardDeps = append(from.forwardDeps, to)
|
|
to.reverseDeps = append(to.reverseDeps, from)
|
|
}
|
|
|
|
create := func(name string) *moduleInfo {
|
|
m := &moduleInfo{
|
|
group: &moduleGroup{
|
|
name: name,
|
|
},
|
|
}
|
|
m.group.modules = modulesOrAliases{m}
|
|
return m
|
|
}
|
|
moduleA := create("A")
|
|
moduleB := create("B")
|
|
moduleC := create("C")
|
|
moduleD := create("D")
|
|
moduleE := create("E")
|
|
moduleF := create("F")
|
|
moduleG := create("G")
|
|
|
|
// A depends on B, B depends on C. Nothing depends on D through G, and they don't depend on
|
|
// anything.
|
|
addDep(moduleA, moduleB)
|
|
addDep(moduleB, moduleC)
|
|
|
|
t.Run("no modules", func(t *testing.T) {
|
|
errs := parallelVisit(nil, bottomUpVisitorImpl{}, 1,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
panic("unexpected call to visitor")
|
|
})
|
|
if errs != nil {
|
|
t.Errorf("expected no errors, got %q", errs)
|
|
}
|
|
})
|
|
t.Run("bottom up", func(t *testing.T) {
|
|
order := ""
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
order += module.group.name
|
|
return false
|
|
})
|
|
if errs != nil {
|
|
t.Errorf("expected no errors, got %q", errs)
|
|
}
|
|
if g, w := order, "CBA"; g != w {
|
|
t.Errorf("expected order %q, got %q", w, g)
|
|
}
|
|
})
|
|
t.Run("pause", func(t *testing.T) {
|
|
order := ""
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
if module == moduleC {
|
|
// Pause module C on module D
|
|
unpause := make(chan struct{})
|
|
pause <- pauseSpec{moduleC, moduleD, unpause}
|
|
<-unpause
|
|
}
|
|
order += module.group.name
|
|
return false
|
|
})
|
|
if errs != nil {
|
|
t.Errorf("expected no errors, got %q", errs)
|
|
}
|
|
if g, w := order, "DCBA"; g != w {
|
|
t.Errorf("expected order %q, got %q", w, g)
|
|
}
|
|
})
|
|
t.Run("cancel", func(t *testing.T) {
|
|
order := ""
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
order += module.group.name
|
|
// Cancel in module B
|
|
return module == moduleB
|
|
})
|
|
if errs != nil {
|
|
t.Errorf("expected no errors, got %q", errs)
|
|
}
|
|
if g, w := order, "CB"; g != w {
|
|
t.Errorf("expected order %q, got %q", w, g)
|
|
}
|
|
})
|
|
t.Run("pause and cancel", func(t *testing.T) {
|
|
order := ""
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
if module == moduleC {
|
|
// Pause module C on module D
|
|
unpause := make(chan struct{})
|
|
pause <- pauseSpec{moduleC, moduleD, unpause}
|
|
<-unpause
|
|
}
|
|
order += module.group.name
|
|
// Cancel in module D
|
|
return module == moduleD
|
|
})
|
|
if errs != nil {
|
|
t.Errorf("expected no errors, got %q", errs)
|
|
}
|
|
if g, w := order, "D"; g != w {
|
|
t.Errorf("expected order %q, got %q", w, g)
|
|
}
|
|
})
|
|
t.Run("parallel", func(t *testing.T) {
|
|
order := ""
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
order += module.group.name
|
|
return false
|
|
})
|
|
if errs != nil {
|
|
t.Errorf("expected no errors, got %q", errs)
|
|
}
|
|
if g, w := order, "CBA"; g != w {
|
|
t.Errorf("expected order %q, got %q", w, g)
|
|
}
|
|
})
|
|
t.Run("pause existing", func(t *testing.T) {
|
|
order := ""
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
if module == moduleA {
|
|
// Pause module A on module B (an existing dependency)
|
|
unpause := make(chan struct{})
|
|
pause <- pauseSpec{moduleA, moduleB, unpause}
|
|
<-unpause
|
|
}
|
|
order += module.group.name
|
|
return false
|
|
})
|
|
if errs != nil {
|
|
t.Errorf("expected no errors, got %q", errs)
|
|
}
|
|
if g, w := order, "CBA"; g != w {
|
|
t.Errorf("expected order %q, got %q", w, g)
|
|
}
|
|
})
|
|
t.Run("cycle", func(t *testing.T) {
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
if module == moduleC {
|
|
// Pause module C on module A (a dependency cycle)
|
|
unpause := make(chan struct{})
|
|
pause <- pauseSpec{moduleC, moduleA, unpause}
|
|
<-unpause
|
|
}
|
|
return false
|
|
})
|
|
want := []string{
|
|
`encountered dependency cycle`,
|
|
`module "C" depends on module "A"`,
|
|
`module "A" depends on module "B"`,
|
|
`module "B" depends on module "C"`,
|
|
}
|
|
for i := range want {
|
|
if len(errs) <= i {
|
|
t.Errorf("missing error %s", want[i])
|
|
} else if !strings.Contains(errs[i].Error(), want[i]) {
|
|
t.Errorf("expected error %s, got %s", want[i], errs[i])
|
|
}
|
|
}
|
|
if len(errs) > len(want) {
|
|
for _, err := range errs[len(want):] {
|
|
t.Errorf("unexpected error %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
t.Run("pause cycle", func(t *testing.T) {
|
|
errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 3,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
if module == moduleC {
|
|
// Pause module C on module D
|
|
unpause := make(chan struct{})
|
|
pause <- pauseSpec{moduleC, moduleD, unpause}
|
|
<-unpause
|
|
}
|
|
if module == moduleD {
|
|
// Pause module D on module C (a pause cycle)
|
|
unpause := make(chan struct{})
|
|
pause <- pauseSpec{moduleD, moduleC, unpause}
|
|
<-unpause
|
|
}
|
|
return false
|
|
})
|
|
want := []string{
|
|
`encountered dependency cycle`,
|
|
`module "D" depends on module "C"`,
|
|
`module "C" depends on module "D"`,
|
|
}
|
|
for i := range want {
|
|
if len(errs) <= i {
|
|
t.Errorf("missing error %s", want[i])
|
|
} else if !strings.Contains(errs[i].Error(), want[i]) {
|
|
t.Errorf("expected error %s, got %s", want[i], errs[i])
|
|
}
|
|
}
|
|
if len(errs) > len(want) {
|
|
for _, err := range errs[len(want):] {
|
|
t.Errorf("unexpected error %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
t.Run("pause cycle with deps", func(t *testing.T) {
|
|
pauseDeps := map[*moduleInfo]*moduleInfo{
|
|
// F and G form a pause cycle
|
|
moduleF: moduleG,
|
|
moduleG: moduleF,
|
|
// D depends on E which depends on the pause cycle, making E the first alphabetical
|
|
// entry in pauseMap, which is not part of the cycle.
|
|
moduleD: moduleE,
|
|
moduleE: moduleF,
|
|
}
|
|
errs := parallelVisit([]*moduleInfo{moduleD, moduleE, moduleF, moduleG}, bottomUpVisitorImpl{}, 4,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
if dep, ok := pauseDeps[module]; ok {
|
|
unpause := make(chan struct{})
|
|
pause <- pauseSpec{module, dep, unpause}
|
|
<-unpause
|
|
}
|
|
return false
|
|
})
|
|
want := []string{
|
|
`encountered dependency cycle`,
|
|
`module "G" depends on module "F"`,
|
|
`module "F" depends on module "G"`,
|
|
}
|
|
for i := range want {
|
|
if len(errs) <= i {
|
|
t.Errorf("missing error %s", want[i])
|
|
} else if !strings.Contains(errs[i].Error(), want[i]) {
|
|
t.Errorf("expected error %s, got %s", want[i], errs[i])
|
|
}
|
|
}
|
|
if len(errs) > len(want) {
|
|
for _, err := range errs[len(want):] {
|
|
t.Errorf("unexpected error %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPackageIncludes(t *testing.T) {
|
|
dir1_foo_bp := `
|
|
blueprint_package_includes {
|
|
match_all: ["use_dir1"],
|
|
}
|
|
foo_module {
|
|
name: "foo",
|
|
}
|
|
`
|
|
dir2_foo_bp := `
|
|
blueprint_package_includes {
|
|
match_all: ["use_dir2"],
|
|
}
|
|
foo_module {
|
|
name: "foo",
|
|
}
|
|
`
|
|
mockFs := map[string][]byte{
|
|
"dir1/Android.bp": []byte(dir1_foo_bp),
|
|
"dir2/Android.bp": []byte(dir2_foo_bp),
|
|
}
|
|
testCases := []struct {
|
|
desc string
|
|
includeTags []string
|
|
expectedDir string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
desc: "use_dir1 is set, use dir1 foo",
|
|
includeTags: []string{"use_dir1"},
|
|
expectedDir: "dir1",
|
|
},
|
|
{
|
|
desc: "use_dir2 is set, use dir2 foo",
|
|
includeTags: []string{"use_dir2"},
|
|
expectedDir: "dir2",
|
|
},
|
|
{
|
|
desc: "duplicate module error if both use_dir1 and use_dir2 are set",
|
|
includeTags: []string{"use_dir1", "use_dir2"},
|
|
expectedDir: "",
|
|
expectedErr: `module "foo" already defined`,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
ctx := NewContext()
|
|
// Register mock FS
|
|
ctx.MockFileSystem(mockFs)
|
|
// Register module types
|
|
ctx.RegisterModuleType("foo_module", newFooModule)
|
|
RegisterPackageIncludesModuleType(ctx)
|
|
// Add include tags for test case
|
|
ctx.AddIncludeTags(tc.includeTags...)
|
|
// Run test
|
|
_, actualErrs := ctx.ParseFileList(".", []string{"dir1/Android.bp", "dir2/Android.bp"}, nil)
|
|
// Evaluate
|
|
if !strings.Contains(fmt.Sprintf("%s", actualErrs), fmt.Sprintf("%s", tc.expectedErr)) {
|
|
t.Errorf("Expected errors: %s, got errors: %s\n", tc.expectedErr, actualErrs)
|
|
}
|
|
if tc.expectedErr != "" {
|
|
return // expectedDir check not necessary
|
|
}
|
|
actualBpFile := ctx.moduleGroupFromName("foo", nil).modules.firstModule().relBlueprintsFile
|
|
if tc.expectedDir != filepath.Dir(actualBpFile) {
|
|
t.Errorf("Expected foo from %s, got %s\n", tc.expectedDir, filepath.Dir(actualBpFile))
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestDeduplicateOrderOnlyDeps(t *testing.T) {
|
|
outputs := func(names ...string) []ninjaString {
|
|
r := make([]ninjaString, len(names))
|
|
for i, name := range names {
|
|
r[i] = literalNinjaString(name)
|
|
}
|
|
return r
|
|
}
|
|
b := func(output string, inputs []string, orderOnlyDeps []string) *buildDef {
|
|
return &buildDef{
|
|
Outputs: outputs(output),
|
|
Inputs: outputs(inputs...),
|
|
OrderOnly: outputs(orderOnlyDeps...),
|
|
}
|
|
}
|
|
m := func(bs ...*buildDef) *moduleInfo {
|
|
return &moduleInfo{actionDefs: localBuildActions{buildDefs: bs}}
|
|
}
|
|
type testcase struct {
|
|
modules []*moduleInfo
|
|
expectedPhonys []*buildDef
|
|
conversions map[string][]ninjaString
|
|
}
|
|
testCases := []testcase{{
|
|
modules: []*moduleInfo{
|
|
m(b("A", nil, []string{"d"})),
|
|
m(b("B", nil, []string{"d"})),
|
|
},
|
|
expectedPhonys: []*buildDef{
|
|
b("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ", []string{"d"}, nil),
|
|
},
|
|
conversions: map[string][]ninjaString{
|
|
"A": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"),
|
|
"B": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"),
|
|
},
|
|
}, {
|
|
modules: []*moduleInfo{
|
|
m(b("A", nil, []string{"a"})),
|
|
m(b("B", nil, []string{"b"})),
|
|
},
|
|
}, {
|
|
modules: []*moduleInfo{
|
|
m(b("A", nil, []string{"a"})),
|
|
m(b("B", nil, []string{"b"})),
|
|
m(b("C", nil, []string{"a"})),
|
|
},
|
|
expectedPhonys: []*buildDef{b("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs", []string{"a"}, nil)},
|
|
conversions: map[string][]ninjaString{
|
|
"A": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"),
|
|
"B": outputs("b"),
|
|
"C": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"),
|
|
},
|
|
}, {
|
|
modules: []*moduleInfo{
|
|
m(b("A", nil, []string{"a", "b"}),
|
|
b("B", nil, []string{"a", "b"})),
|
|
m(b("C", nil, []string{"a", "c"}),
|
|
b("D", nil, []string{"a", "c"})),
|
|
},
|
|
expectedPhonys: []*buildDef{
|
|
b("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM", []string{"a", "b"}, nil),
|
|
b("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E", []string{"a", "c"}, nil)},
|
|
conversions: map[string][]ninjaString{
|
|
"A": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"),
|
|
"B": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"),
|
|
"C": outputs("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E"),
|
|
"D": outputs("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E"),
|
|
},
|
|
}}
|
|
for index, tc := range testCases {
|
|
t.Run(fmt.Sprintf("TestCase-%d", index), func(t *testing.T) {
|
|
ctx := NewContext()
|
|
actualPhonys := ctx.deduplicateOrderOnlyDeps(tc.modules)
|
|
if len(actualPhonys.variables) != 0 {
|
|
t.Errorf("No variables expected but found %v", actualPhonys.variables)
|
|
}
|
|
if len(actualPhonys.rules) != 0 {
|
|
t.Errorf("No rules expected but found %v", actualPhonys.rules)
|
|
}
|
|
if e, a := len(tc.expectedPhonys), len(actualPhonys.buildDefs); e != a {
|
|
t.Errorf("Expected %d build statements but got %d", e, a)
|
|
}
|
|
for i := 0; i < len(tc.expectedPhonys); i++ {
|
|
a := actualPhonys.buildDefs[i]
|
|
e := tc.expectedPhonys[i]
|
|
if !reflect.DeepEqual(e.Outputs, a.Outputs) {
|
|
t.Errorf("phonys expected %v but actualPhonys %v", e.Outputs, a.Outputs)
|
|
}
|
|
if !reflect.DeepEqual(e.Inputs, a.Inputs) {
|
|
t.Errorf("phonys expected %v but actualPhonys %v", e.Inputs, a.Inputs)
|
|
}
|
|
}
|
|
find := func(k string) *buildDef {
|
|
for _, m := range tc.modules {
|
|
for _, b := range m.actionDefs.buildDefs {
|
|
if reflect.DeepEqual(b.Outputs, outputs(k)) {
|
|
return b
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
for k, conversion := range tc.conversions {
|
|
actual := find(k)
|
|
if actual == nil {
|
|
t.Errorf("Couldn't find %s", k)
|
|
}
|
|
if !reflect.DeepEqual(actual.OrderOnly, conversion) {
|
|
t.Errorf("expected %s.OrderOnly = %v but got %v", k, conversion, actual.OrderOnly)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|