|  | // Copyright 2016 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" | 
|  | "testing" | 
|  |  | 
|  | "github.com/google/blueprint" | 
|  | ) | 
|  |  | 
|  | func TestPrebuilts(t *testing.T) { | 
|  | buildOS := TestArchConfig(t.TempDir(), nil, "", nil).BuildOS | 
|  |  | 
|  | var prebuiltsTests = []struct { | 
|  | name      string | 
|  | replaceBp bool // modules is added to default bp boilerplate if false. | 
|  | modules   string | 
|  | prebuilt  []OsType | 
|  | preparer  FixturePreparer | 
|  | }{ | 
|  | { | 
|  | name: "no prebuilt", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | }`, | 
|  | prebuilt: nil, | 
|  | }, | 
|  | { | 
|  | name: "no source prebuilt not preferred", | 
|  | modules: ` | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: false, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "no source prebuilt preferred", | 
|  | modules: ` | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt not preferred", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: false, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | prebuilt: nil, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt preferred", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt no file not preferred", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: false, | 
|  | }`, | 
|  | prebuilt: nil, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt no file preferred", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | }`, | 
|  | prebuilt: nil, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt file from filegroup preferred", | 
|  | modules: ` | 
|  | filegroup { | 
|  | name: "fg", | 
|  | srcs: ["prebuilt_file"], | 
|  | } | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | srcs: [":fg"], | 
|  | }`, | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt module for device only", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | host_supported: false, | 
|  | prefer: true, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | prebuilt: []OsType{Android}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt file for host only", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | target: { | 
|  | host: { | 
|  | srcs: ["prebuilt_file"], | 
|  | }, | 
|  | }, | 
|  | }`, | 
|  | prebuilt: []OsType{buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt override not preferred", | 
|  | modules: ` | 
|  | source { | 
|  | name: "baz", | 
|  | } | 
|  |  | 
|  | override_source { | 
|  | name: "bar", | 
|  | base: "baz", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: false, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | prebuilt: nil, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt override preferred", | 
|  | modules: ` | 
|  | source { | 
|  | name: "baz", | 
|  | } | 
|  |  | 
|  | override_source { | 
|  | name: "bar", | 
|  | base: "baz", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | 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}, | 
|  | }, | 
|  | { | 
|  | name:      "prebuilt properties customizable", | 
|  | replaceBp: true, | 
|  | modules: ` | 
|  | source { | 
|  | name: "foo", | 
|  | deps: [":bar"], | 
|  | } | 
|  |  | 
|  | soong_config_module_type { | 
|  | name: "prebuilt_with_config", | 
|  | module_type: "prebuilt", | 
|  | config_namespace: "any_namespace", | 
|  | bool_variables: ["bool_var"], | 
|  | properties: ["prefer"], | 
|  | } | 
|  |  | 
|  | prebuilt_with_config { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | srcs: ["prebuilt_file"], | 
|  | soong_config_variables: { | 
|  | bool_var: { | 
|  | prefer: false, | 
|  | conditions_default: { | 
|  | prefer: true, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }`, | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt use_source_config_var={acme, use_source} - no var specified", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | use_source_config_var: {config_namespace: "acme", var_name: "use_source"}, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | // When use_source_env is specified then it will use the prebuilt by default if the environment | 
|  | // variable is not set. | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=false", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | use_source_config_var: {config_namespace: "acme", var_name: "use_source"}, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = map[string]map[string]string{ | 
|  | "acme": { | 
|  | "use_source": "false", | 
|  | }, | 
|  | } | 
|  | }), | 
|  | // Setting the environment variable named in use_source_env to false will cause the prebuilt to | 
|  | // be used. | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=true", | 
|  | modules: ` | 
|  | source { | 
|  | name: "bar", | 
|  | } | 
|  |  | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | use_source_config_var: {config_namespace: "acme", var_name: "use_source"}, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = map[string]map[string]string{ | 
|  | "acme": { | 
|  | "use_source": "true", | 
|  | }, | 
|  | } | 
|  | }), | 
|  | // Setting the environment variable named in use_source_env to true will cause the source to be | 
|  | // used. | 
|  | prebuilt: nil, | 
|  | }, | 
|  | { | 
|  | name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=true, no source", | 
|  | modules: ` | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | use_source_config_var: {config_namespace: "acme", var_name: "use_source"}, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`, | 
|  | preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
|  | variables.VendorVars = map[string]map[string]string{ | 
|  | "acme": { | 
|  | "use_source": "true", | 
|  | }, | 
|  | } | 
|  | }), | 
|  | // Although the environment variable says to use source there is no source available. | 
|  | prebuilt: []OsType{Android, buildOS}, | 
|  | }, | 
|  | } | 
|  |  | 
|  | fs := MockFS{ | 
|  | "prebuilt_file": nil, | 
|  | "source_file":   nil, | 
|  | } | 
|  |  | 
|  | for _, test := range prebuiltsTests { | 
|  | t.Run(test.name, func(t *testing.T) { | 
|  | bp := test.modules | 
|  | if !test.replaceBp { | 
|  | bp = bp + ` | 
|  | source { | 
|  | name: "foo", | 
|  | deps: [":bar"], | 
|  | }` | 
|  | } | 
|  |  | 
|  | // 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") | 
|  | } | 
|  |  | 
|  | result := GroupFixturePreparers( | 
|  | PrepareForTestWithArchMutator, | 
|  | PrepareForTestWithPrebuilts, | 
|  | PrepareForTestWithOverrides, | 
|  | PrepareForTestWithFilegroup, | 
|  | // Add a Windows target to the configuration. | 
|  | FixtureModifyConfig(func(config Config) { | 
|  | config.Targets[Windows] = []Target{ | 
|  | {Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true}, | 
|  | } | 
|  | }), | 
|  | fs.AddToFixture(), | 
|  | FixtureRegisterWithContext(registerTestPrebuiltModules), | 
|  | OptionalFixturePreparer(test.preparer), | 
|  | ).RunTestWithBp(t, bp) | 
|  |  | 
|  | for _, variant := range result.ModuleVariantsForTests("foo") { | 
|  | foo := result.ModuleForTests("foo", variant) | 
|  | t.Run(foo.Module().Target().Os.String(), func(t *testing.T) { | 
|  | var dependsOnSourceModule, dependsOnPrebuiltModule bool | 
|  | result.VisitDirectDeps(foo.Module(), func(m blueprint.Module) { | 
|  | if _, ok := m.(*sourceModule); ok { | 
|  | dependsOnSourceModule = true | 
|  | } | 
|  | if p, ok := m.(*prebuiltModule); ok { | 
|  | dependsOnPrebuiltModule = true | 
|  | if !p.Prebuilt().properties.UsePrebuilt { | 
|  | t.Errorf("dependency on prebuilt module not marked used") | 
|  | } | 
|  | } | 
|  | }) | 
|  |  | 
|  | moduleIsDisabled := !foo.Module().Enabled() | 
|  | deps := foo.Module().(*sourceModule).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 len(deps) > 0 && deps[0].String() == "source_file" { | 
|  | usingSourceFile = true | 
|  | } | 
|  | if len(deps) > 0 && deps[0].String() == "prebuilt_file" { | 
|  | usingPrebuiltFile = true | 
|  | } | 
|  |  | 
|  | prebuilt := false | 
|  | for _, os := range test.prebuilt { | 
|  | 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") | 
|  | } | 
|  | if !usingPrebuiltFile { | 
|  | t.Errorf("doesn't use prebuilt_file") | 
|  | } | 
|  |  | 
|  | if dependsOnSourceModule { | 
|  | t.Errorf("depends on source module") | 
|  | } | 
|  | if usingSourceFile { | 
|  | t.Errorf("using source_file") | 
|  | } | 
|  | } else if !moduleIsDisabled { | 
|  | if dependsOnPrebuiltModule { | 
|  | t.Errorf("depends on prebuilt module") | 
|  | } | 
|  | if usingPrebuiltFile { | 
|  | t.Errorf("using prebuilt_file") | 
|  | } | 
|  |  | 
|  | if !dependsOnSourceModule { | 
|  | t.Errorf("doesn't depend on source module") | 
|  | } | 
|  | if !usingSourceFile { | 
|  | t.Errorf("doesn't use source_file") | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func testPrebuiltError(t *testing.T, expectedError, bp string) { | 
|  | t.Helper() | 
|  | fs := MockFS{ | 
|  | "prebuilt_file": nil, | 
|  | } | 
|  | GroupFixturePreparers( | 
|  | PrepareForTestWithArchMutator, | 
|  | PrepareForTestWithPrebuilts, | 
|  | PrepareForTestWithOverrides, | 
|  | fs.AddToFixture(), | 
|  | FixtureRegisterWithContext(registerTestPrebuiltModules), | 
|  | ). | 
|  | ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern(expectedError)). | 
|  | RunTestWithBp(t, bp) | 
|  | } | 
|  |  | 
|  | func TestPrebuiltShouldNotChangePartition(t *testing.T) { | 
|  | testPrebuiltError(t, `partition is different`, ` | 
|  | source { | 
|  | name: "foo", | 
|  | vendor: true, | 
|  | } | 
|  | prebuilt { | 
|  | name: "foo", | 
|  | prefer: true, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`) | 
|  | } | 
|  |  | 
|  | func TestPrebuiltShouldNotChangePartition_WithOverride(t *testing.T) { | 
|  | testPrebuiltError(t, `partition is different`, ` | 
|  | source { | 
|  | name: "foo", | 
|  | vendor: true, | 
|  | } | 
|  | override_source { | 
|  | name: "bar", | 
|  | base: "foo", | 
|  | } | 
|  | prebuilt { | 
|  | name: "bar", | 
|  | prefer: true, | 
|  | srcs: ["prebuilt_file"], | 
|  | }`) | 
|  | } | 
|  |  | 
|  | func registerTestPrebuiltBuildComponents(ctx RegistrationContext) { | 
|  | registerTestPrebuiltModules(ctx) | 
|  |  | 
|  | RegisterPrebuiltMutators(ctx) | 
|  | ctx.PostDepsMutators(RegisterOverridePostDepsMutators) | 
|  | } | 
|  |  | 
|  | var prepareForTestWithFakePrebuiltModules = FixtureRegisterWithContext(registerTestPrebuiltModules) | 
|  |  | 
|  | func registerTestPrebuiltModules(ctx RegistrationContext) { | 
|  | ctx.RegisterModuleType("prebuilt", newPrebuiltModule) | 
|  | ctx.RegisterModuleType("source", newSourceModule) | 
|  | ctx.RegisterModuleType("override_source", newOverrideSourceModule) | 
|  | ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory) | 
|  | ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory) | 
|  | ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory) | 
|  | } | 
|  |  | 
|  | type prebuiltModule struct { | 
|  | ModuleBase | 
|  | prebuilt   Prebuilt | 
|  | properties struct { | 
|  | Srcs []string `android:"path,arch_variant"` | 
|  | } | 
|  | src Path | 
|  | } | 
|  |  | 
|  | func newPrebuiltModule() Module { | 
|  | m := &prebuiltModule{} | 
|  | m.AddProperties(&m.properties) | 
|  | InitPrebuiltModule(m, &m.properties.Srcs) | 
|  | InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon) | 
|  | return m | 
|  | } | 
|  |  | 
|  | func (p *prebuiltModule) Name() string { | 
|  | return p.prebuilt.Name(p.ModuleBase.Name()) | 
|  | } | 
|  |  | 
|  | func (p *prebuiltModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
|  | if len(p.properties.Srcs) >= 1 { | 
|  | p.src = p.prebuilt.SingleSourcePath(ctx) | 
|  | } | 
|  | } | 
|  |  | 
|  | func (p *prebuiltModule) Prebuilt() *Prebuilt { | 
|  | return &p.prebuilt | 
|  | } | 
|  |  | 
|  | func (p *prebuiltModule) OutputFiles(tag string) (Paths, error) { | 
|  | switch tag { | 
|  | case "": | 
|  | return Paths{p.src}, nil | 
|  | default: | 
|  | return nil, fmt.Errorf("unsupported module reference tag %q", tag) | 
|  | } | 
|  | } | 
|  |  | 
|  | type sourceModuleProperties struct { | 
|  | Deps []string `android:"path,arch_variant"` | 
|  | } | 
|  |  | 
|  | type sourceModule struct { | 
|  | ModuleBase | 
|  | OverridableModuleBase | 
|  |  | 
|  | properties                                     sourceModuleProperties | 
|  | dependsOnSourceModule, dependsOnPrebuiltModule bool | 
|  | deps                                           Paths | 
|  | src                                            Path | 
|  | } | 
|  |  | 
|  | func newSourceModule() Module { | 
|  | m := &sourceModule{} | 
|  | m.AddProperties(&m.properties) | 
|  | InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon) | 
|  | InitOverridableModule(m, nil) | 
|  | return m | 
|  | } | 
|  |  | 
|  | func (s *sourceModule) OverridablePropertiesDepsMutator(ctx BottomUpMutatorContext) { | 
|  | // s.properties.Deps are annotated with android:path, so they are | 
|  | // automatically added to the dependency by pathDeps mutator | 
|  | } | 
|  |  | 
|  | func (s *sourceModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
|  | s.deps = PathsForModuleSrc(ctx, s.properties.Deps) | 
|  | s.src = PathForModuleSrc(ctx, "source_file") | 
|  | } | 
|  |  | 
|  | func (s *sourceModule) Srcs() Paths { | 
|  | return Paths{s.src} | 
|  | } | 
|  |  | 
|  | type overrideSourceModule struct { | 
|  | ModuleBase | 
|  | OverrideModuleBase | 
|  | } | 
|  |  | 
|  | func (o *overrideSourceModule) GenerateAndroidBuildActions(_ ModuleContext) { | 
|  | } | 
|  |  | 
|  | func newOverrideSourceModule() Module { | 
|  | m := &overrideSourceModule{} | 
|  | m.AddProperties(&sourceModuleProperties{}) | 
|  |  | 
|  | InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon) | 
|  | InitOverrideModule(m) | 
|  | return m | 
|  | } |