diff options
100 files changed, 3769 insertions, 1618 deletions
diff --git a/Android.bp b/Android.bp index 4db98f851..fff17ef64 100644 --- a/Android.bp +++ b/Android.bp @@ -78,10 +78,12 @@ bootstrap_go_package { "android/env.go", ], testSrcs: [ + "android/android_test.go", "android/arch_test.go", "android/config_test.go", "android/expand_test.go", "android/module_test.go", + "android/mutator_test.go", "android/namespace_test.go", "android/neverallow_test.go", "android/onceper_test.go", @@ -294,6 +296,7 @@ bootstrap_go_package { "java/jdeps_test.go", "java/kotlin_test.go", "java/plugin_test.go", + "java/robolectric_test.go", "java/sdk_test.go", ], pluginFor: ["soong_build"], diff --git a/android/android_test.go b/android/android_test.go new file mode 100644 index 000000000..46b705468 --- /dev/null +++ b/android/android_test.go @@ -0,0 +1,46 @@ +// Copyright 2019 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 ( + "io/ioutil" + "os" + "testing" +) + +var buildDir string + +func setUp() { + var err error + buildDir, err = ioutil.TempDir("", "soong_android_test") + if err != nil { + panic(err) + } +} + +func tearDown() { + os.RemoveAll(buildDir) +} + +func TestMain(m *testing.M) { + run := func() int { + setUp() + defer tearDown() + + return m.Run() + } + + os.Exit(run()) +} diff --git a/android/api_levels.go b/android/api_levels.go index 51d470381..961685aa8 100644 --- a/android/api_levels.go +++ b/android/api_levels.go @@ -85,7 +85,7 @@ func getApiLevelsMap(config Config) map[string]int { // * Numeric API levels are simply converted. // * "minimum" and "current" are not currently handled since the former is // NDK specific and the latter has inconsistent meaning. -func ApiStrToNum(ctx BaseContext, apiLevel string) (int, error) { +func ApiStrToNum(ctx BaseModuleContext, apiLevel string) (int, error) { num, ok := getApiLevelsMap(ctx.Config())[apiLevel] if ok { return num, nil diff --git a/android/arch.go b/android/arch.go index 04eb1e214..46e582c26 100644 --- a/android/arch.go +++ b/android/arch.go @@ -713,7 +713,7 @@ func (target Target) String() string { // If host is supported for the module, the Host and HostCross OsClasses are selected. If device is supported // for the module, the Device OsClass is selected. // Within each selected OsClass, the multilib selection is determined by: -// - The compile_multilib property if it set (which may be overriden by target.android.compile_multlib or +// - The compile_multilib property if it set (which may be overridden by target.android.compile_multilib or // target.host.compile_multilib). // - The default multilib passed to InitAndroidArchModule if compile_multilib was not set. // Valid multilib values include: @@ -1129,7 +1129,7 @@ func InitArchModule(m Module) { var variantReplacer = strings.NewReplacer("-", "_", ".", "_") -func (a *ModuleBase) appendProperties(ctx BottomUpMutatorContext, +func (m *ModuleBase) appendProperties(ctx BottomUpMutatorContext, dst interface{}, src reflect.Value, field, srcPrefix string) reflect.Value { src = src.FieldByName(field) @@ -1167,16 +1167,16 @@ func (a *ModuleBase) appendProperties(ctx BottomUpMutatorContext, } // Rewrite the module's properties structs to contain arch-specific values. -func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { - arch := a.Arch() - os := a.Os() +func (m *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { + arch := m.Arch() + os := m.Os() - for i := range a.generalProperties { - genProps := a.generalProperties[i] - if a.archProperties[i] == nil { + for i := range m.generalProperties { + genProps := m.generalProperties[i] + if m.archProperties[i] == nil { continue } - for _, archProperties := range a.archProperties[i] { + for _, archProperties := range m.archProperties[i] { archPropValues := reflect.ValueOf(archProperties).Elem() archProp := archPropValues.FieldByName("Arch") @@ -1197,7 +1197,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { if arch.ArchType != Common { field := proptools.FieldNameForProperty(t.Name) prefix := "arch." + t.Name - archStruct := a.appendProperties(ctx, genProps, archProp, field, prefix) + archStruct := m.appendProperties(ctx, genProps, archProp, field, prefix) // Handle arch-variant-specific properties in the form: // arch: { @@ -1209,7 +1209,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { if v != "" { field := proptools.FieldNameForProperty(v) prefix := "arch." + t.Name + "." + v - a.appendProperties(ctx, genProps, archStruct, field, prefix) + m.appendProperties(ctx, genProps, archStruct, field, prefix) } // Handle cpu-variant-specific properties in the form: @@ -1223,7 +1223,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { if c != "" { field := proptools.FieldNameForProperty(c) prefix := "arch." + t.Name + "." + c - a.appendProperties(ctx, genProps, archStruct, field, prefix) + m.appendProperties(ctx, genProps, archStruct, field, prefix) } } @@ -1236,7 +1236,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { for _, feature := range arch.ArchFeatures { field := proptools.FieldNameForProperty(feature) prefix := "arch." + t.Name + "." + feature - a.appendProperties(ctx, genProps, archStruct, field, prefix) + m.appendProperties(ctx, genProps, archStruct, field, prefix) } // Handle multilib-specific properties in the form: @@ -1247,7 +1247,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { // }, field = proptools.FieldNameForProperty(t.Multilib) prefix = "multilib." + t.Multilib - a.appendProperties(ctx, genProps, multilibProp, field, prefix) + m.appendProperties(ctx, genProps, multilibProp, field, prefix) } // Handle host-specific properties in the form: @@ -1259,7 +1259,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { if os.Class == Host || os.Class == HostCross { field = "Host" prefix = "target.host" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } // Handle target OS generalities of the form: @@ -1274,24 +1274,24 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { if os.Linux() { field = "Linux" prefix = "target.linux" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) if arch.ArchType != Common { field = "Linux_" + arch.ArchType.Name prefix = "target.linux_" + arch.ArchType.Name - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } } if os.Bionic() { field = "Bionic" prefix = "target.bionic" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) if arch.ArchType != Common { field = "Bionic_" + t.Name prefix = "target.bionic_" + t.Name - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } } @@ -1321,18 +1321,18 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { // }, field = os.Field prefix = "target." + os.Name - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) if arch.ArchType != Common { field = os.Field + "_" + t.Name prefix = "target." + os.Name + "_" + t.Name - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } if (os.Class == Host || os.Class == HostCross) && os != Windows { field := "Not_windows" prefix := "target.not_windows" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } // Handle 64-bit device properties in the form: @@ -1352,11 +1352,11 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { if ctx.Config().Android64() { field := "Android64" prefix := "target.android64" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } else { field := "Android32" prefix := "target.android32" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } if (arch.ArchType == X86 && (hasArmAbi(arch) || @@ -1365,7 +1365,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { hasX86AndroidArch(ctx.Config().Targets[Android])) { field := "Arm_on_x86" prefix := "target.arm_on_x86" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } if (arch.ArchType == X86_64 && (hasArmAbi(arch) || hasArmAndroidArch(ctx.Config().Targets[Android]))) || @@ -1373,7 +1373,7 @@ func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { hasX8664AndroidArch(ctx.Config().Targets[Android])) { field := "Arm_on_x86_64" prefix := "target.arm_on_x86_64" - a.appendProperties(ctx, genProps, targetProp, field, prefix) + m.appendProperties(ctx, genProps, targetProp, field, prefix) } } } diff --git a/android/defaults.go b/android/defaults.go index d4fbf487d..844b4d408 100644 --- a/android/defaults.go +++ b/android/defaults.go @@ -80,6 +80,9 @@ func (d *DefaultsModuleBase) properties() []interface{} { return d.defaultableProperties } +func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) { +} + func InitDefaultsModule(module DefaultableModule) { module.AddProperties( &hostAndDeviceProperties{}, diff --git a/android/defaults_test.go b/android/defaults_test.go new file mode 100644 index 000000000..fa2659563 --- /dev/null +++ b/android/defaults_test.go @@ -0,0 +1,116 @@ +// Copyright 2019 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 ( + "testing" + + "github.com/google/blueprint/proptools" +) + +type defaultsTestProperties struct { + Foo []string +} + +type defaultsTestModule struct { + ModuleBase + DefaultableModuleBase + properties defaultsTestProperties +} + +func (d *defaultsTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { + ctx.Build(pctx, BuildParams{ + Rule: Touch, + Output: PathForModuleOut(ctx, "out"), + }) +} + +func defaultsTestModuleFactory() Module { + module := &defaultsTestModule{} + module.AddProperties(&module.properties) + InitDefaultableModule(module) + InitAndroidModule(module) + return module +} + +type defaultsTestDefaults struct { + ModuleBase + DefaultsModuleBase +} + +func defaultsTestDefaultsFactory() Module { + defaults := &defaultsTestDefaults{} + defaults.AddProperties(&defaultsTestProperties{}) + InitDefaultsModule(defaults) + return defaults +} + +func TestDefaultsAllowMissingDependencies(t *testing.T) { + config := TestConfig(buildDir, nil) + config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true) + + ctx := NewTestContext() + ctx.SetAllowMissingDependencies(true) + + ctx.RegisterModuleType("test", ModuleFactoryAdaptor(defaultsTestModuleFactory)) + ctx.RegisterModuleType("defaults", ModuleFactoryAdaptor(defaultsTestDefaultsFactory)) + + ctx.PreArchMutators(RegisterDefaultsPreArchMutators) + + ctx.Register() + + bp := ` + defaults { + name: "defaults", + defaults: ["missing"], + foo: ["defaults"], + } + + test { + name: "missing_defaults", + defaults: ["missing"], + foo: ["module"], + } + + test { + name: "missing_transitive_defaults", + defaults: ["defaults"], + foo: ["module"], + } + ` + + ctx.MockFileSystem(map[string][]byte{ + "Android.bp": []byte(bp), + }) + + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + FailIfErrored(t, errs) + + missingDefaults := ctx.ModuleForTests("missing_defaults", "").Output("out") + missingTransitiveDefaults := ctx.ModuleForTests("missing_transitive_defaults", "").Output("out") + + if missingDefaults.Rule != ErrorRule { + t.Errorf("expected missing_defaults rule to be ErrorRule, got %#v", missingDefaults.Rule) + } + + if g, w := missingDefaults.Args["error"], "module missing_defaults missing dependencies: missing\n"; g != w { + t.Errorf("want error %q, got %q", w, g) + } + + // TODO: missing transitive defaults is currently not handled + _ = missingTransitiveDefaults +} diff --git a/android/hooks.go b/android/hooks.go index d55678e9d..2d2f797bd 100644 --- a/android/hooks.go +++ b/android/hooks.go @@ -27,7 +27,7 @@ import ( // been applied. type LoadHookContext interface { // TODO: a new context that includes Config() but not Target(), etc.? - BaseContext + BaseModuleContext AppendProperties(...interface{}) PrependProperties(...interface{}) CreateModule(blueprint.ModuleFactory, ...interface{}) @@ -36,7 +36,7 @@ type LoadHookContext interface { // Arch hooks are run after the module has been split into architecture variants, and can be used // to add architecture-specific properties. type ArchHookContext interface { - BaseContext + BaseModuleContext AppendProperties(...interface{}) PrependProperties(...interface{}) } @@ -129,18 +129,18 @@ func registerLoadHookMutator(ctx RegisterMutatorsContext) { func LoadHookMutator(ctx TopDownMutatorContext) { if m, ok := ctx.Module().(Module); ok { - // Cast through *androidTopDownMutatorContext because AppendProperties is implemented - // on *androidTopDownMutatorContext but not exposed through TopDownMutatorContext - var loadHookCtx LoadHookContext = ctx.(*androidTopDownMutatorContext) + // Cast through *topDownMutatorContext because AppendProperties is implemented + // on *topDownMutatorContext but not exposed through TopDownMutatorContext + var loadHookCtx LoadHookContext = ctx.(*topDownMutatorContext) m.base().hooks.runLoadHooks(loadHookCtx, m.base()) } } func archHookMutator(ctx TopDownMutatorContext) { if m, ok := ctx.Module().(Module); ok { - // Cast through *androidTopDownMutatorContext because AppendProperties is implemented - // on *androidTopDownMutatorContext but not exposed through TopDownMutatorContext - var archHookCtx ArchHookContext = ctx.(*androidTopDownMutatorContext) + // Cast through *topDownMutatorContext because AppendProperties is implemented + // on *topDownMutatorContext but not exposed through TopDownMutatorContext + var archHookCtx ArchHookContext = ctx.(*topDownMutatorContext) m.base().hooks.runArchHooks(archHookCtx, m.base()) } } diff --git a/android/module.go b/android/module.go index eb9b0fc2c..87e2ca7d9 100644 --- a/android/module.go +++ b/android/module.go @@ -18,7 +18,6 @@ import ( "fmt" "path" "path/filepath" - "sort" "strings" "text/scanner" @@ -56,7 +55,61 @@ type BuildParams struct { type ModuleBuildParams BuildParams -type androidBaseContext interface { +// BaseModuleContext is the same as blueprint.BaseModuleContext except that Config() returns +// a Config instead of an interface{}, and some methods have been wrapped to use an android.Module +// instead of a blueprint.Module, plus some extra methods that return Android-specific information +// about the current module. +type BaseModuleContext interface { + Module() Module + ModuleName() string + ModuleDir() string + ModuleType() string + Config() Config + + OtherModuleName(m blueprint.Module) string + OtherModuleDir(m blueprint.Module) string + OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{}) + OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag + OtherModuleExists(name string) bool + + GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module + GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module + GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) + + VisitDirectDepsBlueprint(visit func(blueprint.Module)) + VisitDirectDeps(visit func(Module)) + VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) + VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) + // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module + VisitDepsDepthFirst(visit func(Module)) + // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module + VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module)) + WalkDeps(visit func(Module, Module) bool) + WalkDepsBlueprint(visit func(blueprint.Module, blueprint.Module) bool) + // GetWalkPath is supposed to be called in visit function passed in WalkDeps() + // and returns a top-down dependency path from a start module to current child module. + GetWalkPath() []Module + + ContainsProperty(name string) bool + Errorf(pos scanner.Position, fmt string, args ...interface{}) + ModuleErrorf(fmt string, args ...interface{}) + PropertyErrorf(property, fmt string, args ...interface{}) + Failed() bool + + // GlobWithDeps returns a list of files that match the specified pattern but do not match any + // of the patterns in excludes. It also adds efficient dependencies to rerun the primary + // builder whenever a file matching the pattern as added or removed, without rerunning if a + // file that does not match the pattern is added to a searched directory. + GlobWithDeps(pattern string, excludes []string) ([]string, error) + + Glob(globPattern string, excludes []string) Paths + GlobFiles(globPattern string, excludes []string) Paths + + Fs() pathtools.FileSystem + AddNinjaFileDeps(deps ...string) + + AddMissingDependencies(missingDeps []string) + Target() Target TargetPrimary() bool MultiTargets() []Target @@ -78,37 +131,12 @@ type androidBaseContext interface { DeviceConfig() DeviceConfig } +// Deprecated: use BaseModuleContext instead type BaseContext interface { BaseModuleContext - androidBaseContext -} - -// BaseModuleContext is the same as blueprint.BaseModuleContext except that Config() returns -// a Config instead of an interface{}. -type BaseModuleContext interface { - ModuleName() string - ModuleDir() string - ModuleType() string - Config() Config - - ContainsProperty(name string) bool - Errorf(pos scanner.Position, fmt string, args ...interface{}) - ModuleErrorf(fmt string, args ...interface{}) - PropertyErrorf(property, fmt string, args ...interface{}) - Failed() bool - - // GlobWithDeps returns a list of files that match the specified pattern but do not match any - // of the patterns in excludes. It also adds efficient dependencies to rerun the primary - // builder whenever a file matching the pattern as added or removed, without rerunning if a - // file that does not match the pattern is added to a searched directory. - GlobWithDeps(pattern string, excludes []string) ([]string, error) - - Fs() pathtools.FileSystem - AddNinjaFileDeps(deps ...string) } type ModuleContext interface { - androidBaseContext BaseModuleContext // Deprecated: use ModuleContext.Build instead. @@ -117,8 +145,6 @@ type ModuleContext interface { ExpandSources(srcFiles, excludes []string) Paths ExpandSource(srcFile, prop string) Path ExpandOptionalSource(srcFile *string, prop string) OptionalPath - Glob(globPattern string, excludes []string) Paths - GlobFiles(globPattern string, excludes []string) Paths InstallExecutable(installPath OutputPath, name string, srcPath Path, deps ...Path) OutputPath InstallFile(installPath OutputPath, name string, srcPath Path, deps ...Path) OutputPath @@ -126,8 +152,6 @@ type ModuleContext interface { InstallAbsoluteSymlink(installPath OutputPath, name string, absPath string) OutputPath CheckbuildFile(srcPath Path) - AddMissingDependencies(deps []string) - InstallInData() bool InstallInSanitizerDir() bool InstallInRecovery() bool @@ -136,30 +160,8 @@ type ModuleContext interface { HostRequiredModuleNames() []string TargetRequiredModuleNames() []string - // android.ModuleContext methods - // These are duplicated instead of embedded so that can eventually be wrapped to take an - // android.Module instead of a blueprint.Module - OtherModuleName(m blueprint.Module) string - OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{}) - OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag - - GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module - GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module - GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) - ModuleSubDir() string - VisitDirectDepsBlueprint(visit func(blueprint.Module)) - VisitDirectDeps(visit func(Module)) - VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) - VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) - // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module - VisitDepsDepthFirst(visit func(Module)) - // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module - VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module)) - WalkDeps(visit func(Module, Module) bool) - WalkDepsBlueprint(visit func(blueprint.Module, blueprint.Module) bool) - Variable(pctx PackageContext, name, value string) Rule(pctx PackageContext, name string, params blueprint.RuleParams, argNames ...string) blueprint.Rule // Similar to blueprint.ModuleContext.Build, but takes Paths instead of []string, @@ -341,6 +343,8 @@ type commonProperties struct { SkipInstall bool `blueprint:"mutated"` NamespaceExportedToMake bool `blueprint:"mutated"` + + MissingDeps []string `blueprint:"mutated"` } type hostAndDeviceProperties struct { @@ -457,9 +461,9 @@ func InitAndroidMultiTargetsArchModule(m Module, hod HostOrDeviceSupported, defa // The ModuleBase type is responsible for implementing the GenerateBuildActions // method to support the blueprint.Module interface. This method will then call // the module's GenerateAndroidBuildActions method once for each build variant -// that is to be built. GenerateAndroidBuildActions is passed a -// AndroidModuleContext rather than the usual blueprint.ModuleContext. -// AndroidModuleContext exposes extra functionality specific to the Android build +// that is to be built. GenerateAndroidBuildActions is passed a ModuleContext +// rather than the usual blueprint.ModuleContext. +// ModuleContext exposes extra functionality specific to the Android build // system including details about the particular build variant that is to be // generated. // @@ -526,83 +530,83 @@ type ModuleBase struct { prefer32 func(ctx BaseModuleContext, base *ModuleBase, class OsClass) bool } -func (a *ModuleBase) DepsMutator(BottomUpMutatorContext) {} +func (m *ModuleBase) DepsMutator(BottomUpMutatorContext) {} -func (a *ModuleBase) AddProperties(props ...interface{}) { - a.registerProps = append(a.registerProps, props...) +func (m *ModuleBase) AddProperties(props ...interface{}) { + m.registerProps = append(m.registerProps, props...) } -func (a *ModuleBase) GetProperties() []interface{} { - return a.registerProps +func (m *ModuleBase) GetProperties() []interface{} { + return m.registerProps } -func (a *ModuleBase) BuildParamsForTests() []BuildParams { - return a.buildParams +func (m *ModuleBase) BuildParamsForTests() []BuildParams { + return m.buildParams } -func (a *ModuleBase) RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams { - return a.ruleParams +func (m *ModuleBase) RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams { + return m.ruleParams } -func (a *ModuleBase) VariablesForTests() map[string]string { - return a.variables +func (m *ModuleBase) VariablesForTests() map[string]string { + return m.variables } -func (a *ModuleBase) Prefer32(prefer32 func(ctx BaseModuleContext, base *ModuleBase, class OsClass) bool) { - a.prefer32 = prefer32 +func (m *ModuleBase) Prefer32(prefer32 func(ctx BaseModuleContext, base *ModuleBase, class OsClass) bool) { + m.prefer32 = prefer32 } // Name returns the name of the module. It may be overridden by individual module types, for // example prebuilts will prepend prebuilt_ to the name. -func (a *ModuleBase) Name() string { - return String(a.nameProperties.Name) +func (m *ModuleBase) Name() string { + return String(m.nameProperties.Name) } // BaseModuleName returns the name of the module as specified in the blueprints file. -func (a *ModuleBase) BaseModuleName() string { - return String(a.nameProperties.Name) +func (m *ModuleBase) BaseModuleName() string { + return String(m.nameProperties.Name) } -func (a *ModuleBase) base() *ModuleBase { - return a +func (m *ModuleBase) base() *ModuleBase { + return m } -func (a *ModuleBase) SetTarget(target Target, multiTargets []Target, primary bool) { - a.commonProperties.CompileTarget = target - a.commonProperties.CompileMultiTargets = multiTargets - a.commonProperties.CompilePrimary = primary +func (m *ModuleBase) SetTarget(target Target, multiTargets []Target, primary bool) { + m.commonProperties.CompileTarget = target + m.commonProperties.CompileMultiTargets = multiTargets + m.commonProperties.CompilePrimary = primary } -func (a *ModuleBase) Target() Target { - return a.commonProperties.CompileTarget +func (m *ModuleBase) Target() Target { + return m.commonProperties.CompileTarget } -func (a *ModuleBase) TargetPrimary() bool { - return a.commonProperties.CompilePrimary +func (m *ModuleBase) TargetPrimary() bool { + return m.commonProperties.CompilePrimary } -func (a *ModuleBase) MultiTargets() []Target { - return a.commonProperties.CompileMultiTargets +func (m *ModuleBase) MultiTargets() []Target { + return m.commonProperties.CompileMultiTargets } -func (a *ModuleBase) Os() OsType { - return a.Target().Os +func (m *ModuleBase) Os() OsType { + return m.Target().Os } -func (a *ModuleBase) Host() bool { - return a.Os().Class == Host || a.Os().Class == HostCross +func (m *ModuleBase) Host() bool { + return m.Os().Class == Host || m.Os().Class == HostCross } -func (a *ModuleBase) Arch() Arch { - return a.Target().Arch +func (m *ModuleBase) Arch() Arch { + return m.Target().Arch } -func (a *ModuleBase) ArchSpecific() bool { - return a.commonProperties.ArchSpecific +func (m *ModuleBase) ArchSpecific() bool { + return m.commonProperties.ArchSpecific } -func (a *ModuleBase) OsClassSupported() []OsClass { - switch a.commonProperties.HostOrDeviceSupported { +func (m *ModuleBase) OsClassSupported() []OsClass { + switch m.commonProperties.HostOrDeviceSupported { case HostSupported: return []OsClass{Host, HostCross} case HostSupportedNoCross: @@ -611,13 +615,13 @@ func (a *ModuleBase) OsClassSupported() []OsClass { return []OsClass{Device} case HostAndDeviceSupported, HostAndDeviceDefault: var supported []OsClass - if Bool(a.hostAndDeviceProperties.Host_supported) || - (a.commonProperties.HostOrDeviceSupported == HostAndDeviceDefault && - a.hostAndDeviceProperties.Host_supported == nil) { + if Bool(m.hostAndDeviceProperties.Host_supported) || + (m.commonProperties.HostOrDeviceSupported == HostAndDeviceDefault && + m.hostAndDeviceProperties.Host_supported == nil) { supported = append(supported, Host, HostCross) } - if a.hostAndDeviceProperties.Device_supported == nil || - *a.hostAndDeviceProperties.Device_supported { + if m.hostAndDeviceProperties.Device_supported == nil || + *m.hostAndDeviceProperties.Device_supported { supported = append(supported, Device) } return supported @@ -626,49 +630,49 @@ func (a *ModuleBase) OsClassSupported() []OsClass { } } -func (a *ModuleBase) DeviceSupported() bool { - return a.commonProperties.HostOrDeviceSupported == DeviceSupported || - a.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && - (a.hostAndDeviceProperties.Device_supported == nil || - *a.hostAndDeviceProperties.Device_supported) +func (m *ModuleBase) DeviceSupported() bool { + return m.commonProperties.HostOrDeviceSupported == DeviceSupported || + m.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && + (m.hostAndDeviceProperties.Device_supported == nil || + *m.hostAndDeviceProperties.Device_supported) } -func (a *ModuleBase) Platform() bool { - return !a.DeviceSpecific() && !a.SocSpecific() && !a.ProductSpecific() && !a.ProductServicesSpecific() +func (m *ModuleBase) Platform() bool { + return !m.DeviceSpecific() && !m.SocSpecific() && !m.ProductSpecific() && !m.ProductServicesSpecific() } -func (a *ModuleBase) DeviceSpecific() bool { - return Bool(a.commonProperties.Device_specific) +func (m *ModuleBase) DeviceSpecific() bool { + return Bool(m.commonProperties.Device_specific) } -func (a *ModuleBase) SocSpecific() bool { - return Bool(a.commonProperties.Vendor) || Bool(a.commonProperties.Proprietary) || Bool(a.commonProperties.Soc_specific) +func (m *ModuleBase) SocSpecific() bool { + return Bool(m.commonProperties.Vendor) || Bool(m.commonProperties.Proprietary) || Bool(m.commonProperties.Soc_specific) } -func (a *ModuleBase) ProductSpecific() bool { - return Bool(a.commonProperties.Product_specific) +func (m *ModuleBase) ProductSpecific() bool { + return Bool(m.commonProperties.Product_specific) } -func (a *ModuleBase) ProductServicesSpecific() bool { - return Bool(a.commonProperties.Product_services_specific) +func (m *ModuleBase) ProductServicesSpecific() bool { + return Bool(m.commonProperties.Product_services_specific) } -func (a *ModuleBase) Enabled() bool { - if a.commonProperties.Enabled == nil { - return !a.Os().DefaultDisabled +func (m *ModuleBase) Enabled() bool { + if m.commonProperties.Enabled == nil { + return !m.Os().DefaultDisabled } - return *a.commonProperties.Enabled + return *m.commonProperties.Enabled } -func (a *ModuleBase) SkipInstall() { - a.commonProperties.SkipInstall = true +func (m *ModuleBase) SkipInstall() { + m.commonProperties.SkipInstall = true } -func (a *ModuleBase) ExportedToMake() bool { - return a.commonProperties.NamespaceExportedToMake +func (m *ModuleBase) ExportedToMake() bool { + return m.commonProperties.NamespaceExportedToMake } -func (a *ModuleBase) computeInstallDeps( +func (m *ModuleBase) computeInstallDeps( ctx blueprint.ModuleContext) Paths { result := Paths{} @@ -683,35 +687,35 @@ func (a *ModuleBase) computeInstallDeps( return result } -func (a *ModuleBase) filesToInstall() Paths { - return a.installFiles +func (m *ModuleBase) filesToInstall() Paths { + return m.installFiles } -func (p *ModuleBase) NoAddressSanitizer() bool { - return p.noAddressSanitizer +func (m *ModuleBase) NoAddressSanitizer() bool { + return m.noAddressSanitizer } -func (p *ModuleBase) InstallInData() bool { +func (m *ModuleBase) InstallInData() bool { return false } -func (p *ModuleBase) InstallInSanitizerDir() bool { +func (m *ModuleBase) InstallInSanitizerDir() bool { return false } -func (p *ModuleBase) InstallInRecovery() bool { - return Bool(p.commonProperties.Recovery) +func (m *ModuleBase) InstallInRecovery() bool { + return Bool(m.commonProperties.Recovery) } -func (a *ModuleBase) Owner() string { - return String(a.commonProperties.Owner) +func (m *ModuleBase) Owner() string { + return String(m.commonProperties.Owner) } -func (a *ModuleBase) NoticeFile() OptionalPath { - return a.noticeFile +func (m *ModuleBase) NoticeFile() OptionalPath { + return m.noticeFile } -func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) { +func (m *ModuleBase) generateModuleTarget(ctx ModuleContext) { allInstalledFiles := Paths{} allCheckbuildFiles := Paths{} ctx.VisitAllModuleVariants(func(module Module) { @@ -736,7 +740,7 @@ func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) { Default: !ctx.Config().EmbeddedInMake(), }) deps = append(deps, name) - a.installTarget = name + m.installTarget = name } if len(allCheckbuildFiles) > 0 { @@ -747,7 +751,7 @@ func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) { Implicits: allCheckbuildFiles, }) deps = append(deps, name) - a.checkbuildTarget = name + m.checkbuildTarget = name } if len(deps) > 0 { @@ -763,26 +767,26 @@ func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) { Implicits: deps, }) - a.blueprintDir = ctx.ModuleDir() + m.blueprintDir = ctx.ModuleDir() } } -func determineModuleKind(a *ModuleBase, ctx blueprint.BaseModuleContext) moduleKind { - var socSpecific = Bool(a.commonProperties.Vendor) || Bool(a.commonProperties.Proprietary) || Bool(a.commonProperties.Soc_specific) - var deviceSpecific = Bool(a.commonProperties.Device_specific) - var productSpecific = Bool(a.commonProperties.Product_specific) - var productServicesSpecific = Bool(a.commonProperties.Product_services_specific) +func determineModuleKind(m *ModuleBase, ctx blueprint.BaseModuleContext) moduleKind { + var socSpecific = Bool(m.commonProperties.Vendor) || Bool(m.commonProperties.Proprietary) || Bool(m.commonProperties.Soc_specific) + var deviceSpecific = Bool(m.commonProperties.Device_specific) + var productSpecific = Bool(m.commonProperties.Product_specific) + var productServicesSpecific = Bool(m.commonProperties.Product_services_specific) msg := "conflicting value set here" if socSpecific && deviceSpecific { ctx.PropertyErrorf("device_specific", "a module cannot be specific to SoC and device at the same time.") - if Bool(a.commonProperties.Vendor) { + if Bool(m.commonProperties.Vendor) { ctx.PropertyErrorf("vendor", msg) } - if Bool(a.commonProperties.Proprietary) { + if Bool(m.commonProperties.Proprietary) { ctx.PropertyErrorf("proprietary", msg) } - if Bool(a.commonProperties.Soc_specific) { + if Bool(m.commonProperties.Soc_specific) { ctx.PropertyErrorf("soc_specific", msg) } } @@ -801,13 +805,13 @@ func determineModuleKind(a *ModuleBase, ctx blueprint.BaseModuleContext) moduleK if deviceSpecific { ctx.PropertyErrorf("device_specific", msg) } else { - if Bool(a.commonProperties.Vendor) { + if Bool(m.commonProperties.Vendor) { ctx.PropertyErrorf("vendor", msg) } - if Bool(a.commonProperties.Proprietary) { + if Bool(m.commonProperties.Proprietary) { ctx.PropertyErrorf("proprietary", msg) } - if Bool(a.commonProperties.Soc_specific) { + if Bool(m.commonProperties.Soc_specific) { ctx.PropertyErrorf("soc_specific", msg) } } @@ -826,27 +830,36 @@ func determineModuleKind(a *ModuleBase, ctx blueprint.BaseModuleContext) moduleK } } -func (a *ModuleBase) androidBaseContextFactory(ctx blueprint.BaseModuleContext) androidBaseContextImpl { - return androidBaseContextImpl{ - target: a.commonProperties.CompileTarget, - targetPrimary: a.commonProperties.CompilePrimary, - multiTargets: a.commonProperties.CompileMultiTargets, - kind: determineModuleKind(a, ctx), - config: ctx.Config().(Config), +func (m *ModuleBase) baseModuleContextFactory(ctx blueprint.BaseModuleContext) baseModuleContext { + return baseModuleContext{ + BaseModuleContext: ctx, + target: m.commonProperties.CompileTarget, + targetPrimary: m.commonProperties.CompilePrimary, + multiTargets: m.commonProperties.CompileMultiTargets, + kind: determineModuleKind(m, ctx), + config: ctx.Config().(Config), } } -func (a *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext) { - ctx := &androidModuleContext{ - module: a.module, - ModuleContext: blueprintCtx, - androidBaseContextImpl: a.androidBaseContextFactory(blueprintCtx), - installDeps: a.computeInstallDeps(blueprintCtx), - installFiles: a.installFiles, - missingDeps: blueprintCtx.GetMissingDependencies(), - variables: make(map[string]string), +func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext) { + ctx := &moduleContext{ + module: m.module, + bp: blueprintCtx, + baseModuleContext: m.baseModuleContextFactory(blueprintCtx), + installDeps: m.computeInstallDeps(blueprintCtx), + installFiles: m.installFiles, + variables: make(map[string]string), } + // Temporarily continue to call blueprintCtx.GetMissingDependencies() to maintain the previous behavior of never + // reporting missing dependency errors in Blueprint when AllowMissingDependencies == true. + // TODO: This will be removed once defaults modules handle missing dependency errors + blueprintCtx.GetMissingDependencies() + + // For the final GenerateAndroidBuildActions pass, require that all visited dependencies Soong modules and + // are enabled. + ctx.baseModuleContext.strictVisitDeps = true + if ctx.config.captureBuild { ctx.ruleParams = make(map[blueprint.Rule]blueprint.RuleParams) } @@ -869,70 +882,80 @@ func (a *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext) ctx.Variable(pctx, "moduleDescSuffix", s) // Some common property checks for properties that will be used later in androidmk.go - if a.commonProperties.Dist.Dest != nil { - _, err := validateSafePath(*a.commonProperties.Dist.Dest) + if m.commonProperties.Dist.Dest != nil { + _, err := validateSafePath(*m.commonProperties.Dist.Dest) if err != nil { ctx.PropertyErrorf("dist.dest", "%s", err.Error()) } } - if a.commonProperties.Dist.Dir != nil { - _, err := validateSafePath(*a.commonProperties.Dist.Dir) + if m.commonProperties.Dist.Dir != nil { + _, err := validateSafePath(*m.commonProperties.Dist.Dir) if err != nil { ctx.PropertyErrorf("dist.dir", "%s", err.Error()) } } - if a.commonProperties.Dist.Suffix != nil { - if strings.Contains(*a.commonProperties.Dist.Suffix, "/") { + if m.commonProperties.Dist.Suffix != nil { + if strings.Contains(*m.commonProperties.Dist.Suffix, "/") { ctx.PropertyErrorf("dist.suffix", "Suffix may not contain a '/' character.") } } - if a.Enabled() { - a.module.GenerateAndroidBuildActions(ctx) + if m.Enabled() { + m.module.GenerateAndroidBuildActions(ctx) if ctx.Failed() { return } - a.installFiles = append(a.installFiles, ctx.installFiles...) - a.checkbuildFiles = append(a.checkbuildFiles, ctx.checkbuildFiles...) + m.installFiles = append(m.installFiles, ctx.installFiles...) + m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...) - notice := proptools.StringDefault(a.commonProperties.Notice, "NOTICE") - if m := SrcIsModule(notice); m != "" { - a.noticeFile = ctx.ExpandOptionalSource(¬ice, "notice") + notice := proptools.StringDefault(m.commonProperties.Notice, "NOTICE") + if module := SrcIsModule(notice); module != "" { + m.noticeFile = ctx.ExpandOptionalSource(¬ice, "notice") } else { noticePath := filepath.Join(ctx.ModuleDir(), notice) - a.noticeFile = ExistentPathForSource(ctx, noticePath) + m.noticeFile = ExistentPathForSource(ctx, noticePath) } + } else if ctx.Config().AllowMissingDependencies() { + // If the module is not enabled it will not create any build rules, nothing will call + // ctx.GetMissingDependencies(), and blueprint will consider the missing dependencies to be unhandled + // and report them as an error even when AllowMissingDependencies = true. Call + // ctx.GetMissingDependencies() here to tell blueprint not to handle them. + ctx.GetMissingDependencies() } - if a == ctx.FinalModule().(Module).base() { - a.generateModuleTarget(ctx) + if m == ctx.FinalModule().(Module).base() { + m.generateModuleTarget(ctx) if ctx.Failed() { return } } - a.buildParams = ctx.buildParams - a.ruleParams = ctx.ruleParams - a.variables = ctx.variables + m.buildParams = ctx.buildParams + m.ruleParams = ctx.ruleParams + m.variables = ctx.variables } -type androidBaseContextImpl struct { +type baseModuleContext struct { + blueprint.BaseModuleContext target Target multiTargets []Target targetPrimary bool debug bool kind moduleKind config Config + + walkPath []Module + + strictVisitDeps bool // If true, enforce that all dependencies are enabled } -type androidModuleContext struct { - blueprint.ModuleContext - androidBaseContextImpl +type moduleContext struct { + bp blueprint.ModuleContext + baseModuleContext installDeps Paths installFiles Paths checkbuildFiles Paths - missingDeps []string module Module // For tests @@ -941,25 +964,22 @@ type androidModuleContext struct { variables map[string]string } -func (a *androidModuleContext) ninjaError(desc string, outputs []string, err error) { - a.ModuleContext.Build(pctx.PackageContext, blueprint.BuildParams{ - Rule: ErrorRule, - Description: desc, - Outputs: outputs, - Optional: true, +func (m *moduleContext) ninjaError(params BuildParams, err error) (PackageContext, BuildParams) { + return pctx, BuildParams{ + Rule: ErrorRule, + Description: params.Description, + Output: params.Output, + Outputs: params.Outputs, + ImplicitOutput: params.ImplicitOutput, + ImplicitOutputs: params.ImplicitOutputs, Args: map[string]string{ "error": err.Error(), }, - }) - return -} - -func (a *androidModuleContext) Config() Config { - return a.ModuleContext.Config().(Config) + } } -func (a *androidModuleContext) ModuleBuild(pctx PackageContext, params ModuleBuildParams) { - a.Build(pctx, BuildParams(params)) +func (m *moduleContext) ModuleBuild(pctx PackageContext, params ModuleBuildParams) { + m.Build(pctx, BuildParams(params)) } func convertBuildParams(params BuildParams) blueprint.BuildParams { @@ -1002,86 +1022,100 @@ func convertBuildParams(params BuildParams) blueprint.BuildParams { return bparams } -func (a *androidModuleContext) Variable(pctx PackageContext, name, value string) { - if a.config.captureBuild { - a.variables[name] = value +func (m *moduleContext) Variable(pctx PackageContext, name, value string) { + if m.config.captureBuild { + m.variables[name] = value } - a.ModuleContext.Variable(pctx.PackageContext, name, value) + m.bp.Variable(pctx.PackageContext, name, value) } -func (a *androidModuleContext) Rule(pctx PackageContext, name string, params blueprint.RuleParams, +func (m *moduleContext) Rule(pctx PackageContext, name string, params blueprint.RuleParams, argNames ...string) blueprint.Rule { - rule := a.ModuleContext.Rule(pctx.PackageContext, name, params, argNames...) + rule := m.bp.Rule(pctx.PackageContext, name, params, argNames...) - if a.config.captureBuild { - a.ruleParams[rule] = params + if m.config.captureBuild { + m.ruleParams[rule] = params } return rule } -func (a *androidModuleContext) Build(pctx PackageContext, params BuildParams) { - if a.config.captureBuild { - a.buildParams = append(a.buildParams, params) +func (m *moduleContext) Build(pctx PackageContext, params BuildParams) { + if params.Description != "" { + params.Description = "${moduleDesc}" + params.Description + "${moduleDescSuffix}" } - bparams := convertBuildParams(params) - - if bparams.Description != "" { - bparams.Description = "${moduleDesc}" + params.Description + "${moduleDescSuffix}" + if missingDeps := m.GetMissingDependencies(); len(missingDeps) > 0 { + pctx, params = m.ninjaError(params, fmt.Errorf("module %s missing dependencies: %s\n", + m.ModuleName(), strings.Join(missingDeps, ", "))) } - if a.missingDeps != nil { - a.ninjaError(bparams.Description, bparams.Outputs, - fmt.Errorf("module %s missing dependencies: %s\n", - a.ModuleName(), strings.Join(a.missingDeps, ", "))) - return + if m.config.captureBuild { + m.buildParams = append(m.buildParams, params) } - a.ModuleContext.Build(pctx.PackageContext, bparams) + m.bp.Build(pctx.PackageContext, convertBuildParams(params)) +} + +func (b *baseModuleContext) Module() Module { + module, _ := b.BaseModuleContext.Module().(Module) + return module } -func (a *androidModuleContext) GetMissingDependencies() []string { - return a.missingDeps +func (b *baseModuleContext) Config() Config { + return b.BaseModuleContext.Config().(Config) } -func (a *androidModuleContext) AddMissingDependencies(deps []string) { +func (m *moduleContext) GetMissingDependencies() []string { + var missingDeps []string + missingDeps = append(missingDeps, m.Module().base().commonProperties.MissingDeps...) + missingDeps = append(missingDeps, m.bp.GetMissingDependencies()...) + missingDeps = FirstUniqueStrings(missingDeps) + return missingDeps +} + +func (b *baseModuleContext) AddMissingDependencies(deps []string) { if deps != nil { - a.missingDeps = append(a.missingDeps, deps...) - a.missingDeps = FirstUniqueStrings(a.missingDeps) + missingDeps := &b.Module().base().commonProperties.MissingDeps + *missingDeps = append(*missingDeps, deps...) + *missingDeps = FirstUniqueStrings(*missingDeps) } } -func (a *androidModuleContext) validateAndroidModule(module blueprint.Module) Module { +func (b *baseModuleContext) validateAndroidModule(module blueprint.Module, strict bool) Module { aModule, _ := module.(Module) + + if !strict { + return aModule + } + if aModule == nil { - a.ModuleErrorf("module %q not an android module", a.OtherModuleName(aModule)) + b.ModuleErrorf("module %q not an android module", b.OtherModuleName(module)) return nil } if !aModule.Enabled() { - if a.Config().AllowMissingDependencies() { - a.AddMissingDependencies([]string{a.OtherModuleName(aModule)}) + if b.Config().AllowMissingDependencies() { + b.AddMissingDependencies([]string{b.OtherModuleName(aModule)}) } else { - a.ModuleErrorf("depends on disabled module %q", a.OtherModuleName(aModule)) + b.ModuleErrorf("depends on disabled module %q", b.OtherModuleName(aModule)) } return nil } - return aModule } -func (a *androidModuleContext) getDirectDepInternal(name string, tag blueprint.DependencyTag) (blueprint.Module, blueprint.DependencyTag) { +func (b *baseModuleContext) getDirectDepInternal(name string, tag blueprint.DependencyTag) (blueprint.Module, blueprint.DependencyTag) { type dep struct { mod blueprint.Module tag blueprint.DependencyTag } var deps []dep - a.VisitDirectDepsBlueprint(func(m blueprint.Module) { - if aModule, _ := m.(Module); aModule != nil && aModule.base().BaseModuleName() == name { - returnedTag := a.ModuleContext.OtherModuleDependencyTag(aModule) + b.VisitDirectDepsBlueprint(func(module blueprint.Module) { + if aModule, _ := module.(Module); aModule != nil && aModule.base().BaseModuleName() == name { + returnedTag := b.BaseModuleContext.OtherModuleDependencyTag(aModule) if tag == nil || returnedTag == tag { deps = append(deps, dep{aModule, returnedTag}) } @@ -1091,17 +1125,17 @@ func (a *androidModuleContext) getDirectDepInternal(name string, tag blueprint.D return deps[0].mod, deps[0].tag } else if len(deps) >= 2 { panic(fmt.Errorf("Multiple dependencies having same BaseModuleName() %q found from %q", - name, a.ModuleName())) + name, b.ModuleName())) } else { return nil, nil } } -func (a *androidModuleContext) GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module { +func (b *baseModuleContext) GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module { var deps []Module - a.VisitDirectDepsBlueprint(func(m blueprint.Module) { - if aModule, _ := m.(Module); aModule != nil { - if a.ModuleContext.OtherModuleDependencyTag(aModule) == tag { + b.VisitDirectDepsBlueprint(func(module blueprint.Module) { + if aModule, _ := module.(Module); aModule != nil { + if b.BaseModuleContext.OtherModuleDependencyTag(aModule) == tag { deps = append(deps, aModule) } } @@ -1109,42 +1143,42 @@ func (a *androidModuleContext) GetDirectDepsWithTag(tag blueprint.DependencyTag) return deps } -func (a *androidModuleContext) GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module { - m, _ := a.getDirectDepInternal(name, tag) - return m +func (m *moduleContext) GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module { + module, _ := m.getDirectDepInternal(name, tag) + return module } -func (a *androidModuleContext) GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) { - return a.getDirectDepInternal(name, nil) +func (b *baseModuleContext) GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) { + return b.getDirectDepInternal(name, nil) } -func (a *androidModuleContext) VisitDirectDepsBlueprint(visit func(blueprint.Module)) { - a.ModuleContext.VisitDirectDeps(visit) +func (b *baseModuleContext) VisitDirectDepsBlueprint(visit func(blueprint.Module)) { + b.BaseModuleContext.VisitDirectDeps(visit) } -func (a *androidModuleContext) VisitDirectDeps(visit func(Module)) { - a.ModuleContext.VisitDirectDeps(func(module blueprint.Module) { - if aModule := a.validateAndroidModule(module); aModule != nil { +func (b *baseModuleContext) VisitDirectDeps(visit func(Module)) { + b.BaseModuleContext.VisitDirectDeps(func(module blueprint.Module) { + if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil { visit(aModule) } }) } -func (a *androidModuleContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) { - a.ModuleContext.VisitDirectDeps(func(module blueprint.Module) { - if aModule := a.validateAndroidModule(module); aModule != nil { - if a.ModuleContext.OtherModuleDependencyTag(aModule) == tag { +func (b *baseModuleContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) { + b.BaseModuleContext.VisitDirectDeps(func(module blueprint.Module) { + if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil { + if b.BaseModuleContext.OtherModuleDependencyTag(aModule) == tag { visit(aModule) } } }) } -func (a *androidModuleContext) VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) { - a.ModuleContext.VisitDirectDepsIf( +func (b *baseModuleContext) VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) { + b.BaseModuleContext.VisitDirectDepsIf( // pred func(module blueprint.Module) bool { - if aModule := a.validateAndroidModule(module); aModule != nil { + if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil { return pred(aModule) } else { return false @@ -1156,19 +1190,19 @@ func (a *androidModuleContext) VisitDirectDepsIf(pred func(Module) bool, visit f }) } -func (a *androidModuleContext) VisitDepsDepthFirst(visit func(Module)) { - a.ModuleContext.VisitDepsDepthFirst(func(module blueprint.Module) { - if aModule := a.validateAndroidModule(module); aModule != nil { +func (b *baseModuleContext) VisitDepsDepthFirst(visit func(Module)) { + b.BaseModuleContext.VisitDepsDepthFirst(func(module blueprint.Module) { + if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil { visit(aModule) } }) } -func (a *androidModuleContext) VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module)) { - a.ModuleContext.VisitDepsDepthFirstIf( +func (b *baseModuleContext) VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module)) { + b.BaseModuleContext.VisitDepsDepthFirstIf( // pred func(module blueprint.Module) bool { - if aModule := a.validateAndroidModule(module); aModule != nil { + if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil { return pred(aModule) } else { return false @@ -1180,15 +1214,21 @@ func (a *androidModuleContext) VisitDepsDepthFirstIf(pred func(Module) bool, vis }) } -func (a *androidModuleContext) WalkDepsBlueprint(visit func(blueprint.Module, blueprint.Module) bool) { - a.ModuleContext.WalkDeps(visit) +func (b *baseModuleContext) WalkDepsBlueprint(visit func(blueprint.Module, blueprint.Module) bool) { + b.BaseModuleContext.WalkDeps(visit) } -func (a *androidModuleContext) WalkDeps(visit func(Module, Module) bool) { - a.ModuleContext.WalkDeps(func(child, parent blueprint.Module) bool { - childAndroidModule := a.validateAndroidModule(child) - parentAndroidModule := a.validateAndroidModule(parent) +func (b *baseModuleContext) WalkDeps(visit func(Module, Module) bool) { + b.walkPath = []Module{b.Module()} + b.BaseModuleContext.WalkDeps(func(child, parent blueprint.Module) bool { + childAndroidModule, _ := child.(Module) + parentAndroidModule, _ := parent.(Module) if childAndroidModule != nil && parentAndroidModule != nil { + // record walkPath before visit + for b.walkPath[len(b.walkPath)-1] != parentAndroidModule { + b.walkPath = b.walkPath[0 : len(b.walkPath)-1] + } + b.walkPath = append(b.walkPath, childAndroidModule) return visit(childAndroidModule, parentAndroidModule) } else { return false @@ -1196,143 +1236,151 @@ func (a *androidModuleContext) WalkDeps(visit func(Module, Module) bool) { }) } -func (a *androidModuleContext) VisitAllModuleVariants(visit func(Module)) { - a.ModuleContext.VisitAllModuleVariants(func(module blueprint.Module) { +func (b *baseModuleContext) GetWalkPath() []Module { + return b.walkPath +} + +func (m *moduleContext) VisitAllModuleVariants(visit func(Module)) { + m.bp.VisitAllModuleVariants(func(module blueprint.Module) { visit(module.(Module)) }) } -func (a *androidModuleContext) PrimaryModule() Module { - return a.ModuleContext.PrimaryModule().(Module) +func (m *moduleContext) PrimaryModule() Module { + return m.bp.PrimaryModule().(Module) +} + +func (m *moduleContext) FinalModule() Module { + return m.bp.FinalModule().(Module) } -func (a *androidModuleContext) FinalModule() Module { - return a.ModuleContext.FinalModule().(Module) +func (m *moduleContext) ModuleSubDir() string { + return m.bp.ModuleSubDir() } -func (a *androidBaseContextImpl) Target() Target { - return a.target +func (b *baseModuleContext) Target() Target { + return b.target } -func (a *androidBaseContextImpl) TargetPrimary() bool { - return a.targetPrimary +func (b *baseModuleContext) TargetPrimary() bool { + return b.targetPrimary } -func (a *androidBaseContextImpl) MultiTargets() []Target { - return a.multiTargets +func (b *baseModuleContext) MultiTargets() []Target { + return b.multiTargets } -func (a *androidBaseContextImpl) Arch() Arch { - return a.target.Arch +func (b *baseModuleContext) Arch() Arch { + return b.target.Arch } -func (a *androidBaseContextImpl) Os() OsType { - return a.target.Os +func (b *baseModuleContext) Os() OsType { + return b.target.Os } -func (a *androidBaseContextImpl) Host() bool { - return a.target.Os.Class == Host || a.target.Os.Class == HostCross +func (b *baseModuleContext) Host() bool { + return b.target.Os.Class == Host || b.target.Os.Class == HostCross } -func (a *androidBaseContextImpl) Device() bool { - return a.target.Os.Class == Device +func (b *baseModuleContext) Device() bool { + return b.target.Os.Class == Device } -func (a *androidBaseContextImpl) Darwin() bool { - return a.target.Os == Darwin +func (b *baseModuleContext) Darwin() bool { + return b.target.Os == Darwin } -func (a *androidBaseContextImpl) Fuchsia() bool { - return a.target.Os == Fuchsia +func (b *baseModuleContext) Fuchsia() bool { + return b.target.Os == Fuchsia } -func (a *androidBaseContextImpl) Windows() bool { - return a.target.Os == Windows +func (b *baseModuleContext) Windows() bool { + return b.target.Os == Windows } -func (a *androidBaseContextImpl) Debug() bool { - return a.debug +func (b *baseModuleContext) Debug() bool { + return b.debug } -func (a *androidBaseContextImpl) PrimaryArch() bool { - if len(a.config.Targets[a.target.Os]) <= 1 { +func (b *baseModuleContext) PrimaryArch() bool { + if len(b.config.Targets[b.target.Os]) <= 1 { return true } - return a.target.Arch.ArchType == a.config.Targets[a.target.Os][0].Arch.ArchType + return b.target.Arch.ArchType == b.config.Targets[b.target.Os][0].Arch.ArchType } -func (a *androidBaseContextImpl) AConfig() Config { - return a.config +func (b *baseModuleContext) AConfig() Config { + return b.config } -func (a *androidBaseContextImpl) DeviceConfig() DeviceConfig { - return DeviceConfig{a.config.deviceConfig} +func (b *baseModuleContext) DeviceConfig() DeviceConfig { + return DeviceConfig{b.config.deviceConfig} } -func (a *androidBaseContextImpl) Platform() bool { - return a.kind == platformModule +func (b *baseModuleContext) Platform() bool { + return b.kind == platformModule } -func (a *androidBaseContextImpl) DeviceSpecific() bool { - return a.kind == deviceSpecificModule +func (b *baseModuleContext) DeviceSpecific() bool { + return b.kind == deviceSpecificModule } -func (a *androidBaseContextImpl) SocSpecific() bool { - return a.kind == socSpecificModule +func (b *baseModuleContext) SocSpecific() bool { + return b.kind == socSpecificModule } -func (a *androidBaseContextImpl) ProductSpecific() bool { - return a.kind == productSpecificModule +func (b *baseModuleContext) ProductSpecific() bool { + return b.kind == productSpecificModule } -func (a *androidBaseContextImpl) ProductServicesSpecific() bool { - return a.kind == productServicesSpecificModule +func (b *baseModuleContext) ProductServicesSpecific() bool { + return b.kind == productServicesSpecificModule } // Makes this module a platform module, i.e. not specific to soc, device, // product, or product_services. -func (a *ModuleBase) MakeAsPlatform() { - a.commonProperties.Vendor = boolPtr(false) - a.commonProperties.Proprietary = boolPtr(false) - a.commonProperties.Soc_specific = boolPtr(false) - a.commonProperties.Product_specific = boolPtr(false) - a.commonProperties.Product_services_specific = boolPtr(false) +func (m *ModuleBase) MakeAsPlatform() { + m.commonProperties.Vendor = boolPtr(false) + m.commonProperties.Proprietary = boolPtr(false) + m.commonProperties.Soc_specific = boolPtr(false) + m.commonProperties.Product_specific = boolPtr(false) + m.commonProperties.Product_services_specific = boolPtr(false) } -func (a *ModuleBase) EnableNativeBridgeSupportByDefault() { - a.commonProperties.Native_bridge_supported = boolPtr(true) +func (m *ModuleBase) EnableNativeBridgeSupportByDefault() { + m.commonProperties.Native_bridge_supported = boolPtr(true) } -func (a *androidModuleContext) InstallInData() bool { - return a.module.InstallInData() +func (m *moduleContext) InstallInData() bool { + return m.module.InstallInData() } -func (a *androidModuleContext) InstallInSanitizerDir() bool { - return a.module.InstallInSanitizerDir() +func (m *moduleContext) InstallInSanitizerDir() bool { + return m.module.InstallInSanitizerDir() } -func (a *androidModuleContext) InstallInRecovery() bool { - return a.module.InstallInRecovery() +func (m *moduleContext) InstallInRecovery() bool { + return m.module.InstallInRecovery() } -func (a *androidModuleContext) skipInstall(fullInstallPath OutputPath) bool { - if a.module.base().commonProperties.SkipInstall { +func (m *moduleContext) skipInstall(fullInstallPath OutputPath) bool { + if m.module.base().commonProperties.SkipInstall { return true } // We'll need a solution for choosing which of modules with the same name in different // namespaces to install. For now, reuse the list of namespaces exported to Make as the // list of namespaces to install in a Soong-only build. - if !a.module.base().commonProperties.NamespaceExportedToMake { + if !m.module.base().commonProperties.NamespaceExportedToMake { return true } - if a.Device() { - if a.Config().SkipDeviceInstall() { + if m.Device() { + if m.Config().SkipDeviceInstall() { return true } - if a.Config().SkipMegaDeviceInstall(fullInstallPath.String()) { + if m.Config().SkipMegaDeviceInstall(fullInstallPath.String()) { return true } } @@ -1340,29 +1388,29 @@ func (a *androidModuleContext) skipInstall(fullInstallPath OutputPath) bool { return false } -func (a *androidModuleContext) InstallFile(installPath OutputPath, name string, srcPath Path, +func (m *moduleContext) InstallFile(installPath OutputPath, name string, srcPath Path, deps ...Path) OutputPath { - return a.installFile(installPath, name, srcPath, Cp, deps) + return m.installFile(installPath, name, srcPath, Cp, deps) } -func (a *androidModuleContext) InstallExecutable(installPath OutputPath, name string, srcPath Path, +func (m *moduleContext) InstallExecutable(installPath OutputPath, name string, srcPath Path, deps ...Path) OutputPath { - return a.installFile(installPath, name, srcPath, CpExecutable, deps) + return m.installFile(installPath, name, srcPath, CpExecutable, deps) } -func (a *androidModuleContext) installFile(installPath OutputPath, name string, srcPath Path, +func (m *moduleContext) installFile(installPath OutputPath, name string, srcPath Path, rule blueprint.Rule, deps []Path) OutputPath { - fullInstallPath := installPath.Join(a, name) - a.module.base().hooks.runInstallHooks(a, fullInstallPath, false) + fullInstallPath := installPath.Join(m, name) + m.module.base().hooks.runInstallHooks(m, fullInstallPath, false) - if !a.skipInstall(fullInstallPath) { + if !m.skipInstall(fullInstallPath) { - deps = append(deps, a.installDeps...) + deps = append(deps, m.installDeps...) var implicitDeps, orderOnlyDeps Paths - if a.Host() { + if m.Host() { // Installed host modules might be used during the build, depend directly on their // dependencies so their timestamp is updated whenever their dependency is updated implicitDeps = deps @@ -1370,73 +1418,73 @@ func (a *androidModuleContext) installFile(installPath OutputPath, name string, orderOnlyDeps = deps } - a.Build(pctx, BuildParams{ + m.Build(pctx, BuildParams{ Rule: rule, Description: "install " + fullInstallPath.Base(), Output: fullInstallPath, Input: srcPath, Implicits: implicitDeps, OrderOnly: orderOnlyDeps, - Default: !a.Config().EmbeddedInMake(), + Default: !m.Config().EmbeddedInMake(), }) - a.installFiles = append(a.installFiles, fullInstallPath) + m.installFiles = append(m.installFiles, fullInstallPath) } - a.checkbuildFiles = append(a.checkbuildFiles, srcPath) + m.checkbuildFiles = append(m.checkbuildFiles, srcPath) return fullInstallPath } -func (a *androidModuleContext) InstallSymlink(installPath OutputPath, name string, srcPath OutputPath) OutputPath { - fullInstallPath := installPath.Join(a, name) - a.module.base().hooks.runInstallHooks(a, fullInstallPath, true) +func (m *moduleContext) InstallSymlink(installPath OutputPath, name string, srcPath OutputPath) OutputPath { + fullInstallPath := installPath.Join(m, name) + m.module.base().hooks.runInstallHooks(m, fullInstallPath, true) - if !a.skipInstall(fullInstallPath) { + if !m.skipInstall(fullInstallPath) { relPath, err := filepath.Rel(path.Dir(fullInstallPath.String()), srcPath.String()) if err != nil { panic(fmt.Sprintf("Unable to generate symlink between %q and %q: %s", fullInstallPath.Base(), srcPath.Base(), err)) } - a.Build(pctx, BuildParams{ + m.Build(pctx, BuildParams{ Rule: Symlink, Description: "install symlink " + fullInstallPath.Base(), Output: fullInstallPath, OrderOnly: Paths{srcPath}, - Default: !a.Config().EmbeddedInMake(), + Default: !m.Config().EmbeddedInMake(), Args: map[string]string{ "fromPath": relPath, }, }) - a.installFiles = append(a.installFiles, fullInstallPath) - a.checkbuildFiles = append(a.checkbuildFiles, srcPath) + m.installFiles = append(m.installFiles, fullInstallPath) + m.checkbuildFiles = append(m.checkbuildFiles, srcPath) } return fullInstallPath } // installPath/name -> absPath where absPath might be a path that is available only at runtime // (e.g. /apex/...) -func (a *androidModuleContext) InstallAbsoluteSymlink(installPath OutputPath, name string, absPath string) OutputPath { - fullInstallPath := installPath.Join(a, name) - a.module.base().hooks.runInstallHooks(a, fullInstallPath, true) +func (m *moduleContext) InstallAbsoluteSymlink(installPath OutputPath, name string, absPath string) OutputPath { + fullInstallPath := installPath.Join(m, name) + m.module.base().hooks.runInstallHooks(m, fullInstallPath, true) - if !a.skipInstall(fullInstallPath) { - a.Build(pctx, BuildParams{ + if !m.skipInstall(fullInstallPath) { + m.Build(pctx, BuildParams{ Rule: Symlink, Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath, Output: fullInstallPath, - Default: !a.Config().EmbeddedInMake(), + Default: !m.Config().EmbeddedInMake(), Args: map[string]string{ "fromPath": absPath, }, }) - a.installFiles = append(a.installFiles, fullInstallPath) + m.installFiles = append(m.installFiles, fullInstallPath) } return fullInstallPath } -func (a *androidModuleContext) CheckbuildFile(srcPath Path) { - a.checkbuildFiles = append(a.checkbuildFiles, srcPath) +func (m *moduleContext) CheckbuildFile(srcPath Path) { + m.checkbuildFiles = append(m.checkbuildFiles, srcPath) } type fileInstaller interface { @@ -1551,54 +1599,54 @@ type HostToolProvider interface { // be tagged with `android:"path" to support automatic source module dependency resolution. // // Deprecated: use PathsForModuleSrc or PathsForModuleSrcExcludes instead. -func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Paths { - return PathsForModuleSrcExcludes(ctx, srcFiles, excludes) +func (m *moduleContext) ExpandSources(srcFiles, excludes []string) Paths { + return PathsForModuleSrcExcludes(m, srcFiles, excludes) } // Returns a single path expanded from globs and modules referenced using ":module" syntax. The property must // be tagged with `android:"path" to support automatic source module dependency resolution. // // Deprecated: use PathForModuleSrc instead. -func (ctx *androidModuleContext) ExpandSource(srcFile, prop string) Path { - return PathForModuleSrc(ctx, srcFile) +func (m *moduleContext) ExpandSource(srcFile, prop string) Path { + return PathForModuleSrc(m, srcFile) } // Returns an optional single path expanded from globs and modules referenced using ":module" syntax if // the srcFile is non-nil. The property must be tagged with `android:"path" to support automatic source module // dependency resolution. -func (ctx *androidModuleContext) ExpandOptionalSource(srcFile *string, prop string) OptionalPath { +func (m *moduleContext) ExpandOptionalSource(srcFile *string, prop string) OptionalPath { if srcFile != nil { - return OptionalPathForPath(PathForModuleSrc(ctx, *srcFile)) + return OptionalPathForPath(PathForModuleSrc(m, *srcFile)) } return OptionalPath{} } -func (ctx *androidModuleContext) RequiredModuleNames() []string { - return ctx.module.base().commonProperties.Required +func (m *moduleContext) RequiredModuleNames() []string { + return m.module.base().commonProperties.Required } -func (ctx *androidModuleContext) HostRequiredModuleNames() []string { - return ctx.module.base().commonProperties.Host_required +func (m *moduleContext) HostRequiredModuleNames() []string { + return m.module.base().commonProperties.Host_required } -func (ctx *androidModuleContext) TargetRequiredModuleNames() []string { - return ctx.module.base().commonProperties.Target_required +func (m *moduleContext) TargetRequiredModuleNames() []string { + return m.module.base().commonProperties.Target_required } -func (ctx *androidModuleContext) Glob(globPattern string, excludes []string) Paths { - ret, err := ctx.GlobWithDeps(globPattern, excludes) +func (b *baseModuleContext) Glob(globPattern string, excludes []string) Paths { + ret, err := b.GlobWithDeps(globPattern, excludes) if err != nil { - ctx.ModuleErrorf("glob: %s", err.Error()) + b.ModuleErrorf("glob: %s", err.Error()) } - return pathsForModuleSrcFromFullPath(ctx, ret, true) + return pathsForModuleSrcFromFullPath(b, ret, true) } -func (ctx *androidModuleContext) GlobFiles(globPattern string, excludes []string) Paths { - ret, err := ctx.GlobWithDeps(globPattern, excludes) +func (b *baseModuleContext) GlobFiles(globPattern string, excludes []string) Paths { + ret, err := b.GlobWithDeps(globPattern, excludes) if err != nil { - ctx.ModuleErrorf("glob: %s", err.Error()) + b.ModuleErrorf("glob: %s", err.Error()) } - return pathsForModuleSrcFromFullPath(ctx, ret, false) + return pathsForModuleSrcFromFullPath(b, ret, false) } func init() { @@ -1658,17 +1706,8 @@ func (c *buildTargetSingleton) GenerateBuildActions(ctx SingletonContext) { return } - sortedKeys := func(m map[string]Paths) []string { - s := make([]string, 0, len(m)) - for k := range m { - s = append(s, k) - } - sort.Strings(s) - return s - } - // Ensure ancestor directories are in modulesInDir - dirs := sortedKeys(modulesInDir) + dirs := SortedStringKeys(modulesInDir) for _, dir := range dirs { dir := parentDir(dir) for dir != "." && dir != "/" { @@ -1681,7 +1720,6 @@ func (c *buildTargetSingleton) GenerateBuildActions(ctx SingletonContext) { } // Make directories build their direct subdirectories - dirs = sortedKeys(modulesInDir) for _, dir := range dirs { p := parentDir(dir) if p != "." && p != "/" { @@ -1738,8 +1776,7 @@ func (c *buildTargetSingleton) GenerateBuildActions(ctx SingletonContext) { } // Wrap those into host|host-cross|target phony rules - osClasses := sortedKeys(osClass) - for _, class := range osClasses { + for _, class := range SortedStringKeys(osClass) { ctx.Build(pctx, BuildParams{ Rule: blueprint.Phony, Output: PathForPhony(ctx, class), diff --git a/android/mutator.go b/android/mutator.go index 0e802493f..081c2b248 100644 --- a/android/mutator.go +++ b/android/mutator.go @@ -66,8 +66,8 @@ type registerMutatorsContext struct { } type RegisterMutatorsContext interface { - TopDown(name string, m AndroidTopDownMutator) MutatorHandle - BottomUp(name string, m AndroidBottomUpMutator) MutatorHandle + TopDown(name string, m TopDownMutator) MutatorHandle + BottomUp(name string, m BottomUpMutator) MutatorHandle } type RegisterMutatorFunc func(RegisterMutatorsContext) @@ -110,52 +110,27 @@ func PostDepsMutators(f RegisterMutatorFunc) { postDeps = append(postDeps, f) } -type AndroidTopDownMutator func(TopDownMutatorContext) +type TopDownMutator func(TopDownMutatorContext) type TopDownMutatorContext interface { BaseModuleContext - androidBaseContext - OtherModuleExists(name string) bool Rename(name string) - Module() Module - - OtherModuleName(m blueprint.Module) string - OtherModuleDir(m blueprint.Module) string - OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{}) - OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag CreateModule(blueprint.ModuleFactory, ...interface{}) - - GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module - GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) - - VisitDirectDeps(visit func(Module)) - VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) - VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) - VisitDepsDepthFirst(visit func(Module)) - VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module)) - WalkDeps(visit func(Module, Module) bool) - // GetWalkPath is supposed to be called in visit function passed in WalkDeps() - // and returns a top-down dependency path from a start module to current child module. - GetWalkPath() []Module } -type androidTopDownMutatorContext struct { - blueprint.TopDownMutatorContext - androidBaseContextImpl - walkPath []Module +type topDownMutatorContext struct { + bp blueprint.TopDownMutatorContext + baseModuleContext } -type AndroidBottomUpMutator func(BottomUpMutatorContext) +type BottomUpMutator func(BottomUpMutatorContext) type BottomUpMutatorContext interface { BaseModuleContext - androidBaseContext - OtherModuleExists(name string) bool Rename(name string) - Module() blueprint.Module AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) AddReverseDependency(module blueprint.Module, tag blueprint.DependencyTag, name string) @@ -168,17 +143,17 @@ type BottomUpMutatorContext interface { ReplaceDependencies(string) } -type androidBottomUpMutatorContext struct { - blueprint.BottomUpMutatorContext - androidBaseContextImpl +type bottomUpMutatorContext struct { + bp blueprint.BottomUpMutatorContext + baseModuleContext } -func (x *registerMutatorsContext) BottomUp(name string, m AndroidBottomUpMutator) MutatorHandle { +func (x *registerMutatorsContext) BottomUp(name string, m BottomUpMutator) MutatorHandle { f := func(ctx blueprint.BottomUpMutatorContext) { if a, ok := ctx.Module().(Module); ok { - actx := &androidBottomUpMutatorContext{ - BottomUpMutatorContext: ctx, - androidBaseContextImpl: a.base().androidBaseContextFactory(ctx), + actx := &bottomUpMutatorContext{ + bp: ctx, + baseModuleContext: a.base().baseModuleContextFactory(ctx), } m(actx) } @@ -188,12 +163,12 @@ func (x *registerMutatorsContext) BottomUp(name string, m AndroidBottomUpMutator return mutator } -func (x *registerMutatorsContext) TopDown(name string, m AndroidTopDownMutator) MutatorHandle { +func (x *registerMutatorsContext) TopDown(name string, m TopDownMutator) MutatorHandle { f := func(ctx blueprint.TopDownMutatorContext) { if a, ok := ctx.Module().(Module); ok { - actx := &androidTopDownMutatorContext{ - TopDownMutatorContext: ctx, - androidBaseContextImpl: a.base().androidBaseContextFactory(ctx), + actx := &topDownMutatorContext{ + bp: ctx, + baseModuleContext: a.base().baseModuleContextFactory(ctx), } m(actx) } @@ -218,106 +193,13 @@ func depsMutator(ctx BottomUpMutatorContext) { } } -func (a *androidTopDownMutatorContext) Config() Config { - return a.config -} - -func (a *androidBottomUpMutatorContext) Config() Config { - return a.config -} - -func (a *androidTopDownMutatorContext) Module() Module { - module, _ := a.TopDownMutatorContext.Module().(Module) - return module -} - -func (a *androidTopDownMutatorContext) VisitDirectDeps(visit func(Module)) { - a.TopDownMutatorContext.VisitDirectDeps(func(module blueprint.Module) { - if aModule, _ := module.(Module); aModule != nil { - visit(aModule) - } - }) -} - -func (a *androidTopDownMutatorContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) { - a.TopDownMutatorContext.VisitDirectDeps(func(module blueprint.Module) { - if aModule, _ := module.(Module); aModule != nil { - if a.TopDownMutatorContext.OtherModuleDependencyTag(aModule) == tag { - visit(aModule) - } - } - }) -} - -func (a *androidTopDownMutatorContext) VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) { - a.TopDownMutatorContext.VisitDirectDepsIf( - // pred - func(module blueprint.Module) bool { - if aModule, _ := module.(Module); aModule != nil { - return pred(aModule) - } else { - return false - } - }, - // visit - func(module blueprint.Module) { - visit(module.(Module)) - }) -} - -func (a *androidTopDownMutatorContext) VisitDepsDepthFirst(visit func(Module)) { - a.TopDownMutatorContext.VisitDepsDepthFirst(func(module blueprint.Module) { - if aModule, _ := module.(Module); aModule != nil { - visit(aModule) - } - }) -} - -func (a *androidTopDownMutatorContext) VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module)) { - a.TopDownMutatorContext.VisitDepsDepthFirstIf( - // pred - func(module blueprint.Module) bool { - if aModule, _ := module.(Module); aModule != nil { - return pred(aModule) - } else { - return false - } - }, - // visit - func(module blueprint.Module) { - visit(module.(Module)) - }) -} - -func (a *androidTopDownMutatorContext) WalkDeps(visit func(Module, Module) bool) { - a.walkPath = []Module{a.Module()} - a.TopDownMutatorContext.WalkDeps(func(child, parent blueprint.Module) bool { - childAndroidModule, _ := child.(Module) - parentAndroidModule, _ := parent.(Module) - if childAndroidModule != nil && parentAndroidModule != nil { - // record walkPath before visit - for a.walkPath[len(a.walkPath)-1] != parentAndroidModule { - a.walkPath = a.walkPath[0 : len(a.walkPath)-1] - } - a.walkPath = append(a.walkPath, childAndroidModule) - return visit(childAndroidModule, parentAndroidModule) - } else { - return false - } - }) -} - -func (a *androidTopDownMutatorContext) GetWalkPath() []Module { - return a.walkPath -} - -func (a *androidTopDownMutatorContext) AppendProperties(props ...interface{}) { +func (t *topDownMutatorContext) AppendProperties(props ...interface{}) { for _, p := range props { - err := proptools.AppendMatchingProperties(a.Module().base().customizableProperties, + err := proptools.AppendMatchingProperties(t.Module().base().customizableProperties, p, nil) if err != nil { if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { - a.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) + t.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) } else { panic(err) } @@ -325,16 +207,74 @@ func (a *androidTopDownMutatorContext) AppendProperties(props ...interface{}) { } } -func (a *androidTopDownMutatorContext) PrependProperties(props ...interface{}) { +func (t *topDownMutatorContext) PrependProperties(props ...interface{}) { for _, p := range props { - err := proptools.PrependMatchingProperties(a.Module().base().customizableProperties, + err := proptools.PrependMatchingProperties(t.Module().base().customizableProperties, p, nil) if err != nil { if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { - a.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) + t.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) } else { panic(err) } } } } + +// android.topDownMutatorContext either has to embed blueprint.TopDownMutatorContext, in which case every method that +// has an overridden version in android.BaseModuleContext has to be manually forwarded to BaseModuleContext to avoid +// ambiguous method errors, or it has to store a blueprint.TopDownMutatorContext non-embedded, in which case every +// non-overridden method has to be forwarded. There are fewer non-overridden methods, so use the latter. The following +// methods forward to the identical blueprint versions for topDownMutatorContext and bottomUpMutatorContext. + +func (t *topDownMutatorContext) Rename(name string) { + t.bp.Rename(name) +} + +func (t *topDownMutatorContext) CreateModule(factory blueprint.ModuleFactory, props ...interface{}) { + t.bp.CreateModule(factory, props...) +} + +func (b *bottomUpMutatorContext) Rename(name string) { + b.bp.Rename(name) +} + +func (b *bottomUpMutatorContext) AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) { + b.bp.AddDependency(module, tag, name...) +} + +func (b *bottomUpMutatorContext) AddReverseDependency(module blueprint.Module, tag blueprint.DependencyTag, name string) { + b.bp.AddReverseDependency(module, tag, name) +} + +func (b *bottomUpMutatorContext) CreateVariations(variations ...string) []blueprint.Module { + return b.bp.CreateVariations(variations...) +} + +func (b *bottomUpMutatorContext) CreateLocalVariations(variations ...string) []blueprint.Module { + return b.bp.CreateLocalVariations(variations...) +} + +func (b *bottomUpMutatorContext) SetDependencyVariation(variation string) { + b.bp.SetDependencyVariation(variation) +} + +func (b *bottomUpMutatorContext) AddVariationDependencies(variations []blueprint.Variation, tag blueprint.DependencyTag, + names ...string) { + + b.bp.AddVariationDependencies(variations, tag, names...) +} + +func (b *bottomUpMutatorContext) AddFarVariationDependencies(variations []blueprint.Variation, + tag blueprint.DependencyTag, names ...string) { + + b.bp.AddFarVariationDependencies(variations, tag, names...) +} + +func (b *bottomUpMutatorContext) AddInterVariantDependency(tag blueprint.DependencyTag, from, to blueprint.Module) { + b.bp.AddInterVariantDependency(tag, from, to) +} + +func (b *bottomUpMutatorContext) ReplaceDependencies(name string) { + b.bp.ReplaceDependencies(name) +} diff --git a/android/mutator_test.go b/android/mutator_test.go new file mode 100644 index 000000000..4cef40006 --- /dev/null +++ b/android/mutator_test.go @@ -0,0 +1,99 @@ +// Copyright 2015 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 ( + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/google/blueprint/proptools" +) + +type mutatorTestModule struct { + ModuleBase + props struct { + } + + missingDeps []string +} + +func mutatorTestModuleFactory() Module { + module := &mutatorTestModule{} + module.AddProperties(&module.props) + InitAndroidModule(module) + return module +} + +func (m *mutatorTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { + ctx.Build(pctx, BuildParams{ + Rule: Touch, + Output: PathForModuleOut(ctx, "output"), + }) + + m.missingDeps = ctx.GetMissingDependencies() +} + +func (m *mutatorTestModule) DepsMutator(ctx BottomUpMutatorContext) { + ctx.AddDependency(ctx.Module(), nil, "regular_missing_dep") +} + +func addMissingDependenciesMutator(ctx TopDownMutatorContext) { + ctx.AddMissingDependencies([]string{"added_missing_dep"}) +} + +func TestMutatorAddMissingDependencies(t *testing.T) { + buildDir, err := ioutil.TempDir("", "soong_mutator_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(buildDir) + + config := TestConfig(buildDir, nil) + config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true) + + ctx := NewTestContext() + ctx.SetAllowMissingDependencies(true) + + ctx.RegisterModuleType("test", ModuleFactoryAdaptor(mutatorTestModuleFactory)) + ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) { + ctx.TopDown("add_missing_dependencies", addMissingDependenciesMutator) + }) + + bp := ` + test { + name: "foo", + } + ` + + mockFS := map[string][]byte{ + "Android.bp": []byte(bp), + } + + ctx.MockFileSystem(mockFS) + + ctx.Register() + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + FailIfErrored(t, errs) + + foo := ctx.ModuleForTests("foo", "").Module().(*mutatorTestModule) + + if g, w := foo.missingDeps, []string{"added_missing_dep", "regular_missing_dep"}; !reflect.DeepEqual(g, w) { + t.Errorf("want foo missing deps %q, got %q", w, g) + } +} diff --git a/android/namespace.go b/android/namespace.go index 78d7f3cc9..27ec1635e 100644 --- a/android/namespace.go +++ b/android/namespace.go @@ -222,6 +222,11 @@ func (r *NameResolver) parseFullyQualifiedName(name string) (namespaceName strin } func (r *NameResolver) getNamespacesToSearchForModule(sourceNamespace *Namespace) (searchOrder []*Namespace) { + if sourceNamespace.visibleNamespaces == nil { + // When handling dependencies before namespaceMutator, assume they are non-Soong Blueprint modules and give + // access to all namespaces. + return r.sortedNamespaces.sortedItems() + } return sourceNamespace.visibleNamespaces } diff --git a/android/namespace_test.go b/android/namespace_test.go index 9a791a534..20241fe1b 100644 --- a/android/namespace_test.go +++ b/android/namespace_test.go @@ -16,8 +16,6 @@ package android import ( "errors" - "io/ioutil" - "os" "path/filepath" "reflect" "testing" @@ -93,6 +91,28 @@ func TestImplicitlyImportRootNamespace(t *testing.T) { // setupTest will report any errors } +func TestDependingOnBlueprintModuleInRootNamespace(t *testing.T) { + _ = setupTest(t, + map[string]string{ + ".": ` + blueprint_test_module { + name: "a", + } + `, + "dir1": ` + soong_namespace { + } + blueprint_test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + // setupTest will report any errors +} + func TestDependingOnModuleInImportedNamespace(t *testing.T) { ctx := setupTest(t, map[string]string{ @@ -613,18 +633,13 @@ func mockFiles(bps map[string]string) (files map[string][]byte) { } func setupTestFromFiles(bps map[string][]byte) (ctx *TestContext, errs []error) { - buildDir, err := ioutil.TempDir("", "soong_namespace_test") - if err != nil { - return nil, []error{err} - } - defer os.RemoveAll(buildDir) - config := TestConfig(buildDir, nil) ctx = NewTestContext() ctx.MockFileSystem(bps) ctx.RegisterModuleType("test_module", ModuleFactoryAdaptor(newTestModule)) ctx.RegisterModuleType("soong_namespace", ModuleFactoryAdaptor(NamespaceFactory)) + ctx.RegisterModuleType("blueprint_test_module", newBlueprintTestModule) ctx.PreArchMutators(RegisterNamespaceMutator) ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) { ctx.BottomUp("rename", renameMutator) @@ -649,6 +664,7 @@ func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) } func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) { + t.Helper() ctx, errs := setupTestExpectErrs(bps) FailIfErrored(t, errs) return ctx @@ -726,3 +742,22 @@ func newTestModule() Module { InitAndroidModule(m) return m } + +type blueprintTestModule struct { + blueprint.SimpleName + properties struct { + Deps []string + } +} + +func (b *blueprintTestModule) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { + return b.properties.Deps +} + +func (b *blueprintTestModule) GenerateBuildActions(blueprint.ModuleContext) { +} + +func newBlueprintTestModule() (blueprint.Module, []interface{}) { + m := &blueprintTestModule{} + return m, []interface{}{&m.properties, &m.SimpleName.Properties} +} diff --git a/android/neverallow.go b/android/neverallow.go index 93144830e..f35d1fed9 100644 --- a/android/neverallow.go +++ b/android/neverallow.go @@ -95,15 +95,17 @@ func createLibcoreRules() []*rule { "external/icu", "external/okhttp", "external/wycheproof", + + // Not really a core library but still needs access to same capabilities. + "development", } - // Core library constraints. The no_standard_libs can only be used in core - // library projects. Access to core library targets is restricted using - // visibility rules. + // Core library constraints. The sdk_version: "none" can only be used in core library projects. + // Access to core library targets is restricted using visibility rules. rules := []*rule{ neverallow(). - notIn(append(coreLibraryProjects, "development")...). - with("no_standard_libs", "true"), + notIn(coreLibraryProjects...). + with("sdk_version", "none"), } return rules diff --git a/android/neverallow_test.go b/android/neverallow_test.go index 00c51eaab..ee3c94f0c 100644 --- a/android/neverallow_test.go +++ b/android/neverallow_test.go @@ -15,8 +15,6 @@ package android import ( - "io/ioutil" - "os" "testing" ) @@ -148,15 +146,41 @@ var neverallowTests = []struct { }, expectedError: "java_device_for_host can only be used in whitelisted projects", }, + // Libcore rule tests + { + name: "sdk_version: \"none\" inside core libraries", + fs: map[string][]byte{ + "libcore/Blueprints": []byte(` + java_library { + name: "inside_core_libraries", + sdk_version: "none", + }`), + }, + }, + { + name: "sdk_version: \"none\" outside core libraries", + fs: map[string][]byte{ + "Blueprints": []byte(` + java_library { + name: "outside_core_libraries", + sdk_version: "none", + }`), + }, + expectedError: "module \"outside_core_libraries\": violates neverallow", + }, + { + name: "sdk_version: \"current\"", + fs: map[string][]byte{ + "Blueprints": []byte(` + java_library { + name: "outside_core_libraries", + sdk_version: "current", + }`), + }, + }, } func TestNeverallow(t *testing.T) { - buildDir, err := ioutil.TempDir("", "soong_neverallow_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - config := TestConfig(buildDir, nil) for _, test := range neverallowTests { @@ -176,6 +200,7 @@ func testNeverallow(t *testing.T, config Config, fs map[string][]byte) (*TestCon ctx := NewTestContext() ctx.RegisterModuleType("cc_library", ModuleFactoryAdaptor(newMockCcLibraryModule)) ctx.RegisterModuleType("java_library", ModuleFactoryAdaptor(newMockJavaLibraryModule)) + ctx.RegisterModuleType("java_library_host", ModuleFactoryAdaptor(newMockJavaLibraryModule)) ctx.RegisterModuleType("java_device_for_host", ModuleFactoryAdaptor(newMockJavaLibraryModule)) ctx.PostDepsMutators(registerNeverallowMutator) ctx.Register() @@ -227,7 +252,8 @@ func (p *mockCcLibraryModule) GenerateAndroidBuildActions(ModuleContext) { } type mockJavaLibraryProperties struct { - Libs []string + Libs []string + Sdk_version *string } type mockJavaLibraryModule struct { diff --git a/android/paths.go b/android/paths.go index 3915ff414..20b8b823c 100644 --- a/android/paths.go +++ b/android/paths.go @@ -41,9 +41,7 @@ var _ PathContext = SingletonContext(nil) var _ PathContext = ModuleContext(nil) type ModuleInstallPathContext interface { - PathContext - - androidBaseContext + BaseModuleContext InstallInData() bool InstallInSanitizerDir() bool @@ -369,7 +367,7 @@ func expandOneSrcPath(ctx ModuleContext, s string, expandedExcludes []string) (P // each string. If incDirs is false, strip paths with a trailing '/' from the list. // It intended for use in globs that only list files that exist, so it allows '$' in // filenames. -func pathsForModuleSrcFromFullPath(ctx ModuleContext, paths []string, incDirs bool) Paths { +func pathsForModuleSrcFromFullPath(ctx BaseModuleContext, paths []string, incDirs bool) Paths { prefix := filepath.Join(ctx.Config().srcDir, ctx.ModuleDir()) + "/" if prefix == "./" { prefix = "" diff --git a/android/paths_test.go b/android/paths_test.go index 85c8d84bc..7bcfe41d1 100644 --- a/android/paths_test.go +++ b/android/paths_test.go @@ -17,8 +17,6 @@ package android import ( "errors" "fmt" - "io/ioutil" - "os" "reflect" "strings" "testing" @@ -200,7 +198,7 @@ func p(in interface{}) string { } type moduleInstallPathContextImpl struct { - androidBaseContextImpl + baseModuleContext inData bool inSanitizerDir bool @@ -212,7 +210,7 @@ func (moduleInstallPathContextImpl) Fs() pathtools.FileSystem { } func (m moduleInstallPathContextImpl) Config() Config { - return m.androidBaseContextImpl.config + return m.baseModuleContext.config } func (moduleInstallPathContextImpl) AddNinjaFileDeps(deps ...string) {} @@ -244,7 +242,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "host binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: hostTarget, }, }, @@ -255,7 +253,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "system binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, }, }, @@ -265,7 +263,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "vendor binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: socSpecificModule, }, @@ -276,7 +274,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "odm binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: deviceSpecificModule, }, @@ -287,7 +285,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "product binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productSpecificModule, }, @@ -298,7 +296,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "product_services binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productServicesSpecificModule, }, @@ -310,7 +308,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "system native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, }, inData: true, @@ -321,7 +319,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "vendor native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: socSpecificModule, }, @@ -333,7 +331,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "odm native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: deviceSpecificModule, }, @@ -345,7 +343,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "product native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productSpecificModule, }, @@ -358,7 +356,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "product_services native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productServicesSpecificModule, }, @@ -371,7 +369,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized system binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, }, inSanitizerDir: true, @@ -382,7 +380,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized vendor binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: socSpecificModule, }, @@ -394,7 +392,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized odm binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: deviceSpecificModule, }, @@ -406,7 +404,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized product binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productSpecificModule, }, @@ -419,7 +417,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized product_services binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productServicesSpecificModule, }, @@ -432,7 +430,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized system native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, }, inData: true, @@ -444,7 +442,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized vendor native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: socSpecificModule, }, @@ -457,7 +455,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized odm native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: deviceSpecificModule, }, @@ -470,7 +468,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized product native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productSpecificModule, }, @@ -483,7 +481,7 @@ func TestPathForModuleInstall(t *testing.T) { { name: "sanitized product_services native test binary", ctx: &moduleInstallPathContextImpl{ - androidBaseContextImpl: androidBaseContextImpl{ + baseModuleContext: baseModuleContext{ target: deviceTarget, kind: productServicesSpecificModule, }, @@ -497,7 +495,7 @@ func TestPathForModuleInstall(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - tc.ctx.androidBaseContextImpl.config = testConfig + tc.ctx.baseModuleContext.config = testConfig output := PathForModuleInstall(tc.ctx, tc.in...) if output.basePath.path != tc.out { t.Errorf("unexpected path:\n got: %q\nwant: %q\n", @@ -758,6 +756,11 @@ func (p *pathForModuleSrcTestModule) GenerateAndroidBuildActions(ctx ModuleConte if !p.props.Module_handles_missing_deps { p.missingDeps = ctx.GetMissingDependencies() } + + ctx.Build(pctx, BuildParams{ + Rule: Touch, + Output: PathForModuleOut(ctx, "output"), + }) } type pathForModuleSrcOutputFileProviderModule struct { @@ -875,12 +878,6 @@ func testPathForModuleSrc(t *testing.T, buildDir string, tests []pathForModuleSr } func TestPathsForModuleSrc(t *testing.T) { - buildDir, err := ioutil.TempDir("", "soong_paths_for_module_src_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - tests := []pathForModuleSrcTestCase{ { name: "path", @@ -961,12 +958,6 @@ func TestPathsForModuleSrc(t *testing.T) { } func TestPathForModuleSrc(t *testing.T) { - buildDir, err := ioutil.TempDir("", "soong_path_for_module_src_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - tests := []pathForModuleSrcTestCase{ { name: "path", @@ -1034,12 +1025,6 @@ func TestPathForModuleSrc(t *testing.T) { } func TestPathsForModuleSrc_AllowMissingDependencies(t *testing.T) { - buildDir, err := ioutil.TempDir("", "soong_paths_for_module_src_allow_missing_dependencies_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - config := TestConfig(buildDir, nil) config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true) diff --git a/android/prebuilt_etc_test.go b/android/prebuilt_etc_test.go index d977c307e..0a2c7a4f5 100644 --- a/android/prebuilt_etc_test.go +++ b/android/prebuilt_etc_test.go @@ -15,16 +15,13 @@ package android import ( - "io/ioutil" - "os" "path/filepath" "reflect" "testing" ) func testPrebuiltEtc(t *testing.T, bp string) (*TestContext, Config) { - config, buildDir := setUp(t) - defer tearDown(buildDir) + config := TestArchConfig(buildDir, nil) ctx := NewTestArchContext() ctx.RegisterModuleType("prebuilt_etc", ModuleFactoryAdaptor(PrebuiltEtcFactory)) ctx.RegisterModuleType("prebuilt_etc_host", ModuleFactoryAdaptor(PrebuiltEtcHostFactory)) @@ -51,20 +48,6 @@ func testPrebuiltEtc(t *testing.T, bp string) (*TestContext, Config) { return ctx, config } -func setUp(t *testing.T) (config Config, buildDir string) { - buildDir, err := ioutil.TempDir("", "soong_prebuilt_etc_test") - if err != nil { - t.Fatal(err) - } - - config = TestArchConfig(buildDir, nil) - return -} - -func tearDown(buildDir string) { - os.RemoveAll(buildDir) -} - func TestPrebuiltEtcVariants(t *testing.T) { ctx, _ := testPrebuiltEtc(t, ` prebuilt_etc { diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go index a5b85c8c2..0a18e2c90 100644 --- a/android/prebuilt_test.go +++ b/android/prebuilt_test.go @@ -16,8 +16,6 @@ package android import ( "fmt" - "io/ioutil" - "os" "testing" "github.com/google/blueprint" @@ -127,12 +125,6 @@ var prebuiltsTests = []struct { } func TestPrebuilts(t *testing.T) { - buildDir, err := ioutil.TempDir("", "soong_prebuilt_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - config := TestConfig(buildDir, nil) for _, test := range prebuiltsTests { diff --git a/android/rule_builder.go b/android/rule_builder.go index 4a3b02233..8d7e74b7a 100644 --- a/android/rule_builder.go +++ b/android/rule_builder.go @@ -525,6 +525,15 @@ func (c *RuleBuilderCommand) Outputs(paths WritablePaths) *RuleBuilderCommand { return c } +// OutputDir adds the output directory to the command line. This is only available when used with RuleBuilder.Sbox, +// and will be the temporary output directory managed by sbox, not the final one. +func (c *RuleBuilderCommand) OutputDir() *RuleBuilderCommand { + if !c.sbox { + panic("OutputDir only valid with Sbox") + } + return c.Text("__SBOX_OUT_DIR__") +} + // DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command // line, and causes RuleBuilder.Build file to set the depfile flag for ninja. If multiple depfiles are added to // commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together. diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go index df0f25640..cfbc2abee 100644 --- a/android/rule_builder_test.go +++ b/android/rule_builder_test.go @@ -16,8 +16,6 @@ package android import ( "fmt" - "io/ioutil" - "os" "path/filepath" "reflect" "strings" @@ -418,12 +416,6 @@ func testRuleBuilder_Build(ctx BuilderContext, in Path, out, outDep, outDir Writ } func TestRuleBuilder_Build(t *testing.T) { - buildDir, err := ioutil.TempDir("", "soong_test_rule_builder") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - bp := ` rule_builder_test { name: "foo", diff --git a/android/sh_binary_test.go b/android/sh_binary_test.go index becb35a21..c99e18c3e 100644 --- a/android/sh_binary_test.go +++ b/android/sh_binary_test.go @@ -1,19 +1,11 @@ package android import ( - "io/ioutil" - "os" "reflect" "testing" ) func testShBinary(t *testing.T, bp string) (*TestContext, Config) { - buildDir, err := ioutil.TempDir("", "soong_sh_binary_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - config := TestArchConfig(buildDir, nil) ctx := NewTestArchContext() diff --git a/android/testing.go b/android/testing.go index c0db75ecc..44bee4b7d 100644 --- a/android/testing.go +++ b/android/testing.go @@ -179,7 +179,7 @@ func buildParamsFromRule(provider testBuildProvider, rule string) TestingBuildPa func maybeBuildParamsFromDescription(provider testBuildProvider, desc string) TestingBuildParams { for _, p := range provider.BuildParamsForTests() { - if p.Description == desc { + if strings.Contains(p.Description, desc) { return newTestingBuildParams(provider, p) } } diff --git a/android/util.go b/android/util.go index f7a3437c3..3b8bc783d 100644 --- a/android/util.go +++ b/android/util.go @@ -16,6 +16,7 @@ package android import ( "fmt" + "reflect" "regexp" "runtime" "sort" @@ -77,10 +78,15 @@ func JoinWithSuffix(strs []string, suffix string, separator string) string { return string(ret) } -func sortedKeys(m map[string][]string) []string { - s := make([]string, 0, len(m)) - for k := range m { - s = append(s, k) +func SortedStringKeys(m interface{}) []string { + v := reflect.ValueOf(m) + if v.Kind() != reflect.Map { + panic(fmt.Sprintf("%#v is not a map", m)) + } + keys := v.MapKeys() + s := make([]string, 0, len(keys)) + for _, key := range keys { + s = append(s, key.String()) } sort.Strings(s) return s diff --git a/android/variable.go b/android/variable.go index d039a1659..b4f31c619 100644 --- a/android/variable.go +++ b/android/variable.go @@ -402,12 +402,12 @@ func variableMutator(mctx BottomUpMutatorContext) { } } -func (a *ModuleBase) setVariableProperties(ctx BottomUpMutatorContext, +func (m *ModuleBase) setVariableProperties(ctx BottomUpMutatorContext, prefix string, productVariablePropertyValue reflect.Value, variableValue interface{}) { printfIntoProperties(ctx, prefix, productVariablePropertyValue, variableValue) - err := proptools.AppendMatchingProperties(a.generalProperties, + err := proptools.AppendMatchingProperties(m.generalProperties, productVariablePropertyValue.Addr().Interface(), nil) if err != nil { if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { diff --git a/android/visibility_test.go b/android/visibility_test.go index 09c5b1b34..1a514955d 100644 --- a/android/visibility_test.go +++ b/android/visibility_test.go @@ -1,8 +1,6 @@ package android import ( - "io/ioutil" - "os" "testing" "github.com/google/blueprint" @@ -663,12 +661,6 @@ var visibilityTests = []struct { } func TestVisibility(t *testing.T) { - buildDir, err := ioutil.TempDir("", "soong_neverallow_test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(buildDir) - for _, test := range visibilityTests { t.Run(test.name, func(t *testing.T) { _, errs := testVisibility(buildDir, test.fs) @@ -759,6 +751,3 @@ func defaultsFactory() Module { InitDefaultsModule(m) return m } - -func (*mockDefaults) GenerateAndroidBuildActions(ctx ModuleContext) { -} diff --git a/android/vts_config_test.go b/android/vts_config_test.go index 7d4c9b1cf..142b2f591 100644 --- a/android/vts_config_test.go +++ b/android/vts_config_test.go @@ -15,19 +15,11 @@ package android import ( - "io/ioutil" - "os" "testing" ) func testVtsConfig(test *testing.T, bpFileContents string) *TestContext { - buildDir, err := ioutil.TempDir("", "soong_vts_config_test") - if err != nil { - test.Fatal(err) - } - config := TestArchConfig(buildDir, nil) - defer func() { os.RemoveAll(buildDir) }() ctx := NewTestArchContext() ctx.RegisterModuleType("vts_config", ModuleFactoryAdaptor(VtsConfigFactory)) diff --git a/androidmk/Android.bp b/androidmk/Android.bp index 1d939b0c5..79fe530d2 100644 --- a/androidmk/Android.bp +++ b/androidmk/Android.bp @@ -30,6 +30,7 @@ blueprint_go_binary { "androidmk-parser", "blueprint-parser", "bpfix-lib", + "soong-android", ], } diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go index 24057af3f..af81e439b 100644 --- a/androidmk/cmd/androidmk/android.go +++ b/androidmk/cmd/androidmk/android.go @@ -15,9 +15,9 @@ package main import ( + "android/soong/android" mkparser "android/soong/androidmk/parser" "fmt" - "sort" "strings" bpparser "github.com/google/blueprint/parser" @@ -185,7 +185,6 @@ func init() { "LOCAL_NO_CRT": "nocrt", "LOCAL_ALLOW_UNDEFINED_SYMBOLS": "allow_undefined_symbols", "LOCAL_RTTI_FLAG": "rtti", - "LOCAL_NO_STANDARD_LIBRARIES": "no_standard_libs", "LOCAL_PACK_MODULE_RELOCATIONS": "pack_relocations", "LOCAL_TIDY": "tidy", "LOCAL_USE_CLANG_LLD": "use_clang_lld", @@ -335,15 +334,6 @@ func classifyLocalOrGlobalPath(value bpparser.Expression) (string, bpparser.Expr } } -func sortedMapKeys(inputMap map[string]string) (sortedKeys []string) { - keys := make([]string, 0, len(inputMap)) - for key := range inputMap { - keys = append(keys, key) - } - sort.Strings(keys) - return keys -} - // splitAndAssign splits a Make list into components and then // creates the corresponding variable assignments. func splitAndAssign(ctx variableAssignmentContext, splitFunc listSplitFunc, namesByClassification map[string]string) error { @@ -357,7 +347,7 @@ func splitAndAssign(ctx variableAssignmentContext, splitFunc listSplitFunc, name return err } - for _, nameClassification := range sortedMapKeys(namesByClassification) { + for _, nameClassification := range android.SortedStringKeys(namesByClassification) { name := namesByClassification[nameClassification] if component, ok := lists[nameClassification]; ok && !emptyList(component) { err = setVariable(ctx.file, ctx.append, ctx.prefix, name, component, true) diff --git a/apex/apex.go b/apex/apex.go index aed7d6fd2..84e5497ce 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -61,7 +61,7 @@ var ( `--key ${key} ${opt_flags} ${image_dir} ${out} `, CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}", "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", - "${soong_zip}", "${zipalign}", "${aapt2}"}, + "${soong_zip}", "${zipalign}", "${aapt2}", "prebuilts/sdk/current/public/android.jar"}, Description: "APEX ${image_dir} => ${out}", }, "tool_path", "image_dir", "copy_commands", "manifest", "file_contexts", "canned_fs_config", "key", "opt_flags") @@ -552,7 +552,7 @@ func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) { } } -func (a *apexBundle) getCertString(ctx android.BaseContext) string { +func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string { certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName()) if overridden { return ":" + certificate @@ -1062,6 +1062,10 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, apexType ap Description: "signapk", Output: a.outputFiles[apexType], Input: unsignedOutputFile, + Implicits: []android.Path{ + a.container_certificate_file, + a.container_private_key_file, + }, Args: map[string]string{ "certificates": a.container_certificate_file.String() + " " + a.container_private_key_file.String(), "flags": "-a 4096", //alignment @@ -1295,9 +1299,6 @@ type Defaults struct { android.DefaultsModuleBase } -func (*Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { -} - func defaultsFactory() android.Module { return DefaultsFactory() } diff --git a/cc/androidmk.go b/cc/androidmk.go index 32ccf4c15..272d3d443 100644 --- a/cc/androidmk.go +++ b/cc/androidmk.go @@ -156,12 +156,18 @@ func makeOverrideModuleNames(ctx AndroidMkContext, overrides []string) []string func (library *libraryDecorator) androidMkWriteExportedFlags(w io.Writer) { exportedFlags := library.exportedFlags() + for _, dir := range library.exportedDirs() { + exportedFlags = append(exportedFlags, "-I"+dir) + } + for _, dir := range library.exportedSystemDirs() { + exportedFlags = append(exportedFlags, "-isystem "+dir) + } if len(exportedFlags) > 0 { fmt.Fprintln(w, "LOCAL_EXPORT_CFLAGS :=", strings.Join(exportedFlags, " ")) } - exportedFlagsDeps := library.exportedFlagsDeps() - if len(exportedFlagsDeps) > 0 { - fmt.Fprintln(w, "LOCAL_EXPORT_C_INCLUDE_DEPS :=", strings.Join(exportedFlagsDeps.Strings(), " ")) + exportedDeps := library.exportedDeps() + if len(exportedDeps) > 0 { + fmt.Fprintln(w, "LOCAL_EXPORT_C_INCLUDE_DEPS :=", strings.Join(exportedDeps.Strings(), " ")) } } diff --git a/cc/binary.go b/cc/binary.go index 9bb0b1673..1757f1cd6 100644 --- a/cc/binary.go +++ b/cc/binary.go @@ -106,13 +106,17 @@ func (binary *binaryDecorator) linkerProps() []interface{} { } -func (binary *binaryDecorator) getStem(ctx BaseModuleContext) string { +func (binary *binaryDecorator) getStemWithoutSuffix(ctx BaseModuleContext) string { stem := ctx.baseModuleName() if String(binary.Properties.Stem) != "" { stem = String(binary.Properties.Stem) } - return stem + String(binary.Properties.Suffix) + return stem +} + +func (binary *binaryDecorator) getStem(ctx BaseModuleContext) string { + return binary.getStemWithoutSuffix(ctx) + String(binary.Properties.Suffix) } func (binary *binaryDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps { @@ -384,11 +388,11 @@ func (binary *binaryDecorator) link(ctx ModuleContext, TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true, - builderFlags, outputFile) + builderFlags, outputFile, nil) objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...) objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...) - binary.coverageOutputFile = TransformCoverageFilesToLib(ctx, objs, builderFlags, binary.getStem(ctx)) + binary.coverageOutputFile = TransformCoverageFilesToZip(ctx, objs, binary.getStem(ctx)) // Need to determine symlinks early since some targets (ie APEX) need this // information but will not call 'install' @@ -398,11 +402,11 @@ func (binary *binaryDecorator) link(ctx ModuleContext, } if Bool(binary.Properties.Symlink_preferred_arch) { - if String(binary.Properties.Stem) == "" && String(binary.Properties.Suffix) == "" { - ctx.PropertyErrorf("symlink_preferred_arch", "must also specify stem or suffix") + if String(binary.Properties.Suffix) == "" { + ctx.PropertyErrorf("symlink_preferred_arch", "must also specify suffix") } if ctx.TargetPrimary() { - binary.symlinks = append(binary.symlinks, ctx.baseModuleName()) + binary.symlinks = append(binary.symlinks, binary.getStemWithoutSuffix(ctx)) } } diff --git a/cc/builder.go b/cc/builder.go index d2727b3bf..1e1236173 100644 --- a/cc/builder.go +++ b/cc/builder.go @@ -22,10 +22,10 @@ import ( "fmt" "path/filepath" "runtime" - "strconv" "strings" "github.com/google/blueprint" + "github.com/google/blueprint/pathtools" "android/soong/android" "android/soong/cc/config" @@ -92,20 +92,6 @@ var ( }, "arCmd", "arFlags") - darwinAr = pctx.AndroidStaticRule("darwinAr", - blueprint.RuleParams{ - Command: "rm -f ${out} && ${config.MacArPath} $arFlags $out $in", - CommandDeps: []string{"${config.MacArPath}"}, - }, - "arFlags") - - darwinAppendAr = pctx.AndroidStaticRule("darwinAppendAr", - blueprint.RuleParams{ - Command: "cp -f ${inAr} ${out}.tmp && ${config.MacArPath} $arFlags ${out}.tmp $in && mv ${out}.tmp ${out}", - CommandDeps: []string{"${config.MacArPath}", "${inAr}"}, - }, - "arFlags", "inAr") - darwinStrip = pctx.AndroidStaticRule("darwinStrip", blueprint.RuleParams{ Command: "${config.MacStripPath} -u -r -o $out $in", @@ -227,6 +213,14 @@ var ( blueprint.RuleParams{ Command: "gunzip -c $in > $out", }) + + zip = pctx.AndroidStaticRule("zip", + blueprint.RuleParams{ + Command: "cat $out.rsp | tr ' ' '\\n' | tr -d \\' | sort -u > ${out}.tmp && ${SoongZipCmd} -o ${out} -C $$OUT_DIR -l ${out}.tmp", + CommandDeps: []string{"${SoongZipCmd}"}, + Rspfile: "$out.rsp", + RspfileContent: "$in", + }) ) func init() { @@ -239,6 +233,8 @@ func init() { // Darwin doesn't have /proc pctx.StaticVariable("relPwd", "") } + + pctx.HostBinToolVariable("SoongZipCmd", "soong_zip") } type builderFlags struct { @@ -504,11 +500,6 @@ func TransformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles and func TransformObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths, flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) { - if ctx.Darwin() { - transformDarwinObjToStaticLib(ctx, objFiles, flags, outputFile, deps) - return - } - arCmd := "${config.ClangBin}/llvm-ar" arFlags := "crsD" if !ctx.Darwin() { @@ -531,87 +522,11 @@ func TransformObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths, }) } -// Generate a rule for compiling multiple .o files to a static library (.a) on -// darwin. The darwin ar tool doesn't support @file for list files, and has a -// very small command line length limit, so we have to split the ar into multiple -// steps, each appending to the previous one. -func transformDarwinObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths, - flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) { - - arFlags := "cqs" - - if len(objFiles) == 0 { - dummy := android.PathForModuleOut(ctx, "dummy"+objectExtension) - dummyAr := android.PathForModuleOut(ctx, "dummy"+staticLibraryExtension) - - ctx.Build(pctx, android.BuildParams{ - Rule: emptyFile, - Description: "empty object file", - Output: dummy, - Implicits: deps, - }) - - ctx.Build(pctx, android.BuildParams{ - Rule: darwinAr, - Description: "empty static archive", - Output: dummyAr, - Input: dummy, - Args: map[string]string{ - "arFlags": arFlags, - }, - }) - - ctx.Build(pctx, android.BuildParams{ - Rule: darwinAppendAr, - Description: "static link " + outputFile.Base(), - Output: outputFile, - Input: dummy, - Args: map[string]string{ - "arFlags": "d", - "inAr": dummyAr.String(), - }, - }) - - return - } - - // ARG_MAX on darwin is 262144, use half that to be safe - objFilesLists, err := splitListForSize(objFiles, 131072) - if err != nil { - ctx.ModuleErrorf("%s", err.Error()) - } - - var in, out android.WritablePath - for i, l := range objFilesLists { - in = out - out = outputFile - if i != len(objFilesLists)-1 { - out = android.PathForModuleOut(ctx, outputFile.Base()+strconv.Itoa(i)) - } - - build := android.BuildParams{ - Rule: darwinAr, - Description: "static link " + out.Base(), - Output: out, - Inputs: l, - Implicits: deps, - Args: map[string]string{ - "arFlags": arFlags, - }, - } - if i != 0 { - build.Rule = darwinAppendAr - build.Args["inAr"] = in.String() - } - ctx.Build(pctx, build) - } -} - // Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries, // and shared libraries, to a shared library (.so) or dynamic executable func TransformObjToDynamicBinary(ctx android.ModuleContext, objFiles, sharedLibs, staticLibs, lateStaticLibs, wholeStaticLibs, deps android.Paths, - crtBegin, crtEnd android.OptionalPath, groupLate bool, flags builderFlags, outputFile android.WritablePath) { + crtBegin, crtEnd android.OptionalPath, groupLate bool, flags builderFlags, outputFile android.WritablePath, implicitOutputs android.WritablePaths) { ldCmd := "${config.ClangBin}/clang++" @@ -648,7 +563,11 @@ func TransformObjToDynamicBinary(ctx android.ModuleContext, } for _, lib := range sharedLibs { - libFlagsList = append(libFlagsList, lib.String()) + libFile := lib.String() + if ctx.Windows() { + libFile = pathtools.ReplaceExtension(libFile, "lib") + } + libFlagsList = append(libFlagsList, libFile) } deps = append(deps, staticLibs...) @@ -659,11 +578,12 @@ func TransformObjToDynamicBinary(ctx android.ModuleContext, } ctx.Build(pctx, android.BuildParams{ - Rule: ld, - Description: "link " + outputFile.Base(), - Output: outputFile, - Inputs: objFiles, - Implicits: deps, + Rule: ld, + Description: "link " + outputFile.Base(), + Output: outputFile, + ImplicitOutputs: implicitOutputs, + Inputs: objFiles, + Implicits: deps, Args: map[string]string{ "ldCmd": ldCmd, "crtBegin": crtBegin.String(), @@ -877,13 +797,18 @@ func TransformDarwinStrip(ctx android.ModuleContext, inputFile android.Path, }) } -func TransformCoverageFilesToLib(ctx android.ModuleContext, - inputs Objects, flags builderFlags, baseName string) android.OptionalPath { +func TransformCoverageFilesToZip(ctx android.ModuleContext, + inputs Objects, baseName string) android.OptionalPath { if len(inputs.coverageFiles) > 0 { - outputFile := android.PathForModuleOut(ctx, baseName+".gcnodir") + outputFile := android.PathForModuleOut(ctx, baseName+".zip") - TransformObjToStaticLib(ctx, inputs.coverageFiles, flags, outputFile, nil) + ctx.Build(pctx, android.BuildParams{ + Rule: zip, + Description: "zip " + outputFile.Base(), + Inputs: inputs.coverageFiles, + Output: outputFile, + }) return android.OptionalPathForPath(outputFile) } @@ -119,8 +119,13 @@ type PathDeps struct { GeneratedSources android.Paths GeneratedHeaders android.Paths - Flags, ReexportedFlags []string - ReexportedFlagsDeps android.Paths + Flags []string + IncludeDirs []string + SystemIncludeDirs []string + ReexportedDirs []string + ReexportedSystemDirs []string + ReexportedFlags []string + ReexportedDeps android.Paths // Paths to crt*.o files CrtBegin, CrtEnd android.OptionalPath @@ -278,7 +283,7 @@ type ModuleContext interface { } type BaseModuleContext interface { - android.BaseContext + android.BaseModuleContext ModuleContextIntf } @@ -641,7 +646,7 @@ func installToBootstrap(name string, config android.Config) bool { } type baseModuleContext struct { - android.BaseContext + android.BaseModuleContext moduleContextImpl } @@ -988,6 +993,14 @@ func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) { flags.ConlyFlags, _ = filterList(flags.ConlyFlags, config.IllegalFlags) flags.GlobalFlags = append(flags.GlobalFlags, deps.Flags...) + + for _, dir := range deps.IncludeDirs { + flags.GlobalFlags = append(flags.GlobalFlags, "-I"+dir) + } + for _, dir := range deps.SystemIncludeDirs { + flags.GlobalFlags = append(flags.GlobalFlags, "-isystem "+dir) + } + c.flags = flags // We need access to all the flags seen by a source file. if c.sabi != nil { @@ -1040,7 +1053,7 @@ func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) { } } -func (c *Module) toolchain(ctx android.BaseContext) config.Toolchain { +func (c *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain { if c.cachedToolchain == nil { c.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch()) } @@ -1161,7 +1174,7 @@ func (c *Module) deps(ctx DepsContext) Deps { func (c *Module) beginMutator(actx android.BottomUpMutatorContext) { ctx := &baseModuleContext{ - BaseContext: actx, + BaseModuleContext: actx, moduleContextImpl: moduleContextImpl{ mod: c, }, @@ -1578,6 +1591,13 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { llndkLibraries := llndkLibraries(ctx.Config()) vendorPublicLibraries := vendorPublicLibraries(ctx.Config()) + reexportExporter := func(exporter exportedFlagsProducer) { + depPaths.ReexportedDirs = append(depPaths.ReexportedDirs, exporter.exportedDirs()...) + depPaths.ReexportedSystemDirs = append(depPaths.ReexportedSystemDirs, exporter.exportedSystemDirs()...) + depPaths.ReexportedFlags = append(depPaths.ReexportedFlags, exporter.exportedFlags()...) + depPaths.ReexportedDeps = append(depPaths.ReexportedDeps, exporter.exportedDeps()...) + } + ctx.VisitDirectDeps(func(dep android.Module) { depName := ctx.OtherModuleName(dep) depTag := ctx.OtherModuleDependencyTag(dep) @@ -1599,14 +1619,13 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { if genRule, ok := dep.(genrule.SourceFileGenerator); ok { depPaths.GeneratedHeaders = append(depPaths.GeneratedHeaders, genRule.GeneratedDeps()...) - flags := includeDirsToFlags(genRule.GeneratedHeaderDirs()) - depPaths.Flags = append(depPaths.Flags, flags) + dirs := genRule.GeneratedHeaderDirs().Strings() + depPaths.IncludeDirs = append(depPaths.IncludeDirs, dirs...) if depTag == genHeaderExportDepTag { - depPaths.ReexportedFlags = append(depPaths.ReexportedFlags, flags) - depPaths.ReexportedFlagsDeps = append(depPaths.ReexportedFlagsDeps, - genRule.GeneratedDeps()...) + depPaths.ReexportedDirs = append(depPaths.ReexportedDirs, dirs...) + depPaths.ReexportedDeps = append(depPaths.ReexportedDeps, genRule.GeneratedDeps()...) // Add these re-exported flags to help header-abi-dumper to infer the abi exported by a library. - c.sabi.Properties.ReexportedIncludeFlags = append(c.sabi.Properties.ReexportedIncludeFlags, flags) + c.sabi.Properties.ReexportedIncludes = append(c.sabi.Properties.ReexportedIncludes, dirs...) } } else { @@ -1644,10 +1663,9 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { if depTag == reuseObjTag { if l, ok := ccDep.compiler.(libraryInterface); ok { c.staticVariant = ccDep - objs, flags, deps := l.reuseObjs() + objs, exporter := l.reuseObjs() depPaths.Objs = depPaths.Objs.Append(objs) - depPaths.ReexportedFlags = append(depPaths.ReexportedFlags, flags...) - depPaths.ReexportedFlagsDeps = append(depPaths.ReexportedFlagsDeps, deps...) + reexportExporter(exporter) return } } @@ -1710,18 +1728,20 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { } if i, ok := ccDep.linker.(exportedFlagsProducer); ok { - flags := i.exportedFlags() - deps := i.exportedFlagsDeps() - depPaths.Flags = append(depPaths.Flags, flags...) - depPaths.GeneratedHeaders = append(depPaths.GeneratedHeaders, deps...) + depPaths.IncludeDirs = append(depPaths.IncludeDirs, i.exportedDirs()...) + depPaths.SystemIncludeDirs = append(depPaths.SystemIncludeDirs, i.exportedSystemDirs()...) + depPaths.GeneratedHeaders = append(depPaths.GeneratedHeaders, i.exportedDeps()...) + depPaths.Flags = append(depPaths.Flags, i.exportedFlags()...) if t.reexportFlags { - depPaths.ReexportedFlags = append(depPaths.ReexportedFlags, flags...) - depPaths.ReexportedFlagsDeps = append(depPaths.ReexportedFlagsDeps, deps...) + reexportExporter(i) // Add these re-exported flags to help header-abi-dumper to infer the abi exported by a library. // Re-exported shared library headers must be included as well since they can help us with type information // about template instantiations (instantiated from their headers). - c.sabi.Properties.ReexportedIncludeFlags = append(c.sabi.Properties.ReexportedIncludeFlags, flags...) + // -isystem headers are not included since for bionic libraries, abi-filtering is taken care of by version + // scripts. + c.sabi.Properties.ReexportedIncludes = append( + c.sabi.Properties.ReexportedIncludes, i.exportedDirs()...) } } @@ -1883,12 +1903,16 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { // Dedup exported flags from dependencies depPaths.Flags = android.FirstUniqueStrings(depPaths.Flags) + depPaths.IncludeDirs = android.FirstUniqueStrings(depPaths.IncludeDirs) + depPaths.SystemIncludeDirs = android.FirstUniqueStrings(depPaths.SystemIncludeDirs) depPaths.GeneratedHeaders = android.FirstUniquePaths(depPaths.GeneratedHeaders) + depPaths.ReexportedDirs = android.FirstUniqueStrings(depPaths.ReexportedDirs) + depPaths.ReexportedSystemDirs = android.FirstUniqueStrings(depPaths.ReexportedSystemDirs) depPaths.ReexportedFlags = android.FirstUniqueStrings(depPaths.ReexportedFlags) - depPaths.ReexportedFlagsDeps = android.FirstUniquePaths(depPaths.ReexportedFlagsDeps) + depPaths.ReexportedDeps = android.FirstUniquePaths(depPaths.ReexportedDeps) if c.sabi != nil { - c.sabi.Properties.ReexportedIncludeFlags = android.FirstUniqueStrings(c.sabi.Properties.ReexportedIncludeFlags) + c.sabi.Properties.ReexportedIncludes = android.FirstUniqueStrings(c.sabi.Properties.ReexportedIncludes) } return depPaths @@ -2036,9 +2060,6 @@ type Defaults struct { android.ApexModuleBase } -func (*Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { -} - // cc_defaults provides a set of properties that can be inherited by other cc // modules. A module can use the properties from a cc_defaults using // `defaults: ["<:default_module_name>"]`. Properties of both modules are diff --git a/cc/compiler.go b/cc/compiler.go index 7667ae7ab..fd6184b3a 100644 --- a/cc/compiler.go +++ b/cc/compiler.go @@ -225,11 +225,6 @@ func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps { deps = protoDeps(ctx, deps, &compiler.Proto, Bool(compiler.Properties.Proto.Static)) } - if compiler.hasSrcExt(".sysprop") { - deps.HeaderLibs = append(deps.HeaderLibs, "libbase_headers") - deps.SharedLibs = append(deps.SharedLibs, "liblog") - } - if Bool(compiler.Properties.Openmp) { deps.StaticLibs = append(deps.StaticLibs, "libomp") } diff --git a/cc/config/global.go b/cc/config/global.go index 24075d9fa..a27246e72 100644 --- a/cc/config/global.go +++ b/cc/config/global.go @@ -122,8 +122,8 @@ var ( // prebuilts/clang default settings. ClangDefaultBase = "prebuilts/clang/host" - ClangDefaultVersion = "clang-r353983c" - ClangDefaultShortVersion = "9.0.3" + ClangDefaultVersion = "clang-r353983d" + ClangDefaultShortVersion = "9.0.4" // Directories with warnings from Android.bp files. WarningAllowedProjects = []string{ @@ -16,6 +16,7 @@ package cc import ( "path/filepath" + "strings" "github.com/google/blueprint" @@ -36,22 +37,13 @@ var ( CommandDeps: []string{"$lexCmd"}, }) - aidl = pctx.AndroidStaticRule("aidl", - blueprint.RuleParams{ - Command: "$aidlCmd -d${out}.d --ninja $aidlFlags $in $outDir $out", - CommandDeps: []string{"$aidlCmd"}, - Depfile: "${out}.d", - Deps: blueprint.DepsGCC, - }, - "aidlFlags", "outDir") - sysprop = pctx.AndroidStaticRule("sysprop", blueprint.RuleParams{ - Command: "$syspropCmd --header-dir=$headerOutDir --system-header-dir=$systemOutDir " + + Command: "$syspropCmd --header-dir=$headerOutDir --public-header-dir=$publicOutDir " + "--source-dir=$srcOutDir --include-name=$includeName $in", CommandDeps: []string{"$syspropCmd"}, }, - "headerOutDir", "systemOutDir", "srcOutDir", "includeName") + "headerOutDir", "publicOutDir", "srcOutDir", "includeName") windmc = pctx.AndroidStaticRule("windmc", blueprint.RuleParams{ @@ -114,20 +106,37 @@ func genYacc(ctx android.ModuleContext, rule *android.RuleBuilder, yaccFile andr return ret } -func genAidl(ctx android.ModuleContext, aidlFile android.Path, outFile android.ModuleGenPath, aidlFlags string) android.Paths { - ctx.Build(pctx, android.BuildParams{ - Rule: aidl, - Description: "aidl " + aidlFile.Rel(), - Output: outFile, - Input: aidlFile, - Args: map[string]string{ - "aidlFlags": aidlFlags, - "outDir": android.PathForModuleGen(ctx, "aidl").String(), - }, - }) +func genAidl(ctx android.ModuleContext, rule *android.RuleBuilder, aidlFile android.Path, + outFile, depFile android.ModuleGenPath, aidlFlags string) android.Paths { + + aidlPackage := strings.TrimSuffix(aidlFile.Rel(), aidlFile.Base()) + baseName := strings.TrimSuffix(aidlFile.Base(), aidlFile.Ext()) + shortName := strings.TrimPrefix(baseName, "I") - // TODO: This should return the generated headers, not the source file. - return android.Paths{outFile} + outDir := android.PathForModuleGen(ctx, "aidl") + headerI := outDir.Join(ctx, aidlPackage, baseName+".h") + headerBn := outDir.Join(ctx, aidlPackage, "Bn"+shortName+".h") + headerBp := outDir.Join(ctx, aidlPackage, "Bp"+shortName+".h") + + cmd := rule.Command() + cmd.Tool(ctx.Config().HostToolPath(ctx, "aidl-cpp")). + FlagWithDepFile("-d", depFile). + Flag("--ninja"). + Flag(aidlFlags). + Input(aidlFile). + OutputDir(). + Output(outFile). + ImplicitOutputs(android.WritablePaths{ + headerI, + headerBn, + headerBp, + }) + + return android.Paths{ + headerI, + headerBn, + headerBp, + } } func genLex(ctx android.ModuleContext, lexFile android.Path, outFile android.ModuleGenPath) { @@ -141,7 +150,7 @@ func genLex(ctx android.ModuleContext, lexFile android.Path, outFile android.Mod func genSysprop(ctx android.ModuleContext, syspropFile android.Path) (android.Path, android.Path) { headerFile := android.PathForModuleGen(ctx, "sysprop", "include", syspropFile.Rel()+".h") - systemHeaderFile := android.PathForModuleGen(ctx, "sysprop/system", "include", syspropFile.Rel()+".h") + publicHeaderFile := android.PathForModuleGen(ctx, "sysprop/public", "include", syspropFile.Rel()+".h") cppFile := android.PathForModuleGen(ctx, "sysprop", syspropFile.Rel()+".cpp") ctx.Build(pctx, android.BuildParams{ @@ -152,7 +161,7 @@ func genSysprop(ctx android.ModuleContext, syspropFile android.Path) (android.Pa Input: syspropFile, Args: map[string]string{ "headerOutDir": filepath.Dir(headerFile.String()), - "systemOutDir": filepath.Dir(systemHeaderFile.String()), + "publicOutDir": filepath.Dir(publicHeaderFile.String()), "srcOutDir": filepath.Dir(cppFile.String()), "includeName": syspropFile.Rel() + ".h", }, @@ -187,6 +196,8 @@ func genSources(ctx android.ModuleContext, srcFiles android.Paths, var deps android.Paths var rsFiles android.Paths + var aidlRule *android.RuleBuilder + var yaccRule_ *android.RuleBuilder yaccRule := func() *android.RuleBuilder { if yaccRule_ == nil { @@ -218,9 +229,13 @@ func genSources(ctx android.ModuleContext, srcFiles android.Paths, srcFiles[i] = ccFile deps = append(deps, headerFile) case ".aidl": + if aidlRule == nil { + aidlRule = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "aidl")) + } cppFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp") + depFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp.d") srcFiles[i] = cppFile - deps = append(deps, genAidl(ctx, srcFile, cppFile, buildFlags.aidlFlags)...) + deps = append(deps, genAidl(ctx, aidlRule, srcFile, cppFile, depFile, buildFlags.aidlFlags)...) case ".rs", ".fs": cppFile := rsGeneratedCppFile(ctx, srcFile) rsFiles = append(rsFiles, srcFiles[i]) @@ -236,6 +251,10 @@ func genSources(ctx android.ModuleContext, srcFiles android.Paths, } } + if aidlRule != nil { + aidlRule.Build(pctx, ctx, "aidl", "gen aidl") + } + if yaccRule_ != nil { yaccRule_.Build(pctx, ctx, "yacc", "gen yacc") } diff --git a/cc/gen_test.go b/cc/gen_test.go index a0f7308c2..e4219d999 100644 --- a/cc/gen_test.go +++ b/cc/gen_test.go @@ -15,6 +15,7 @@ package cc import ( + "path/filepath" "testing" ) @@ -32,7 +33,7 @@ func TestGen(t *testing.T) { aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_core_shared").Rule("aidl") libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_core_shared").Module().(*Module) - if !inList("-I"+aidl.Args["outDir"], libfoo.flags.GlobalFlags) { + if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.GlobalFlags) { t.Errorf("missing aidl includes in global flags") } }) @@ -55,7 +56,7 @@ func TestGen(t *testing.T) { aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_core_shared").Rule("aidl") libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_core_shared").Module().(*Module) - if !inList("-I"+aidl.Args["outDir"], libfoo.flags.GlobalFlags) { + if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.GlobalFlags) { t.Errorf("missing aidl includes in global flags") } }) diff --git a/cc/genrule.go b/cc/genrule.go index decf6ea57..e594f4b2f 100644 --- a/cc/genrule.go +++ b/cc/genrule.go @@ -42,5 +42,7 @@ func genRuleFactory() android.Module { android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibBoth) + android.InitApexModule(module) + return module } diff --git a/cc/kernel_headers.go b/cc/kernel_headers.go index c1da578b5..fff419e60 100644 --- a/cc/kernel_headers.go +++ b/cc/kernel_headers.go @@ -25,9 +25,7 @@ type kernelHeadersDecorator struct { func (stub *kernelHeadersDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path { if ctx.Device() { f := &stub.libraryDecorator.flagExporter - for _, dir := range ctx.DeviceConfig().DeviceKernelHeaderDirs() { - f.flags = append(f.flags, "-isystem "+dir) - } + f.reexportSystemDirs(ctx.DeviceConfig().DeviceKernelHeaderDirs()...) } return stub.libraryDecorator.linkStatic(ctx, flags, deps, objs) } diff --git a/cc/library.go b/cc/library.go index d9c00dc56..f98cd3623 100644 --- a/cc/library.go +++ b/cc/library.go @@ -15,6 +15,7 @@ package cc import ( + "fmt" "io" "path/filepath" "regexp" @@ -207,8 +208,10 @@ func LibraryHeaderFactory() android.Module { type flagExporter struct { Properties FlagExporterProperties - flags []string - flagsDeps android.Paths + dirs []string + systemDirs []string + flags []string + deps android.Paths } func (f *flagExporter) exportedIncludes(ctx ModuleContext) android.Paths { @@ -219,32 +222,57 @@ func (f *flagExporter) exportedIncludes(ctx ModuleContext) android.Paths { } } -func (f *flagExporter) exportIncludes(ctx ModuleContext, inc string) { - includeDirs := f.exportedIncludes(ctx) - for _, dir := range includeDirs.Strings() { - f.flags = append(f.flags, inc+dir) - } +func (f *flagExporter) exportIncludes(ctx ModuleContext) { + f.dirs = append(f.dirs, f.exportedIncludes(ctx).Strings()...) +} + +func (f *flagExporter) exportIncludesAsSystem(ctx ModuleContext) { + f.systemDirs = append(f.systemDirs, f.exportedIncludes(ctx).Strings()...) +} + +func (f *flagExporter) reexportDirs(dirs ...string) { + f.dirs = append(f.dirs, dirs...) +} + +func (f *flagExporter) reexportSystemDirs(dirs ...string) { + f.systemDirs = append(f.systemDirs, dirs...) } -func (f *flagExporter) reexportFlags(flags []string) { +func (f *flagExporter) reexportFlags(flags ...string) { + for _, flag := range flags { + if strings.HasPrefix(flag, "-I") || strings.HasPrefix(flag, "-isystem") { + panic(fmt.Errorf("Exporting invalid flag %q: "+ + "use reexportDirs or reexportSystemDirs to export directories", flag)) + } + } f.flags = append(f.flags, flags...) } -func (f *flagExporter) reexportDeps(deps android.Paths) { - f.flagsDeps = append(f.flagsDeps, deps...) +func (f *flagExporter) reexportDeps(deps ...android.Path) { + f.deps = append(f.deps, deps...) +} + +func (f *flagExporter) exportedDirs() []string { + return f.dirs +} + +func (f *flagExporter) exportedSystemDirs() []string { + return f.systemDirs } func (f *flagExporter) exportedFlags() []string { return f.flags } -func (f *flagExporter) exportedFlagsDeps() android.Paths { - return f.flagsDeps +func (f *flagExporter) exportedDeps() android.Paths { + return f.deps } type exportedFlagsProducer interface { + exportedDirs() []string + exportedSystemDirs() []string exportedFlags() []string - exportedFlagsDeps() android.Paths + exportedDeps() android.Paths } var _ exportedFlagsProducer = (*flagExporter)(nil) @@ -256,9 +284,7 @@ type libraryDecorator struct { MutatedProperties LibraryMutatedProperties // For reusing static library objects for shared library - reuseObjects Objects - reuseExportedFlags []string - reuseExportedDeps android.Paths + reuseObjects Objects // table-of-contents file to optimize out relinking when possible tocFile android.OptionalPath @@ -360,9 +386,10 @@ func (library *libraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Fla ) } } else { - f = append(f, - "-shared", - "-Wl,-soname,"+libName+flags.Toolchain.ShlibSuffix()) + f = append(f, "-shared") + if !ctx.Windows() { + f = append(f, "-Wl,-soname,"+libName+flags.Toolchain.ShlibSuffix()) + } } flags.LdFlags = append(f, flags.LdFlags...) @@ -404,25 +431,6 @@ func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags, d return flags } -func extractExportIncludesFromFlags(flags []string) []string { - // This method is used in the generation of rules which produce - // abi-dumps for source files. Exported headers are needed to infer the - // abi exported by a library and filter out the rest of the abi dumped - // from a source. We extract the include flags exported by a library. - // This includes the flags exported which are re-exported from static - // library dependencies, exported header library dependencies and - // generated header dependencies. -isystem headers are not included - // since for bionic libraries, abi-filtering is taken care of by version - // scripts. - var exportedIncludes []string - for _, flag := range flags { - if strings.HasPrefix(flag, "-I") { - exportedIncludes = append(exportedIncludes, flag) - } - } - return exportedIncludes -} - func (library *libraryDecorator) shouldCreateVndkSourceAbiDump(ctx ModuleContext) bool { if library.Properties.Header_abi_checker.Enabled != nil { return Bool(library.Properties.Header_abi_checker.Enabled) @@ -455,8 +463,8 @@ func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps Pa for _, dir := range exportIncludeDirs.Strings() { SourceAbiFlags = append(SourceAbiFlags, "-I"+dir) } - for _, reexportedInclude := range extractExportIncludesFromFlags(library.sabi.Properties.ReexportedIncludeFlags) { - SourceAbiFlags = append(SourceAbiFlags, reexportedInclude) + for _, reexportedInclude := range library.sabi.Properties.ReexportedIncludes { + SourceAbiFlags = append(SourceAbiFlags, "-I"+reexportedInclude) } flags.SAbiFlags = SourceAbiFlags total_length := len(library.baseCompiler.Properties.Srcs) + len(deps.GeneratedSources) + len(library.Properties.Shared.Srcs) + @@ -486,7 +494,7 @@ type libraryInterface interface { getWholeStaticMissingDeps() []string static() bool objs() Objects - reuseObjs() (Objects, []string, android.Paths) + reuseObjs() (Objects, exportedFlagsProducer) toc() android.OptionalPath // Returns true if the build options for the module have selected a static or shared build @@ -643,7 +651,7 @@ func (library *libraryDecorator) linkStatic(ctx ModuleContext, TransformObjToStaticLib(ctx, library.objects.objFiles, builderFlags, outputFile, objs.tidyFiles) - library.coverageOutputFile = TransformCoverageFilesToLib(ctx, library.objects, builderFlags, + library.coverageOutputFile = TransformCoverageFilesToZip(ctx, library.objects, ctx.ModuleName()+library.MutatedProperties.VariantName) library.wholeStaticMissingDeps = ctx.GetMissingDependencies() @@ -696,6 +704,14 @@ func (library *libraryDecorator) linkShared(ctx ModuleContext, outputFile := android.PathForModuleOut(ctx, fileName) ret := outputFile + var implicitOutputs android.WritablePaths + if ctx.Windows() { + importLibraryPath := android.PathForModuleOut(ctx, pathtools.ReplaceExtension(fileName, "lib")) + + flags.LdFlags = append(flags.LdFlags, "-Wl,--out-implib="+importLibraryPath.String()) + implicitOutputs = append(implicitOutputs, importLibraryPath) + } + builderFlags := flagsToBuilderFlags(flags) // Optimize out relinking against shared libraries whose interface hasn't changed by @@ -747,7 +763,7 @@ func (library *libraryDecorator) linkShared(ctx ModuleContext, TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs, - linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile) + linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs) objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...) objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...) @@ -755,7 +771,7 @@ func (library *libraryDecorator) linkShared(ctx ModuleContext, objs.sAbiDumpFiles = append(objs.sAbiDumpFiles, deps.StaticLibObjs.sAbiDumpFiles...) objs.sAbiDumpFiles = append(objs.sAbiDumpFiles, deps.WholeStaticLibObjs.sAbiDumpFiles...) - library.coverageOutputFile = TransformCoverageFilesToLib(ctx, objs, builderFlags, library.getLibName(ctx)) + library.coverageOutputFile = TransformCoverageFilesToZip(ctx, objs, library.getLibName(ctx)) library.linkSAbiDumpFiles(ctx, objs, fileName, ret) return ret @@ -805,8 +821,8 @@ func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, objs Objec for _, dir := range exportIncludeDirs.Strings() { SourceAbiFlags = append(SourceAbiFlags, "-I"+dir) } - for _, reexportedInclude := range extractExportIncludesFromFlags(library.sabi.Properties.ReexportedIncludeFlags) { - SourceAbiFlags = append(SourceAbiFlags, reexportedInclude) + for _, reexportedInclude := range library.sabi.Properties.ReexportedIncludes { + SourceAbiFlags = append(SourceAbiFlags, "-I"+reexportedInclude) } exportedHeaderFlags := strings.Join(SourceAbiFlags, " ") library.sAbiOutputFile = TransformDumpToLinkedDump(ctx, objs.sAbiDumpFiles, soFile, fileName, exportedHeaderFlags, @@ -833,19 +849,17 @@ func (library *libraryDecorator) link(ctx ModuleContext, out = library.linkShared(ctx, flags, deps, objs) } - library.exportIncludes(ctx, "-I") - library.reexportFlags(deps.ReexportedFlags) - library.reexportDeps(deps.ReexportedFlagsDeps) + library.exportIncludes(ctx) + library.reexportDirs(deps.ReexportedDirs...) + library.reexportSystemDirs(deps.ReexportedSystemDirs...) + library.reexportFlags(deps.ReexportedFlags...) + library.reexportDeps(deps.ReexportedDeps...) if Bool(library.Properties.Aidl.Export_aidl_headers) { if library.baseCompiler.hasSrcExt(".aidl") { - flags := []string{ - "-I" + android.PathForModuleGen(ctx, "aidl").String(), - } - library.reexportFlags(flags) - library.reuseExportedFlags = append(library.reuseExportedFlags, flags...) - library.reexportDeps(library.baseCompiler.pathDeps) // TODO: restrict to aidl deps - library.reuseExportedDeps = append(library.reuseExportedDeps, library.baseCompiler.pathDeps...) + dir := android.PathForModuleGen(ctx, "aidl").String() + library.reexportDirs(dir) + library.reexportDeps(library.baseCompiler.pathDeps...) // TODO: restrict to aidl deps } } @@ -853,45 +867,34 @@ func (library *libraryDecorator) link(ctx ModuleContext, if library.baseCompiler.hasSrcExt(".proto") { includes := []string{} if flags.proto.CanonicalPathFromRoot { - includes = append(includes, "-I"+flags.proto.SubDir.String()) + includes = append(includes, flags.proto.SubDir.String()) } - includes = append(includes, "-I"+flags.proto.Dir.String()) - library.reexportFlags(includes) - library.reuseExportedFlags = append(library.reuseExportedFlags, includes...) - library.reexportDeps(library.baseCompiler.pathDeps) // TODO: restrict to proto deps - library.reuseExportedDeps = append(library.reuseExportedDeps, library.baseCompiler.pathDeps...) + includes = append(includes, flags.proto.Dir.String()) + library.reexportDirs(includes...) + library.reexportDeps(library.baseCompiler.pathDeps...) // TODO: restrict to proto deps } } if library.baseCompiler.hasSrcExt(".sysprop") { - internalFlags := []string{ - "-I" + android.PathForModuleGen(ctx, "sysprop", "include").String(), - } - systemFlags := []string{ - "-I" + android.PathForModuleGen(ctx, "sysprop/system", "include").String(), - } - - flags := internalFlags - + dir := android.PathForModuleGen(ctx, "sysprop", "include").String() if library.Properties.Sysprop.Platform != nil { isProduct := ctx.ProductSpecific() && !ctx.useVndk() isVendor := ctx.useVndk() isOwnerPlatform := Bool(library.Properties.Sysprop.Platform) - useSystem := isProduct || (isOwnerPlatform == isVendor) + usePublic := isProduct || (isOwnerPlatform == isVendor) - if useSystem { - flags = systemFlags + if usePublic { + dir = android.PathForModuleGen(ctx, "sysprop/public", "include").String() } } - library.reexportFlags(flags) - library.reexportDeps(library.baseCompiler.pathDeps) - library.reuseExportedFlags = append(library.reuseExportedFlags, flags...) + library.reexportDirs(dir) + library.reexportDeps(library.baseCompiler.pathDeps...) } if library.buildStubs() { - library.reexportFlags([]string{"-D" + versioningMacroName(ctx.ModuleName()) + "=" + library.stubsVersion()}) + library.reexportFlags("-D" + versioningMacroName(ctx.ModuleName()) + "=" + library.stubsVersion()) } return out @@ -913,8 +916,8 @@ func (library *libraryDecorator) objs() Objects { return library.objects } -func (library *libraryDecorator) reuseObjs() (Objects, []string, android.Paths) { - return library.reuseObjects, library.reuseExportedFlags, library.reuseExportedDeps +func (library *libraryDecorator) reuseObjs() (Objects, exportedFlagsProducer) { + return library.reuseObjects, &library.flagExporter } func (library *libraryDecorator) toc() android.OptionalPath { diff --git a/cc/linker.go b/cc/linker.go index fafefdc69..dda2fcb33 100644 --- a/cc/linker.go +++ b/cc/linker.go @@ -295,10 +295,6 @@ func (linker *baseLinker) useClangLld(ctx ModuleContext) bool { if ctx.Darwin() { return false } - // http://b/110800681 - lld cannot link Android's Windows modules yet. - if ctx.Windows() { - return false - } if linker.Properties.Use_clang_lld != nil { return Bool(linker.Properties.Use_clang_lld) } @@ -352,7 +348,7 @@ func (linker *baseLinker) linkerFlags(ctx ModuleContext, flags Flags) Flags { // darwin defaults to treating undefined symbols as errors flags.LdFlags = append(flags.LdFlags, "-Wl,-undefined,dynamic_lookup") } - } else if !ctx.Darwin() { + } else if !ctx.Darwin() && !ctx.Windows() { flags.LdFlags = append(flags.LdFlags, "-Wl,--no-undefined") } @@ -389,7 +385,7 @@ func (linker *baseLinker) linkerFlags(ctx ModuleContext, flags Flags) Flags { flags.LdFlags = append(flags.LdFlags, proptools.NinjaAndShellEscapeList(linker.Properties.Ldflags)...) - if ctx.Host() { + if ctx.Host() && !ctx.Windows() { rpath_prefix := `\$$ORIGIN/` if ctx.Darwin() { rpath_prefix = "@loader_path/" diff --git a/cc/llndk_library.go b/cc/llndk_library.go index 6cdf5c700..82901037b 100644 --- a/cc/llndk_library.go +++ b/cc/llndk_library.go @@ -134,6 +134,7 @@ func (stub *llndkStubDecorator) link(ctx ModuleContext, flags Flags, deps PathDe if !Bool(stub.Properties.Unversioned) { linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() flags.LdFlags = append(flags.LdFlags, linkerScriptFlag) + flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) } if len(stub.Properties.Export_preprocessed_headers) > 0 { @@ -144,17 +145,17 @@ func (stub *llndkStubDecorator) link(ctx ModuleContext, flags Flags, deps PathDe timestampFiles = append(timestampFiles, stub.processHeaders(ctx, dir, genHeaderOutDir)) } - includePrefix := "-I" if Bool(stub.Properties.Export_headers_as_system) { - includePrefix = "-isystem " + stub.reexportSystemDirs(genHeaderOutDir.String()) + } else { + stub.reexportDirs(genHeaderOutDir.String()) } - stub.reexportFlags([]string{includePrefix + genHeaderOutDir.String()}) - stub.reexportDeps(timestampFiles) + stub.reexportDeps(timestampFiles...) } if Bool(stub.Properties.Export_headers_as_system) { - stub.exportIncludes(ctx, "-isystem ") + stub.exportIncludesAsSystem(ctx) stub.libraryDecorator.flagExporter.Properties.Export_include_dirs = []string{} } diff --git a/cc/ndk_library.go b/cc/ndk_library.go index ff990b5f7..969cb3fa7 100644 --- a/cc/ndk_library.go +++ b/cc/ndk_library.go @@ -121,7 +121,7 @@ func intMax(a int, b int) int { } } -func normalizeNdkApiLevel(ctx android.BaseContext, apiLevel string, +func normalizeNdkApiLevel(ctx android.BaseModuleContext, apiLevel string, arch android.Arch) (string, error) { if apiLevel == "current" { @@ -167,7 +167,7 @@ func getFirstGeneratedVersion(firstSupportedVersion string, platformVersion int) return strconv.Atoi(firstSupportedVersion) } -func shouldUseVersionScript(ctx android.BaseContext, stub *stubDecorator) (bool, error) { +func shouldUseVersionScript(ctx android.BaseModuleContext, stub *stubDecorator) (bool, error) { // unversioned_until is normally empty, in which case we should use the version script. if String(stub.properties.Unversioned_until) == "" { return true, nil @@ -337,6 +337,7 @@ func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, if useVersionScript { linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() flags.LdFlags = append(flags.LdFlags, linkerScriptFlag) + flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) } return stub.libraryDecorator.link(ctx, flags, deps, objs) diff --git a/cc/ndk_prebuilt.go b/cc/ndk_prebuilt.go index fb168872b..4356732ef 100644 --- a/cc/ndk_prebuilt.go +++ b/cc/ndk_prebuilt.go @@ -151,7 +151,7 @@ func (ndk *ndkPrebuiltStlLinker) link(ctx ModuleContext, flags Flags, ctx.ModuleErrorf("NDK prebuilt libraries must have an ndk_lib prefixed name") } - ndk.exportIncludes(ctx, "-isystem ") + ndk.exportIncludesAsSystem(ctx) libName := strings.TrimPrefix(ctx.ModuleName(), "ndk_") libExt := flags.Toolchain.ShlibSuffix() @@ -27,10 +27,9 @@ import ( var ( // Add flags to ignore warnings that profiles are old or missing for - // some functions, and turn on the experimental new pass manager. + // some functions. profileUseOtherFlags = []string{ "-Wno-backend-plugin", - "-fexperimental-new-pass-manager", } globalPgoProfileProjects = []string{ diff --git a/cc/prebuilt.go b/cc/prebuilt.go index f92c50d1e..dc6c43a7c 100644 --- a/cc/prebuilt.go +++ b/cc/prebuilt.go @@ -85,9 +85,11 @@ func (p *prebuiltLibraryLinker) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path { // TODO(ccross): verify shared library dependencies if len(p.properties.Srcs) > 0 { - p.libraryDecorator.exportIncludes(ctx, "-I") - p.libraryDecorator.reexportFlags(deps.ReexportedFlags) - p.libraryDecorator.reexportDeps(deps.ReexportedFlagsDeps) + p.libraryDecorator.exportIncludes(ctx) + p.libraryDecorator.reexportDirs(deps.ReexportedDirs...) + p.libraryDecorator.reexportSystemDirs(deps.ReexportedSystemDirs...) + p.libraryDecorator.reexportFlags(deps.ReexportedFlags...) + p.libraryDecorator.reexportDeps(deps.ReexportedDeps...) builderFlags := flagsToBuilderFlags(flags) @@ -72,11 +72,12 @@ func rsGenerateCpp(ctx android.ModuleContext, rsFiles android.Paths, rsFlags str stampFile := android.PathForModuleGen(ctx, "rs", "rs.stamp") depFiles := make(android.WritablePaths, 0, len(rsFiles)) genFiles := make(android.WritablePaths, 0, 2*len(rsFiles)) + headers := make(android.Paths, 0, len(rsFiles)) for _, rsFile := range rsFiles { depFiles = append(depFiles, rsGeneratedDepFile(ctx, rsFile)) - genFiles = append(genFiles, - rsGeneratedCppFile(ctx, rsFile), - rsGeneratedHFile(ctx, rsFile)) + headerFile := rsGeneratedHFile(ctx, rsFile) + genFiles = append(genFiles, rsGeneratedCppFile(ctx, rsFile), headerFile) + headers = append(headers, headerFile) } ctx.Build(pctx, android.BuildParams{ @@ -92,7 +93,7 @@ func rsGenerateCpp(ctx android.ModuleContext, rsFiles android.Paths, rsFlags str }, }) - return android.Paths{stampFile} + return headers } func rsFlags(ctx ModuleContext, flags Flags, properties *BaseCompilerProperties) Flags { diff --git a/cc/sabi.go b/cc/sabi.go index 451176f1b..ae7b31d28 100644 --- a/cc/sabi.go +++ b/cc/sabi.go @@ -28,8 +28,8 @@ var ( ) type SAbiProperties struct { - CreateSAbiDumps bool `blueprint:"mutated"` - ReexportedIncludeFlags []string + CreateSAbiDumps bool `blueprint:"mutated"` + ReexportedIncludes []string `blueprint:"mutated"` } type sabi struct { diff --git a/cc/sanitize.go b/cc/sanitize.go index fdda7bec3..0af06592f 100644 --- a/cc/sanitize.go +++ b/cc/sanitize.go @@ -40,7 +40,8 @@ var ( hwasanCflags = []string{"-fno-omit-frame-pointer", "-Wno-frame-larger-than=", "-mllvm", "-hwasan-create-frame-descriptions=0", "-mllvm", "-hwasan-allow-ifunc", - "-fsanitize-hwaddress-abi=platform"} + "-fsanitize-hwaddress-abi=platform", + "-fno-experimental-new-pass-manager"} cfiCflags = []string{"-flto", "-fsanitize-cfi-cross-dso", "-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"} diff --git a/cc/test.go b/cc/test.go index dae2a3774..c735fd91f 100644 --- a/cc/test.go +++ b/cc/test.go @@ -64,6 +64,10 @@ type TestBinaryProperties struct { // Test options. Test_options TestOptions + + // Add RootTargetPreparer to auto generated test config. This guarantees the test to run + // with root permission. + Require_root *bool } func init() { @@ -273,18 +277,19 @@ func (test *testBinary) linkerFlags(ctx ModuleContext, flags Flags) Flags { func (test *testBinary) install(ctx ModuleContext, file android.Path) { test.data = android.PathsForModuleSrc(ctx, test.Properties.Data) - optionsMap := map[string]string{} + var configs []tradefed.Config + if Bool(test.Properties.Require_root) { + configs = append(configs, tradefed.Preparer{"com.android.tradefed.targetprep.RootTargetPreparer"}) + } if Bool(test.testDecorator.Properties.Isolated) { - optionsMap["not-shardable"] = "true" + configs = append(configs, tradefed.Option{"not-shardable", "true"}) } - if test.Properties.Test_options.Run_test_as != nil { - optionsMap["run-test-as"] = String(test.Properties.Test_options.Run_test_as) + configs = append(configs, tradefed.Option{"run-test-as", String(test.Properties.Test_options.Run_test_as)}) } test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config, - test.Properties.Test_config_template, - test.Properties.Test_suites, optionsMap) + test.Properties.Test_config_template, test.Properties.Test_suites, configs) test.binaryDecorator.baseInstaller.dir = "nativetest" test.binaryDecorator.baseInstaller.dir64 = "nativetest64" @@ -371,6 +376,10 @@ type BenchmarkProperties struct { // the name of the test configuration template (for example "AndroidTestTemplate.xml") that // should be installed with the module. Test_config_template *string `android:"path,arch_variant"` + + // Add RootTargetPreparer to auto generated test config. This guarantees the test to run + // with root permission. + Require_root *bool } type benchmarkDecorator struct { @@ -403,8 +412,12 @@ func (benchmark *benchmarkDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps func (benchmark *benchmarkDecorator) install(ctx ModuleContext, file android.Path) { benchmark.data = android.PathsForModuleSrc(ctx, benchmark.Properties.Data) + var configs []tradefed.Config + if Bool(benchmark.Properties.Require_root) { + configs = append(configs, tradefed.Preparer{"com.android.tradefed.targetprep.RootTargetPreparer"}) + } benchmark.testConfig = tradefed.AutoGenNativeBenchmarkTestConfig(ctx, benchmark.Properties.Test_config, - benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites) + benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites, configs) benchmark.binaryDecorator.baseInstaller.dir = filepath.Join("benchmarktest", ctx.ModuleName()) benchmark.binaryDecorator.baseInstaller.dir64 = filepath.Join("benchmarktest64", ctx.ModuleName()) diff --git a/cc/util.go b/cc/util.go index 3862728bb..2e1bb2590 100644 --- a/cc/util.go +++ b/cc/util.go @@ -29,10 +29,6 @@ func includeDirsToFlags(dirs android.Paths) string { return android.JoinWithPrefix(dirs.Strings(), "-I") } -func includeFilesToFlags(files android.Paths) string { - return android.JoinWithPrefix(files.Strings(), "-include ") -} - func ldDirsToFlags(dirs []string) string { return android.JoinWithPrefix(dirs, "-L") } diff --git a/cc/vendor_public_library.go b/cc/vendor_public_library.go index 5738d25ab..f0de267c1 100644 --- a/cc/vendor_public_library.go +++ b/cc/vendor_public_library.go @@ -125,6 +125,7 @@ func (stub *vendorPublicLibraryStubDecorator) link(ctx ModuleContext, flags Flag if !Bool(stub.Properties.Unversioned) { linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() flags.LdFlags = append(flags.LdFlags, linkerScriptFlag) + flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) } return stub.libraryDecorator.link(ctx, flags, deps, objs) } diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go index 330c5dd27..1171a6521 100644 --- a/cmd/multiproduct_kati/main.go +++ b/cmd/multiproduct_kati/main.go @@ -156,10 +156,12 @@ type mpContext struct { } func main() { - writer := terminal.NewWriter(terminal.StdioImpl{}) - defer writer.Finish() + stdio := terminal.StdioImpl{} - log := logger.New(writer) + output := terminal.NewStatusOutput(stdio.Stdout(), "", + build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")) + + log := logger.New(output) defer log.Cleanup() flag.Parse() @@ -172,8 +174,7 @@ func main() { stat := &status.Status{} defer stat.Finish() - stat.AddOutput(terminal.NewStatusOutput(writer, "", - build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))) + stat.AddOutput(output) var failures failureCount stat.AddOutput(&failures) @@ -188,7 +189,7 @@ func main() { Context: ctx, Logger: log, Tracer: trace, - Writer: writer, + Writer: output, Status: stat, }} @@ -341,7 +342,7 @@ func main() { } else if failures > 1 { log.Fatalf("%d failures", failures) } else { - writer.Print("Success") + fmt.Fprintln(output, "Success") } } @@ -386,7 +387,7 @@ func buildProduct(mpctx *mpContext, product string) { Context: mpctx.Context, Logger: log, Tracer: mpctx.Tracer, - Writer: terminal.NewWriter(terminal.NewCustomStdio(nil, f, f)), + Writer: f, Thread: mpctx.Tracer.NewThread(product), Status: &status.Status{}, }} @@ -466,3 +467,8 @@ func (f *failureCount) Message(level status.MsgLevel, message string) { } func (f *failureCount) Flush() {} + +func (f *failureCount) Write(p []byte) (int, error) { + // discard writes + return len(p), nil +} diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go index 5f9bd016e..f5276c335 100644 --- a/cmd/soong_ui/main.go +++ b/cmd/soong_ui/main.go @@ -109,10 +109,10 @@ func main() { os.Exit(1) } - writer := terminal.NewWriter(c.stdio()) - defer writer.Finish() + output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), + build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")) - log := logger.New(writer) + log := logger.New(output) defer log.Cleanup() ctx, cancel := context.WithCancel(context.Background()) @@ -125,8 +125,7 @@ func main() { stat := &status.Status{} defer stat.Finish() - stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS"), - build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))) + stat.AddOutput(output) stat.AddOutput(trace.StatusTracer()) build.SetupSignals(log, cancel, func() { @@ -140,7 +139,7 @@ func main() { Logger: log, Metrics: met, Tracer: trace, - Writer: writer, + Writer: output, Status: stat, }} @@ -312,13 +311,13 @@ func dumpVarConfig(ctx build.Context, args ...string) build.Config { func make(ctx build.Context, config build.Config, _ []string, logsDir string) { if config.IsVerbose() { writer := ctx.Writer - writer.Print("! The argument `showcommands` is no longer supported.") - writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:") - writer.Print("!") - writer.Print(fmt.Sprintf("! gzip -cd %s/verbose.log.gz | less -R", logsDir)) - writer.Print("!") - writer.Print("! Older versions are saved in verbose.log.#.gz files") - writer.Print("") + fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.") + fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:") + fmt.Fprintln(writer, "!") + fmt.Fprintf(writer, "! gzip -cd %s/verbose.log.gz | less -R\n", logsDir) + fmt.Fprintln(writer, "!") + fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files") + fmt.Fprintln(writer, "") time.Sleep(5 * time.Second) } diff --git a/cmd/zipsync/zipsync.go b/cmd/zipsync/zipsync.go index ea755f5b2..a6023d3bf 100644 --- a/cmd/zipsync/zipsync.go +++ b/cmd/zipsync/zipsync.go @@ -30,6 +30,7 @@ var ( outputDir = flag.String("d", "", "output dir") outputFile = flag.String("l", "", "output list file") filter = flag.String("f", "", "optional filter pattern") + zipPrefix = flag.String("zip-prefix", "", "optional prefix within the zip file to extract, stripping the prefix") ) func must(err error) { @@ -77,6 +78,10 @@ func main() { var files []string seen := make(map[string]string) + if *zipPrefix != "" { + *zipPrefix = filepath.Clean(*zipPrefix) + "/" + } + for _, input := range inputs { reader, err := zip.OpenReader(input) if err != nil { @@ -85,23 +90,30 @@ func main() { defer reader.Close() for _, f := range reader.File { + name := f.Name + if *zipPrefix != "" { + if !strings.HasPrefix(name, *zipPrefix) { + continue + } + name = strings.TrimPrefix(name, *zipPrefix) + } if *filter != "" { - if match, err := filepath.Match(*filter, filepath.Base(f.Name)); err != nil { + if match, err := filepath.Match(*filter, filepath.Base(name)); err != nil { log.Fatal(err) } else if !match { continue } } - if filepath.IsAbs(f.Name) { - log.Fatalf("%q in %q is an absolute path", f.Name, input) + if filepath.IsAbs(name) { + log.Fatalf("%q in %q is an absolute path", name, input) } - if prev, exists := seen[f.Name]; exists { - log.Fatalf("%q found in both %q and %q", f.Name, prev, input) + if prev, exists := seen[name]; exists { + log.Fatalf("%q found in both %q and %q", name, prev, input) } - seen[f.Name] = input + seen[name] = input - filename := filepath.Join(*outputDir, f.Name) + filename := filepath.Join(*outputDir, name) if f.FileInfo().IsDir() { must(os.MkdirAll(filename, f.FileInfo().Mode())) } else { diff --git a/genrule/genrule.go b/genrule/genrule.go index 87e6747e9..b0657ff14 100644 --- a/genrule/genrule.go +++ b/genrule/genrule.go @@ -101,6 +101,7 @@ type generatorProperties struct { type Module struct { android.ModuleBase android.DefaultableModuleBase + android.ApexModuleBase // For other packages to make their own genrules with extra // properties @@ -582,9 +583,6 @@ type Defaults struct { android.DefaultsModuleBase } -func (*Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { -} - func defaultsFactory() android.Module { return DefaultsFactory() } diff --git a/java/aapt2.go b/java/aapt2.go index bcc8e9765..a8151608f 100644 --- a/java/aapt2.go +++ b/java/aapt2.go @@ -94,32 +94,20 @@ func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Pat return ret } -func aapt2CompileDirs(ctx android.ModuleContext, flata android.WritablePath, dirs android.Paths, deps android.Paths) { - ctx.Build(pctx, android.BuildParams{ - Rule: aapt2CompileRule, - Description: "aapt2 compile dirs", - Implicits: deps, - Output: flata, - Args: map[string]string{ - "outDir": flata.String(), - // Always set --pseudo-localize, it will be stripped out later for release - // builds that don't want it. - "cFlags": "--pseudo-localize " + android.JoinWithPrefix(dirs.Strings(), "--dir "), - }, - }) -} - var aapt2CompileZipRule = pctx.AndroidStaticRule("aapt2CompileZip", blueprint.RuleParams{ - Command: `${config.ZipSyncCmd} -d $resZipDir $in && ` + + Command: `${config.ZipSyncCmd} -d $resZipDir $zipSyncFlags $in && ` + `${config.Aapt2Cmd} compile -o $out $cFlags --legacy --dir $resZipDir`, CommandDeps: []string{ "${config.Aapt2Cmd}", "${config.ZipSyncCmd}", }, - }, "cFlags", "resZipDir") + }, "cFlags", "resZipDir", "zipSyncFlags") -func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path) { +func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path, zipPrefix string) { + if zipPrefix != "" { + zipPrefix = "--zip-prefix " + zipPrefix + } ctx.Build(pctx, android.BuildParams{ Rule: aapt2CompileZipRule, Description: "aapt2 compile zip", @@ -128,8 +116,9 @@ func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip Args: map[string]string{ // Always set --pseudo-localize, it will be stripped out later for release // builds that don't want it. - "cFlags": "--pseudo-localize", - "resZipDir": android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(), + "cFlags": "--pseudo-localize", + "resZipDir": android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(), + "zipSyncFlags": zipPrefix, }, }) } diff --git a/java/aar.go b/java/aar.go index 1b84a47b0..47f6e5f6a 100644 --- a/java/aar.go +++ b/java/aar.go @@ -188,8 +188,7 @@ func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, mani return linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resourceZips } -func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkContext sdkContext) { - sdkDep := decodeSdkDep(ctx, sdkContext) +func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkDep sdkDep) { if sdkDep.frameworkResModule != "" { ctx.AddVariationDependencies(nil, frameworkResTag, sdkDep.frameworkResModule) } @@ -245,7 +244,7 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex for i, zip := range resZips { flata := android.PathForModuleOut(ctx, fmt.Sprintf("reszip.%d.flata", i)) - aapt2CompileZip(ctx, flata, zip) + aapt2CompileZip(ctx, flata, zip, "") compiledResDirs = append(compiledResDirs, android.Paths{flata}) } @@ -401,8 +400,9 @@ var _ AndroidLibraryDependency = (*AndroidLibrary)(nil) func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { a.Module.deps(ctx) - if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) { - a.aapt.deps(ctx, sdkContext(a)) + sdkDep := decodeSdkDep(ctx, sdkContext(a)) + if sdkDep.hasFrameworkLibs() { + a.aapt.deps(ctx, sdkDep) } } @@ -513,6 +513,10 @@ func (a *AARImport) targetSdkVersion() string { return a.sdkVersion() } +func (a *AARImport) noFrameworkLibs() bool { + return false +} + var _ AndroidLibraryDependency = (*AARImport)(nil) func (a *AARImport) ExportPackage() android.Path { @@ -556,13 +560,13 @@ func (a *AARImport) DepsMutator(ctx android.BottomUpMutatorContext) { } // Unzip an AAR into its constituent files and directories. Any files in Outputs that don't exist in the AAR will be -// touched to create an empty file, and any directories in $expectedDirs will be created. +// touched to create an empty file. The res directory is not extracted, as it will be extracted in its own rule. var unzipAAR = pctx.AndroidStaticRule("unzipAAR", blueprint.RuleParams{ - Command: `rm -rf $outDir && mkdir -p $outDir $expectedDirs && ` + - `unzip -qo -d $outDir $in && touch $out`, + Command: `rm -rf $outDir && mkdir -p $outDir && ` + + `unzip -qo -d $outDir $in && rm -rf $outDir/res && touch $out`, }, - "expectedDirs", "outDir") + "outDir") func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { if len(a.properties.Aars) != 1 { @@ -580,7 +584,6 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { } extractedAARDir := android.PathForModuleOut(ctx, "aar") - extractedResDir := extractedAARDir.Join(ctx, "res") a.classpathFile = extractedAARDir.Join(ctx, "classes.jar") a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt") a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml") @@ -591,16 +594,13 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { Outputs: android.WritablePaths{a.classpathFile, a.proguardFlags, a.manifest}, Description: "unzip AAR", Args: map[string]string{ - "expectedDirs": extractedResDir.String(), - "outDir": extractedAARDir.String(), + "outDir": extractedAARDir.String(), }, }) compiledResDir := android.PathForModuleOut(ctx, "flat-res") - aaptCompileDeps := android.Paths{a.classpathFile} - aaptCompileDirs := android.Paths{extractedResDir} flata := compiledResDir.Join(ctx, "gen_res.flata") - aapt2CompileDirs(ctx, flata, aaptCompileDirs, aaptCompileDeps) + aapt2CompileZip(ctx, flata, aar, "res") a.exportPackage = android.PathForModuleOut(ctx, "package-res.apk") srcJar := android.PathForModuleGen(ctx, "R.jar") diff --git a/java/app.go b/java/app.go index 3c8f84748..cab97de45 100644 --- a/java/app.go +++ b/java/app.go @@ -159,8 +159,9 @@ func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) { ctx.PropertyErrorf("stl", "sdk_version must be set in order to use c++_shared") } - if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) { - a.aapt.deps(ctx, sdkContext(a)) + sdkDep := decodeSdkDep(ctx, sdkContext(a)) + if sdkDep.hasFrameworkLibs() { + a.aapt.deps(ctx, sdkDep) } embedJni := a.shouldEmbedJnis(ctx) @@ -180,7 +181,7 @@ func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) { } } - a.usesLibrary.deps(ctx, Bool(a.properties.No_framework_libs)) + a.usesLibrary.deps(ctx, sdkDep.hasFrameworkLibs()) } func (a *AndroidApp) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) { @@ -482,7 +483,7 @@ func collectAppDeps(ctx android.ModuleContext) ([]jniLib, []Certificate) { return jniLibs, certificates } -func (a *AndroidApp) getCertString(ctx android.BaseContext) string { +func (a *AndroidApp) getCertString(ctx android.BaseModuleContext) string { certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName()) if overridden { return ":" + certificate @@ -783,7 +784,7 @@ func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) { ctx.AddDependency(ctx.Module(), certificateTag, cert) } - a.usesLibrary.deps(ctx, false) + a.usesLibrary.deps(ctx, true) } func (a *AndroidAppImport) uncompressEmbeddedJniLibs( @@ -937,17 +938,22 @@ type usesLibrary struct { usesLibraryProperties UsesLibraryProperties } -func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, noFrameworkLibs bool) { - ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...) - ctx.AddVariationDependencies(nil, usesLibTag, u.presentOptionalUsesLibs(ctx)...) - if !noFrameworkLibs { - // dexpreopt/dexpreopt.go needs the paths to the dex jars of these libraries in case construct_context.sh needs - // to pass them to dex2oat. Add them as a dependency so we can determine the path to the dex jar of each - // library to dexpreopt. - ctx.AddVariationDependencies(nil, usesLibTag, - "org.apache.http.legacy", - "android.hidl.base-V1.0-java", - "android.hidl.manager-V1.0-java") +func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) { + if !ctx.Config().UnbundledBuild() { + ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...) + ctx.AddVariationDependencies(nil, usesLibTag, u.presentOptionalUsesLibs(ctx)...) + // Only add these extra dependencies if the module depends on framework libs. This avoids + // creating a cyclic dependency: + // e.g. framework-res -> org.apache.http.legacy -> ... -> framework-res. + if hasFrameworkLibs { + // dexpreopt/dexpreopt.go needs the paths to the dex jars of these libraries in case construct_context.sh needs + // to pass them to dex2oat. Add them as a dependency so we can determine the path to the dex jar of each + // library to dexpreopt. + ctx.AddVariationDependencies(nil, usesLibTag, + "org.apache.http.legacy", + "android.hidl.base-V1.0-java", + "android.hidl.manager-V1.0-java") + } } } diff --git a/java/app_builder.go b/java/app_builder.go index fa77bbf1d..348c8b4bb 100644 --- a/java/app_builder.go +++ b/java/app_builder.go @@ -31,7 +31,7 @@ import ( var ( Signapk = pctx.AndroidStaticRule("signapk", blueprint.RuleParams{ - Command: `${config.JavaCmd} -Djava.library.path=$$(dirname $signapkJniLibrary) ` + + Command: `${config.JavaCmd} ${config.JavaVmFlags} -Djava.library.path=$$(dirname $signapkJniLibrary) ` + `-jar $signapkCmd $flags $certificates $in $out`, CommandDeps: []string{"$signapkCmd", "$signapkJniLibrary"}, }, diff --git a/java/app_test.go b/java/app_test.go index bb39c165c..27802cd21 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -546,7 +546,7 @@ func TestAppSdkVersion(t *testing.T) { } } -func TestJNIABI(t *testing.T) { +func TestJNIABI_no_framework_libs_true(t *testing.T) { ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` cc_library { name: "libjni", @@ -619,7 +619,80 @@ func TestJNIABI(t *testing.T) { } } -func TestJNIPackaging(t *testing.T) { +func TestJNIABI(t *testing.T) { + ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + cc_library { + name: "libjni", + system_shared_libs: [], + stl: "none", + } + + android_test { + name: "test", + sdk_version: "core_platform", + jni_libs: ["libjni"], + } + + android_test { + name: "test_first", + sdk_version: "core_platform", + compile_multilib: "first", + jni_libs: ["libjni"], + } + + android_test { + name: "test_both", + sdk_version: "core_platform", + compile_multilib: "both", + jni_libs: ["libjni"], + } + + android_test { + name: "test_32", + sdk_version: "core_platform", + compile_multilib: "32", + jni_libs: ["libjni"], + } + + android_test { + name: "test_64", + sdk_version: "core_platform", + compile_multilib: "64", + jni_libs: ["libjni"], + } + `) + + testCases := []struct { + name string + abis []string + }{ + {"test", []string{"arm64-v8a"}}, + {"test_first", []string{"arm64-v8a"}}, + {"test_both", []string{"arm64-v8a", "armeabi-v7a"}}, + {"test_32", []string{"armeabi-v7a"}}, + {"test_64", []string{"arm64-v8a"}}, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + app := ctx.ModuleForTests(test.name, "android_common") + jniLibZip := app.Output("jnilibs.zip") + var abis []string + args := strings.Fields(jniLibZip.Args["jarArgs"]) + for i := 0; i < len(args); i++ { + if args[i] == "-P" { + abis = append(abis, filepath.Base(args[i+1])) + i++ + } + } + if !reflect.DeepEqual(abis, test.abis) { + t.Errorf("want abis %v, got %v", test.abis, abis) + } + }) + } +} + +func TestJNIPackaging_no_framework_libs_true(t *testing.T) { ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` cc_library { name: "libjni", @@ -700,7 +773,89 @@ func TestJNIPackaging(t *testing.T) { } }) } +} + +func TestJNIPackaging(t *testing.T) { + ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + cc_library { + name: "libjni", + system_shared_libs: [], + stl: "none", + } + + android_app { + name: "app", + jni_libs: ["libjni"], + } + + android_app { + name: "app_noembed", + jni_libs: ["libjni"], + use_embedded_native_libs: false, + } + + android_app { + name: "app_embed", + jni_libs: ["libjni"], + use_embedded_native_libs: true, + } + + android_test { + name: "test", + sdk_version: "core_platform", + jni_libs: ["libjni"], + } + + android_test { + name: "test_noembed", + sdk_version: "core_platform", + jni_libs: ["libjni"], + use_embedded_native_libs: false, + } + + android_test_helper_app { + name: "test_helper", + sdk_version: "core_platform", + jni_libs: ["libjni"], + } + + android_test_helper_app { + name: "test_helper_noembed", + sdk_version: "core_platform", + jni_libs: ["libjni"], + use_embedded_native_libs: false, + } + `) + + testCases := []struct { + name string + packaged bool + compressed bool + }{ + {"app", false, false}, + {"app_noembed", false, false}, + {"app_embed", true, false}, + {"test", true, false}, + {"test_noembed", true, true}, + {"test_helper", true, false}, + {"test_helper_noembed", true, true}, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + app := ctx.ModuleForTests(test.name, "android_common") + jniLibZip := app.MaybeOutput("jnilibs.zip") + if g, w := (jniLibZip.Rule != nil), test.packaged; g != w { + t.Errorf("expected jni packaged %v, got %v", w, g) + } + if jniLibZip.Rule != nil { + if g, w := !strings.Contains(jniLibZip.Args["jarArgs"], "-L 0"), test.compressed; g != w { + t.Errorf("expected jni compressed %v, got %v", w, g) + } + } + }) + } } func TestCertificates(t *testing.T) { diff --git a/java/builder.go b/java/builder.go index d257d1da1..e1a912b2f 100644 --- a/java/builder.go +++ b/java/builder.go @@ -43,7 +43,8 @@ var ( Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" && 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} ${config.JavacWrapper}${config.JavacCmd} ${config.JavacHeapFlags} ${config.CommonJdkFlags} ` + + `${config.SoongJavacWrapper} ${config.JavacWrapper}${config.JavacCmd} ` + + `${config.JavacHeapFlags} ${config.JavacVmFlags} ${config.CommonJdkFlags} ` + `$processorpath $processor $javacFlags $bootClasspath $classpath ` + `-source $javaVersion -target $javaVersion ` + `-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list ; fi ) && ` + @@ -64,7 +65,7 @@ var ( turbine = pctx.AndroidStaticRule("turbine", blueprint.RuleParams{ Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + - `${config.JavaCmd} -jar ${config.TurbineJar} --output $out.tmp ` + + `${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.TurbineJar} --output $out.tmp ` + `--temp_dir "$outDir" --sources @$out.rsp --source_jars $srcJars ` + `--javacopts ${config.CommonJdkFlags} ` + `$javacFlags -source $javaVersion -target $javaVersion -- $bootClasspath $classpath && ` + @@ -108,7 +109,7 @@ var ( jarjar = pctx.AndroidStaticRule("jarjar", blueprint.RuleParams{ - Command: "${config.JavaCmd} -jar ${config.JarjarCmd} process $rulesFile $in $out", + Command: "${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.JarjarCmd} process $rulesFile $in $out", CommandDeps: []string{"${config.JavaCmd}", "${config.JarjarCmd}", "$rulesFile"}, }, "rulesFile") @@ -124,7 +125,7 @@ var ( jetifier = pctx.AndroidStaticRule("jetifier", blueprint.RuleParams{ - Command: "${config.JavaCmd} -jar ${config.JetifierJar} -l error -o $out -i $in", + Command: "${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.JetifierJar} -l error -o $out -i $in", CommandDeps: []string{"${config.JavaCmd}", "${config.JetifierJar}"}, }, ) diff --git a/java/config/config.go b/java/config/config.go index f9552d5a1..6ade6493f 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -69,6 +69,8 @@ func init() { // b/65004097: prevent using java.lang.invoke.StringConcatFactory when using -target 1.9 `-XDstringConcat=inline`, }, " ")) + pctx.StaticVariable("JavaVmFlags", "-XX:OnError=\"cat hs_err_pid%p.log\" -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads") + pctx.StaticVariable("JavacVmFlags", "-J-XX:OnError=\"cat hs_err_pid%p.log\" -J-XX:CICompilerCount=6 -J-XX:+UseDynamicNumberOfGCThreads") pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS) diff --git a/java/config/kotlin.go b/java/config/kotlin.go index 7cea0429e..fd8e3dbe9 100644 --- a/java/config/kotlin.go +++ b/java/config/kotlin.go @@ -32,6 +32,7 @@ func init() { pctx.SourcePathVariable("KotlinScriptRuntimeJar", "external/kotlinc/lib/kotlin-script-runtime.jar") pctx.SourcePathVariable("KotlinTrove4jJar", "external/kotlinc/lib/trove4j.jar") pctx.SourcePathVariable("KotlinKaptJar", "external/kotlinc/lib/kotlin-annotation-processing.jar") + pctx.SourcePathVariable("KotlinAnnotationJar", "external/kotlinc/lib/annotations-13.0.jar") pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar) // These flags silence "Illegal reflective access" warnings when running kotlinc in OpenJDK9 diff --git a/java/config/makevars.go b/java/config/makevars.go index 9c7851174..ead298acd 100644 --- a/java/config/makevars.go +++ b/java/config/makevars.go @@ -39,8 +39,8 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("ANDROID_JAVA8_HOME", "prebuilts/jdk/jdk8/${hostPrebuiltTag}") ctx.Strict("ANDROID_JAVA9_HOME", "prebuilts/jdk/jdk9/${hostPrebuiltTag}") ctx.Strict("ANDROID_JAVA_TOOLCHAIN", "${JavaToolchain}") - ctx.Strict("JAVA", "${JavaCmd}") - ctx.Strict("JAVAC", "${JavacCmd}") + ctx.Strict("JAVA", "${JavaCmd} ${JavaVmFlags}") + ctx.Strict("JAVAC", "${JavacCmd} ${JavacVmFlags}") ctx.Strict("JAR", "${JarCmd}") ctx.Strict("JAR_ARGS", "${JarArgsCmd}") ctx.Strict("JAVADOC", "${JavadocCmd}") @@ -58,8 +58,8 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("ERROR_PRONE_CHECKS", "${ErrorProneChecks}") } - ctx.Strict("TARGET_JAVAC", "${JavacCmd} ${CommonJdkFlags}") - ctx.Strict("HOST_JAVAC", "${JavacCmd} ${CommonJdkFlags}") + ctx.Strict("TARGET_JAVAC", "${JavacCmd} ${JavacVmFlags} ${CommonJdkFlags}") + ctx.Strict("HOST_JAVAC", "${JavacCmd} ${JavacVmFlags} ${CommonJdkFlags}") ctx.Strict("JLINK", "${JlinkCmd}") ctx.Strict("JMOD", "${JmodCmd}") diff --git a/java/droiddoc.go b/java/droiddoc.go index 0ec7799a8..a8cf1c034 100644 --- a/java/droiddoc.go +++ b/java/droiddoc.go @@ -73,7 +73,7 @@ var ( Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && ` + `mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.JavaCmd} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` + + `${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` + `$bootclasspathArgs $classpathArgs $sourcepathArgs --no-banner --color --quiet --format=v2 ` + `$opts && ` + `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir && ` + @@ -95,7 +95,7 @@ var ( blueprint.RuleParams{ Command: `( rm -rf "$srcJarDir" && mkdir -p "$srcJarDir" && ` + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.JavaCmd} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` + + `${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` + `$bootclasspathArgs $classpathArgs $sourcepathArgs --no-banner --color --quiet --format=v2 ` + `$opts && touch $out && rm -rf "$srcJarDir") || ` + `( echo -e "$msg" ; exit 38 )`, @@ -120,7 +120,7 @@ var ( Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && ` + `mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.JavaCmd} -jar ${config.DokkaJar} $srcJarDir ` + + `${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.DokkaJar} $srcJarDir ` + `$classpathArgs -format dac -dacRoot /reference/kotlin -output $outDir $opts && ` + `${config.SoongZipCmd} -write_if_changed -d -o $docZip -C $outDir -D $outDir && ` + `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir && ` + @@ -171,10 +171,6 @@ type JavadocProperties struct { // list of java libraries that will be in the classpath. Libs []string `android:"arch_variant"` - // don't build against the default libraries (bootclasspath, ext, and framework for device - // targets) - No_standard_libs *bool - // don't build against the framework libraries (ext, and framework for device targets) No_framework_libs *bool @@ -538,16 +534,20 @@ func (j *Javadoc) targetSdkVersion() string { return j.sdkVersion() } +func (j *Javadoc) noFrameworkLibs() bool { + return Bool(j.properties.No_framework_libs) +} + func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) { if ctx.Device() { - if !Bool(j.properties.No_standard_libs) { - sdkDep := decodeSdkDep(ctx, sdkContext(j)) + sdkDep := decodeSdkDep(ctx, sdkContext(j)) + if sdkDep.hasStandardLibs() { if sdkDep.useDefaultLibs { ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...) if ctx.Config().TargetOpenJDK9() { ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules) } - if !Bool(j.properties.No_framework_libs) { + if sdkDep.hasFrameworkLibs() { ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...) } } else if sdkDep.useModule { @@ -1751,7 +1751,7 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { jdiff := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jdiff.jar") jdiffImplicits = append(jdiffImplicits, android.Paths{jdiff, d.apiXmlFile, d.lastReleasedApiXmlFile}...) - opts := " -encoding UTF-8 -source 1.8 -J-Xmx1600m -XDignore.symbol.file " + + opts := " -source 1.8 -J-Xmx1600m -XDignore.symbol.file " + "-doclet jdiff.JDiff -docletpath " + jdiff.String() + " -quiet " + "-newapi " + strings.TrimSuffix(d.apiXmlFile.Base(), d.apiXmlFile.Ext()) + " -newapidir " + filepath.Dir(d.apiXmlFile.String()) + @@ -1808,9 +1808,6 @@ type DocDefaults struct { android.DefaultsModuleBase } -func (*DocDefaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { -} - func DocDefaultsFactory() android.Module { module := &DocDefaults{} diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go index b1ddab48d..cf9f49285 100644 --- a/java/hiddenapi_singleton.go +++ b/java/hiddenapi_singleton.go @@ -15,11 +15,14 @@ package java import ( + "fmt" + "android/soong/android" ) func init() { android.RegisterSingletonType("hiddenapi", hiddenAPISingletonFactory) + android.RegisterModuleType("hiddenapi_flags", hiddenAPIFlagsFactory) } type hiddenAPISingletonPathsStruct struct { @@ -307,3 +310,48 @@ func commitChangeForRestat(rule *android.RuleBuilder, tempPath, outputPath andro Text("fi"). Text(")") } + +type hiddenAPIFlagsProperties struct { + // name of the file into which the flags will be copied. + Filename *string +} + +type hiddenAPIFlags struct { + android.ModuleBase + + properties hiddenAPIFlagsProperties + + outputFilePath android.OutputPath +} + +func (h *hiddenAPIFlags) GenerateAndroidBuildActions(ctx android.ModuleContext) { + filename := String(h.properties.Filename) + + inputPath := hiddenAPISingletonPaths(ctx).flags + h.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath + + // This ensures that outputFilePath has the correct name for others to + // use, as the source file may have a different name. + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Output: h.outputFilePath, + Input: inputPath, + }) +} + +func (h *hiddenAPIFlags) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case "": + return android.Paths{h.outputFilePath}, nil + default: + return nil, fmt.Errorf("unsupported module reference tag %q", tag) + } +} + +// hiddenapi-flags provides access to the hiddenapi-flags.csv file generated during the build. +func hiddenAPIFlagsFactory() android.Module { + module := &hiddenAPIFlags{} + module.AddProperties(&module.properties) + android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) + return module +} diff --git a/java/jacoco.go b/java/jacoco.go index 8b6d4ac87..bce9822f4 100644 --- a/java/jacoco.go +++ b/java/jacoco.go @@ -31,7 +31,7 @@ var ( jacoco = pctx.AndroidStaticRule("jacoco", blueprint.RuleParams{ Command: `rm -rf $tmpDir && mkdir -p $tmpDir && ` + `${config.Zip2ZipCmd} -i $in -o $strippedJar $stripSpec && ` + - `${config.JavaCmd} -jar ${config.JacocoCLIJar} ` + + `${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.JacocoCLIJar} ` + ` instrument --quiet --dest $tmpDir $strippedJar && ` + `${config.Ziptime} $tmpJar && ` + `${config.MergeZipsCmd} --ignore-duplicates -j $out $tmpJar $in`, diff --git a/java/java.go b/java/java.go index 5544f5763..4b3845161 100644 --- a/java/java.go +++ b/java/java.go @@ -82,10 +82,6 @@ type CompilerProperties struct { // list of files that should be excluded from java_resources and java_resource_dirs Exclude_java_resources []string `android:"path,arch_variant"` - // don't build against the default libraries (bootclasspath, ext, and framework for device - // targets) - No_standard_libs *bool - // don't build against the framework libraries (ext, and framework for device targets) No_framework_libs *bool @@ -380,8 +376,8 @@ type Dependency interface { } type SdkLibraryDependency interface { - SdkHeaderJars(ctx android.BaseContext, sdkVersion string) android.Paths - SdkImplementationJars(ctx android.BaseContext, sdkVersion string) android.Paths + SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths + SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths } type SrcDependency interface { @@ -440,6 +436,16 @@ type sdkDep struct { jars android.Paths aidl android.OptionalPath + + noStandardLibs, noFrameworksLibs bool +} + +func (s sdkDep) hasStandardLibs() bool { + return !s.noStandardLibs +} + +func (s sdkDep) hasFrameworkLibs() bool { + return !s.noStandardLibs && !s.noFrameworksLibs } type jniLib struct { @@ -448,11 +454,11 @@ type jniLib struct { target android.Target } -func (j *Module) shouldInstrument(ctx android.BaseContext) bool { +func (j *Module) shouldInstrument(ctx android.BaseModuleContext) bool { return j.properties.Instrument && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") } -func (j *Module) shouldInstrumentStatic(ctx android.BaseContext) bool { +func (j *Module) shouldInstrumentStatic(ctx android.BaseModuleContext) bool { return j.shouldInstrument(ctx) && (ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_STATIC") || ctx.Config().UnbundledBuild()) @@ -476,14 +482,18 @@ func (j *Module) targetSdkVersion() string { return j.sdkVersion() } +func (j *Module) noFrameworkLibs() bool { + return Bool(j.properties.No_framework_libs) +} + func (j *Module) deps(ctx android.BottomUpMutatorContext) { if ctx.Device() { - if !Bool(j.properties.No_standard_libs) { - sdkDep := decodeSdkDep(ctx, sdkContext(j)) + sdkDep := decodeSdkDep(ctx, sdkContext(j)) + if sdkDep.hasStandardLibs() { if sdkDep.useDefaultLibs { ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...) ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules) - if !Bool(j.properties.No_framework_libs) { + if sdkDep.hasFrameworkLibs() { ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...) } } else if sdkDep.useModule { @@ -495,8 +505,8 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { } } } else if j.deviceProperties.System_modules == nil { - ctx.PropertyErrorf("no_standard_libs", - "system_modules is required to be set when no_standard_libs is true, did you mean no_framework_libs?") + ctx.PropertyErrorf("sdk_version", + `system_modules is required to be set when sdk_version is "none", did you mean no_framework_libs?`) } else if *j.deviceProperties.System_modules != "none" { ctx.AddVariationDependencies(nil, systemModulesTag, *j.deviceProperties.System_modules) } @@ -664,7 +674,7 @@ func getLinkType(m *Module, name string) (ret linkType, stubs bool) { return javaSdk, true case ver == "current": return javaSdk, false - case ver == "": + case ver == "" || ver == "none" || ver == "core_platform": return javaPlatform, false default: if _, err := strconv.Atoi(ver); err != nil { @@ -842,7 +852,8 @@ func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sd var ret string v := sdkContext.sdkVersion() // For PDK builds, use the latest SDK version instead of "current" - if ctx.Config().IsPdkBuild() && (v == "" || v == "current") { + if ctx.Config().IsPdkBuild() && + (v == "" || v == "none" || v == "core_platform" || v == "current") { sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int) latestSdkVersion := 0 if len(sdkVersions) > 0 { @@ -861,7 +872,11 @@ func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sd ret = "1.7" } else if ctx.Device() && sdk <= 29 || !ctx.Config().TargetOpenJDK9() { ret = "1.8" - } else if ctx.Device() && sdkContext.sdkVersion() != "" && sdk == android.FutureApiLevel { + } else if ctx.Device() && + sdkContext.sdkVersion() != "" && + sdkContext.sdkVersion() != "none" && + sdkContext.sdkVersion() != "core_platform" && + sdk == android.FutureApiLevel { // TODO(ccross): once we generate stubs we should be able to use 1.9 for sdk_version: "current" ret = "1.8" } else { @@ -913,7 +928,7 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB flags.processor = strings.Join(deps.processorClasses, ",") if len(flags.bootClasspath) == 0 && ctx.Host() && flags.javaVersion != "1.9" && - !Bool(j.properties.No_standard_libs) && + decodeSdkDep(ctx, sdkContext(j)).hasStandardLibs() && inList(flags.javaVersion, []string{"1.6", "1.7", "1.8"}) { // Give host-side tools a version of OpenJDK's standard libraries // close to what they're targeting. As of Dec 2017, AOSP is only @@ -2131,9 +2146,6 @@ type Defaults struct { android.DefaultsModuleBase } -func (*Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { -} - // java_defaults provides a set of properties that can be inherited by other java or android modules. // // A module can use the properties from a java_defaults module using `defaults: ["defaults_module_name"]`. Each diff --git a/java/java_test.go b/java/java_test.go index 4d161c5ac..22dec073f 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -212,7 +212,7 @@ func run(t *testing.T, ctx *android.TestContext, config android.Config) { setDexpreoptTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx)) ctx.Register() - _, errs := ctx.ParseFileList(".", []string{"Android.bp", "prebuilts/sdk/Android.bp"}) + _, errs := ctx.ParseBlueprintsFiles("Android.bp") android.FailIfErrored(t, errs) _, errs = ctx.PrepareBuildActions(config) android.FailIfErrored(t, errs) @@ -842,6 +842,19 @@ func TestExcludeFileGroupInSrcs(t *testing.T) { } } +func TestJavaLibrary(t *testing.T) { + config := testConfig(nil) + ctx := testContext(config, "", map[string][]byte{ + "libcore/Android.bp": []byte(` + java_library { + name: "core", + sdk_version: "none", + system_modules: "none", + }`), + }) + run(t, ctx, config) +} + func TestJavaSdkLibrary(t *testing.T) { ctx := testJava(t, ` droiddoc_template { @@ -1000,7 +1013,7 @@ func TestPatchModule(t *testing.T) { java_library { name: "bar", srcs: ["b.java"], - no_standard_libs: true, + sdk_version: "none", system_modules: "none", patch_module: "java.base", } diff --git a/java/kotlin.go b/java/kotlin.go index 33167bafd..83069072c 100644 --- a/java/kotlin.go +++ b/java/kotlin.go @@ -44,6 +44,7 @@ var kotlinc = pctx.AndroidGomaStaticRule("kotlinc", "${config.KotlinScriptRuntimeJar}", "${config.KotlinStdlibJar}", "${config.KotlinTrove4jJar}", + "${config.KotlinAnnotationJar}", "${config.GenKotlinBuildFileCmd}", "${config.SoongZipCmd}", "${config.ZipSyncCmd}", diff --git a/java/robolectric.go b/java/robolectric.go index b87ee0d6d..1de56a5cd 100644 --- a/java/robolectric.go +++ b/java/robolectric.go @@ -17,6 +17,7 @@ package java import ( "fmt" "io" + "strconv" "strings" "android/soong/android" @@ -40,6 +41,9 @@ type robolectricProperties struct { Test_options struct { // Timeout in seconds when running the tests. Timeout *int64 + + // Number of shards to use when running the tests. + Shards *int64 } } @@ -48,7 +52,8 @@ type robolectricTest struct { robolectricProperties robolectricProperties - libs []string + libs []string + tests []string } func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) { @@ -69,6 +74,39 @@ func (r *robolectricTest) GenerateAndroidBuildActions(ctx android.ModuleContext) for _, dep := range ctx.GetDirectDepsWithTag(libTag) { r.libs = append(r.libs, ctx.OtherModuleName(dep)) } + + // TODO: this could all be removed if tradefed was used as the test runner, it will find everything + // annotated as a test and run it. + for _, src := range r.compiledJavaSrcs { + s := src.Rel() + if !strings.HasSuffix(s, "Test.java") { + continue + } else if strings.HasSuffix(s, "/BaseRobolectricTest.java") { + continue + } else if strings.HasPrefix(s, "src/") { + s = strings.TrimPrefix(s, "src/") + } + r.tests = append(r.tests, s) + } +} + +func shardTests(paths []string, shards int) [][]string { + if shards > len(paths) { + shards = len(paths) + } + if shards == 0 { + return nil + } + ret := make([][]string, 0, shards) + shardSize := (len(paths) + shards - 1) / shards + for len(paths) > shardSize { + ret = append(ret, paths[0:shardSize]) + paths = paths[shardSize:] + } + if len(paths) > 0 { + ret = append(ret, paths) + } + return ret } func (r *robolectricTest) AndroidMk() android.AndroidMkData { @@ -77,24 +115,50 @@ func (r *robolectricTest) AndroidMk() android.AndroidMkData { data.Custom = func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { android.WriteAndroidMkData(w, data) - fmt.Fprintln(w, "") - fmt.Fprintln(w, "include $(CLEAR_VARS)") - fmt.Fprintln(w, "LOCAL_MODULE := Run"+name) - fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", name) - fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " ")) - fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for)) - if t := r.robolectricProperties.Test_options.Timeout; t != nil { - fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t) + if s := r.robolectricProperties.Test_options.Shards; s != nil && *s > 1 { + shards := shardTests(r.tests, int(*s)) + for i, shard := range shards { + r.writeTestRunner(w, name, "Run"+name+strconv.Itoa(i), shard) + } + + // TODO: add rules to dist the outputs of the individual tests, or combine them together? + fmt.Fprintln(w, "") + fmt.Fprintln(w, ".PHONY:", "Run"+name) + fmt.Fprintln(w, "Run"+name, ": \\") + for i := range shards { + fmt.Fprintln(w, " ", "Run"+name+strconv.Itoa(i), "\\") + } + fmt.Fprintln(w, "") + } else { + r.writeTestRunner(w, name, "Run"+name, r.tests) } - fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk") } return data } +func (r *robolectricTest) writeTestRunner(w io.Writer, module, name string, tests []string) { + fmt.Fprintln(w, "") + fmt.Fprintln(w, "include $(CLEAR_VARS)") + fmt.Fprintln(w, "LOCAL_MODULE :=", name) + fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", module) + fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " ")) + fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for)) + fmt.Fprintln(w, "LOCAL_ROBOTEST_FILES :=", strings.Join(tests, " ")) + if t := r.robolectricProperties.Test_options.Timeout; t != nil { + fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t) + } + fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk") + +} + // An android_robolectric_test module compiles tests against the Robolectric framework that can run on the local host // instead of on a device. It also generates a rule with the name of the module prefixed with "Run" that can be // used to run the tests. Running the tests with build rule will eventually be deprecated and replaced with atest. +// +// The test runner considers any file listed in srcs whose name ends with Test.java to be a test class, unless +// it is named BaseRobolectricTest.java. The path to the each source file must exactly match the package +// name, or match the package name when the prefix "src/" is removed. func RobolectricTestFactory() android.Module { module := &robolectricTest{} diff --git a/java/robolectric_test.go b/java/robolectric_test.go new file mode 100644 index 000000000..e89c6e74c --- /dev/null +++ b/java/robolectric_test.go @@ -0,0 +1,88 @@ +// Copyright 2019 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 java + +import ( + "reflect" + "testing" +) + +func Test_shardTests(t *testing.T) { + type args struct { + paths []string + shards int + } + tests := []struct { + name string + args args + want [][]string + }{ + { + name: "empty", + args: args{ + paths: nil, + shards: 1, + }, + want: [][]string(nil), + }, + { + name: "too many shards", + args: args{ + paths: []string{"a", "b"}, + shards: 3, + }, + want: [][]string{{"a"}, {"b"}}, + }, + { + name: "single shard", + args: args{ + paths: []string{"a", "b"}, + shards: 1, + }, + want: [][]string{{"a", "b"}}, + }, + { + name: "shard per input", + args: args{ + paths: []string{"a", "b", "c"}, + shards: 3, + }, + want: [][]string{{"a"}, {"b"}, {"c"}}, + }, + { + name: "balanced shards", + args: args{ + paths: []string{"a", "b", "c", "d"}, + shards: 2, + }, + want: [][]string{{"a", "b"}, {"c", "d"}}, + }, + { + name: "unbalanced shards", + args: args{ + paths: []string{"a", "b", "c"}, + shards: 2, + }, + want: [][]string{{"a", "b"}, {"c"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := shardTests(tt.args.paths, tt.args.shards); !reflect.DeepEqual(got, tt.want) { + t.Errorf("shardTests() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/java/sdk.go b/java/sdk.go index 506edfb51..6ffe399fb 100644 --- a/java/sdk.go +++ b/java/sdk.go @@ -38,17 +38,20 @@ var sdkFrameworkAidlPathKey = android.NewOnceKey("sdkFrameworkAidlPathKey") var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey") type sdkContext interface { - // sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set. + // sdkVersion returns the sdk_version property of the current module, or an empty string if it is not set. sdkVersion() string // minSdkVersion returns the min_sdk_version property of the current module, or sdkVersion() if it is not set. minSdkVersion() string // targetSdkVersion returns the target_sdk_version property of the current module, or sdkVersion() if it is not set. targetSdkVersion() string + + // Temporarily provide access to the no_frameworks_libs property (where present). + noFrameworkLibs() bool } -func sdkVersionOrDefault(ctx android.BaseContext, v string) string { +func sdkVersionOrDefault(ctx android.BaseModuleContext, v string) string { switch v { - case "", "current", "system_current", "test_current", "core_current": + case "", "none", "current", "test_current", "system_current", "core_current", "core_platform": return ctx.Config().DefaultAppTargetSdk() default: return v @@ -57,9 +60,9 @@ func sdkVersionOrDefault(ctx android.BaseContext, v string) string { // Returns a sdk version as a number. For modules targeting an unreleased SDK (meaning it does not yet have a number) // it returns android.FutureApiLevel (10000). -func sdkVersionToNumber(ctx android.BaseContext, v string) (int, error) { +func sdkVersionToNumber(ctx android.BaseModuleContext, v string) (int, error) { switch v { - case "", "current", "test_current", "system_current", "core_current": + case "", "none", "current", "test_current", "system_current", "core_current", "core_platform": return ctx.Config().DefaultAppTargetSdkInt(), nil default: n := android.GetNumericSdkVersion(v) @@ -71,7 +74,7 @@ func sdkVersionToNumber(ctx android.BaseContext, v string) (int, error) { } } -func sdkVersionToNumberAsString(ctx android.BaseContext, v string) (string, error) { +func sdkVersionToNumberAsString(ctx android.BaseModuleContext, v string) (string, error) { n, err := sdkVersionToNumber(ctx, v) if err != nil { return "", err @@ -79,7 +82,7 @@ func sdkVersionToNumberAsString(ctx android.BaseContext, v string) (string, erro return strconv.Itoa(n), nil } -func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep { +func decodeSdkDep(ctx android.BaseModuleContext, sdkContext sdkContext) sdkDep { v := sdkContext.sdkVersion() // For PDK builds, use the latest SDK version instead of "current" if ctx.Config().IsPdkBuild() && (v == "" || v == "current") { @@ -138,6 +141,9 @@ func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep { useFiles: true, jars: android.Paths{jarPath.Path(), lambdaStubsPath}, aidl: android.OptionalPathForPath(aidlPath.Path()), + + // Pass value straight through for now to match previous behavior. + noFrameworksLibs: sdkContext.noFrameworkLibs(), } } @@ -148,6 +154,9 @@ func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep { systemModules: m + "_system_modules", frameworkResModule: r, aidl: android.OptionalPathForPath(aidl), + + // Pass value straight through for now to match previous behavior. + noFrameworksLibs: sdkContext.noFrameworkLibs(), } if m == "core.current.stubs" { @@ -173,7 +182,8 @@ func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep { } } - if ctx.Config().UnbundledBuildUsePrebuiltSdks() && v != "" { + if ctx.Config().UnbundledBuildUsePrebuiltSdks() && + v != "" && v != "none" && v != "core_platform" { return toPrebuilt(v) } @@ -182,6 +192,19 @@ func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep { return sdkDep{ useDefaultLibs: true, frameworkResModule: "framework-res", + + // Pass value straight through for now to match previous behavior. + noFrameworksLibs: sdkContext.noFrameworkLibs(), + } + case "none": + return sdkDep{ + noStandardLibs: true, + } + case "core_platform": + return sdkDep{ + useDefaultLibs: true, + frameworkResModule: "framework-res", + noFrameworksLibs: true, } case "current": return toModule("android_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx)) diff --git a/java/sdk_library.go b/java/sdk_library.go index 5b65c0ca0..b4a3f296c 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -159,7 +159,8 @@ func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { ctx.AddVariationDependencies(nil, publicApiStubsTag, module.stubsName(apiScopePublic)) ctx.AddVariationDependencies(nil, publicApiFileTag, module.docsName(apiScopePublic)) - if !Bool(module.properties.No_standard_libs) { + sdkDep := decodeSdkDep(ctx, sdkContext(&module.Library)) + if sdkDep.hasStandardLibs() { ctx.AddVariationDependencies(nil, systemApiStubsTag, module.stubsName(apiScopeSystem)) ctx.AddVariationDependencies(nil, systemApiFileTag, module.docsName(apiScopeSystem)) ctx.AddVariationDependencies(nil, testApiFileTag, module.docsName(apiScopeTest)) @@ -384,7 +385,6 @@ func (module *SdkLibrary) createStubsLibrary(mctx android.LoadHookContext, apiSc Device_specific *bool Product_specific *bool Compile_dex *bool - No_standard_libs *bool System_modules *string Java_version *string Product_variables struct { @@ -401,17 +401,22 @@ func (module *SdkLibrary) createStubsLibrary(mctx android.LoadHookContext, apiSc } }{} + sdkVersion := module.sdkVersion(apiScope) + sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library)) + if !sdkDep.hasStandardLibs() { + sdkVersion = "none" + } + props.Name = proptools.StringPtr(module.stubsName(apiScope)) // sources are generated from the droiddoc props.Srcs = []string{":" + module.docsName(apiScope)} - props.Sdk_version = proptools.StringPtr(module.sdkVersion(apiScope)) + props.Sdk_version = proptools.StringPtr(sdkVersion) props.Libs = module.sdkLibraryProperties.Stub_only_libs // Unbundled apps will use the prebult one from /prebuilts/sdk if mctx.Config().UnbundledBuildUsePrebuiltSdks() { props.Product_variables.Unbundled_build.Enabled = proptools.BoolPtr(false) } props.Product_variables.Pdk.Enabled = proptools.BoolPtr(false) - props.No_standard_libs = module.Library.Module.properties.No_standard_libs props.System_modules = module.Library.Module.deviceProperties.System_modules props.Openjdk9.Srcs = module.Library.Module.properties.Openjdk9.Srcs props.Openjdk9.Javacflags = module.Library.Module.properties.Openjdk9.Javacflags @@ -441,13 +446,13 @@ func (module *SdkLibrary) createDocs(mctx android.LoadHookContext, apiScope apiS Srcs_lib *string Srcs_lib_whitelist_dirs []string Srcs_lib_whitelist_pkgs []string + Sdk_version *string Libs []string Arg_files []string Args *string Api_tag_name *string Api_filename *string Removed_api_filename *string - No_standard_libs *bool Java_version *string Merge_annotations_dirs []string Merge_inclusion_annotations_dirs []string @@ -462,9 +467,16 @@ func (module *SdkLibrary) createDocs(mctx android.LoadHookContext, apiScope apiS } }{} + sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library)) + sdkVersion := "" + if !sdkDep.hasStandardLibs() { + sdkVersion = "none" + } + props.Name = proptools.StringPtr(module.docsName(apiScope)) props.Srcs = append(props.Srcs, module.Library.Module.properties.Srcs...) props.Srcs = append(props.Srcs, module.sdkLibraryProperties.Api_srcs...) + props.Sdk_version = proptools.StringPtr(sdkVersion) props.Installable = proptools.BoolPtr(false) // A droiddoc module has only one Libs property and doesn't distinguish between // shared libs and static libs. So we need to add both of these libs to Libs property. @@ -472,7 +484,6 @@ func (module *SdkLibrary) createDocs(mctx android.LoadHookContext, apiScope apiS props.Libs = append(props.Libs, module.Library.Module.properties.Static_libs...) props.Aidl.Include_dirs = module.Library.Module.deviceProperties.Aidl.Include_dirs props.Aidl.Local_include_dirs = module.Library.Module.deviceProperties.Aidl.Local_include_dirs - props.No_standard_libs = module.Library.Module.properties.No_standard_libs props.Java_version = module.Library.Module.properties.Java_version props.Merge_annotations_dirs = module.sdkLibraryProperties.Merge_annotations_dirs @@ -591,9 +602,9 @@ func (module *SdkLibrary) createXmlFile(mctx android.LoadHookContext) { mctx.CreateModule(android.ModuleFactoryAdaptor(android.PrebuiltEtcFactory), &etcProps) } -func (module *SdkLibrary) PrebuiltJars(ctx android.BaseContext, sdkVersion string) android.Paths { +func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths { var api, v string - if sdkVersion == "" { + if sdkVersion == "" || sdkVersion == "none" { api = "system" v = "current" } else if strings.Contains(sdkVersion, "_") { @@ -615,7 +626,7 @@ func (module *SdkLibrary) PrebuiltJars(ctx android.BaseContext, sdkVersion strin } // to satisfy SdkLibraryDependency interface -func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseContext, sdkVersion string) android.Paths { +func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths { // This module is just a wrapper for the stubs. if ctx.Config().UnbundledBuildUsePrebuiltSdks() { return module.PrebuiltJars(ctx, sdkVersion) @@ -631,7 +642,7 @@ func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseContext, sdkVersion stri } // to satisfy SdkLibraryDependency interface -func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseContext, sdkVersion string) android.Paths { +func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths { // This module is just a wrapper for the stubs. if ctx.Config().UnbundledBuildUsePrebuiltSdks() { return module.PrebuiltJars(ctx, sdkVersion) @@ -701,7 +712,8 @@ func (module *SdkLibrary) CreateInternalModules(mctx android.LoadHookContext) { module.createStubsLibrary(mctx, apiScopePublic) module.createDocs(mctx, apiScopePublic) - if !Bool(module.properties.No_standard_libs) { + sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library)) + if sdkDep.hasStandardLibs() { // for system API stubs module.createStubsLibrary(mctx, apiScopeSystem) module.createDocs(mctx, apiScopeSystem) @@ -840,13 +852,13 @@ func (module *sdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleCo } // to satisfy SdkLibraryDependency interface -func (module *sdkLibraryImport) SdkHeaderJars(ctx android.BaseContext, sdkVersion string) android.Paths { +func (module *sdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths { // This module is just a wrapper for the prebuilt stubs. return module.stubsPath } // to satisfy SdkLibraryDependency interface -func (module *sdkLibraryImport) SdkImplementationJars(ctx android.BaseContext, sdkVersion string) android.Paths { +func (module *sdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths { // This module is just a wrapper for the stubs. return module.stubsPath } diff --git a/java/sdk_test.go b/java/sdk_test.go index cc4da2eca..915333ec9 100644 --- a/java/sdk_test.go +++ b/java/sdk_test.go @@ -47,6 +47,22 @@ func TestClasspath(t *testing.T) { aidl: "-Iframework/aidl", }, { + name: "no_framework_libs:true", + properties: `no_framework_libs:true`, + bootclasspath: config.DefaultBootclasspathLibraries, + system: config.DefaultSystemModules, + classpath: []string{}, + aidl: "", + }, + { + name: `sdk_version:"core_platform"`, + properties: `sdk_version:"core_platform"`, + bootclasspath: config.DefaultBootclasspathLibraries, + system: config.DefaultSystemModules, + classpath: []string{}, + aidl: "", + }, + { name: "blank sdk version", properties: `sdk_version: "",`, bootclasspath: config.DefaultBootclasspathLibraries, @@ -106,7 +122,7 @@ func TestClasspath(t *testing.T) { { name: "nostdlib", - properties: `no_standard_libs: true, system_modules: "none"`, + properties: `sdk_version: "none", system_modules: "none"`, system: "none", bootclasspath: []string{`""`}, classpath: []string{}, @@ -114,7 +130,7 @@ func TestClasspath(t *testing.T) { { name: "nostdlib system_modules", - properties: `no_standard_libs: true, system_modules: "core-platform-api-stubs-system-modules"`, + properties: `sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules"`, system: "core-platform-api-stubs-system-modules", bootclasspath: []string{`""`}, classpath: []string{}, @@ -129,13 +145,6 @@ func TestClasspath(t *testing.T) { classpath: []string{}, }, { - name: "host nostdlib", - moduleType: "java_library_host", - host: android.Host, - properties: `no_standard_libs: true`, - classpath: []string{}, - }, - { name: "host supported default", host: android.Host, @@ -146,7 +155,7 @@ func TestClasspath(t *testing.T) { { name: "host supported nostdlib", host: android.Host, - properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`, + properties: `host_supported: true, sdk_version: "none", system_modules: "none"`, classpath: []string{}, }, { diff --git a/java/system_modules.go b/java/system_modules.go index 2ec3dfbd5..5a86f3c82 100644 --- a/java/system_modules.go +++ b/java/system_modules.go @@ -107,12 +107,6 @@ type SystemModules struct { type SystemModulesProperties struct { // List of java library modules that should be included in the system modules Libs []string - - // List of prebuilt jars that should be included in the system modules - Jars []string - - // Sdk version that should be included in the system modules - Sdk_version *string } func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleContext) { @@ -123,8 +117,6 @@ func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleConte jars = append(jars, dep.HeaderJars()...) }) - jars = append(jars, android.PathsForModuleSrc(ctx, system.properties.Jars)...) - system.outputFile = TransformJarsToSystemModules(ctx, "java.base", jars) } diff --git a/java/testing.go b/java/testing.go index e1b06a075..5d116a787 100644 --- a/java/testing.go +++ b/java/testing.go @@ -54,8 +54,7 @@ func GatherRequiredDepsForTest() string { java_library { name: "%s", srcs: ["a.java"], - no_standard_libs: true, - sdk_version: "core_current", + sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules", } `, extra) @@ -65,8 +64,7 @@ func GatherRequiredDepsForTest() string { java_library { name: "framework", srcs: ["a.java"], - no_standard_libs: true, - sdk_version: "core_current", + sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules", aidl: { export_include_dirs: ["framework/aidl"], @@ -75,14 +73,13 @@ func GatherRequiredDepsForTest() string { android_app { name: "framework-res", - no_framework_libs: true, + sdk_version: "core_platform", } java_library { name: "android.hidl.base-V1.0-java", srcs: ["a.java"], - no_standard_libs: true, - sdk_version: "core_current", + sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules", installable: true, } @@ -90,8 +87,7 @@ func GatherRequiredDepsForTest() string { java_library { name: "android.hidl.manager-V1.0-java", srcs: ["a.java"], - no_standard_libs: true, - sdk_version: "core_current", + sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules", installable: true, } @@ -99,8 +95,7 @@ func GatherRequiredDepsForTest() string { java_library { name: "org.apache.http.legacy", srcs: ["a.java"], - no_standard_libs: true, - sdk_version: "core_current", + sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules", installable: true, } diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go index 3f2709e29..86061c6a2 100644 --- a/sysprop/sysprop_library.go +++ b/sysprop/sysprop_library.go @@ -123,6 +123,8 @@ func syspropLibraryHook(ctx android.LoadHookContext, m *syspropLibrary) { Sysprop struct { Platform *bool } + Header_libs []string + Shared_libs []string }{} ccProps.Name = proptools.StringPtr(m.CcModuleName()) @@ -130,6 +132,8 @@ func syspropLibraryHook(ctx android.LoadHookContext, m *syspropLibrary) { ccProps.Device_specific = proptools.BoolPtr(deviceSpecific) ccProps.Product_specific = proptools.BoolPtr(productSpecific) ccProps.Sysprop.Platform = proptools.BoolPtr(owner == "Platform") + ccProps.Header_libs = []string{"libbase_headers"} + ccProps.Shared_libs = []string{"liblog"} ctx.CreateModule(android.ModuleFactoryAdaptor(cc.LibraryFactory), &m.commonProperties, &ccProps) } diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go index b470ba531..0566036a2 100644 --- a/sysprop/sysprop_test.go +++ b/sysprop/sysprop_test.go @@ -313,13 +313,13 @@ func TestSyspropLibrary(t *testing.T) { vendorVariant := "android_arm64_armv8-a_vendor_static" platformInternalPath := "libsysprop-platform/android_arm64_armv8-a_core_static/gen/sysprop/include" - platformSystemCorePath := "libsysprop-platform/android_arm64_armv8-a_core_static/gen/sysprop/system/include" - platformSystemVendorPath := "libsysprop-platform/android_arm64_armv8-a_vendor_static/gen/sysprop/system/include" + platformPublicCorePath := "libsysprop-platform/android_arm64_armv8-a_core_static/gen/sysprop/public/include" + platformPublicVendorPath := "libsysprop-platform/android_arm64_armv8-a_vendor_static/gen/sysprop/public/include" - platformOnProductPath := "libsysprop-platform-on-product/android_arm64_armv8-a_core_static/gen/sysprop/system/include" + platformOnProductPath := "libsysprop-platform-on-product/android_arm64_armv8-a_core_static/gen/sysprop/public/include" vendorInternalPath := "libsysprop-vendor/android_arm64_armv8-a_vendor_static/gen/sysprop/include" - vendorSystemPath := "libsysprop-vendor/android_arm64_armv8-a_core_static/gen/sysprop/system/include" + vendorPublicPath := "libsysprop-vendor/android_arm64_armv8-a_core_static/gen/sysprop/public/include" platformClient := ctx.ModuleForTests("cc-client-platform", coreVariant) platformFlags := platformClient.Rule("cc").Args["cFlags"] @@ -342,20 +342,20 @@ func TestSyspropLibrary(t *testing.T) { productClient := ctx.ModuleForTests("cc-client-product", coreVariant) productFlags := productClient.Rule("cc").Args["cFlags"] - // Product should use platform's and vendor's system headers + // Product should use platform's and vendor's public headers if !strings.Contains(productFlags, platformOnProductPath) || - !strings.Contains(productFlags, vendorSystemPath) { + !strings.Contains(productFlags, vendorPublicPath) { t.Errorf("flags for product must contain %#v and %#v, but was %#v.", - platformSystemCorePath, vendorSystemPath, productFlags) + platformPublicCorePath, vendorPublicPath, productFlags) } vendorClient := ctx.ModuleForTests("cc-client-vendor", vendorVariant) vendorFlags := vendorClient.Rule("cc").Args["cFlags"] - // Vendor should use platform's system header and vendor's internal header - if !strings.Contains(vendorFlags, platformSystemVendorPath) || + // Vendor should use platform's public header and vendor's internal header + if !strings.Contains(vendorFlags, platformPublicVendorPath) || !strings.Contains(vendorFlags, vendorInternalPath) { t.Errorf("flags for vendor must contain %#v and %#v, but was %#v.", - platformSystemVendorPath, vendorInternalPath, vendorFlags) + platformPublicVendorPath, vendorInternalPath, vendorFlags) } } diff --git a/tradefed/autogen.go b/tradefed/autogen.go index da5dabe6d..952b02236 100644 --- a/tradefed/autogen.go +++ b/tradefed/autogen.go @@ -16,7 +16,6 @@ package tradefed import ( "fmt" - "sort" "strings" "github.com/google/blueprint" @@ -39,9 +38,9 @@ func getTestConfig(ctx android.ModuleContext, prop *string) android.Path { } var autogenTestConfig = pctx.StaticRule("autogenTestConfig", blueprint.RuleParams{ - Command: "sed 's&{MODULE}&${name}&g;s&{EXTRA_OPTIONS}&'${extraOptions}'&g' $template > $out", + Command: "sed 's&{MODULE}&${name}&g;s&{EXTRA_CONFIGS}&'${extraConfigs}'&g' $template > $out", CommandDeps: []string{"$template"}, -}, "name", "template", "extraOptions") +}, "name", "template", "extraConfigs") func testConfigPath(ctx android.ModuleContext, prop *string, testSuites []string) (path android.Path, autogenPath android.WritablePath) { if p := getTestConfig(ctx, prop); p != nil { @@ -57,17 +56,38 @@ func testConfigPath(ctx android.ModuleContext, prop *string, testSuites []string } } -func autogenTemplate(ctx android.ModuleContext, output android.WritablePath, template string, optionsMap map[string]string) { - // If no test option found, delete {EXTRA_OPTIONS} line. - var options []string - for optionName, value := range optionsMap { - if value != "" { - options = append(options, fmt.Sprintf(`<option name="%s" value="%s" />`, optionName, value)) - } +type Config interface { + Config() string +} + +type Option struct { + Name string + Value string +} + +var _ Config = Option{} + +func (o Option) Config() string { + return fmt.Sprintf(`<option name="%s" value="%s" />`, o.Name, o.Value) +} + +type Preparer struct { + Class string +} + +var _ Config = Preparer{} + +func (p Preparer) Config() string { + return fmt.Sprintf(`<target_preparer class="%s" />`, p.Class) +} + +func autogenTemplate(ctx android.ModuleContext, output android.WritablePath, template string, configs []Config) { + var configStrings []string + for _, config := range configs { + configStrings = append(configStrings, config.Config()) } - sort.Strings(options) - extraOptions := strings.Join(options, "\n ") - extraOptions = proptools.NinjaAndShellEscape(extraOptions) + extraConfigs := strings.Join(configStrings, "\n ") + extraConfigs = proptools.NinjaAndShellEscape(extraConfigs) ctx.Build(pctx, android.BuildParams{ Rule: autogenTestConfig, @@ -76,26 +96,23 @@ func autogenTemplate(ctx android.ModuleContext, output android.WritablePath, tem Args: map[string]string{ "name": ctx.ModuleName(), "template": template, - "extraOptions": extraOptions, + "extraConfigs": extraConfigs, }, }) } func AutoGenNativeTestConfig(ctx android.ModuleContext, testConfigProp *string, - testConfigTemplateProp *string, testSuites []string, - optionsMap map[string]string) android.Path { + testConfigTemplateProp *string, testSuites []string, config []Config) android.Path { path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites) if autogenPath != nil { templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp) if templatePath.Valid() { - autogenTemplate(ctx, autogenPath, templatePath.String(), optionsMap) + autogenTemplate(ctx, autogenPath, templatePath.String(), config) } else { if ctx.Device() { - autogenTemplate(ctx, autogenPath, "${NativeTestConfigTemplate}", - optionsMap) + autogenTemplate(ctx, autogenPath, "${NativeTestConfigTemplate}", config) } else { - autogenTemplate(ctx, autogenPath, "${NativeHostTestConfigTemplate}", - optionsMap) + autogenTemplate(ctx, autogenPath, "${NativeHostTestConfigTemplate}", config) } } return autogenPath @@ -104,14 +121,14 @@ func AutoGenNativeTestConfig(ctx android.ModuleContext, testConfigProp *string, } func AutoGenNativeBenchmarkTestConfig(ctx android.ModuleContext, testConfigProp *string, - testConfigTemplateProp *string, testSuites []string) android.Path { + testConfigTemplateProp *string, testSuites []string, configs []Config) android.Path { path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites) if autogenPath != nil { templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp) if templatePath.Valid() { - autogenTemplate(ctx, autogenPath, templatePath.String(), nil) + autogenTemplate(ctx, autogenPath, templatePath.String(), configs) } else { - autogenTemplate(ctx, autogenPath, "${NativeBenchmarkTestConfigTemplate}", nil) + autogenTemplate(ctx, autogenPath, "${NativeBenchmarkTestConfigTemplate}", configs) } return autogenPath } diff --git a/ui/build/config.go b/ui/build/config.go index c298f0008..6df9529fe 100644 --- a/ui/build/config.go +++ b/ui/build/config.go @@ -61,6 +61,28 @@ type configImpl struct { const srcDirFileCheck = "build/soong/root.bp" +type BuildAction uint + +const ( + // Builds all of the modules and their dependencies of a specified directory, relative to the root + // directory of the source tree. + BUILD_MODULES_IN_A_DIRECTORY BuildAction = iota + + // Builds all of the modules and their dependencies of a list of specified directories. All specified + // directories are relative to the root directory of the source tree. + BUILD_MODULES_IN_DIRECTORIES +) + +// checkTopDir validates that the current directory is at the root directory of the source tree. +func checkTopDir(ctx Context) { + if _, err := os.Stat(srcDirFileCheck); err != nil { + if os.IsNotExist(err) { + ctx.Fatalf("Current working directory must be the source tree. %q not found.", srcDirFileCheck) + } + ctx.Fatalln("Error verifying tree state:", err) + } +} + func NewConfig(ctx Context, args ...string) Config { ret := &configImpl{ environ: OsEnvironment(), @@ -154,12 +176,7 @@ func NewConfig(ctx Context, args ...string) Config { ret.environ.Set("TMPDIR", absPath(ctx, ret.TempDir())) // Precondition: the current directory is the top of the source tree - if _, err := os.Stat(srcDirFileCheck); err != nil { - if os.IsNotExist(err) { - log.Fatalf("Current working directory must be the source tree. %q not found", srcDirFileCheck) - } - log.Fatalln("Error verifying tree state:", err) - } + checkTopDir(ctx) if srcDir := absPath(ctx, "."); strings.ContainsRune(srcDir, ' ') { log.Println("You are building in a directory whose absolute path contains a space character:") @@ -229,6 +246,203 @@ func NewConfig(ctx Context, args ...string) Config { return Config{ret} } +// NewBuildActionConfig returns a build configuration based on the build action. The arguments are +// processed based on the build action and extracts any arguments that belongs to the build action. +func NewBuildActionConfig(action BuildAction, dir string, buildDependencies bool, ctx Context, args ...string) Config { + return NewConfig(ctx, getConfigArgs(action, dir, buildDependencies, ctx, args)...) +} + +// getConfigArgs processes the command arguments based on the build action and creates a set of new +// arguments to be accepted by Config. +func getConfigArgs(action BuildAction, dir string, buildDependencies bool, ctx Context, args []string) []string { + // The next block of code verifies that the current directory is the root directory of the source + // tree. It then finds the relative path of dir based on the root directory of the source tree + // and verify that dir is inside of the source tree. + checkTopDir(ctx) + topDir, err := os.Getwd() + if err != nil { + ctx.Fatalf("Error retrieving top directory: %v", err) + } + dir, err = filepath.Abs(dir) + if err != nil { + ctx.Fatalf("Unable to find absolute path %s: %v", dir, err) + } + relDir, err := filepath.Rel(topDir, dir) + if err != nil { + ctx.Fatalf("Unable to find relative path %s of %s: %v", relDir, topDir, err) + } + // If there are ".." in the path, it's not in the source tree. + if strings.Contains(relDir, "..") { + ctx.Fatalf("Directory %s is not under the source tree %s", dir, topDir) + } + + configArgs := args[:] + + // If the arguments contains GET-INSTALL-PATH, change the target name prefix from MODULES-IN- to + // GET-INSTALL-PATH-IN- to extract the installation path instead of building the modules. + targetNamePrefix := "MODULES-IN-" + if inList("GET-INSTALL-PATH", configArgs) { + targetNamePrefix = "GET-INSTALL-PATH-IN-" + configArgs = removeFromList("GET-INSTALL-PATH", configArgs) + } + + var buildFiles []string + var targets []string + + switch action { + case BUILD_MODULES_IN_A_DIRECTORY: + // If dir is the root source tree, all the modules are built of the source tree are built so + // no need to find the build file. + if topDir == dir { + break + } + // Find the build file from the directory where the build action was triggered by traversing up + // the source tree. If a blank build filename is returned, simply use the directory where the build + // action was invoked. + buildFile := findBuildFile(ctx, relDir) + if buildFile == "" { + buildFile = filepath.Join(relDir, "Android.mk") + } + buildFiles = []string{buildFile} + targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)} + case BUILD_MODULES_IN_DIRECTORIES: + newConfigArgs, dirs := splitArgs(configArgs) + configArgs = newConfigArgs + targets, buildFiles = getTargetsFromDirs(ctx, relDir, dirs, targetNamePrefix) + } + + // This is to support building modules without building their dependencies. Soon, this will be + // deprecated. + if !buildDependencies && len(buildFiles) > 0 { + if err := os.Setenv("ONE_SHOT_MAKEFILE", strings.Join(buildFiles, " ")); err != nil { + ctx.Fatalf("Unable to set ONE_SHOT_MAKEFILE environment variable: %v", err) + } + } + + // Tidy only override all other specified targets. + tidyOnly := os.Getenv("WITH_TIDY_ONLY") + if tidyOnly == "true" || tidyOnly == "1" { + configArgs = append(configArgs, "tidy_only") + } else { + configArgs = append(configArgs, targets...) + } + + return configArgs +} + +// convertToTarget replaces "/" to "-" in dir and pre-append the targetNamePrefix to the target name. +func convertToTarget(dir string, targetNamePrefix string) string { + return targetNamePrefix + strings.ReplaceAll(dir, "/", "-") +} + +// findBuildFile finds a build file (makefile or blueprint file) by looking at dir first. If not +// found, go up one level and repeat again until one is found and the path of that build file +// relative to the root directory of the source tree is returned. The returned filename of build +// file is "Android.mk". If one was not found, a blank string is returned. +func findBuildFile(ctx Context, dir string) string { + // If the string is empty, assume it is top directory of the source tree. + if dir == "" { + return "" + } + + for ; dir != "."; dir = filepath.Dir(dir) { + for _, buildFile := range []string{"Android.bp", "Android.mk"} { + _, err := os.Stat(filepath.Join(dir, buildFile)) + if err == nil { + // Returning the filename Android.mk as it might be used for ONE_SHOT_MAKEFILE variable. + return filepath.Join(dir, "Android.mk") + } + if !os.IsNotExist(err) { + ctx.Fatalf("Error retrieving the build file stats: %v", err) + } + } + } + + return "" +} + +// splitArgs iterates over the arguments list and splits into two lists: arguments and directories. +func splitArgs(args []string) (newArgs []string, dirs []string) { + specialArgs := map[string]bool{ + "showcommands": true, + "snod": true, + "dist": true, + "checkbuild": true, + } + + newArgs = []string{} + dirs = []string{} + + for _, arg := range args { + // It's a dash argument if it starts with "-" or it's a key=value pair, it's not a directory. + if strings.IndexRune(arg, '-') == 0 || strings.IndexRune(arg, '=') != -1 { + newArgs = append(newArgs, arg) + continue + } + + if _, ok := specialArgs[arg]; ok { + newArgs = append(newArgs, arg) + continue + } + + dirs = append(dirs, arg) + } + + return newArgs, dirs +} + +// getTargetsFromDirs iterates over the dirs list and creates a list of targets to build. If a +// directory from the dirs list does not exist, a fatal error is raised. relDir is related to the +// source root tree where the build action command was invoked. Each directory is validated if the +// build file can be found and follows the format "dir1:target1,target2,...". Target is optional. +func getTargetsFromDirs(ctx Context, relDir string, dirs []string, targetNamePrefix string) (targets []string, buildFiles []string) { + for _, dir := range dirs { + // The directory may have specified specific modules to build. ":" is the separator to separate + // the directory and the list of modules. + s := strings.Split(dir, ":") + l := len(s) + if l > 2 { // more than one ":" was specified. + ctx.Fatalf("%s not in proper directory:target1,target2,... format (\":\" was specified more than once)", dir) + } + + dir = filepath.Join(relDir, s[0]) + if _, err := os.Stat(dir); err != nil { + ctx.Fatalf("couldn't find directory %s", dir) + } + + // Verify that if there are any targets specified after ":". Each target is separated by ",". + var newTargets []string + if l == 2 && s[1] != "" { + newTargets = strings.Split(s[1], ",") + if inList("", newTargets) { + ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir) + } + } + + buildFile := findBuildFile(ctx, dir) + if buildFile == "" { + ctx.Fatalf("Build file not found for %s directory", dir) + } + buildFileDir := filepath.Dir(buildFile) + + // If there are specified targets, find the build file in the directory. If dir does not + // contain the build file, bail out as it is required for one shot build. If there are no + // target specified, build all the modules in dir (or the closest one in the dir path). + if len(newTargets) > 0 { + if buildFileDir != dir { + ctx.Fatalf("Couldn't locate a build file from %s directory", dir) + } + } else { + newTargets = []string{convertToTarget(buildFileDir, targetNamePrefix)} + } + + buildFiles = append(buildFiles, buildFile) + targets = append(targets, newTargets...) + } + + return targets, buildFiles +} + func (c *configImpl) parseArgs(ctx Context, args []string) { for i := 0; i < len(args); i++ { arg := strings.TrimSpace(args[i]) diff --git a/ui/build/config_test.go b/ui/build/config_test.go index 242e3afb0..1ef545666 100644 --- a/ui/build/config_test.go +++ b/ui/build/config_test.go @@ -17,19 +17,22 @@ package build import ( "bytes" "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" "reflect" "strings" "testing" "android/soong/ui/logger" - "android/soong/ui/terminal" ) func testContext() Context { return Context{&ContextImpl{ Context: context.Background(), Logger: logger.New(&bytes.Buffer{}), - Writer: terminal.NewWriter(terminal.NewCustomStdio(&bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{})), + Writer: &bytes.Buffer{}, }} } @@ -173,3 +176,877 @@ func TestConfigParseArgsVars(t *testing.T) { }) } } + +func TestConfigCheckTopDir(t *testing.T) { + ctx := testContext() + buildRootDir := filepath.Dir(srcDirFileCheck) + expectedErrStr := fmt.Sprintf("Current working directory must be the source tree. %q not found.", srcDirFileCheck) + + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // ********* Action ********* + // If set to true, the build root file is created. + rootBuildFile bool + + // The current path where Soong is being executed. + path string + + // ********* Validation ********* + // Expecting error and validate the error string against expectedErrStr. + wantErr bool + }{{ + description: "current directory is the root source tree", + rootBuildFile: true, + path: ".", + wantErr: false, + }, { + description: "one level deep in the source tree", + rootBuildFile: true, + path: "1", + wantErr: true, + }, { + description: "very deep in the source tree", + rootBuildFile: true, + path: "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7", + wantErr: true, + }, { + description: "outside of source tree", + rootBuildFile: false, + path: "1/2/3/4/5", + wantErr: true, + }} + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + defer logger.Recover(func(err error) { + if !tt.wantErr { + t.Fatalf("Got unexpected error: %v", err) + } + if expectedErrStr != err.Error() { + t.Fatalf("expected %s, got %s", expectedErrStr, err.Error()) + } + }) + + // Create the root source tree. + rootDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rootDir) + + // Create the build root file. This is to test if topDir returns an error if the build root + // file does not exist. + if tt.rootBuildFile { + dir := filepath.Join(rootDir, buildRootDir) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Errorf("failed to create %s directory: %v", dir, err) + } + f := filepath.Join(rootDir, srcDirFileCheck) + if err := ioutil.WriteFile(f, []byte{}, 0644); err != nil { + t.Errorf("failed to create file %s: %v", f, err) + } + } + + // Next block of code is to set the current directory. + dir := rootDir + if tt.path != "" { + dir = filepath.Join(dir, tt.path) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Errorf("failed to create %s directory: %v", dir, err) + } + } + curDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get the current directory: %v", err) + } + defer func() { os.Chdir(curDir) }() + + if err := os.Chdir(dir); err != nil { + t.Fatalf("failed to change directory to %s: %v", dir, err) + } + + checkTopDir(ctx) + }) + } +} + +func TestConfigConvertToTarget(t *testing.T) { + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // ********* Action ********* + // The current directory where Soong is being executed. + dir string + + // The current prefix string to be pre-appended to the target. + prefix string + + // ********* Validation ********* + // The expected target to be invoked in ninja. + expectedTarget string + }{{ + description: "one level directory in source tree", + dir: "test1", + prefix: "MODULES-IN-", + expectedTarget: "MODULES-IN-test1", + }, { + description: "multiple level directories in source tree", + dir: "test1/test2/test3/test4", + prefix: "GET-INSTALL-PATH-IN-", + expectedTarget: "GET-INSTALL-PATH-IN-test1-test2-test3-test4", + }} + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + target := convertToTarget(tt.dir, tt.prefix) + if target != tt.expectedTarget { + t.Errorf("expected %s, got %s for target", tt.expectedTarget, target) + } + }) + } +} + +func setTop(t *testing.T, dir string) func() { + curDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get current directory: %v", err) + } + if err := os.Chdir(dir); err != nil { + t.Fatalf("failed to change directory to top dir %s: %v", dir, err) + } + return func() { os.Chdir(curDir) } +} + +func createBuildFiles(t *testing.T, topDir string, buildFiles []string) { + for _, buildFile := range buildFiles { + buildFile = filepath.Join(topDir, buildFile) + if err := ioutil.WriteFile(buildFile, []byte{}, 0644); err != nil { + t.Errorf("failed to create file %s: %v", buildFile, err) + } + } +} + +func createDirectories(t *testing.T, topDir string, dirs []string) { + for _, dir := range dirs { + dir = filepath.Join(topDir, dir) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Errorf("failed to create %s directory: %v", dir, err) + } + } +} + +func TestConfigGetTargets(t *testing.T) { + ctx := testContext() + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // Directories that exist in the source tree. + dirsInTrees []string + + // Build files that exists in the source tree. + buildFiles []string + + // ********* Action ********* + // Directories passed in to soong_ui. + dirs []string + + // Current directory that the user executed the build action command. + curDir string + + // ********* Validation ********* + // Expected targets from the function. + expectedTargets []string + + // Expected build from the build system. + expectedBuildFiles []string + + // Expecting error from running test case. + errStr string + }{{ + description: "one target dir specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3"}, + curDir: "0", + expectedTargets: []string{"MODULES-IN-0-1-2-3"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, build file does not exist", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{}, + dirs: []string{"1/2/3"}, + curDir: "0", + errStr: "Build file not found for 0/1/2/3 directory", + }, { + description: "one target dir specified, invalid targets specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{}, + dirs: []string{"1/2/3:t1:t2"}, + curDir: "0", + errStr: "1/2/3:t1:t2 not in proper directory:target1,target2,... format (\":\" was specified more than once)", + }, { + description: "one target dir specified, no targets specified but has colon", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:"}, + curDir: "0", + expectedTargets: []string{"MODULES-IN-0-1-2-3"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, two targets specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:t1,t2"}, + curDir: "0", + expectedTargets: []string{"t1", "t2"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, no targets and has a comma", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:,"}, + curDir: "0", + errStr: "0/1/2/3 not in proper directory:target1,target2,... format", + }, { + description: "one target dir specified, improper targets defined", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:,t1"}, + curDir: "0", + errStr: "0/1/2/3 not in proper directory:target1,target2,... format", + }, { + description: "one target dir specified, blank target", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:t1,"}, + curDir: "0", + errStr: "0/1/2/3 not in proper directory:target1,target2,... format", + }, { + description: "one target dir specified, many targets specified", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.bp"}, + dirs: []string{"1/2/3:t1,t2,t3,t4,t5,t6,t7,t8,t9,t10"}, + curDir: "0", + expectedTargets: []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk"}, + }, { + description: "one target dir specified, one target specified, build file does not exist", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{}, + dirs: []string{"1/2/3:t1"}, + curDir: "0", + errStr: "Build file not found for 0/1/2/3 directory", + }, { + description: "one target dir specified, one target specified, build file not in target dir", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/Android.mk"}, + dirs: []string{"1/2/3:t1"}, + curDir: "0", + errStr: "Couldn't locate a build file from 0/1/2/3 directory", + }, { + description: "one target dir specified, build file not in target dir", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/Android.mk"}, + dirs: []string{"1/2/3"}, + curDir: "0", + expectedTargets: []string{"MODULES-IN-0-1-2"}, + expectedBuildFiles: []string{"0/1/2/Android.mk"}, + }, { + description: "multiple targets dir specified, targets specified", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{"1/2/3:t1,t2", "3/4:t3,t4,t5"}, + curDir: "0", + expectedTargets: []string{"t1", "t2", "t3", "t4", "t5"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"}, + }, { + description: "multiple targets dir specified, one directory has targets specified", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{"1/2/3:t1,t2", "3/4"}, + curDir: "0", + expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"}, + }, { + description: "two dirs specified, only one dir exist", + dirsInTrees: []string{"0/1/2/3"}, + buildFiles: []string{"0/1/2/3/Android.mk"}, + dirs: []string{"1/2/3:t1", "3/4"}, + curDir: "0", + errStr: "couldn't find directory 0/3/4", + }, { + description: "multiple targets dirs specified at root source tree", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{"0/1/2/3:t1,t2", "0/3/4"}, + curDir: ".", + expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, + expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"}, + }, { + description: "no directories specified", + dirsInTrees: []string{"0/1/2/3", "0/3/4"}, + buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, + dirs: []string{}, + curDir: ".", + }} + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + defer logger.Recover(func(err error) { + if tt.errStr == "" { + t.Fatalf("Got unexpected error: %v", err) + } + if tt.errStr != err.Error() { + t.Errorf("expected %s, got %s", tt.errStr, err.Error()) + } + }) + + // Create the root source tree. + topDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(topDir) + + createDirectories(t, topDir, tt.dirsInTrees) + createBuildFiles(t, topDir, tt.buildFiles) + r := setTop(t, topDir) + defer r() + + targets, buildFiles := getTargetsFromDirs(ctx, tt.curDir, tt.dirs, "MODULES-IN-") + if !reflect.DeepEqual(targets, tt.expectedTargets) { + t.Errorf("expected %v, got %v for targets", tt.expectedTargets, targets) + } + if !reflect.DeepEqual(buildFiles, tt.expectedBuildFiles) { + t.Errorf("expected %v, got %v for build files", tt.expectedBuildFiles, buildFiles) + } + + // If the execution reached here and there was an expected error code, the unit test case failed. + if tt.errStr != "" { + t.Errorf("expecting error %s", tt.errStr) + } + }) + } +} + +func TestConfigFindBuildFile(t *testing.T) { + ctx := testContext() + + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // Array of build files to create in dir. + buildFiles []string + + // ********* Action ********* + // Directory to create, also the base directory is where findBuildFile is invoked. + dir string + + // ********* Validation ********* + // Expected build file path to find. + expectedBuildFile string + }{{ + description: "build file exists at leaf directory", + buildFiles: []string{"1/2/3/Android.bp"}, + dir: "1/2/3", + expectedBuildFile: "1/2/3/Android.mk", + }, { + description: "build file exists in all directory paths", + buildFiles: []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"}, + dir: "1/2/3", + expectedBuildFile: "1/2/3/Android.mk", + }, { + description: "build file does not exist in all directory paths", + buildFiles: []string{}, + dir: "1/2/3", + expectedBuildFile: "", + }, { + description: "build file exists only at top directory", + buildFiles: []string{"Android.bp"}, + dir: "1/2/3", + expectedBuildFile: "", + }, { + description: "build file exist in a subdirectory", + buildFiles: []string{"1/2/Android.bp"}, + dir: "1/2/3", + expectedBuildFile: "1/2/Android.mk", + }, { + description: "build file exists in a subdirectory", + buildFiles: []string{"1/Android.mk"}, + dir: "1/2/3", + expectedBuildFile: "1/Android.mk", + }, { + description: "top directory", + buildFiles: []string{"Android.bp"}, + dir: ".", + expectedBuildFile: "", + }} + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + defer logger.Recover(func(err error) { + t.Fatalf("Got unexpected error: %v", err) + }) + + topDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(topDir) + + if tt.dir != "" { + createDirectories(t, topDir, []string{tt.dir}) + } + + createBuildFiles(t, topDir, tt.buildFiles) + + curDir, err := os.Getwd() + if err != nil { + t.Fatalf("Could not get working directory: %v", err) + } + defer func() { os.Chdir(curDir) }() + if err := os.Chdir(topDir); err != nil { + t.Fatalf("Could not change top dir to %s: %v", topDir, err) + } + + buildFile := findBuildFile(ctx, tt.dir) + if buildFile != tt.expectedBuildFile { + t.Errorf("expected %q, got %q for build file", tt.expectedBuildFile, buildFile) + } + }) + } +} + +func TestConfigSplitArgs(t *testing.T) { + tests := []struct { + // ********* Setup ********* + // Test description. + description string + + // ********* Action ********* + // Arguments passed in to soong_ui. + args []string + + // ********* Validation ********* + // Expected newArgs list after extracting the directories. + expectedNewArgs []string + + // Expected directories + expectedDirs []string + }{{ + description: "flags but no directories specified", + args: []string{"showcommands", "-j", "-k"}, + expectedNewArgs: []string{"showcommands", "-j", "-k"}, + expectedDirs: []string{}, + }, { + description: "flags and one directory specified", + args: []string{"snod", "-j", "dir:target1,target2"}, + expectedNewArgs: []string{"snod", "-j"}, + expectedDirs: []string{"dir:target1,target2"}, + }, { + description: "flags and directories specified", + args: []string{"dist", "-k", "dir1", "dir2:target1,target2"}, + expectedNewArgs: []string{"dist", "-k"}, + expectedDirs: []string{"dir1", "dir2:target1,target2"}, + }, { + description: "only directories specified", + args: []string{"dir1", "dir2", "dir3:target1,target2"}, + expectedNewArgs: []string{}, + expectedDirs: []string{"dir1", "dir2", "dir3:target1,target2"}, + }} + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + args, dirs := splitArgs(tt.args) + if !reflect.DeepEqual(tt.expectedNewArgs, args) { + t.Errorf("expected %v, got %v for arguments", tt.expectedNewArgs, args) + } + if !reflect.DeepEqual(tt.expectedDirs, dirs) { + t.Errorf("expected %v, got %v for directories", tt.expectedDirs, dirs) + } + }) + } +} + +type envVar struct { + name string + value string +} + +type buildActionTestCase struct { + // ********* Setup ********* + // Test description. + description string + + // Directories that exist in the source tree. + dirsInTrees []string + + // Build files that exists in the source tree. + buildFiles []string + + // ********* Action ********* + // Arguments passed in to soong_ui. + args []string + + // Directory where the build action was invoked. + curDir string + + // WITH_TIDY_ONLY environment variable specified. + tidyOnly string + + // ********* Validation ********* + // Expected arguments to be in Config instance. + expectedArgs []string + + // Expected environment variables to be set. + expectedEnvVars []envVar +} + +func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction, buildDependencies bool) { + ctx := testContext() + + // Environment variables to set it to blank on every test case run. + resetEnvVars := []string{ + "ONE_SHOT_MAKEFILE", + "WITH_TIDY_ONLY", + } + + for _, name := range resetEnvVars { + if err := os.Unsetenv(name); err != nil { + t.Fatalf("failed to unset environment variable %s: %v", name, err) + } + } + if tt.tidyOnly != "" { + if err := os.Setenv("WITH_TIDY_ONLY", tt.tidyOnly); err != nil { + t.Errorf("failed to set WITH_TIDY_ONLY to %s: %v", tt.tidyOnly, err) + } + } + + // Create the root source tree. + topDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(topDir) + + createDirectories(t, topDir, tt.dirsInTrees) + createBuildFiles(t, topDir, tt.buildFiles) + + r := setTop(t, topDir) + defer r() + + // The next block is to create the root build file. + rootBuildFileDir := filepath.Dir(srcDirFileCheck) + if err := os.MkdirAll(rootBuildFileDir, 0755); err != nil { + t.Fatalf("Failed to create %s directory: %v", rootBuildFileDir, err) + } + + if err := ioutil.WriteFile(srcDirFileCheck, []byte{}, 0644); err != nil { + t.Fatalf("failed to create %s file: %v", srcDirFileCheck, err) + } + + args := getConfigArgs(action, tt.curDir, buildDependencies, ctx, tt.args) + if !reflect.DeepEqual(tt.expectedArgs, args) { + t.Fatalf("expected %v, got %v for config arguments", tt.expectedArgs, args) + } + + for _, env := range tt.expectedEnvVars { + if val := os.Getenv(env.name); val != env.value { + t.Errorf("expecting %s, got %s for environment variable %s", env.value, val, env.name) + } + } +} + +// TODO: Remove this test case once mm shell build command has been deprecated. +func TestGetConfigArgsBuildModulesInDirecotoryNoDeps(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/2/Android.mk"}, + args: []string{"-j", "-k", "showcommands", "fake-module"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"-j", "-k", "showcommands", "fake-module", "MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/Android.mk"}}, + }, { + description: "makefile in parent directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/Android.mk"}}, + }, { + description: "build file not found", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/Android.mk"}}, + }, { + description: "build action executed at root directory", + dirsInTrees: []string{}, + buildFiles: []string{}, + args: []string{}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "GET-INSTALL-PATH specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/Android.mk"}}, + }, { + description: "tidy only environment variable specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "true", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/Android.mk"}}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIR without their dependencies, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, false) + }) + } +} + +func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/2/Android.mk"}, + args: []string{"fake-module"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{}, + }, { + description: "build file in parent directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1"}, + expectedEnvVars: []envVar{}, + }, + { + description: "build file in parent directory, multiple module names passed in", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"fake-module1", "fake-module2", "fake-module3"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"fake-module1", "fake-module2", "fake-module3", "MODULES-IN-0-1"}, + expectedEnvVars: []envVar{}, + }, { + description: "build file in 2nd level parent directory", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/Android.bp"}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0"}, + expectedEnvVars: []envVar{}, + }, { + description: "build action executed at root directory", + dirsInTrees: []string{}, + buildFiles: []string{}, + args: []string{}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{}, + expectedEnvVars: []envVar{}, + }, { + description: "build file not found - no error is expected to return", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{}, + args: []string{}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2"}, + expectedEnvVars: []envVar{}, + }, { + description: "GET-INSTALL-PATH specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"}, + expectedEnvVars: []envVar{}, + }, { + description: "tidy only environment variable specified,", + dirsInTrees: []string{"0/1/2"}, + buildFiles: []string{"0/1/Android.mk"}, + args: []string{"GET-INSTALL-PATH"}, + curDir: "0/1/2", + tidyOnly: "true", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{}, + }, { + description: "normal execution in root directory with args", + dirsInTrees: []string{}, + buildFiles: []string{}, + args: []string{"-j", "-k", "fake_module"}, + curDir: "", + tidyOnly: "", + expectedArgs: []string{"-j", "-k", "fake_module"}, + expectedEnvVars: []envVar{}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, true) + }) + } +} + +// TODO: Remove this test case once mmm shell build command has been deprecated. +func TestGetConfigArgsBuildModulesInDirectoriesNoDeps(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"3.1/:t1,t2", "3.2/:t3,t4", "3.3/:t5,t6"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"t1", "t2", "t3", "t4", "t5", "t6"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }, { + description: "GET-INSTALL-PATH specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "t6"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }, { + description: "tidy only environment variable specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"}, + curDir: "0/1/2", + tidyOnly: "1", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }, { + description: "normal execution from top dir directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"0/1/2/3.1", "0/1/2/3.2/:t3,t4", "0/1/2/3.3/:t5,t6"}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "t3", "t4", "t5", "t6"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIRS_NO_DEPS, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, false) + }) + } +} + +func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) { + tests := []buildActionTestCase{{ + description: "normal execution in a directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"3.1/", "3.2/", "3.3/"}, + curDir: "0/1/2", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "GET-INSTALL-PATH specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"}, + curDir: "0/1", + tidyOnly: "", + expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "GET-INSTALL-PATH-IN-0-1"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "tidy only environment variable specified", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, + args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"}, + curDir: "0/1/2", + tidyOnly: "1", + expectedArgs: []string{"tidy_only"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }, { + description: "normal execution from top dir directory", + dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, + buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"}, + args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, + curDir: ".", + tidyOnly: "", + expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"}, + expectedEnvVars: []envVar{ + envVar{ + name: "ONE_SHOT_MAKEFILE", + value: ""}}, + }} + for _, tt := range tests { + t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) { + testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, true) + }) + } +} diff --git a/ui/build/context.go b/ui/build/context.go index 249e89822..7ff98ef78 100644 --- a/ui/build/context.go +++ b/ui/build/context.go @@ -16,12 +16,12 @@ package build import ( "context" + "io" "android/soong/ui/logger" "android/soong/ui/metrics" "android/soong/ui/metrics/metrics_proto" "android/soong/ui/status" - "android/soong/ui/terminal" "android/soong/ui/tracer" ) @@ -35,7 +35,7 @@ type ContextImpl struct { Metrics *metrics.Metrics - Writer terminal.Writer + Writer io.Writer Status *status.Status Thread tracer.Thread diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go index 4335667d1..266130f04 100644 --- a/ui/build/dumpvars.go +++ b/ui/build/dumpvars.go @@ -249,7 +249,7 @@ func runMakeProductConfig(ctx Context, config Config) { env := config.Environment() // Print the banner like make does if !env.IsEnvTrue("ANDROID_QUIET_BUILD") { - ctx.Writer.Print(Banner(make_vars)) + fmt.Fprintln(ctx.Writer, Banner(make_vars)) } // Populate the environment diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go index d92649f44..c9946e297 100644 --- a/ui/build/paths/config.go +++ b/ui/build/paths/config.go @@ -79,7 +79,6 @@ var Configuration = map[string]PathConfig{ // We need bzip2 here even though we provide a bzip2 binary because // GNU tar seems to avoid calling ours. "bzip2": Allowed, - "date": Allowed, "dd": Allowed, "diff": Allowed, "egrep": Allowed, @@ -131,6 +130,7 @@ var Configuration = map[string]PathConfig{ "cp": LinuxOnlyPrebuilt, "comm": LinuxOnlyPrebuilt, "cut": LinuxOnlyPrebuilt, + "date": LinuxOnlyPrebuilt, "dirname": LinuxOnlyPrebuilt, "du": LinuxOnlyPrebuilt, "echo": LinuxOnlyPrebuilt, diff --git a/ui/build/util.go b/ui/build/util.go index 0676a860d..75e6753b7 100644 --- a/ui/build/util.go +++ b/ui/build/util.go @@ -44,6 +44,17 @@ func inList(s string, list []string) bool { return indexList(s, list) != -1 } +// removeFromlist removes all occurrences of the string in list. +func removeFromList(s string, list []string) []string { + filteredList := make([]string, 0, len(list)) + for _, ls := range list { + if s != ls { + filteredList = append(filteredList, ls) + } + } + return filteredList +} + // ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger. func ensureDirectoriesExist(ctx Context, dirs ...string) { for _, dir := range dirs { diff --git a/ui/status/log.go b/ui/status/log.go index 921aa4401..7badac73c 100644 --- a/ui/status/log.go +++ b/ui/status/log.go @@ -71,6 +71,11 @@ func (v *verboseLog) Message(level MsgLevel, message string) { fmt.Fprintf(v.w, "%s%s\n", level.Prefix(), message) } +func (v *verboseLog) Write(p []byte) (int, error) { + fmt.Fprint(v.w, string(p)) + return len(p), nil +} + type errorLog struct { w io.WriteCloser @@ -134,3 +139,8 @@ func (e *errorLog) Message(level MsgLevel, message string) { fmt.Fprintf(e.w, "error: %s\n", message) } + +func (e *errorLog) Write(p []byte) (int, error) { + fmt.Fprint(e.w, string(p)) + return len(p), nil +} diff --git a/ui/status/status.go b/ui/status/status.go index 46ec72e80..3d8cd7a2c 100644 --- a/ui/status/status.go +++ b/ui/status/status.go @@ -173,6 +173,9 @@ type StatusOutput interface { // Flush is called when your outputs should be flushed / closed. No // output is expected after this call. Flush() + + // Write lets StatusOutput implement io.Writer + Write(p []byte) (n int, err error) } // Status is the multiplexer / accumulator between ToolStatus instances (via diff --git a/ui/status/status_test.go b/ui/status/status_test.go index e62785f43..949458222 100644 --- a/ui/status/status_test.go +++ b/ui/status/status_test.go @@ -27,6 +27,11 @@ func (c *counterOutput) FinishAction(result ActionResult, counts Counts) { func (c counterOutput) Message(level MsgLevel, msg string) {} func (c counterOutput) Flush() {} +func (c counterOutput) Write(p []byte) (int, error) { + // Discard writes + return len(p), nil +} + func (c counterOutput) Expect(t *testing.T, counts Counts) { if Counts(c) == counts { return diff --git a/ui/terminal/Android.bp b/ui/terminal/Android.bp index 7104a5047..b533b0d30 100644 --- a/ui/terminal/Android.bp +++ b/ui/terminal/Android.bp @@ -17,11 +17,15 @@ bootstrap_go_package { pkgPath: "android/soong/ui/terminal", deps: ["soong-ui-status"], srcs: [ + "dumb_status.go", + "format.go", + "smart_status.go", "status.go", - "writer.go", + "stdio.go", "util.go", ], testSrcs: [ + "status_test.go", "util_test.go", ], darwin: { diff --git a/ui/terminal/dumb_status.go b/ui/terminal/dumb_status.go new file mode 100644 index 000000000..201770fac --- /dev/null +++ b/ui/terminal/dumb_status.go @@ -0,0 +1,71 @@ +// Copyright 2019 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 terminal + +import ( + "fmt" + "io" + + "android/soong/ui/status" +) + +type dumbStatusOutput struct { + writer io.Writer + formatter formatter +} + +// NewDumbStatusOutput returns a StatusOutput that represents the +// current build status similarly to Ninja's built-in terminal +// output. +func NewDumbStatusOutput(w io.Writer, formatter formatter) status.StatusOutput { + return &dumbStatusOutput{ + writer: w, + formatter: formatter, + } +} + +func (s *dumbStatusOutput) Message(level status.MsgLevel, message string) { + if level >= status.StatusLvl { + fmt.Fprintln(s.writer, s.formatter.message(level, message)) + } +} + +func (s *dumbStatusOutput) StartAction(action *status.Action, counts status.Counts) { +} + +func (s *dumbStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) { + str := result.Description + if str == "" { + str = result.Command + } + + progress := s.formatter.progress(counts) + str + + output := s.formatter.result(result) + output = string(stripAnsiEscapes([]byte(output))) + + if output != "" { + fmt.Fprint(s.writer, progress, "\n", output) + } else { + fmt.Fprintln(s.writer, progress) + } +} + +func (s *dumbStatusOutput) Flush() {} + +func (s *dumbStatusOutput) Write(p []byte) (int, error) { + fmt.Fprint(s.writer, string(p)) + return len(p), nil +} diff --git a/ui/terminal/format.go b/ui/terminal/format.go new file mode 100644 index 000000000..4205bdc22 --- /dev/null +++ b/ui/terminal/format.go @@ -0,0 +1,123 @@ +// Copyright 2019 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 terminal + +import ( + "fmt" + "strings" + "time" + + "android/soong/ui/status" +) + +type formatter struct { + format string + quiet bool + start time.Time +} + +// newFormatter returns a formatter for formatting output to +// the terminal in a format similar to Ninja. +// format takes nearly all the same options as NINJA_STATUS. +// %c is currently unsupported. +func newFormatter(format string, quiet bool) formatter { + return formatter{ + format: format, + quiet: quiet, + start: time.Now(), + } +} + +func (s formatter) message(level status.MsgLevel, message string) string { + if level >= status.ErrorLvl { + return fmt.Sprintf("FAILED: %s", message) + } else if level > status.StatusLvl { + return fmt.Sprintf("%s%s", level.Prefix(), message) + } else if level == status.StatusLvl { + return message + } + return "" +} + +func (s formatter) progress(counts status.Counts) string { + if s.format == "" { + return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions) + } + + buf := &strings.Builder{} + for i := 0; i < len(s.format); i++ { + c := s.format[i] + if c != '%' { + buf.WriteByte(c) + continue + } + + i = i + 1 + if i == len(s.format) { + buf.WriteByte(c) + break + } + + c = s.format[i] + switch c { + case '%': + buf.WriteByte(c) + case 's': + fmt.Fprintf(buf, "%d", counts.StartedActions) + case 't': + fmt.Fprintf(buf, "%d", counts.TotalActions) + case 'r': + fmt.Fprintf(buf, "%d", counts.RunningActions) + case 'u': + fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions) + case 'f': + fmt.Fprintf(buf, "%d", counts.FinishedActions) + case 'o': + fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds()) + case 'c': + // TODO: implement? + buf.WriteRune('?') + case 'p': + fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions) + case 'e': + fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds()) + default: + buf.WriteString("unknown placeholder '") + buf.WriteByte(c) + buf.WriteString("'") + } + } + return buf.String() +} + +func (s formatter) result(result status.ActionResult) string { + var ret string + if result.Error != nil { + targets := strings.Join(result.Outputs, " ") + if s.quiet || result.Command == "" { + ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output) + } else { + ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output) + } + } else if result.Output != "" { + ret = result.Output + } + + if len(ret) > 0 && ret[len(ret)-1] != '\n' { + ret += "\n" + } + + return ret +} diff --git a/ui/terminal/smart_status.go b/ui/terminal/smart_status.go new file mode 100644 index 000000000..8fa9effcc --- /dev/null +++ b/ui/terminal/smart_status.go @@ -0,0 +1,158 @@ +// Copyright 2019 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 terminal + +import ( + "fmt" + "io" + "strings" + "sync" + + "android/soong/ui/status" +) + +type smartStatusOutput struct { + writer io.Writer + formatter formatter + + lock sync.Mutex + + haveBlankLine bool +} + +// NewSmartStatusOutput returns a StatusOutput that represents the +// current build status similarly to Ninja's built-in terminal +// output. +func NewSmartStatusOutput(w io.Writer, formatter formatter) status.StatusOutput { + return &smartStatusOutput{ + writer: w, + formatter: formatter, + + haveBlankLine: true, + } +} + +func (s *smartStatusOutput) Message(level status.MsgLevel, message string) { + if level < status.StatusLvl { + return + } + + str := s.formatter.message(level, message) + + s.lock.Lock() + defer s.lock.Unlock() + + if level > status.StatusLvl { + s.print(str) + } else { + s.statusLine(str) + } +} + +func (s *smartStatusOutput) StartAction(action *status.Action, counts status.Counts) { + str := action.Description + if str == "" { + str = action.Command + } + + progress := s.formatter.progress(counts) + + s.lock.Lock() + defer s.lock.Unlock() + + s.statusLine(progress + str) +} + +func (s *smartStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) { + str := result.Description + if str == "" { + str = result.Command + } + + progress := s.formatter.progress(counts) + str + + output := s.formatter.result(result) + + s.lock.Lock() + defer s.lock.Unlock() + + if output != "" { + s.statusLine(progress) + s.requestLine() + s.print(output) + } else { + s.statusLine(progress) + } +} + +func (s *smartStatusOutput) Flush() { + s.lock.Lock() + defer s.lock.Unlock() + + s.requestLine() +} + +func (s *smartStatusOutput) Write(p []byte) (int, error) { + s.lock.Lock() + defer s.lock.Unlock() + s.print(string(p)) + return len(p), nil +} + +func (s *smartStatusOutput) requestLine() { + if !s.haveBlankLine { + fmt.Fprintln(s.writer) + s.haveBlankLine = true + } +} + +func (s *smartStatusOutput) print(str string) { + if !s.haveBlankLine { + fmt.Fprint(s.writer, "\r", "\x1b[K") + s.haveBlankLine = true + } + fmt.Fprint(s.writer, str) + if len(str) == 0 || str[len(str)-1] != '\n' { + fmt.Fprint(s.writer, "\n") + } +} + +func (s *smartStatusOutput) statusLine(str string) { + idx := strings.IndexRune(str, '\n') + if idx != -1 { + str = str[0:idx] + } + + // Limit line width to the terminal width, otherwise we'll wrap onto + // another line and we won't delete the previous line. + // + // Run this on every line in case the window has been resized while + // we're printing. This could be optimized to only re-run when we get + // SIGWINCH if it ever becomes too time consuming. + if max, ok := termWidth(s.writer); ok { + if len(str) > max { + // TODO: Just do a max. Ninja elides the middle, but that's + // more complicated and these lines aren't that important. + str = str[:max] + } + } + + // Move to the beginning on the line, turn on bold, print the output, + // turn off bold, then clear the rest of the line. + start := "\r\x1b[1m" + end := "\x1b[0m\x1b[K" + fmt.Fprint(s.writer, start, str, end) + s.haveBlankLine = false +} diff --git a/ui/terminal/status.go b/ui/terminal/status.go index 2445c5b2c..69a2a0929 100644 --- a/ui/terminal/status.go +++ b/ui/terminal/status.go @@ -15,131 +15,23 @@ package terminal import ( - "fmt" - "strings" - "time" + "io" "android/soong/ui/status" ) -type statusOutput struct { - writer Writer - format string - - start time.Time - quiet bool -} - // NewStatusOutput returns a StatusOutput that represents the // current build status similarly to Ninja's built-in terminal // output. // // statusFormat takes nearly all the same options as NINJA_STATUS. // %c is currently unsupported. -func NewStatusOutput(w Writer, statusFormat string, quietBuild bool) status.StatusOutput { - return &statusOutput{ - writer: w, - format: statusFormat, - - start: time.Now(), - quiet: quietBuild, - } -} - -func (s *statusOutput) Message(level status.MsgLevel, message string) { - if level >= status.ErrorLvl { - s.writer.Print(fmt.Sprintf("FAILED: %s", message)) - } else if level > status.StatusLvl { - s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message)) - } else if level == status.StatusLvl { - s.writer.StatusLine(message) - } -} - -func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) { - if !s.writer.isSmartTerminal() { - return - } - - str := action.Description - if str == "" { - str = action.Command - } +func NewStatusOutput(w io.Writer, statusFormat string, quietBuild bool) status.StatusOutput { + formatter := newFormatter(statusFormat, quietBuild) - s.writer.StatusLine(s.progress(counts) + str) -} - -func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) { - str := result.Description - if str == "" { - str = result.Command - } - - progress := s.progress(counts) + str - - if result.Error != nil { - targets := strings.Join(result.Outputs, " ") - if s.quiet || result.Command == "" { - s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)) - } else { - s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)) - } - } else if result.Output != "" { - s.writer.StatusAndMessage(progress, result.Output) + if isSmartTerminal(w) { + return NewSmartStatusOutput(w, formatter) } else { - s.writer.StatusLine(progress) - } -} - -func (s *statusOutput) Flush() {} - -func (s *statusOutput) progress(counts status.Counts) string { - if s.format == "" { - return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions) - } - - buf := &strings.Builder{} - for i := 0; i < len(s.format); i++ { - c := s.format[i] - if c != '%' { - buf.WriteByte(c) - continue - } - - i = i + 1 - if i == len(s.format) { - buf.WriteByte(c) - break - } - - c = s.format[i] - switch c { - case '%': - buf.WriteByte(c) - case 's': - fmt.Fprintf(buf, "%d", counts.StartedActions) - case 't': - fmt.Fprintf(buf, "%d", counts.TotalActions) - case 'r': - fmt.Fprintf(buf, "%d", counts.RunningActions) - case 'u': - fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions) - case 'f': - fmt.Fprintf(buf, "%d", counts.FinishedActions) - case 'o': - fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds()) - case 'c': - // TODO: implement? - buf.WriteRune('?') - case 'p': - fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions) - case 'e': - fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds()) - default: - buf.WriteString("unknown placeholder '") - buf.WriteByte(c) - buf.WriteString("'") - } + return NewDumbStatusOutput(w, formatter) } - return buf.String() } diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go new file mode 100644 index 000000000..a87a7f07d --- /dev/null +++ b/ui/terminal/status_test.go @@ -0,0 +1,272 @@ +// Copyright 2018 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 terminal + +import ( + "bytes" + "fmt" + "testing" + + "android/soong/ui/status" +) + +func TestStatusOutput(t *testing.T) { + tests := []struct { + name string + calls func(stat status.StatusOutput) + smart string + dumb string + }{ + { + name: "two actions", + calls: twoActions, + smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n", + dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n", + }, + { + name: "two parallel actions", + calls: twoParallelActions, + smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n", + dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n", + }, + { + name: "action with output", + calls: actionsWithOutput, + smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\noutput1\noutput2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n", + dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n", + }, + { + name: "action with output without newline", + calls: actionsWithOutputWithoutNewline, + smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\noutput1\noutput2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n", + dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n", + }, + { + name: "action with error", + calls: actionsWithError, + smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n", + dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n", + }, + { + name: "action with empty description", + calls: actionWithEmptyDescription, + smart: "\r\x1b[1m[ 0% 0/1] command1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] command1\x1b[0m\x1b[K\n", + dumb: "[100% 1/1] command1\n", + }, + { + name: "messages", + calls: actionsWithMessages, + smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1mstatus\x1b[0m\x1b[K\r\x1b[Kprint\nFAILED: error\n\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n", + dumb: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n", + }, + { + name: "action with long description", + calls: actionWithLongDescription, + smart: "\r\x1b[1m[ 0% 0/2] action with very long descrip\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action with very long descrip\x1b[0m\x1b[K\n", + dumb: "[ 50% 1/2] action with very long description to test eliding\n", + }, + { + name: "action with output with ansi codes", + calls: actionWithOuptutWithAnsiCodes, + smart: "\r\x1b[1m[ 0% 0/1] action1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] action1\x1b[0m\x1b[K\n\x1b[31mcolor\x1b[0m\n", + dumb: "[100% 1/1] action1\ncolor\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Run("smart", func(t *testing.T) { + smart := &fakeSmartTerminal{termWidth: 40} + stat := NewStatusOutput(smart, "", false) + tt.calls(stat) + stat.Flush() + + if g, w := smart.String(), tt.smart; g != w { + t.Errorf("want:\n%q\ngot:\n%q", w, g) + } + }) + + t.Run("dumb", func(t *testing.T) { + dumb := &bytes.Buffer{} + stat := NewStatusOutput(dumb, "", false) + tt.calls(stat) + stat.Flush() + + if g, w := dumb.String(), tt.dumb; g != w { + t.Errorf("want:\n%q\ngot:\n%q", w, g) + } + }) + }) + } +} + +type runner struct { + counts status.Counts + stat status.StatusOutput +} + +func newRunner(stat status.StatusOutput, totalActions int) *runner { + return &runner{ + counts: status.Counts{TotalActions: totalActions}, + stat: stat, + } +} + +func (r *runner) startAction(action *status.Action) { + r.counts.StartedActions++ + r.counts.RunningActions++ + r.stat.StartAction(action, r.counts) +} + +func (r *runner) finishAction(result status.ActionResult) { + r.counts.FinishedActions++ + r.counts.RunningActions-- + r.stat.FinishAction(result, r.counts) +} + +func (r *runner) finishAndStartAction(result status.ActionResult, action *status.Action) { + r.counts.FinishedActions++ + r.stat.FinishAction(result, r.counts) + + r.counts.StartedActions++ + r.stat.StartAction(action, r.counts) +} + +var ( + action1 = &status.Action{Description: "action1"} + result1 = status.ActionResult{Action: action1} + action2 = &status.Action{Description: "action2"} + result2 = status.ActionResult{Action: action2} + action3 = &status.Action{Description: "action3"} + result3 = status.ActionResult{Action: action3} +) + +func twoActions(stat status.StatusOutput) { + runner := newRunner(stat, 2) + runner.startAction(action1) + runner.finishAction(result1) + runner.startAction(action2) + runner.finishAction(result2) +} + +func twoParallelActions(stat status.StatusOutput) { + runner := newRunner(stat, 2) + runner.startAction(action1) + runner.startAction(action2) + runner.finishAction(result1) + runner.finishAction(result2) +} + +func actionsWithOutput(stat status.StatusOutput) { + result2WithOutput := status.ActionResult{Action: action2, Output: "output1\noutput2\n"} + + runner := newRunner(stat, 3) + runner.startAction(action1) + runner.finishAction(result1) + runner.startAction(action2) + runner.finishAction(result2WithOutput) + runner.startAction(action3) + runner.finishAction(result3) +} + +func actionsWithOutputWithoutNewline(stat status.StatusOutput) { + result2WithOutputWithoutNewline := status.ActionResult{Action: action2, Output: "output1\noutput2"} + + runner := newRunner(stat, 3) + runner.startAction(action1) + runner.finishAction(result1) + runner.startAction(action2) + runner.finishAction(result2WithOutputWithoutNewline) + runner.startAction(action3) + runner.finishAction(result3) +} + +func actionsWithError(stat status.StatusOutput) { + action2WithError := &status.Action{Description: "action2", Outputs: []string{"f1", "f2"}, Command: "touch f1 f2"} + result2WithError := status.ActionResult{Action: action2WithError, Output: "error1\nerror2\n", Error: fmt.Errorf("error1")} + + runner := newRunner(stat, 3) + runner.startAction(action1) + runner.finishAction(result1) + runner.startAction(action2WithError) + runner.finishAction(result2WithError) + runner.startAction(action3) + runner.finishAction(result3) +} + +func actionWithEmptyDescription(stat status.StatusOutput) { + action1 := &status.Action{Command: "command1"} + result1 := status.ActionResult{Action: action1} + + runner := newRunner(stat, 1) + runner.startAction(action1) + runner.finishAction(result1) +} + +func actionsWithMessages(stat status.StatusOutput) { + runner := newRunner(stat, 2) + + runner.startAction(action1) + runner.finishAction(result1) + + stat.Message(status.VerboseLvl, "verbose") + stat.Message(status.StatusLvl, "status") + stat.Message(status.PrintLvl, "print") + stat.Message(status.ErrorLvl, "error") + + runner.startAction(action2) + runner.finishAction(result2) +} + +func actionWithLongDescription(stat status.StatusOutput) { + action1 := &status.Action{Description: "action with very long description to test eliding"} + result1 := status.ActionResult{Action: action1} + + runner := newRunner(stat, 2) + + runner.startAction(action1) + + runner.finishAction(result1) +} + +func actionWithOuptutWithAnsiCodes(stat status.StatusOutput) { + result1WithOutputWithAnsiCodes := status.ActionResult{Action: action1, Output: "\x1b[31mcolor\x1b[0m"} + + runner := newRunner(stat, 1) + runner.startAction(action1) + runner.finishAction(result1WithOutputWithAnsiCodes) +} + +func TestSmartStatusOutputWidthChange(t *testing.T) { + smart := &fakeSmartTerminal{termWidth: 40} + stat := NewStatusOutput(smart, "", false) + + runner := newRunner(stat, 2) + + action := &status.Action{Description: "action with very long description to test eliding"} + result := status.ActionResult{Action: action} + + runner.startAction(action) + smart.termWidth = 30 + runner.finishAction(result) + + stat.Flush() + + w := "\r\x1b[1m[ 0% 0/2] action with very long descrip\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action with very lo\x1b[0m\x1b[K\n" + + if g := smart.String(); g != w { + t.Errorf("want:\n%q\ngot:\n%q", w, g) + } +} diff --git a/ui/terminal/stdio.go b/ui/terminal/stdio.go new file mode 100644 index 000000000..dec296312 --- /dev/null +++ b/ui/terminal/stdio.go @@ -0,0 +1,55 @@ +// Copyright 2018 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 terminal provides a set of interfaces that can be used to interact +// with the terminal (including falling back when the terminal is detected to +// be a redirect or other dumb terminal) +package terminal + +import ( + "io" + "os" +) + +// StdioInterface represents a set of stdin/stdout/stderr Reader/Writers +type StdioInterface interface { + Stdin() io.Reader + Stdout() io.Writer + Stderr() io.Writer +} + +// StdioImpl uses the OS stdin/stdout/stderr to implement StdioInterface +type StdioImpl struct{} + +func (StdioImpl) Stdin() io.Reader { return os.Stdin } +func (StdioImpl) Stdout() io.Writer { return os.Stdout } +func (StdioImpl) Stderr() io.Writer { return os.Stderr } + +var _ StdioInterface = StdioImpl{} + +type customStdio struct { + stdin io.Reader + stdout io.Writer + stderr io.Writer +} + +func NewCustomStdio(stdin io.Reader, stdout, stderr io.Writer) StdioInterface { + return customStdio{stdin, stdout, stderr} +} + +func (c customStdio) Stdin() io.Reader { return c.stdin } +func (c customStdio) Stdout() io.Writer { return c.stdout } +func (c customStdio) Stderr() io.Writer { return c.stderr } + +var _ StdioInterface = customStdio{} diff --git a/ui/terminal/util.go b/ui/terminal/util.go index a85a517b9..3a11b79bb 100644 --- a/ui/terminal/util.go +++ b/ui/terminal/util.go @@ -22,13 +22,15 @@ import ( "unsafe" ) -func isTerminal(w io.Writer) bool { +func isSmartTerminal(w io.Writer) bool { if f, ok := w.(*os.File); ok { var termios syscall.Termios _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), ioctlGetTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) return err == 0 + } else if _, ok := w.(*fakeSmartTerminal); ok { + return true } return false } @@ -43,6 +45,8 @@ func termWidth(w io.Writer) (int, bool) { syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)), 0, 0, 0) return int(winsize.ws_column), err == 0 + } else if f, ok := w.(*fakeSmartTerminal); ok { + return f.termWidth, true } return 0, false } @@ -99,3 +103,8 @@ func stripAnsiEscapes(input []byte) []byte { return input } + +type fakeSmartTerminal struct { + bytes.Buffer + termWidth int +} diff --git a/ui/terminal/writer.go b/ui/terminal/writer.go deleted file mode 100644 index ebe4b2aad..000000000 --- a/ui/terminal/writer.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2018 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 terminal provides a set of interfaces that can be used to interact -// with the terminal (including falling back when the terminal is detected to -// be a redirect or other dumb terminal) -package terminal - -import ( - "fmt" - "io" - "os" - "strings" - "sync" -) - -// Writer provides an interface to write temporary and permanent messages to -// the terminal. -// -// The terminal is considered to be a dumb terminal if TERM==dumb, or if a -// terminal isn't detected on stdout/stderr (generally because it's a pipe or -// file). Dumb terminals will strip out all ANSI escape sequences, including -// colors. -type Writer interface { - // Print prints the string to the terminal, overwriting any current - // status being displayed. - // - // On a dumb terminal, the status messages will be kept. - Print(str string) - - // Status prints the first line of the string to the terminal, - // overwriting any previous status line. Strings longer than the width - // of the terminal will be cut off. - // - // On a dumb terminal, previous status messages will remain, and the - // entire first line of the string will be printed. - StatusLine(str string) - - // StatusAndMessage prints the first line of status to the terminal, - // similarly to StatusLine(), then prints the full msg below that. The - // status line is retained. - // - // There is guaranteed to be no other output in between the status and - // message. - StatusAndMessage(status, msg string) - - // Finish ensures that the output ends with a newline (preserving any - // current status line that is current displayed). - // - // This does nothing on dumb terminals. - Finish() - - // Write implements the io.Writer interface. This is primarily so that - // the logger can use this interface to print to stderr without - // breaking the other semantics of this interface. - // - // Try to use any of the other functions if possible. - Write(p []byte) (n int, err error) - - isSmartTerminal() bool -} - -// NewWriter creates a new Writer based on the stdio and the TERM -// environment variable. -func NewWriter(stdio StdioInterface) Writer { - w := &writerImpl{ - stdio: stdio, - - haveBlankLine: true, - } - - if term, ok := os.LookupEnv("TERM"); ok && term != "dumb" { - w.smartTerminal = isTerminal(stdio.Stdout()) - } - w.stripEscapes = !w.smartTerminal - - return w -} - -type writerImpl struct { - stdio StdioInterface - - haveBlankLine bool - - // Protecting the above, we assume that smartTerminal and stripEscapes - // does not change after initial setup. - lock sync.Mutex - - smartTerminal bool - stripEscapes bool -} - -func (w *writerImpl) isSmartTerminal() bool { - return w.smartTerminal -} - -func (w *writerImpl) requestLine() { - if !w.haveBlankLine { - fmt.Fprintln(w.stdio.Stdout()) - w.haveBlankLine = true - } -} - -func (w *writerImpl) Print(str string) { - if w.stripEscapes { - str = string(stripAnsiEscapes([]byte(str))) - } - - w.lock.Lock() - defer w.lock.Unlock() - w.print(str) -} - -func (w *writerImpl) print(str string) { - if !w.haveBlankLine { - fmt.Fprint(w.stdio.Stdout(), "\r", "\x1b[K") - w.haveBlankLine = true - } - fmt.Fprint(w.stdio.Stdout(), str) - if len(str) == 0 || str[len(str)-1] != '\n' { - fmt.Fprint(w.stdio.Stdout(), "\n") - } -} - -func (w *writerImpl) StatusLine(str string) { - w.lock.Lock() - defer w.lock.Unlock() - - w.statusLine(str) -} - -func (w *writerImpl) statusLine(str string) { - if !w.smartTerminal { - fmt.Fprintln(w.stdio.Stdout(), str) - return - } - - idx := strings.IndexRune(str, '\n') - if idx != -1 { - str = str[0:idx] - } - - // Limit line width to the terminal width, otherwise we'll wrap onto - // another line and we won't delete the previous line. - // - // Run this on every line in case the window has been resized while - // we're printing. This could be optimized to only re-run when we get - // SIGWINCH if it ever becomes too time consuming. - if max, ok := termWidth(w.stdio.Stdout()); ok { - if len(str) > max { - // TODO: Just do a max. Ninja elides the middle, but that's - // more complicated and these lines aren't that important. - str = str[:max] - } - } - - // Move to the beginning on the line, print the output, then clear - // the rest of the line. - fmt.Fprint(w.stdio.Stdout(), "\r", str, "\x1b[K") - w.haveBlankLine = false -} - -func (w *writerImpl) StatusAndMessage(status, msg string) { - if w.stripEscapes { - msg = string(stripAnsiEscapes([]byte(msg))) - } - - w.lock.Lock() - defer w.lock.Unlock() - - w.statusLine(status) - w.requestLine() - w.print(msg) -} - -func (w *writerImpl) Finish() { - w.lock.Lock() - defer w.lock.Unlock() - - w.requestLine() -} - -func (w *writerImpl) Write(p []byte) (n int, err error) { - w.Print(string(p)) - return len(p), nil -} - -// StdioInterface represents a set of stdin/stdout/stderr Reader/Writers -type StdioInterface interface { - Stdin() io.Reader - Stdout() io.Writer - Stderr() io.Writer -} - -// StdioImpl uses the OS stdin/stdout/stderr to implement StdioInterface -type StdioImpl struct{} - -func (StdioImpl) Stdin() io.Reader { return os.Stdin } -func (StdioImpl) Stdout() io.Writer { return os.Stdout } -func (StdioImpl) Stderr() io.Writer { return os.Stderr } - -var _ StdioInterface = StdioImpl{} - -type customStdio struct { - stdin io.Reader - stdout io.Writer - stderr io.Writer -} - -func NewCustomStdio(stdin io.Reader, stdout, stderr io.Writer) StdioInterface { - return customStdio{stdin, stdout, stderr} -} - -func (c customStdio) Stdin() io.Reader { return c.stdin } -func (c customStdio) Stdout() io.Writer { return c.stdout } -func (c customStdio) Stderr() io.Writer { return c.stderr } - -var _ StdioInterface = customStdio{} diff --git a/ui/tracer/status.go b/ui/tracer/status.go index af50e2d4d..c83125514 100644 --- a/ui/tracer/status.go +++ b/ui/tracer/status.go @@ -85,3 +85,8 @@ func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Co func (s *statusOutput) Flush() {} func (s *statusOutput) Message(level status.MsgLevel, message string) {} + +func (s *statusOutput) Write(p []byte) (int, error) { + // Discard writes + return len(p), nil +} |