diff --git a/android/Android.bp b/android/Android.bp index 6450a06ca..cfa2be38f 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -19,6 +19,9 @@ bootstrap_go_package { "soong-ui-metrics_proto", "golang-protobuf-proto", "golang-protobuf-encoding-prototext", + + // Only used for tests. + "androidmk-parser", ], srcs: [ "androidmk.go", diff --git a/android/makevars.go b/android/makevars.go index 20db65a50..7d8864ac6 100644 --- a/android/makevars.go +++ b/android/makevars.go @@ -142,15 +142,19 @@ type SingletonMakeVarsProvider interface { var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey") +func getSingletonMakevarsProviders(config Config) *[]makeVarsProvider { + return config.Once(singletonMakeVarsProvidersKey, func() interface{} { + return &[]makeVarsProvider{} + }).(*[]makeVarsProvider) +} + // registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to // the list of MakeVarsProviders to run. func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) { // Singletons are registered on the Context and may be different between different Contexts, // for example when running multiple tests. Store the SingletonMakeVarsProviders in the // Config so they are attached to the Context. - singletonMakeVarsProviders := config.Once(singletonMakeVarsProvidersKey, func() interface{} { - return &[]makeVarsProvider{} - }).(*[]makeVarsProvider) + singletonMakeVarsProviders := getSingletonMakevarsProviders(config) *singletonMakeVarsProviders = append(*singletonMakeVarsProviders, makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)}) @@ -175,7 +179,9 @@ func makeVarsSingletonFunc() Singleton { return &makeVarsSingleton{} } -type makeVarsSingleton struct{} +type makeVarsSingleton struct { + installsForTesting []byte +} type makeVarsProvider struct { pctx PackageContext @@ -238,7 +244,7 @@ func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) { var katiSymlinks []katiInstall providers := append([]makeVarsProvider(nil), makeVarsInitProviders...) - providers = append(providers, *ctx.Config().Get(singletonMakeVarsProvidersKey).(*[]makeVarsProvider)...) + providers = append(providers, *getSingletonMakevarsProviders(ctx.Config())...) for _, provider := range providers { mctx := &makeVarsContext{ @@ -313,6 +319,8 @@ func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) { if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil { ctx.Errorf(err.Error()) } + + s.installsForTesting = installsBytes } func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte { diff --git a/android/module_test.go b/android/module_test.go index 9e2b0ca29..236cae79a 100644 --- a/android/module_test.go +++ b/android/module_test.go @@ -15,7 +15,12 @@ package android import ( + "bytes" + "path/filepath" + "runtime" "testing" + + mkparser "android/soong/androidmk/parser" ) func TestSrcIsModule(t *testing.T) { @@ -199,17 +204,28 @@ type depsModule struct { } } +func (m *depsModule) InstallBypassMake() bool { + return true +} + func (m *depsModule) GenerateAndroidBuildActions(ctx ModuleContext) { + outputFile := PathForModuleOut(ctx, ctx.ModuleName()) + ctx.Build(pctx, BuildParams{ + Rule: Touch, + Output: outputFile, + }) + installFile := ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile) + ctx.InstallSymlink(PathForModuleInstall(ctx, "symlinks"), ctx.ModuleName(), installFile) } func (m *depsModule) DepsMutator(ctx BottomUpMutatorContext) { - ctx.AddDependency(ctx.Module(), nil, m.props.Deps...) + ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...) } func depsModuleFactory() Module { m := &depsModule{} m.AddProperties(&m.props) - InitAndroidModule(m) + InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon) return m } @@ -320,3 +336,282 @@ func TestDistErrorChecking(t *testing.T) { ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(expectedErrs)). RunTestWithBp(t, bp) } + +func TestInstall(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("requires linux") + } + bp := ` + deps { + name: "foo", + deps: ["bar"], + } + + deps { + name: "bar", + deps: ["baz", "qux"], + } + + deps { + name: "baz", + deps: ["qux"], + } + + deps { + name: "qux", + } + ` + + result := GroupFixturePreparers( + prepareForModuleTests, + PrepareForTestWithArchMutator, + ).RunTestWithBp(t, bp) + + module := func(name string, host bool) TestingModule { + variant := "android_common" + if host { + variant = result.Config.BuildOSCommonTarget.String() + } + return result.ModuleForTests(name, variant) + } + + outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) } + + installRule := func(name string) TestingBuildParams { + return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system", name)) + } + + symlinkRule := func(name string) TestingBuildParams { + return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system/symlinks", name)) + } + + hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) } + + hostInstallRule := func(name string) TestingBuildParams { + return module(name, true).Output(filepath.Join("out/soong/host/linux-x86", name)) + } + + hostSymlinkRule := func(name string) TestingBuildParams { + return module(name, true).Output(filepath.Join("out/soong/host/linux-x86/symlinks", name)) + } + + assertInputs := func(params TestingBuildParams, inputs ...Path) { + t.Helper() + AssertArrayString(t, "expected inputs", Paths(inputs).Strings(), + append(PathsIfNonNil(params.Input), params.Inputs...).Strings()) + } + + assertImplicits := func(params TestingBuildParams, implicits ...Path) { + t.Helper() + AssertArrayString(t, "expected implicit dependencies", Paths(implicits).Strings(), + append(PathsIfNonNil(params.Implicit), params.Implicits...).Strings()) + } + + assertOrderOnlys := func(params TestingBuildParams, orderonlys ...Path) { + t.Helper() + AssertArrayString(t, "expected orderonly dependencies", Paths(orderonlys).Strings(), + params.OrderOnly.Strings()) + } + + // Check host install rule dependencies + assertInputs(hostInstallRule("foo"), hostOutputRule("foo").Output) + assertImplicits(hostInstallRule("foo"), + hostInstallRule("bar").Output, + hostSymlinkRule("bar").Output, + hostInstallRule("baz").Output, + hostSymlinkRule("baz").Output, + hostInstallRule("qux").Output, + hostSymlinkRule("qux").Output, + ) + assertOrderOnlys(hostInstallRule("foo")) + + // Check host symlink rule dependencies + assertInputs(hostSymlinkRule("foo"), hostInstallRule("foo").Output) + assertImplicits(hostSymlinkRule("foo")) + assertOrderOnlys(hostSymlinkRule("foo")) + + // Check device install rule dependencies + assertInputs(installRule("foo"), outputRule("foo").Output) + assertImplicits(installRule("foo")) + assertOrderOnlys(installRule("foo"), + installRule("bar").Output, + symlinkRule("bar").Output, + installRule("baz").Output, + symlinkRule("baz").Output, + installRule("qux").Output, + symlinkRule("qux").Output, + ) + + // Check device symlink rule dependencies + assertInputs(symlinkRule("foo"), installRule("foo").Output) + assertImplicits(symlinkRule("foo")) + assertOrderOnlys(symlinkRule("foo")) +} + +func TestInstallBypassMake(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("requires linux") + } + bp := ` + deps { + name: "foo", + deps: ["bar"], + } + + deps { + name: "bar", + deps: ["baz", "qux"], + } + + deps { + name: "baz", + deps: ["qux"], + } + + deps { + name: "qux", + } + ` + + result := GroupFixturePreparers( + prepareForModuleTests, + PrepareForTestWithArchMutator, + FixtureModifyConfig(SetKatiEnabledForTests), + FixtureRegisterWithContext(func(ctx RegistrationContext) { + ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc) + }), + ).RunTestWithBp(t, bp) + + installs := result.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).installsForTesting + buf := bytes.NewBuffer(append([]byte(nil), installs...)) + parser := mkparser.NewParser("makevars", buf) + + nodes, errs := parser.Parse() + if len(errs) > 0 { + t.Fatalf("error parsing install rules: %s", errs[0]) + } + + rules := parseMkRules(t, result.Config, nodes) + + module := func(name string, host bool) TestingModule { + variant := "android_common" + if host { + variant = result.Config.BuildOSCommonTarget.String() + } + return result.ModuleForTests(name, variant) + } + + outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) } + + ruleForOutput := func(output string) installMakeRule { + for _, rule := range rules { + if rule.target == output { + return rule + } + } + t.Fatalf("no make install rule for %s", output) + return installMakeRule{} + } + + installRule := func(name string) installMakeRule { + return ruleForOutput(filepath.Join("out/target/product/test_device/system", name)) + } + + symlinkRule := func(name string) installMakeRule { + return ruleForOutput(filepath.Join("out/target/product/test_device/system/symlinks", name)) + } + + hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) } + + hostInstallRule := func(name string) installMakeRule { + return ruleForOutput(filepath.Join("out/host/linux-x86", name)) + } + + hostSymlinkRule := func(name string) installMakeRule { + return ruleForOutput(filepath.Join("out/host/linux-x86/symlinks", name)) + } + + assertDeps := func(rule installMakeRule, deps ...string) { + t.Helper() + AssertArrayString(t, "expected inputs", deps, rule.deps) + } + + assertOrderOnlys := func(rule installMakeRule, orderonlys ...string) { + t.Helper() + AssertArrayString(t, "expected orderonly dependencies", orderonlys, rule.orderOnlyDeps) + } + + // Check host install rule dependencies + assertDeps(hostInstallRule("foo"), + hostOutputRule("foo").Output.String(), + hostInstallRule("bar").target, + hostSymlinkRule("bar").target, + hostInstallRule("baz").target, + hostSymlinkRule("baz").target, + hostInstallRule("qux").target, + hostSymlinkRule("qux").target, + ) + assertOrderOnlys(hostInstallRule("foo")) + + // Check host symlink rule dependencies + assertDeps(hostSymlinkRule("foo")) + assertOrderOnlys(hostSymlinkRule("foo"), hostInstallRule("foo").target) + + // Check device install rule dependencies + assertDeps(installRule("foo"), outputRule("foo").Output.String()) + assertOrderOnlys(installRule("foo"), + installRule("bar").target, + symlinkRule("bar").target, + installRule("baz").target, + symlinkRule("baz").target, + installRule("qux").target, + symlinkRule("qux").target, + ) + + // Check device symlink rule dependencies + assertDeps(symlinkRule("foo")) + assertOrderOnlys(symlinkRule("foo"), installRule("foo").target) +} + +type installMakeRule struct { + target string + deps []string + orderOnlyDeps []string +} + +func parseMkRules(t *testing.T, config Config, nodes []mkparser.Node) []installMakeRule { + var rules []installMakeRule + for _, node := range nodes { + if mkParserRule, ok := node.(*mkparser.Rule); ok { + var rule installMakeRule + + if targets := mkParserRule.Target.Words(); len(targets) == 0 { + t.Fatalf("no targets for rule %s", mkParserRule.Dump()) + } else if len(targets) > 1 { + t.Fatalf("unsupported multiple targets for rule %s", mkParserRule.Dump()) + } else if !targets[0].Const() { + t.Fatalf("unsupported non-const target for rule %s", mkParserRule.Dump()) + } else { + rule.target = normalizeStringRelativeToTop(config, targets[0].Value(nil)) + } + + prereqList := &rule.deps + for _, prereq := range mkParserRule.Prerequisites.Words() { + if !prereq.Const() { + t.Fatalf("unsupported non-const prerequisite for rule %s", mkParserRule.Dump()) + } + + if prereq.Value(nil) == "|" { + prereqList = &rule.orderOnlyDeps + continue + } + + *prereqList = append(*prereqList, normalizeStringRelativeToTop(config, prereq.Value(nil))) + } + + rules = append(rules, rule) + } + } + + return rules +}