diff options
Diffstat (limited to 'cc')
-rw-r--r-- | cc/Android.bp | 1 | ||||
-rw-r--r-- | cc/androidmk.go | 2 | ||||
-rw-r--r-- | cc/api_level.go | 71 | ||||
-rw-r--r-- | cc/binary_sdk_member.go | 2 | ||||
-rw-r--r-- | cc/cc.go | 106 | ||||
-rw-r--r-- | cc/cc_test.go | 1 | ||||
-rw-r--r-- | cc/compiler.go | 10 | ||||
-rw-r--r-- | cc/config/arm64_device.go | 6 | ||||
-rw-r--r-- | cc/config/clang.go | 14 | ||||
-rw-r--r-- | cc/config/global.go | 12 | ||||
-rw-r--r-- | cc/config/x86_windows_host.go | 3 | ||||
-rw-r--r-- | cc/library.go | 100 | ||||
-rw-r--r-- | cc/library_sdk_member.go | 2 | ||||
-rw-r--r-- | cc/library_test.go | 10 | ||||
-rw-r--r-- | cc/linkable.go | 4 | ||||
-rw-r--r-- | cc/lto.go | 56 | ||||
-rw-r--r-- | cc/ndk_library.go | 200 | ||||
-rwxr-xr-x | cc/ndkstubgen/__init__.py | 29 | ||||
-rw-r--r-- | cc/ndkstubgen/mypy.ini | 2 | ||||
-rwxr-xr-x | cc/ndkstubgen/test_ndkstubgen.py | 79 | ||||
-rw-r--r-- | cc/pgo.go | 52 | ||||
-rw-r--r-- | cc/prebuilt.go | 8 | ||||
-rw-r--r-- | cc/stl.go | 20 | ||||
-rw-r--r-- | cc/symbolfile/__init__.py | 139 | ||||
-rw-r--r-- | cc/symbolfile/mypy.ini | 2 | ||||
-rw-r--r-- | cc/symbolfile/test_symbolfile.py | 381 |
26 files changed, 735 insertions, 577 deletions
diff --git a/cc/Android.bp b/cc/Android.bp index 831911e45..ff2cdf374 100644 --- a/cc/Android.bp +++ b/cc/Android.bp @@ -13,6 +13,7 @@ bootstrap_go_package { ], srcs: [ "androidmk.go", + "api_level.go", "builder.go", "cc.go", "ccdeps.go", diff --git a/cc/androidmk.go b/cc/androidmk.go index fcaff557d..f0e615217 100644 --- a/cc/androidmk.go +++ b/cc/androidmk.go @@ -458,7 +458,7 @@ func (installer *baseInstaller) AndroidMkEntries(ctx AndroidMkContext, entries * } func (c *stubDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) { - entries.SubName = ndkLibrarySuffix + "." + c.properties.ApiLevel + entries.SubName = ndkLibrarySuffix + "." + c.apiLevel.String() entries.Class = "SHARED_LIBRARIES" entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) { diff --git a/cc/api_level.go b/cc/api_level.go new file mode 100644 index 000000000..c93d6eda3 --- /dev/null +++ b/cc/api_level.go @@ -0,0 +1,71 @@ +// Copyright 2020 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cc + +import ( + "fmt" + + "android/soong/android" +) + +func minApiForArch(ctx android.BaseModuleContext, + arch android.ArchType) android.ApiLevel { + + switch arch { + case android.Arm, android.X86: + return ctx.Config().MinSupportedSdkVersion() + case android.Arm64, android.X86_64: + return android.FirstLp64Version + default: + panic(fmt.Errorf("Unknown arch %q", arch)) + } +} + +func nativeApiLevelFromUser(ctx android.BaseModuleContext, + raw string) (android.ApiLevel, error) { + + min := minApiForArch(ctx, ctx.Arch().ArchType) + if raw == "minimum" { + return min, nil + } + + value, err := android.ApiLevelFromUser(ctx, raw) + if err != nil { + return android.NoneApiLevel, err + } + + if value.LessThan(min) { + return min, nil + } + + return value, nil +} + +func nativeApiLevelFromUserWithDefault(ctx android.BaseModuleContext, + raw string, defaultValue string) (android.ApiLevel, error) { + if raw == "" { + raw = defaultValue + } + return nativeApiLevelFromUser(ctx, raw) +} + +func nativeApiLevelOrPanic(ctx android.BaseModuleContext, + raw string) android.ApiLevel { + value, err := nativeApiLevelFromUser(ctx, raw) + if err != nil { + panic(err.Error()) + } + return value +} diff --git a/cc/binary_sdk_member.go b/cc/binary_sdk_member.go index a1abc728a..55e400e8e 100644 --- a/cc/binary_sdk_member.go +++ b/cc/binary_sdk_member.go @@ -44,7 +44,7 @@ func (mt *binarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorConte for _, target := range targets { name, version := StubsLibNameAndVersion(lib) if version == "" { - version = LatestStubsVersionFor(mctx.Config(), name) + version = "latest" } variations := target.Variations() if mctx.Device() { @@ -47,7 +47,8 @@ func RegisterCCBuildComponents(ctx android.RegistrationContext) { ctx.BottomUp("link", LinkageMutator).Parallel() ctx.BottomUp("ndk_api", NdkApiMutator).Parallel() ctx.BottomUp("test_per_src", TestPerSrcMutator).Parallel() - ctx.BottomUp("version", VersionMutator).Parallel() + ctx.BottomUp("version_selector", versionSelectorMutator).Parallel() + ctx.BottomUp("version", versionMutator).Parallel() ctx.BottomUp("begin", BeginMutator).Parallel() ctx.BottomUp("sysprop_cc", SyspropMutator).Parallel() ctx.BottomUp("vendor_snapshot", VendorSnapshotMutator).Parallel() @@ -359,7 +360,7 @@ type ModuleContextIntf interface { useClangLld(actx ModuleContext) bool isForPlatform() bool apexVariationName() string - apexSdkVersion() int + apexSdkVersion() android.ApiLevel hasStubsVariants() bool isStubs() bool bootstrap() bool @@ -620,7 +621,7 @@ type Module struct { kytheFiles android.Paths // For apex variants, this is set as apex.min_sdk_version - apexSdkVersion int + apexSdkVersion android.ApiLevel } func (c *Module) Toc() android.OptionalPath { @@ -635,7 +636,7 @@ func (c *Module) Toc() android.OptionalPath { func (c *Module) ApiLevel() string { if c.linker != nil { if stub, ok := c.linker.(*stubDecorator); ok { - return stub.properties.ApiLevel + return stub.apiLevel.String() } } panic(fmt.Errorf("ApiLevel() called on non-stub library module: %q", c.BaseModuleName())) @@ -790,7 +791,28 @@ func (c *Module) BuildStubs() bool { panic(fmt.Errorf("BuildStubs called on non-library module: %q", c.BaseModuleName())) } -func (c *Module) SetStubsVersions(version string) { +func (c *Module) SetAllStubsVersions(versions []string) { + if library, ok := c.linker.(*libraryDecorator); ok { + library.MutatedProperties.AllStubsVersions = versions + return + } + if llndk, ok := c.linker.(*llndkStubDecorator); ok { + llndk.libraryDecorator.MutatedProperties.AllStubsVersions = versions + return + } +} + +func (c *Module) AllStubsVersions() []string { + if library, ok := c.linker.(*libraryDecorator); ok { + return library.MutatedProperties.AllStubsVersions + } + if llndk, ok := c.linker.(*llndkStubDecorator); ok { + return llndk.libraryDecorator.MutatedProperties.AllStubsVersions + } + return nil +} + +func (c *Module) SetStubsVersion(version string) { if c.linker != nil { if library, ok := c.linker.(*libraryDecorator); ok { library.MutatedProperties.StubsVersion = version @@ -801,7 +823,7 @@ func (c *Module) SetStubsVersions(version string) { return } } - panic(fmt.Errorf("SetStubsVersions called on non-library module: %q", c.BaseModuleName())) + panic(fmt.Errorf("SetStubsVersion called on non-library module: %q", c.BaseModuleName())) } func (c *Module) StubsVersion() string { @@ -934,9 +956,9 @@ func (c *Module) Init() android.Module { c.AddProperties(feature.props()...) } - c.Prefer32(func(ctx android.BaseModuleContext, base *android.ModuleBase, class android.OsClass) bool { + c.Prefer32(func(ctx android.BaseModuleContext, base *android.ModuleBase, os android.OsType) bool { // Windows builds always prefer 32-bit - return class == android.HostCross + return os == android.Windows }) android.InitAndroidArchModule(c, c.hod, c.multilib) android.InitApexModule(c) @@ -1312,7 +1334,7 @@ func (ctx *moduleContextImpl) apexVariationName() string { return ctx.mod.ApexVariationName() } -func (ctx *moduleContextImpl) apexSdkVersion() int { +func (ctx *moduleContextImpl) apexSdkVersion() android.ApiLevel { return ctx.mod.apexSdkVersion } @@ -1688,11 +1710,13 @@ func (c *Module) begin(ctx BaseModuleContext) { feature.begin(ctx) } if ctx.useSdk() && c.IsSdkVariant() { - version, err := normalizeNdkApiLevel(ctx, ctx.sdkVersion(), ctx.Arch()) + version, err := nativeApiLevelFromUser(ctx, ctx.sdkVersion()) if err != nil { ctx.PropertyErrorf("sdk_version", err.Error()) + c.Properties.Sdk_version = nil + } else { + c.Properties.Sdk_version = StringPtr(version.String()) } - c.Properties.Sdk_version = StringPtr(version) } } @@ -2002,18 +2026,20 @@ func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) { variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version}) depTag.explicitlyVersioned = true } - actx.AddVariationDependencies(variations, depTag, name) + deps := actx.AddVariationDependencies(variations, depTag, name) // If the version is not specified, add dependency to all stubs libraries. // The stubs library will be used when the depending module is built for APEX and // the dependent module is not in the same APEX. if version == "" && VersionVariantAvailable(c) { - for _, ver := range stubsVersionsFor(actx.Config())[name] { - // Note that depTag.ExplicitlyVersioned is false in this case. - actx.AddVariationDependencies([]blueprint.Variation{ - {Mutator: "link", Variation: "shared"}, - {Mutator: "version", Variation: ver}, - }, depTag, name) + if dep, ok := deps[0].(*Module); ok { + for _, ver := range dep.AllStubsVersions() { + // Note that depTag.ExplicitlyVersioned is false in this case. + ctx.AddVariationDependencies([]blueprint.Variation{ + {Mutator: "link", Variation: "shared"}, + {Mutator: "version", Variation: ver}, + }, depTag, name) + } } } } @@ -2299,7 +2325,7 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { // For the dependency from platform to apex, use the latest stubs c.apexSdkVersion = android.FutureApiLevel if !c.IsForPlatform() { - c.apexSdkVersion = c.ApexProperties.Info.MinSdkVersion + c.apexSdkVersion = c.ApexProperties.Info.MinSdkVersion(ctx) } if android.InList("hwaddress", ctx.Config().SanitizeDevice()) { @@ -2403,7 +2429,7 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { if libDepTag, ok := depTag.(libraryDependencyTag); ok { // Only use static unwinder for legacy (min_sdk_version = 29) apexes (b/144430859) - if libDepTag.staticUnwinder && c.apexSdkVersion > android.SdkVersion_Android10 { + if libDepTag.staticUnwinder && c.apexSdkVersion.GreaterThan(android.SdkVersion_Android10) { return } @@ -2447,7 +2473,7 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { // when to use (unspecified) stubs, check min_sdk_version and choose the right one if useThisDep && depIsStubs && !libDepTag.explicitlyVersioned { - versionToUse, err := c.ChooseSdkVersion(ccDep.StubsVersions(), c.apexSdkVersion) + versionToUse, err := c.ChooseSdkVersion(ccDep.StubsVersions(), c.apexSdkVersion.FinalOrFutureInt()) if err != nil { ctx.OtherModuleErrorf(dep, err.Error()) return @@ -2465,12 +2491,12 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { if m, ok := ccDep.(*Module); ok && m.IsStubs() { // LLNDK // by default, use current version of LLNDK versionToUse := "" - versions := stubsVersionsFor(ctx.Config())[depName] + versions := m.AllStubsVersions() if c.ApexVariationName() != "" && len(versions) > 0 { // if this is for use_vendor apex && dep has stubsVersions // apply the same rule of apex sdk enforcement to choose right version var err error - versionToUse, err = c.ChooseSdkVersion(versions, c.apexSdkVersion) + versionToUse, err = c.ChooseSdkVersion(versions, c.apexSdkVersion.FinalOrFutureInt()) if err != nil { ctx.OtherModuleErrorf(dep, err.Error()) return @@ -2994,21 +3020,8 @@ func (c *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Modu return true } -// b/154667674: refactor this to handle "current" in a consistent way -func decodeSdkVersionString(ctx android.BaseModuleContext, versionString string) (int, error) { - if versionString == "" { - return 0, fmt.Errorf("not specified") - } - if versionString == "current" { - if ctx.Config().PlatformSdkCodename() == "REL" { - return ctx.Config().PlatformSdkVersionInt(), nil - } - return android.FutureApiLevel, nil - } - return android.ApiStrToNum(ctx, versionString) -} - -func (c *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error { +func (c *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, + sdkVersion android.ApiLevel) error { // We ignore libclang_rt.* prebuilt libs since they declare sdk_version: 14(b/121358700) if strings.HasPrefix(ctx.OtherModuleName(c), "libclang_rt") { return nil @@ -3032,11 +3045,17 @@ func (c *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersi // non-SDK variant resets sdk_version, which works too. minSdkVersion = c.SdkVersion() } - ver, err := decodeSdkVersionString(ctx, minSdkVersion) + if minSdkVersion == "" { + return fmt.Errorf("neither min_sdk_version nor sdk_version specificed") + } + // Not using nativeApiLevelFromUser because the context here is not + // necessarily a native context. + ver, err := android.ApiLevelFromUser(ctx, minSdkVersion) if err != nil { return err } - if ver > sdkVersion { + + if ver.GreaterThan(sdkVersion) { return fmt.Errorf("newer SDK(%v)", ver) } return nil @@ -3127,13 +3146,6 @@ func (c *Module) IsSdkVariant() bool { return c.Properties.IsSdkVariant || c.AlwaysSdk() } -func getCurrentNdkPrebuiltVersion(ctx DepsContext) string { - if ctx.Config().PlatformSdkVersionInt() > config.NdkMaxPrebuiltVersionInt { - return strconv.Itoa(config.NdkMaxPrebuiltVersionInt) - } - return ctx.Config().PlatformSdkVersion() -} - func kytheExtractAllFactory() android.Singleton { return &kytheExtractAllSingleton{} } diff --git a/cc/cc_test.go b/cc/cc_test.go index a4c067772..132d09136 100644 --- a/cc/cc_test.go +++ b/cc/cc_test.go @@ -3025,6 +3025,7 @@ func TestStaticLibDepReorderingWithShared(t *testing.T) { } func checkEquals(t *testing.T, message string, expected, actual interface{}) { + t.Helper() if !reflect.DeepEqual(actual, expected) { t.Errorf(message+ "\nactual: %v"+ diff --git a/cc/compiler.go b/cc/compiler.go index c268dec2b..bb5c7bf2a 100644 --- a/cc/compiler.go +++ b/cc/compiler.go @@ -354,10 +354,16 @@ func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags, deps flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX_NAME__='\""+ctx.apexVariationName()+"\"'") } if ctx.Device() { - flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_SDK_VERSION__="+strconv.Itoa(ctx.apexSdkVersion())) + flags.Global.CommonFlags = append(flags.Global.CommonFlags, + fmt.Sprintf("-D__ANDROID_SDK_VERSION__=%d", + ctx.apexSdkVersion().FinalOrFutureInt())) } } + if ctx.Target().NativeBridge == android.NativeBridgeEnabled { + flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_NATIVE_BRIDGE__") + } + instructionSet := String(compiler.Properties.Instruction_set) if flags.RequiredInstructionSet != "" { instructionSet = flags.RequiredInstructionSet @@ -386,7 +392,7 @@ func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags, deps if ctx.Os().Class == android.Device { version := ctx.sdkVersion() if version == "" || version == "current" { - target += strconv.Itoa(android.FutureApiLevel) + target += strconv.Itoa(android.FutureApiLevelInt) } else { target += version } diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go index 62d8cc8fb..e6024aa45 100644 --- a/cc/config/arm64_device.go +++ b/cc/config/arm64_device.go @@ -34,6 +34,9 @@ var ( "armv8-2a": []string{ "-march=armv8.2-a", }, + "armv8-2a-dotprod": []string{ + "-march=armv8.2-a+dotprod", + }, } arm64Ldflags = []string{ @@ -100,6 +103,7 @@ func init() { pctx.StaticVariable("Arm64ClangArmv8ACflags", strings.Join(arm64ArchVariantCflags["armv8-a"], " ")) pctx.StaticVariable("Arm64ClangArmv82ACflags", strings.Join(arm64ArchVariantCflags["armv8-2a"], " ")) + pctx.StaticVariable("Arm64ClangArmv82ADotprodCflags", strings.Join(arm64ArchVariantCflags["armv8-2a-dotprod"], " ")) pctx.StaticVariable("Arm64ClangCortexA53Cflags", strings.Join(arm64ClangCpuVariantCflags["cortex-a53"], " ")) @@ -121,6 +125,7 @@ var ( arm64ClangArchVariantCflagsVar = map[string]string{ "armv8-a": "${config.Arm64ClangArmv8ACflags}", "armv8-2a": "${config.Arm64ClangArmv82ACflags}", + "armv8-2a-dotprod": "${config.Arm64ClangArmv82ADotprodCflags}", } arm64ClangCpuVariantCflagsVar = map[string]string{ @@ -198,6 +203,7 @@ func arm64ToolchainFactory(arch android.Arch) Toolchain { switch arch.ArchVariant { case "armv8-a": case "armv8-2a": + case "armv8-2a-dotprod": // Nothing extra for armv8-a/armv8-2a default: panic(fmt.Sprintf("Unknown ARM architecture version: %q", arch.ArchVariant)) diff --git a/cc/config/clang.go b/cc/config/clang.go index 7db405c2e..441bff2a5 100644 --- a/cc/config/clang.go +++ b/cc/config/clang.go @@ -42,7 +42,6 @@ var ClangUnknownCflags = sorted([]string{ "-Wno-literal-suffix", "-Wno-maybe-uninitialized", "-Wno-old-style-declaration", - "-Wno-psabi", "-Wno-unused-but-set-parameter", "-Wno-unused-but-set-variable", "-Wno-unused-local-typedefs", @@ -93,7 +92,9 @@ var ClangLibToolingUnknownCflags = sorted([]string{}) // updated, some checks enabled by this module may be disabled if they have // become more strict, or if they are a new match for a wildcard group like // `modernize-*`. -var ClangTidyDisableChecks = []string{} +var ClangTidyDisableChecks = []string{ + "misc-no-recursion", +} func init() { pctx.StaticVariable("ClangExtraCflags", strings.Join([]string{ @@ -103,6 +104,10 @@ func init() { // not emit the table by default on Android since NDK still uses GNU binutils. "-faddrsig", + // Turn on -fcommon explicitly, since Clang now defaults to -fno-common. The cleanup bug + // tracking this is http://b/151457797. + "-fcommon", + // Help catch common 32/64-bit errors. "-Werror=int-conversion", @@ -183,6 +188,8 @@ func init() { "-Wno-enum-enum-conversion", // http://b/154138986 "-Wno-enum-float-conversion", // http://b/154255917 "-Wno-pessimizing-move", // http://b/154270751 + // New warnings to be fixed after clang-r399163 + "-Wno-non-c-typedef-for-linkage", // http://b/161304145 }, " ")) // Extra cflags for external third-party projects to disable warnings that @@ -205,6 +212,9 @@ func init() { "-Wno-xor-used-as-pow", // http://b/145211022 "-Wno-final-dtor-non-final-class", + + // http://b/165945989 + "-Wno-psabi", }, " ")) } diff --git a/cc/config/global.go b/cc/config/global.go index 32f163d88..f9b3cc8dd 100644 --- a/cc/config/global.go +++ b/cc/config/global.go @@ -114,6 +114,12 @@ var ( noOverrideGlobalCflags = []string{ "-Werror=int-to-pointer-cast", "-Werror=pointer-to-int-cast", + // http://b/161386391 for -Wno-void-pointer-to-enum-cast + "-Wno-void-pointer-to-enum-cast", + // http://b/161386391 for -Wno-void-pointer-to-int-cast + "-Wno-void-pointer-to-int-cast", + // http://b/161386391 for -Wno-pointer-to-int-cast + "-Wno-pointer-to-int-cast", "-Werror=fortify-source", } @@ -126,12 +132,10 @@ var ( ExperimentalCStdVersion = "gnu11" ExperimentalCppStdVersion = "gnu++2a" - NdkMaxPrebuiltVersionInt = 27 - // prebuilts/clang default settings. ClangDefaultBase = "prebuilts/clang/host" - ClangDefaultVersion = "clang-r383902b" - ClangDefaultShortVersion = "11.0.2" + ClangDefaultVersion = "clang-r399163" + ClangDefaultShortVersion = "11.0.4" // Directories with warnings from Android.bp files. WarningAllowedProjects = []string{ diff --git a/cc/config/x86_windows_host.go b/cc/config/x86_windows_host.go index b5b555322..b77df7906 100644 --- a/cc/config/x86_windows_host.go +++ b/cc/config/x86_windows_host.go @@ -39,6 +39,9 @@ var ( // Get 64-bit off_t and related functions. "-D_FILE_OFFSET_BITS=64", + // Don't adjust the layout of bitfields like msvc does. + "-mno-ms-bitfields", + "--sysroot ${WindowsGccRoot}/${WindowsGccTriple}", } windowsClangCflags = append(ClangFilterUnknownCflags(windowsCflags), []string{}...) diff --git a/cc/library.go b/cc/library.go index 92853b5d3..8048f0002 100644 --- a/cc/library.go +++ b/cc/library.go @@ -19,8 +19,6 @@ import ( "io" "path/filepath" "regexp" - "sort" - "strconv" "strings" "sync" @@ -152,6 +150,8 @@ type LibraryMutatedProperties struct { BuildStubs bool `blueprint:"mutated"` // Version of the stubs lib StubsVersion string `blueprint:"mutated"` + // List of all stubs versions associated with an implementation lib + AllStubsVersions []string `blueprint:"mutated"` } type FlagExporterProperties struct { @@ -1517,56 +1517,39 @@ func LinkageMutator(mctx android.BottomUpMutatorContext) { } } -var stubVersionsKey = android.NewOnceKey("stubVersions") - -// maps a module name to the list of stubs versions available for the module -func stubsVersionsFor(config android.Config) map[string][]string { - return config.Once(stubVersionsKey, func() interface{} { - return make(map[string][]string) - }).(map[string][]string) -} - -var stubsVersionsLock sync.Mutex - -func LatestStubsVersionFor(config android.Config, name string) string { - versions, ok := stubsVersionsFor(config)[name] - if ok && len(versions) > 0 { - // the versions are alreay sorted in ascending order - return versions[len(versions)-1] - } - return "" -} - func normalizeVersions(ctx android.BaseModuleContext, versions []string) { - numVersions := make([]int, len(versions)) + var previous android.ApiLevel for i, v := range versions { - numVer, err := android.ApiStrToNum(ctx, v) + ver, err := android.ApiLevelFromUser(ctx, v) if err != nil { ctx.PropertyErrorf("versions", "%s", err.Error()) return } - numVersions[i] = numVer - } - if !sort.IsSorted(sort.IntSlice(numVersions)) { - ctx.PropertyErrorf("versions", "not sorted: %v", versions) - } - for i, v := range numVersions { - versions[i] = strconv.Itoa(v) + if i > 0 && ver.LessThanOrEqualTo(previous) { + ctx.PropertyErrorf("versions", "not sorted: %v", versions) + } + versions[i] = ver.String() + previous = ver } } func createVersionVariations(mctx android.BottomUpMutatorContext, versions []string) { - // "" is for the non-stubs variant - versions = append([]string{""}, versions...) + // "" is for the non-stubs (implementation) variant. + variants := append([]string{""}, versions...) - modules := mctx.CreateLocalVariations(versions...) + modules := mctx.CreateLocalVariations(variants...) for i, m := range modules { - if versions[i] != "" { + if variants[i] != "" { m.(LinkableInterface).SetBuildStubs() - m.(LinkableInterface).SetStubsVersions(versions[i]) + m.(LinkableInterface).SetStubsVersion(variants[i]) } } mctx.AliasVariation("") + latestVersion := "" + if len(versions) > 0 { + latestVersion = versions[len(versions)-1] + } + mctx.CreateAliasVariation("latest", latestVersion) } func VersionVariantAvailable(module interface { @@ -1577,44 +1560,41 @@ func VersionVariantAvailable(module interface { return !module.Host() && !module.InRamdisk() && !module.InRecovery() } -// VersionMutator splits a module into the mandatory non-stubs variant -// (which is unnamed) and zero or more stubs variants. -func VersionMutator(mctx android.BottomUpMutatorContext) { +// versionSelector normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions, +// and propagates the value from implementation libraries to llndk libraries with the same name. +func versionSelectorMutator(mctx android.BottomUpMutatorContext) { if library, ok := mctx.Module().(LinkableInterface); ok && VersionVariantAvailable(library) { if library.CcLibrary() && library.BuildSharedVariant() && len(library.StubsVersions()) > 0 && !library.IsSdkVariant() { + versions := library.StubsVersions() normalizeVersions(mctx, versions) if mctx.Failed() { return } - - stubsVersionsLock.Lock() - defer stubsVersionsLock.Unlock() - // save the list of versions for later use - stubsVersionsFor(mctx.Config())[mctx.ModuleName()] = versions - - createVersionVariations(mctx, versions) + // Set the versions on the pre-mutated module so they can be read by any llndk modules that + // depend on the implementation library and haven't been mutated yet. + library.SetAllStubsVersions(versions) return } if c, ok := library.(*Module); ok && c.IsStubs() { - stubsVersionsLock.Lock() - defer stubsVersionsLock.Unlock() - // For LLNDK llndk_library, we borrow stubs.versions from its implementation library. - // Since llndk_library has dependency to its implementation library, - // we can safely access stubsVersionsFor() with its baseModuleName. - versions := stubsVersionsFor(mctx.Config())[c.BaseModuleName()] - // save the list of versions for later use - stubsVersionsFor(mctx.Config())[mctx.ModuleName()] = versions - - createVersionVariations(mctx, versions) - return + // Get the versions from the implementation module. + impls := mctx.GetDirectDepsWithTag(llndkImplDep) + if len(impls) > 1 { + panic(fmt.Errorf("Expected single implmenetation library, got %d", len(impls))) + } else if len(impls) == 1 { + c.SetAllStubsVersions(impls[0].(*Module).AllStubsVersions()) + } } + } +} - mctx.CreateLocalVariations("") - mctx.AliasVariation("") - return +// versionMutator splits a module into the mandatory non-stubs variant +// (which is unnamed) and zero or more stubs variants. +func versionMutator(mctx android.BottomUpMutatorContext) { + if library, ok := mctx.Module().(LinkableInterface); ok && VersionVariantAvailable(library) { + createVersionVariations(mctx, library.AllStubsVersions()) } } diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go index ecfdc9977..2f1554427 100644 --- a/cc/library_sdk_member.go +++ b/cc/library_sdk_member.go @@ -80,7 +80,7 @@ func (mt *librarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorCont for _, target := range targets { name, version := StubsLibNameAndVersion(lib) if version == "" { - version = LatestStubsVersionFor(mctx.Config(), name) + version = "latest" } variations := target.Variations() if mctx.Device() { diff --git a/cc/library_test.go b/cc/library_test.go index cb167252d..49838b48e 100644 --- a/cc/library_test.go +++ b/cc/library_test.go @@ -195,7 +195,7 @@ func TestStubsVersions(t *testing.T) { name: "libfoo", srcs: ["foo.c"], stubs: { - versions: ["29", "R", "10000"], + versions: ["29", "R", "current"], }, } ` @@ -204,7 +204,7 @@ func TestStubsVersions(t *testing.T) { ctx := testCcWithConfig(t, config) variants := ctx.ModuleVariantsForTests("libfoo") - for _, expectedVer := range []string{"29", "9000", "10000"} { + for _, expectedVer := range []string{"29", "R", "current"} { expectedVariant := "android_arm_armv7-a-neon_shared_" + expectedVer if !inList(expectedVariant, variants) { t.Errorf("missing expected variant: %q", expectedVariant) @@ -218,7 +218,7 @@ func TestStubsVersions_NotSorted(t *testing.T) { name: "libfoo", srcs: ["foo.c"], stubs: { - versions: ["29", "10000", "R"], + versions: ["29", "current", "R"], }, } ` @@ -233,10 +233,10 @@ func TestStubsVersions_ParseError(t *testing.T) { name: "libfoo", srcs: ["foo.c"], stubs: { - versions: ["29", "10000", "X"], + versions: ["29", "current", "X"], }, } ` - testCcError(t, `"libfoo" .*: versions: SDK version should be`, bp) + testCcError(t, `"libfoo" .*: versions: "X" could not be parsed as an integer and is not a recognized codename`, bp) } diff --git a/cc/linkable.go b/cc/linkable.go index 4c8416347..6d8a4b71e 100644 --- a/cc/linkable.go +++ b/cc/linkable.go @@ -26,8 +26,10 @@ type LinkableInterface interface { StubsVersions() []string BuildStubs() bool SetBuildStubs() - SetStubsVersions(string) + SetStubsVersion(string) StubsVersion() string + SetAllStubsVersions([]string) + AllStubsVersions() []string HasStubsVariants() bool SelectedStl() string ApiLevel() string @@ -45,6 +45,8 @@ type LTOProperties struct { Thin *bool `android:"arch_variant"` } `android:"arch_variant"` + GlobalThin *bool `blueprint:"mutated"` + // Dep properties indicate that this module needs to be built with LTO // since it is an object dependency of an LTO module. FullDep bool `blueprint:"mutated"` @@ -52,6 +54,9 @@ type LTOProperties struct { // Use clang lld instead of gnu ld. Use_clang_lld *bool + + // Use -fwhole-program-vtables cflag. + Whole_program_vtables *bool } type lto struct { @@ -65,6 +70,8 @@ func (lto *lto) props() []interface{} { func (lto *lto) begin(ctx BaseModuleContext) { if ctx.Config().IsEnvTrue("DISABLE_LTO") { lto.Properties.Lto.Never = boolPtr(true) + } else if ctx.Config().IsEnvTrue("GLOBAL_THINLTO") { + lto.Properties.GlobalThin = boolPtr(true) } } @@ -88,7 +95,7 @@ func (lto *lto) flags(ctx BaseModuleContext, flags Flags) Flags { if lto.LTO() { var ltoFlag string - if Bool(lto.Properties.Lto.Thin) { + if lto.ThinLTO() { ltoFlag = "-flto=thin -fsplit-lto-unit" } else { ltoFlag = "-flto" @@ -97,7 +104,11 @@ func (lto *lto) flags(ctx BaseModuleContext, flags Flags) Flags { flags.Local.CFlags = append(flags.Local.CFlags, ltoFlag) flags.Local.LdFlags = append(flags.Local.LdFlags, ltoFlag) - if ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && Bool(lto.Properties.Lto.Thin) && lto.useClangLld(ctx) { + if Bool(lto.Properties.Whole_program_vtables) { + flags.Local.CFlags = append(flags.Local.CFlags, "-fwhole-program-vtables") + } + + if lto.ThinLTO() && ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && lto.useClangLld(ctx) { // Set appropriate ThinLTO cache policy cacheDirFormat := "-Wl,--thinlto-cache-dir=" cacheDir := android.PathForOutput(ctx, "thinlto-cache").String() @@ -110,12 +121,11 @@ func (lto *lto) flags(ctx BaseModuleContext, flags Flags) Flags { flags.Local.LdFlags = append(flags.Local.LdFlags, cachePolicyFormat+policy) } - // If the module does not have a profile, be conservative and do not inline - // or unroll loops during LTO, in order to prevent significant size bloat. + // If the module does not have a profile, be conservative and limit cross TU inline + // limit to 5 LLVM IR instructions, to balance binary size increase and performance. if !ctx.isPgoCompile() { flags.Local.LdFlags = append(flags.Local.LdFlags, - "-Wl,-plugin-opt,-inline-threshold=0", - "-Wl,-plugin-opt,-unroll-threshold=0") + "-Wl,-plugin-opt,-import-instr-limit=5") } } return flags @@ -127,9 +137,21 @@ func (lto *lto) LTO() bool { return false } - full := Bool(lto.Properties.Lto.Full) - thin := Bool(lto.Properties.Lto.Thin) - return full || thin + return lto.FullLTO() || lto.ThinLTO() +} + +func (lto *lto) FullLTO() bool { + return Bool(lto.Properties.Lto.Full) +} + +func (lto *lto) ThinLTO() bool { + if Bool(lto.Properties.GlobalThin) { + if !lto.Disabled() && !lto.FullLTO() { + return true + } + } + + return Bool(lto.Properties.Lto.Thin) } // Is lto.never explicitly set to true? @@ -140,8 +162,8 @@ func (lto *lto) Disabled() bool { // Propagate lto requirements down from binaries func ltoDepsMutator(mctx android.TopDownMutatorContext) { if m, ok := mctx.Module().(*Module); ok && m.lto.LTO() { - full := Bool(m.lto.Properties.Lto.Full) - thin := Bool(m.lto.Properties.Lto.Thin) + full := m.lto.FullLTO() + thin := m.lto.ThinLTO() if full && thin { mctx.PropertyErrorf("LTO", "FullLTO and ThinLTO are mutually exclusive") } @@ -163,10 +185,10 @@ func ltoDepsMutator(mctx android.TopDownMutatorContext) { if dep, ok := dep.(*Module); ok && dep.lto != nil && !dep.lto.Disabled() { - if full && !Bool(dep.lto.Properties.Lto.Full) { + if full && !dep.lto.FullLTO() { dep.lto.Properties.FullDep = true } - if thin && !Bool(dep.lto.Properties.Lto.Thin) { + if thin && !dep.lto.ThinLTO() { dep.lto.Properties.ThinDep = true } } @@ -183,19 +205,19 @@ func ltoMutator(mctx android.BottomUpMutatorContext) { // Create variations for LTO types required as static // dependencies variationNames := []string{""} - if m.lto.Properties.FullDep && !Bool(m.lto.Properties.Lto.Full) { + if m.lto.Properties.FullDep && !m.lto.FullLTO() { variationNames = append(variationNames, "lto-full") } - if m.lto.Properties.ThinDep && !Bool(m.lto.Properties.Lto.Thin) { + if m.lto.Properties.ThinDep && !m.lto.ThinLTO() { variationNames = append(variationNames, "lto-thin") } // Use correct dependencies if LTO property is explicitly set // (mutually exclusive) - if Bool(m.lto.Properties.Lto.Full) { + if m.lto.FullLTO() { mctx.SetDependencyVariation("lto-full") } - if Bool(m.lto.Properties.Lto.Thin) { + if m.lto.ThinLTO() { mctx.SetDependencyVariation("lto-thin") } diff --git a/cc/ndk_library.go b/cc/ndk_library.go index fe3efc01e..5682d1c6b 100644 --- a/cc/ndk_library.go +++ b/cc/ndk_library.go @@ -16,7 +16,6 @@ package cc import ( "fmt" - "strconv" "strings" "sync" @@ -52,6 +51,10 @@ var ( ndkKnownLibsLock sync.Mutex ) +// The First_version and Unversioned_until properties of this struct should not +// be used directly, but rather through the ApiLevel returning methods +// firstVersion() and unversionedUntil(). + // Creates a stub shared library based on the provided version file. // // Example: @@ -77,9 +80,7 @@ type libraryProperties struct { // https://github.com/android-ndk/ndk/issues/265. Unversioned_until *string - // Private property for use by the mutator that splits per-API level. Can be - // one of <number:sdk_version> or <codename> or "current" passed to - // "ndkstubgen.py" as it is + // Use via apiLevel on the stubDecorator. ApiLevel string `blueprint:"mutated"` // True if this API is not yet ready to be shipped in the NDK. It will be @@ -96,125 +97,33 @@ type stubDecorator struct { versionScriptPath android.ModuleGenPath parsedCoverageXmlPath android.ModuleOutPath installPath android.Path -} - -// OMG GO -func intMax(a int, b int) int { - if a > b { - return a - } else { - return b - } -} - -func normalizeNdkApiLevel(ctx android.BaseModuleContext, apiLevel string, - arch android.Arch) (string, error) { - - if apiLevel == "" { - panic("empty apiLevel not allowed") - } - - if apiLevel == "current" { - return apiLevel, nil - } - - minVersion := ctx.Config().MinSupportedSdkVersion() - firstArchVersions := map[android.ArchType]int{ - android.Arm: minVersion, - android.Arm64: 21, - android.X86: minVersion, - android.X86_64: 21, - } - - firstArchVersion, ok := firstArchVersions[arch.ArchType] - if !ok { - panic(fmt.Errorf("Arch %q not found in firstArchVersions", arch.ArchType)) - } - - if apiLevel == "minimum" { - return strconv.Itoa(firstArchVersion), nil - } - // If the NDK drops support for a platform version, we don't want to have to - // fix up every module that was using it as its SDK version. Clip to the - // supported version here instead. - version, err := strconv.Atoi(apiLevel) - if err != nil { - // Non-integer API levels are codenames. - return apiLevel, nil - } - version = intMax(version, minVersion) - - return strconv.Itoa(intMax(version, firstArchVersion)), nil + apiLevel android.ApiLevel + firstVersion android.ApiLevel + unversionedUntil android.ApiLevel } -func getFirstGeneratedVersion(firstSupportedVersion string, platformVersion int) (int, error) { - if firstSupportedVersion == "current" { - return platformVersion + 1, nil - } - - return strconv.Atoi(firstSupportedVersion) -} - -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 - } - - if String(stub.properties.Unversioned_until) == "current" { - if stub.properties.ApiLevel == "current" { - return true, nil - } else { - return false, nil - } - } - - if stub.properties.ApiLevel == "current" { - return true, nil - } - - unversionedUntil, err := android.ApiStrToNum(ctx, String(stub.properties.Unversioned_until)) - if err != nil { - return true, err - } - - version, err := android.ApiStrToNum(ctx, stub.properties.ApiLevel) - if err != nil { - return true, err - } - - return version >= unversionedUntil, nil +func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool { + return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil) } func generatePerApiVariants(ctx android.BottomUpMutatorContext, m *Module, - propName string, propValue string, perSplit func(*Module, string)) { - platformVersion := ctx.Config().PlatformSdkVersionInt() - - firstSupportedVersion, err := normalizeNdkApiLevel(ctx, propValue, - ctx.Arch()) - if err != nil { - ctx.PropertyErrorf(propName, err.Error()) - } - - firstGenVersion, err := getFirstGeneratedVersion(firstSupportedVersion, - platformVersion) - if err != nil { - // In theory this is impossible because we've already run this through - // normalizeNdkApiLevel above. - ctx.PropertyErrorf(propName, err.Error()) - } - - var versionStrs []string - for version := firstGenVersion; version <= platformVersion; version++ { - versionStrs = append(versionStrs, strconv.Itoa(version)) + from android.ApiLevel, perSplit func(*Module, android.ApiLevel)) { + + var versions []android.ApiLevel + versionStrs := []string{} + for _, version := range ctx.Config().AllSupportedApiLevels() { + if version.GreaterThanOrEqualTo(from) { + versions = append(versions, version) + versionStrs = append(versionStrs, version.String()) + } } - versionStrs = append(versionStrs, ctx.Config().PlatformVersionActiveCodenames()...) - versionStrs = append(versionStrs, "current") + versions = append(versions, android.FutureApiLevel) + versionStrs = append(versionStrs, android.FutureApiLevel.String()) modules := ctx.CreateVariations(versionStrs...) for i, module := range modules { - perSplit(module.(*Module), versionStrs[i]) + perSplit(module.(*Module), versions[i]) } } @@ -228,25 +137,56 @@ func NdkApiMutator(ctx android.BottomUpMutatorContext) { ctx.Module().Disable() return } - generatePerApiVariants(ctx, m, "first_version", - String(compiler.properties.First_version), - func(m *Module, version string) { + firstVersion, err := nativeApiLevelFromUser(ctx, + String(compiler.properties.First_version)) + if err != nil { + ctx.PropertyErrorf("first_version", err.Error()) + return + } + generatePerApiVariants(ctx, m, firstVersion, + func(m *Module, version android.ApiLevel) { m.compiler.(*stubDecorator).properties.ApiLevel = - version + version.String() }) } else if m.SplitPerApiLevel() && m.IsSdkVariant() { if ctx.Os() != android.Android { return } - generatePerApiVariants(ctx, m, "min_sdk_version", - m.MinSdkVersion(), func(m *Module, version string) { - m.Properties.Sdk_version = &version + from, err := nativeApiLevelFromUser(ctx, m.MinSdkVersion()) + if err != nil { + ctx.PropertyErrorf("min_sdk_version", err.Error()) + return + } + generatePerApiVariants(ctx, m, from, + func(m *Module, version android.ApiLevel) { + m.Properties.Sdk_version = StringPtr(version.String()) }) } } } } +func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool { + this.apiLevel = nativeApiLevelOrPanic(ctx, this.properties.ApiLevel) + + var err error + this.firstVersion, err = nativeApiLevelFromUser(ctx, + String(this.properties.First_version)) + if err != nil { + ctx.PropertyErrorf("first_version", err.Error()) + return false + } + + this.unversionedUntil, err = nativeApiLevelFromUserWithDefault(ctx, + String(this.properties.Unversioned_until), "minimum") + if err != nil { + ctx.PropertyErrorf("unversioned_until", err.Error()) + return false + } + + return true +} + func (c *stubDecorator) compilerInit(ctx BaseModuleContext) { c.baseCompiler.compilerInit(ctx) @@ -340,11 +280,16 @@ func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) O ctx.PropertyErrorf("symbol_file", "must end with .map.txt") } + if !c.initializeProperties(ctx) { + // Emits its own errors, so we don't need to. + return Objects{} + } + symbolFile := String(c.properties.Symbol_file) objs, versionScript := compileStubLibrary(ctx, flags, symbolFile, - c.properties.ApiLevel, "") + c.apiLevel.String(), "") c.versionScriptPath = versionScript - if c.properties.ApiLevel == "current" && ctx.PrimaryArch() { + if c.apiLevel.IsCurrent() && ctx.PrimaryArch() { c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile) } return objs @@ -366,12 +311,7 @@ func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags { func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path { - useVersionScript, err := shouldUseVersionScript(ctx, stub) - if err != nil { - ctx.ModuleErrorf(err.Error()) - } - - if useVersionScript { + if shouldUseVersionScript(ctx, stub) { linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag) flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) @@ -386,8 +326,6 @@ func (stub *stubDecorator) nativeCoverage() bool { func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) { arch := ctx.Target().Arch.ArchType.Name - apiLevel := stub.properties.ApiLevel - // arm64 isn't actually a multilib toolchain, so unlike the other LP64 // architectures it's just installed to lib. libDir := "lib" @@ -396,7 +334,7 @@ func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) { } installDir := getNdkInstallBase(ctx).Join(ctx, fmt.Sprintf( - "platforms/android-%s/arch-%s/usr/%s", apiLevel, arch, libDir)) + "platforms/android-%s/arch-%s/usr/%s", stub.apiLevel, arch, libDir)) stub.installPath = ctx.InstallFile(installDir, path.Base(), path) } diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py index 2f4326a9c..86bf6ff85 100755 --- a/cc/ndkstubgen/__init__.py +++ b/cc/ndkstubgen/__init__.py @@ -20,13 +20,16 @@ import json import logging import os import sys +from typing import Iterable, TextIO import symbolfile +from symbolfile import Arch, Version class Generator: """Output generator that writes stub source files and version scripts.""" - def __init__(self, src_file, version_script, arch, api, llndk, apex): + def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch, + api: int, llndk: bool, apex: bool) -> None: self.src_file = src_file self.version_script = version_script self.arch = arch @@ -34,12 +37,12 @@ class Generator: self.llndk = llndk self.apex = apex - def write(self, versions): + def write(self, versions: Iterable[Version]) -> None: """Writes all symbol data to the output files.""" for version in versions: self.write_version(version) - def write_version(self, version): + def write_version(self, version: Version) -> None: """Writes a single version block's data to the output files.""" if symbolfile.should_omit_version(version, self.arch, self.api, self.llndk, self.apex): @@ -84,7 +87,7 @@ class Generator: self.version_script.write('}' + base + ';\n') -def parse_args(): +def parse_args() -> argparse.Namespace: """Parses and returns command line arguments.""" parser = argparse.ArgumentParser() @@ -100,23 +103,31 @@ def parse_args(): parser.add_argument( '--apex', action='store_true', help='Use the APEX variant.') + # https://github.com/python/mypy/issues/1317 + # mypy has issues with using os.path.realpath as an argument here. parser.add_argument( - '--api-map', type=os.path.realpath, required=True, + '--api-map', + type=os.path.realpath, # type: ignore + required=True, help='Path to the API level map JSON file.') parser.add_argument( - 'symbol_file', type=os.path.realpath, help='Path to symbol file.') + 'symbol_file', + type=os.path.realpath, # type: ignore + help='Path to symbol file.') parser.add_argument( - 'stub_src', type=os.path.realpath, + 'stub_src', + type=os.path.realpath, # type: ignore help='Path to output stub source file.') parser.add_argument( - 'version_script', type=os.path.realpath, + 'version_script', + type=os.path.realpath, # type: ignore help='Path to output version script.') return parser.parse_args() -def main(): +def main() -> None: """Program entry point.""" args = parse_args() diff --git a/cc/ndkstubgen/mypy.ini b/cc/ndkstubgen/mypy.ini new file mode 100644 index 000000000..82aa7eb9d --- /dev/null +++ b/cc/ndkstubgen/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +disallow_untyped_defs = True diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py index 70bcf781c..6d2c9d673 100755 --- a/cc/ndkstubgen/test_ndkstubgen.py +++ b/cc/ndkstubgen/test_ndkstubgen.py @@ -21,19 +21,20 @@ import unittest import ndkstubgen import symbolfile +from symbolfile import Arch, Tag # pylint: disable=missing-docstring class GeneratorTest(unittest.TestCase): - def test_omit_version(self): + def test_omit_version(self) -> None: # Thorough testing of the cases involved here is handled by # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, False) version = symbolfile.Version('VERSION_PRIVATE', None, [], [ symbolfile.Symbol('foo', []), @@ -42,74 +43,75 @@ class GeneratorTest(unittest.TestCase): self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) - version = symbolfile.Version('VERSION', None, ['x86'], [ + version = symbolfile.Version('VERSION', None, [Tag('x86')], [ symbolfile.Symbol('foo', []), ]) generator.write_version(version) self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) - version = symbolfile.Version('VERSION', None, ['introduced=14'], [ + version = symbolfile.Version('VERSION', None, [Tag('introduced=14')], [ symbolfile.Symbol('foo', []), ]) generator.write_version(version) self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) - def test_omit_symbol(self): + def test_omit_symbol(self) -> None: # Thorough testing of the cases involved here is handled by # SymbolPresenceTest. src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, False) version = symbolfile.Version('VERSION_1', None, [], [ - symbolfile.Symbol('foo', ['x86']), + symbolfile.Symbol('foo', [Tag('x86')]), ]) generator.write_version(version) self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) version = symbolfile.Version('VERSION_1', None, [], [ - symbolfile.Symbol('foo', ['introduced=14']), + symbolfile.Symbol('foo', [Tag('introduced=14')]), ]) generator.write_version(version) self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) version = symbolfile.Version('VERSION_1', None, [], [ - symbolfile.Symbol('foo', ['llndk']), + symbolfile.Symbol('foo', [Tag('llndk')]), ]) generator.write_version(version) self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) version = symbolfile.Version('VERSION_1', None, [], [ - symbolfile.Symbol('foo', ['apex']), + symbolfile.Symbol('foo', [Tag('apex')]), ]) generator.write_version(version) self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) - def test_write(self): + def test_write(self) -> None: src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, False) versions = [ symbolfile.Version('VERSION_1', None, [], [ symbolfile.Symbol('foo', []), - symbolfile.Symbol('bar', ['var']), - symbolfile.Symbol('woodly', ['weak']), - symbolfile.Symbol('doodly', ['weak', 'var']), + symbolfile.Symbol('bar', [Tag('var')]), + symbolfile.Symbol('woodly', [Tag('weak')]), + symbolfile.Symbol('doodly', + [Tag('weak'), Tag('var')]), ]), symbolfile.Version('VERSION_2', 'VERSION_1', [], [ symbolfile.Symbol('baz', []), ]), symbolfile.Version('VERSION_3', 'VERSION_1', [], [ - symbolfile.Symbol('qux', ['versioned=14']), + symbolfile.Symbol('qux', [Tag('versioned=14')]), ]), ] @@ -141,7 +143,7 @@ class GeneratorTest(unittest.TestCase): class IntegrationTest(unittest.TestCase): - def test_integration(self): + def test_integration(self) -> None: api_map = { 'O': 9000, 'P': 9001, @@ -178,14 +180,14 @@ class IntegrationTest(unittest.TestCase): wobble; } VERSION_4; """)) - parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, - False, False) + parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'), + 9, False, False) versions = parser.parse() src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, False) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -213,7 +215,7 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) - def test_integration_future_api(self): + def test_integration_future_api(self) -> None: api_map = { 'O': 9000, 'P': 9001, @@ -230,14 +232,14 @@ class IntegrationTest(unittest.TestCase): *; }; """)) - parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9001, - False, False) + parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'), + 9001, False, False) versions = parser.parse() src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9001, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9001, False, False) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -255,7 +257,7 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) - def test_multiple_definition(self): + def test_multiple_definition(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { global: @@ -280,8 +282,8 @@ class IntegrationTest(unittest.TestCase): } VERSION_2; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, - False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) with self.assertRaises( symbolfile.MultiplyDefinedSymbolError) as ex_context: @@ -289,7 +291,7 @@ class IntegrationTest(unittest.TestCase): self.assertEqual(['bar', 'foo'], ex_context.exception.multiply_defined_symbols) - def test_integration_with_apex(self): + def test_integration_with_apex(self) -> None: api_map = { 'O': 9000, 'P': 9001, @@ -328,14 +330,14 @@ class IntegrationTest(unittest.TestCase): wobble; } VERSION_4; """)) - parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, - False, True) + parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'), + 9, False, True) versions = parser.parse() src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, True) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, True) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -369,7 +371,8 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) -def main(): + +def main() -> None: suite = unittest.TestLoader().loadTestsFromName(__name__) unittest.TextTestRunner(verbosity=3).run(suite) @@ -70,6 +70,7 @@ type PgoProperties struct { PgoPresent bool `blueprint:"mutated"` ShouldProfileModule bool `blueprint:"mutated"` PgoCompile bool `blueprint:"mutated"` + PgoInstrLink bool `blueprint:"mutated"` } type pgo struct { @@ -89,13 +90,12 @@ func (pgo *pgo) props() []interface{} { } func (props *PgoProperties) addInstrumentationProfileGatherFlags(ctx ModuleContext, flags Flags) Flags { - flags.Local.CFlags = append(flags.Local.CFlags, props.Pgo.Cflags...) - - flags.Local.CFlags = append(flags.Local.CFlags, profileInstrumentFlag) - // The profile runtime is added below in deps(). Add the below - // flag, which is the only other link-time action performed by - // the Clang driver during link. - flags.Local.LdFlags = append(flags.Local.LdFlags, "-u__llvm_profile_runtime") + // Add to C flags iff PGO is explicitly enabled for this module. + if props.ShouldProfileModule { + flags.Local.CFlags = append(flags.Local.CFlags, props.Pgo.Cflags...) + flags.Local.CFlags = append(flags.Local.CFlags, profileInstrumentFlag) + } + flags.Local.LdFlags = append(flags.Local.LdFlags, profileInstrumentFlag) return flags } func (props *PgoProperties) addSamplingProfileGatherFlags(ctx ModuleContext, flags Flags) Flags { @@ -250,10 +250,12 @@ func (pgo *pgo) begin(ctx BaseModuleContext) { if pgoBenchmarksMap["all"] == true || pgoBenchmarksMap["ALL"] == true { pgo.Properties.ShouldProfileModule = true + pgo.Properties.PgoInstrLink = pgo.Properties.isInstrumentation() } else { for _, b := range pgo.Properties.Pgo.Benchmarks { if pgoBenchmarksMap[b] == true { pgo.Properties.ShouldProfileModule = true + pgo.Properties.PgoInstrLink = pgo.Properties.isInstrumentation() break } } @@ -286,10 +288,42 @@ func (pgo *pgo) flags(ctx ModuleContext, flags Flags) Flags { return flags } - props := pgo.Properties + // Deduce PgoInstrLink property i.e. whether this module needs to be + // linked with profile-generation flags. Here, we're setting it if any + // dependency needs PGO instrumentation. It is initially set in + // begin() if PGO is directly enabled for this module. + if ctx.static() && !ctx.staticBinary() { + // For static libraries, check if any whole_static_libs are + // linked with profile generation + ctx.VisitDirectDeps(func(m android.Module) { + if depTag, ok := ctx.OtherModuleDependencyTag(m).(libraryDependencyTag); ok { + if depTag.static() && depTag.wholeStatic { + if cc, ok := m.(*Module); ok { + if cc.pgo.Properties.PgoInstrLink { + pgo.Properties.PgoInstrLink = true + } + } + } + } + }) + } else { + // For executables and shared libraries, check all static dependencies. + ctx.VisitDirectDeps(func(m android.Module) { + if depTag, ok := ctx.OtherModuleDependencyTag(m).(libraryDependencyTag); ok { + if depTag.static() { + if cc, ok := m.(*Module); ok { + if cc.pgo.Properties.PgoInstrLink { + pgo.Properties.PgoInstrLink = true + } + } + } + } + }) + } + props := pgo.Properties // Add flags to profile this module based on its profile_kind - if props.ShouldProfileModule && props.isInstrumentation() { + if (props.ShouldProfileModule && props.isInstrumentation()) || props.PgoInstrLink { // Instrumentation PGO use and gather flags cannot coexist. return props.addInstrumentationProfileGatherFlags(ctx, flags) } else if props.ShouldProfileModule && props.isSampling() { diff --git a/cc/prebuilt.go b/cc/prebuilt.go index 1ee096e18..9d1b01608 100644 --- a/cc/prebuilt.go +++ b/cc/prebuilt.go @@ -16,6 +16,7 @@ package cc import ( "android/soong/android" + "path/filepath" ) func init() { @@ -360,13 +361,18 @@ func (p *prebuiltBinaryLinker) link(ctx ModuleContext, sharedLibPaths = append(sharedLibPaths, deps.SharedLibs...) sharedLibPaths = append(sharedLibPaths, deps.LateSharedLibs...) + var fromPath = in.String() + if !filepath.IsAbs(fromPath) { + fromPath = "$$PWD/" + fromPath + } + ctx.Build(pctx, android.BuildParams{ Rule: android.Symlink, Output: outputFile, Input: in, Implicits: sharedLibPaths, Args: map[string]string{ - "fromPath": "$$PWD/" + in.String(), + "fromPath": fromPath, }, }) @@ -17,7 +17,6 @@ package cc import ( "android/soong/android" "fmt" - "strconv" ) func getNdkStlFamily(m LinkableInterface) string { @@ -136,23 +135,8 @@ func (stl *stl) begin(ctx BaseModuleContext) { } func needsLibAndroidSupport(ctx BaseModuleContext) bool { - versionStr, err := normalizeNdkApiLevel(ctx, ctx.sdkVersion(), ctx.Arch()) - if err != nil { - ctx.PropertyErrorf("sdk_version", err.Error()) - } - - if versionStr == "current" { - return false - } - - version, err := strconv.Atoi(versionStr) - if err != nil { - panic(fmt.Sprintf( - "invalid API level returned from normalizeNdkApiLevel: %q", - versionStr)) - } - - return version < 21 + version := nativeApiLevelOrPanic(ctx, ctx.sdkVersion()) + return version.LessThan(android.FirstNonLibAndroidSupportVersion) } func staticUnwinder(ctx android.BaseModuleContext) string { diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py index faa3823f3..5678e7d83 100644 --- a/cc/symbolfile/__init__.py +++ b/cc/symbolfile/__init__.py @@ -14,15 +14,31 @@ # limitations under the License. # """Parser for Android's version script information.""" +from dataclasses import dataclass import logging import re +from typing import ( + Dict, + Iterable, + List, + Mapping, + NewType, + Optional, + TextIO, + Tuple, +) + + +ApiMap = Mapping[str, int] +Arch = NewType('Arch', str) +Tag = NewType('Tag', str) ALL_ARCHITECTURES = ( - 'arm', - 'arm64', - 'x86', - 'x86_64', + Arch('arm'), + Arch('arm64'), + Arch('x86'), + Arch('x86_64'), ) @@ -30,18 +46,36 @@ ALL_ARCHITECTURES = ( FUTURE_API_LEVEL = 10000 -def logger(): +def logger() -> logging.Logger: """Return the main logger for this module.""" return logging.getLogger(__name__) -def get_tags(line): +@dataclass +class Symbol: + """A symbol definition from a symbol file.""" + + name: str + tags: List[Tag] + + +@dataclass +class Version: + """A version block of a symbol file.""" + + name: str + base: Optional[str] + tags: List[Tag] + symbols: List[Symbol] + + +def get_tags(line: str) -> List[Tag]: """Returns a list of all tags on this line.""" _, _, all_tags = line.strip().partition('#') - return [e for e in re.split(r'\s+', all_tags) if e.strip()] + return [Tag(e) for e in re.split(r'\s+', all_tags) if e.strip()] -def is_api_level_tag(tag): +def is_api_level_tag(tag: Tag) -> bool: """Returns true if this tag has an API level that may need decoding.""" if tag.startswith('introduced='): return True @@ -52,7 +86,7 @@ def is_api_level_tag(tag): return False -def decode_api_level(api, api_map): +def decode_api_level(api: str, api_map: ApiMap) -> int: """Decodes the API level argument into the API level number. For the average case, this just decodes the integer value from the string, @@ -70,12 +104,13 @@ def decode_api_level(api, api_map): return api_map[api] -def decode_api_level_tags(tags, api_map): +def decode_api_level_tags(tags: Iterable[Tag], api_map: ApiMap) -> List[Tag]: """Decodes API level code names in a list of tags. Raises: ParseError: An unknown version name was found in a tag. """ + decoded_tags = list(tags) for idx, tag in enumerate(tags): if not is_api_level_tag(tag): continue @@ -83,13 +118,13 @@ def decode_api_level_tags(tags, api_map): try: decoded = str(decode_api_level(value, api_map)) - tags[idx] = '='.join([name, decoded]) + decoded_tags[idx] = Tag('='.join([name, decoded])) except KeyError: - raise ParseError('Unknown version name in tag: {}'.format(tag)) - return tags + raise ParseError(f'Unknown version name in tag: {tag}') + return decoded_tags -def split_tag(tag): +def split_tag(tag: Tag) -> Tuple[str, str]: """Returns a key/value tuple of the tag. Raises: @@ -103,7 +138,7 @@ def split_tag(tag): return key, value -def get_tag_value(tag): +def get_tag_value(tag: Tag) -> str: """Returns the value of a key/value tag. Raises: @@ -114,12 +149,13 @@ def get_tag_value(tag): return split_tag(tag)[1] -def version_is_private(version): +def version_is_private(version: str) -> bool: """Returns True if the version name should be treated as private.""" return version.endswith('_PRIVATE') or version.endswith('_PLATFORM') -def should_omit_version(version, arch, api, llndk, apex): +def should_omit_version(version: Version, arch: Arch, api: int, llndk: bool, + apex: bool) -> bool: """Returns True if the version section should be ommitted. We want to omit any sections that do not have any symbols we'll have in the @@ -145,7 +181,8 @@ def should_omit_version(version, arch, api, llndk, apex): return False -def should_omit_symbol(symbol, arch, api, llndk, apex): +def should_omit_symbol(symbol: Symbol, arch: Arch, api: int, llndk: bool, + apex: bool) -> bool: """Returns True if the symbol should be omitted.""" no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags keep = no_llndk_no_apex or \ @@ -160,7 +197,7 @@ def should_omit_symbol(symbol, arch, api, llndk, apex): return False -def symbol_in_arch(tags, arch): +def symbol_in_arch(tags: Iterable[Tag], arch: Arch) -> bool: """Returns true if the symbol is present for the given architecture.""" has_arch_tags = False for tag in tags: @@ -175,7 +212,7 @@ def symbol_in_arch(tags, arch): return not has_arch_tags -def symbol_in_api(tags, arch, api): +def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool: """Returns true if the symbol is present for the given API level.""" introduced_tag = None arch_specific = False @@ -197,7 +234,7 @@ def symbol_in_api(tags, arch, api): return api >= int(get_tag_value(introduced_tag)) -def symbol_versioned_in_api(tags, api): +def symbol_versioned_in_api(tags: Iterable[Tag], api: int) -> bool: """Returns true if the symbol should be versioned for the given API. This models the `versioned=API` tag. This should be a very uncommonly @@ -223,68 +260,40 @@ class ParseError(RuntimeError): class MultiplyDefinedSymbolError(RuntimeError): """A symbol name was multiply defined.""" - def __init__(self, multiply_defined_symbols): - super(MultiplyDefinedSymbolError, self).__init__( + def __init__(self, multiply_defined_symbols: Iterable[str]) -> None: + super().__init__( 'Version script contains multiple definitions for: {}'.format( ', '.join(multiply_defined_symbols))) self.multiply_defined_symbols = multiply_defined_symbols -class Version: - """A version block of a symbol file.""" - def __init__(self, name, base, tags, symbols): - self.name = name - self.base = base - self.tags = tags - self.symbols = symbols - - def __eq__(self, other): - if self.name != other.name: - return False - if self.base != other.base: - return False - if self.tags != other.tags: - return False - if self.symbols != other.symbols: - return False - return True - - -class Symbol: - """A symbol definition from a symbol file.""" - def __init__(self, name, tags): - self.name = name - self.tags = tags - - def __eq__(self, other): - return self.name == other.name and set(self.tags) == set(other.tags) - - class SymbolFileParser: """Parses NDK symbol files.""" - def __init__(self, input_file, api_map, arch, api, llndk, apex): + def __init__(self, input_file: TextIO, api_map: ApiMap, arch: Arch, + api: int, llndk: bool, apex: bool) -> None: self.input_file = input_file self.api_map = api_map self.arch = arch self.api = api self.llndk = llndk self.apex = apex - self.current_line = None + self.current_line: Optional[str] = None - def parse(self): + def parse(self) -> List[Version]: """Parses the symbol file and returns a list of Version objects.""" versions = [] while self.next_line() != '': + assert self.current_line is not None if '{' in self.current_line: versions.append(self.parse_version()) else: raise ParseError( - 'Unexpected contents at top level: ' + self.current_line) + f'Unexpected contents at top level: {self.current_line}') self.check_no_duplicate_symbols(versions) return versions - def check_no_duplicate_symbols(self, versions): + def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None: """Raises errors for multiply defined symbols. This situation is the normal case when symbol versioning is actually @@ -312,12 +321,13 @@ class SymbolFileParser: raise MultiplyDefinedSymbolError( sorted(list(multiply_defined_symbols))) - def parse_version(self): + def parse_version(self) -> Version: """Parses a single version section and returns a Version object.""" + assert self.current_line is not None name = self.current_line.split('{')[0].strip() tags = get_tags(self.current_line) tags = decode_api_level_tags(tags, self.api_map) - symbols = [] + symbols: List[Symbol] = [] global_scope = True cpp_symbols = False while self.next_line() != '': @@ -333,9 +343,7 @@ class SymbolFileParser: cpp_symbols = False else: base = base.rstrip(';').rstrip() - if base == '': - base = None - return Version(name, base, tags, symbols) + return Version(name, base or None, tags, symbols) elif 'extern "C++" {' in self.current_line: cpp_symbols = True elif not cpp_symbols and ':' in self.current_line: @@ -354,8 +362,9 @@ class SymbolFileParser: pass raise ParseError('Unexpected EOF in version block.') - def parse_symbol(self): + def parse_symbol(self) -> Symbol: """Parses a single symbol line and returns a Symbol object.""" + assert self.current_line is not None if ';' not in self.current_line: raise ParseError( 'Expected ; to terminate symbol: ' + self.current_line) @@ -368,7 +377,7 @@ class SymbolFileParser: tags = decode_api_level_tags(tags, self.api_map) return Symbol(name, tags) - def next_line(self): + def next_line(self) -> str: """Returns the next non-empty non-comment line. A return value of '' indicates EOF. diff --git a/cc/symbolfile/mypy.ini b/cc/symbolfile/mypy.ini new file mode 100644 index 000000000..82aa7eb9d --- /dev/null +++ b/cc/symbolfile/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +disallow_untyped_defs = True diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py index c91131fee..92b13999e 100644 --- a/cc/symbolfile/test_symbolfile.py +++ b/cc/symbolfile/test_symbolfile.py @@ -19,12 +19,13 @@ import textwrap import unittest import symbolfile +from symbolfile import Arch, Tag # pylint: disable=missing-docstring class DecodeApiLevelTest(unittest.TestCase): - def test_decode_api_level(self): + def test_decode_api_level(self) -> None: self.assertEqual(9, symbolfile.decode_api_level('9', {})) self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000})) @@ -33,70 +34,73 @@ class DecodeApiLevelTest(unittest.TestCase): class TagsTest(unittest.TestCase): - def test_get_tags_no_tags(self): + def test_get_tags_no_tags(self) -> None: self.assertEqual([], symbolfile.get_tags('')) self.assertEqual([], symbolfile.get_tags('foo bar baz')) - def test_get_tags(self): + def test_get_tags(self) -> None: self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar')) self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz')) - def test_split_tag(self): - self.assertTupleEqual(('foo', 'bar'), symbolfile.split_tag('foo=bar')) - self.assertTupleEqual(('foo', 'bar=baz'), symbolfile.split_tag('foo=bar=baz')) + def test_split_tag(self) -> None: + self.assertTupleEqual(('foo', 'bar'), + symbolfile.split_tag(Tag('foo=bar'))) + self.assertTupleEqual(('foo', 'bar=baz'), + symbolfile.split_tag(Tag('foo=bar=baz'))) with self.assertRaises(ValueError): - symbolfile.split_tag('foo') + symbolfile.split_tag(Tag('foo')) - def test_get_tag_value(self): - self.assertEqual('bar', symbolfile.get_tag_value('foo=bar')) - self.assertEqual('bar=baz', symbolfile.get_tag_value('foo=bar=baz')) + def test_get_tag_value(self) -> None: + self.assertEqual('bar', symbolfile.get_tag_value(Tag('foo=bar'))) + self.assertEqual('bar=baz', + symbolfile.get_tag_value(Tag('foo=bar=baz'))) with self.assertRaises(ValueError): - symbolfile.get_tag_value('foo') + symbolfile.get_tag_value(Tag('foo')) - def test_is_api_level_tag(self): - self.assertTrue(symbolfile.is_api_level_tag('introduced=24')) - self.assertTrue(symbolfile.is_api_level_tag('introduced-arm=24')) - self.assertTrue(symbolfile.is_api_level_tag('versioned=24')) + def test_is_api_level_tag(self) -> None: + self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced=24'))) + self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced-arm=24'))) + self.assertTrue(symbolfile.is_api_level_tag(Tag('versioned=24'))) # Shouldn't try to process things that aren't a key/value tag. - self.assertFalse(symbolfile.is_api_level_tag('arm')) - self.assertFalse(symbolfile.is_api_level_tag('introduced')) - self.assertFalse(symbolfile.is_api_level_tag('versioned')) + self.assertFalse(symbolfile.is_api_level_tag(Tag('arm'))) + self.assertFalse(symbolfile.is_api_level_tag(Tag('introduced'))) + self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned'))) # We don't support arch specific `versioned` tags. - self.assertFalse(symbolfile.is_api_level_tag('versioned-arm=24')) + self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned-arm=24'))) - def test_decode_api_level_tags(self): + def test_decode_api_level_tags(self) -> None: api_map = { 'O': 9000, 'P': 9001, } tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=O', - 'introduced=P', + Tag('introduced=9'), + Tag('introduced-arm=14'), + Tag('versioned=16'), + Tag('arm'), + Tag('introduced=O'), + Tag('introduced=P'), ] expected_tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=9000', - 'introduced=9001', + Tag('introduced=9'), + Tag('introduced-arm=14'), + Tag('versioned=16'), + Tag('arm'), + Tag('introduced=9000'), + Tag('introduced=9001'), ] self.assertListEqual( expected_tags, symbolfile.decode_api_level_tags(tags, api_map)) with self.assertRaises(symbolfile.ParseError): - symbolfile.decode_api_level_tags(['introduced=O'], {}) + symbolfile.decode_api_level_tags([Tag('introduced=O')], {}) class PrivateVersionTest(unittest.TestCase): - def test_version_is_private(self): + def test_version_is_private(self) -> None: self.assertFalse(symbolfile.version_is_private('foo')) self.assertFalse(symbolfile.version_is_private('PRIVATE')) self.assertFalse(symbolfile.version_is_private('PLATFORM')) @@ -110,191 +114,227 @@ class PrivateVersionTest(unittest.TestCase): class SymbolPresenceTest(unittest.TestCase): - def test_symbol_in_arch(self): - self.assertTrue(symbolfile.symbol_in_arch([], 'arm')) - self.assertTrue(symbolfile.symbol_in_arch(['arm'], 'arm')) - - self.assertFalse(symbolfile.symbol_in_arch(['x86'], 'arm')) - - def test_symbol_in_api(self): - self.assertTrue(symbolfile.symbol_in_api([], 'arm', 9)) - self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 9)) - self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 14)) - self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) - self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) - self.assertTrue(symbolfile.symbol_in_api(['introduced-x86=14'], 'arm', 9)) - self.assertTrue(symbolfile.symbol_in_api( - ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14)) - self.assertTrue(symbolfile.symbol_in_api( - ['introduced=9', 'introduced-x86=21'], 'arm', 14)) - self.assertTrue(symbolfile.symbol_in_api( - ['introduced=21', 'introduced-arm=9'], 'arm', 14)) - self.assertTrue(symbolfile.symbol_in_api( - ['future'], 'arm', symbolfile.FUTURE_API_LEVEL)) - - self.assertFalse(symbolfile.symbol_in_api(['introduced=14'], 'arm', 9)) - self.assertFalse(symbolfile.symbol_in_api(['introduced-arm=14'], 'arm', 9)) - self.assertFalse(symbolfile.symbol_in_api(['future'], 'arm', 9)) - self.assertFalse(symbolfile.symbol_in_api( - ['introduced=9', 'future'], 'arm', 14)) - self.assertFalse(symbolfile.symbol_in_api( - ['introduced-arm=9', 'future'], 'arm', 14)) - self.assertFalse(symbolfile.symbol_in_api( - ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14)) - self.assertFalse(symbolfile.symbol_in_api( - ['introduced=9', 'introduced-arm=21'], 'arm', 14)) - self.assertFalse(symbolfile.symbol_in_api( - ['introduced=21', 'introduced-x86=9'], 'arm', 14)) + def test_symbol_in_arch(self) -> None: + self.assertTrue(symbolfile.symbol_in_arch([], Arch('arm'))) + self.assertTrue(symbolfile.symbol_in_arch([Tag('arm')], Arch('arm'))) + + self.assertFalse(symbolfile.symbol_in_arch([Tag('x86')], Arch('arm'))) + + def test_symbol_in_api(self) -> None: + self.assertTrue(symbolfile.symbol_in_api([], Arch('arm'), 9)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 9)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 14)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'), + 14)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'), + 14)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced-x86=14')], Arch('arm'), + 9)) + self.assertTrue( + symbolfile.symbol_in_api( + [Tag('introduced-arm=9'), + Tag('introduced-x86=21')], Arch('arm'), 14)) + self.assertTrue( + symbolfile.symbol_in_api( + [Tag('introduced=9'), + Tag('introduced-x86=21')], Arch('arm'), 14)) + self.assertTrue( + symbolfile.symbol_in_api( + [Tag('introduced=21'), + Tag('introduced-arm=9')], Arch('arm'), 14)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('future')], Arch('arm'), + symbolfile.FUTURE_API_LEVEL)) + + self.assertFalse( + symbolfile.symbol_in_api([Tag('introduced=14')], Arch('arm'), 9)) + self.assertFalse( + symbolfile.symbol_in_api([Tag('introduced-arm=14')], Arch('arm'), + 9)) + self.assertFalse( + symbolfile.symbol_in_api([Tag('future')], Arch('arm'), 9)) + self.assertFalse( + symbolfile.symbol_in_api( + [Tag('introduced=9'), Tag('future')], Arch('arm'), 14)) + self.assertFalse( + symbolfile.symbol_in_api([Tag('introduced-arm=9'), + Tag('future')], Arch('arm'), 14)) + self.assertFalse( + symbolfile.symbol_in_api( + [Tag('introduced-arm=21'), + Tag('introduced-x86=9')], Arch('arm'), 14)) + self.assertFalse( + symbolfile.symbol_in_api( + [Tag('introduced=9'), + Tag('introduced-arm=21')], Arch('arm'), 14)) + self.assertFalse( + symbolfile.symbol_in_api( + [Tag('introduced=21'), + Tag('introduced-x86=9')], Arch('arm'), 14)) # Interesting edge case: this symbol should be omitted from the # library, but this call should still return true because none of the # tags indiciate that it's not present in this API level. - self.assertTrue(symbolfile.symbol_in_api(['x86'], 'arm', 9)) + self.assertTrue(symbolfile.symbol_in_api([Tag('x86')], Arch('arm'), 9)) - def test_verioned_in_api(self): + def test_verioned_in_api(self) -> None: self.assertTrue(symbolfile.symbol_versioned_in_api([], 9)) - self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 9)) - self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 14)) + self.assertTrue( + symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 9)) + self.assertTrue( + symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 14)) - self.assertFalse(symbolfile.symbol_versioned_in_api(['versioned=14'], 9)) + self.assertFalse( + symbolfile.symbol_versioned_in_api([Tag('versioned=14')], 9)) class OmitVersionTest(unittest.TestCase): - def test_omit_private(self): + def test_omit_private(self) -> None: self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, [], []), 'arm', 9, False, + symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False, False)) self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo_PRIVATE', None, [], []), 'arm', 9, - False, False)) + symbolfile.Version('foo_PRIVATE', None, [], []), Arch('arm'), + 9, False, False)) self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo_PLATFORM', None, [], []), 'arm', 9, - False, False)) + symbolfile.Version('foo_PLATFORM', None, [], []), Arch('arm'), + 9, False, False)) self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['platform-only'], []), 'arm', - 9, False, False)) + symbolfile.Version('foo', None, [Tag('platform-only')], []), + Arch('arm'), 9, False, False)) - def test_omit_llndk(self): + def test_omit_llndk(self) -> None: self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, - False, False)) + symbolfile.Version('foo', None, [Tag('llndk')], []), + Arch('arm'), 9, False, False)) self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, [], []), 'arm', 9, True, + symbolfile.Version('foo', None, [], []), Arch('arm'), 9, True, False)) self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, True, - False)) + symbolfile.Version('foo', None, [Tag('llndk')], []), + Arch('arm'), 9, True, False)) - def test_omit_apex(self): + def test_omit_apex(self) -> None: self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, - False)) + symbolfile.Version('foo', None, [Tag('apex')], []), + Arch('arm'), 9, False, False)) self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, [], []), 'arm', 9, False, + symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False, True)) self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, - True)) + symbolfile.Version('foo', None, [Tag('apex')], []), + Arch('arm'), 9, False, True)) - def test_omit_arch(self): + def test_omit_arch(self) -> None: self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, [], []), 'arm', 9, False, + symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False, False)) self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['arm'], []), 'arm', 9, False, - False)) + symbolfile.Version('foo', None, [Tag('arm')], []), Arch('arm'), + 9, False, False)) self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['x86'], []), 'arm', 9, False, - False)) + symbolfile.Version('foo', None, [Tag('x86')], []), Arch('arm'), + 9, False, False)) - def test_omit_api(self): + def test_omit_api(self) -> None: self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, [], []), 'arm', 9, False, + symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False, False)) self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['introduced=9'], []), 'arm', - 9, False, False)) + symbolfile.Version('foo', None, [Tag('introduced=9')], []), + Arch('arm'), 9, False, False)) self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['introduced=14'], []), 'arm', - 9, False, False)) + symbolfile.Version('foo', None, [Tag('introduced=14')], []), + Arch('arm'), 9, False, False)) class OmitSymbolTest(unittest.TestCase): - def test_omit_llndk(self): + def test_omit_llndk(self) -> None: self.assertTrue( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), - 'arm', 9, False, False)) + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9, + False, False)) self.assertFalse( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', - 9, True, False)) + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), + Arch('arm'), 9, True, False)) self.assertFalse( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), - 'arm', 9, True, False)) + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9, True, + False)) - def test_omit_apex(self): + def test_omit_apex(self) -> None: self.assertTrue( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']), - 'arm', 9, False, False)) + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False, + False)) self.assertFalse( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', - 9, False, True)) + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), + Arch('arm'), 9, False, True)) self.assertFalse( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']), - 'arm', 9, False, True)) + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False, + True)) - def test_omit_arch(self): + def test_omit_arch(self) -> None: self.assertFalse( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', - 9, False, False)) + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), + Arch('arm'), 9, False, False)) self.assertFalse( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['arm']), - 'arm', 9, False, False)) + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', [Tag('arm')]), Arch('arm'), 9, False, + False)) self.assertTrue( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['x86']), - 'arm', 9, False, False)) + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', [Tag('x86')]), Arch('arm'), 9, False, + False)) - def test_omit_api(self): + def test_omit_api(self) -> None: self.assertFalse( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', - 9, False, False)) + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), + Arch('arm'), 9, False, False)) self.assertFalse( symbolfile.should_omit_symbol( - symbolfile.Symbol('foo', ['introduced=9']), 'arm', 9, False, - False)) + symbolfile.Symbol('foo', [Tag('introduced=9')]), Arch('arm'), + 9, False, False)) self.assertTrue( symbolfile.should_omit_symbol( - symbolfile.Symbol('foo', ['introduced=14']), 'arm', 9, False, - False)) + symbolfile.Symbol('foo', [Tag('introduced=14')]), Arch('arm'), + 9, False, False)) class SymbolFileParseTest(unittest.TestCase): - def test_next_line(self): + def test_next_line(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ foo @@ -302,10 +342,12 @@ class SymbolFileParseTest(unittest.TestCase): # baz qux """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) self.assertIsNone(parser.current_line) self.assertEqual('foo', parser.next_line().strip()) + assert parser.current_line is not None self.assertEqual('foo', parser.current_line.strip()) self.assertEqual('bar', parser.next_line().strip()) @@ -317,7 +359,7 @@ class SymbolFileParseTest(unittest.TestCase): self.assertEqual('', parser.next_line()) self.assertEqual('', parser.current_line) - def test_parse_version(self): + def test_parse_version(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { # foo bar baz; @@ -327,7 +369,8 @@ class SymbolFileParseTest(unittest.TestCase): VERSION_2 { } VERSION_1; # asdf """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() version = parser.parse_version() @@ -337,7 +380,7 @@ class SymbolFileParseTest(unittest.TestCase): expected_symbols = [ symbolfile.Symbol('baz', []), - symbolfile.Symbol('qux', ['woodly', 'doodly']), + symbolfile.Symbol('qux', [Tag('woodly'), Tag('doodly')]), ] self.assertEqual(expected_symbols, version.symbols) @@ -347,32 +390,35 @@ class SymbolFileParseTest(unittest.TestCase): self.assertEqual('VERSION_1', version.base) self.assertEqual([], version.tags) - def test_parse_version_eof(self): + def test_parse_version_eof(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_unknown_scope_label(self): + def test_unknown_scope_label(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { foo: } """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_parse_symbol(self): + def test_parse_symbol(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ foo; bar; # baz qux """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() symbol = parser.parse_symbol() @@ -384,48 +430,51 @@ class SymbolFileParseTest(unittest.TestCase): self.assertEqual('bar', symbol.name) self.assertEqual(['baz', 'qux'], symbol.tags) - def test_wildcard_symbol_global(self): + def test_wildcard_symbol_global(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { *; }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_wildcard_symbol_local(self): + def test_wildcard_symbol_local(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { local: *; }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() version = parser.parse_version() self.assertEqual([], version.symbols) - def test_missing_semicolon(self): + def test_missing_semicolon(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { foo }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_parse_fails_invalid_input(self): + def test_parse_fails_invalid_input(self) -> None: with self.assertRaises(symbolfile.ParseError): input_file = io.StringIO('foo') - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, - False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), + 16, False, False) parser.parse() - def test_parse(self): + def test_parse(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { local: @@ -443,23 +492,24 @@ class SymbolFileParseTest(unittest.TestCase): qwerty; } VERSION_1; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) versions = parser.parse() expected = [ symbolfile.Version('VERSION_1', None, [], [ symbolfile.Symbol('foo', []), - symbolfile.Symbol('bar', ['baz']), + symbolfile.Symbol('bar', [Tag('baz')]), ]), - symbolfile.Version('VERSION_2', 'VERSION_1', ['wasd'], [ + symbolfile.Version('VERSION_2', 'VERSION_1', [Tag('wasd')], [ symbolfile.Symbol('woodly', []), - symbolfile.Symbol('doodly', ['asdf']), + symbolfile.Symbol('doodly', [Tag('asdf')]), ]), ] self.assertEqual(expected, versions) - def test_parse_llndk_apex_symbol(self): + def test_parse_llndk_apex_symbol(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { foo; @@ -468,7 +518,8 @@ class SymbolFileParseTest(unittest.TestCase): qux; # apex }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, True) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, True) parser.next_line() version = parser.parse_version() @@ -477,14 +528,14 @@ class SymbolFileParseTest(unittest.TestCase): expected_symbols = [ symbolfile.Symbol('foo', []), - symbolfile.Symbol('bar', ['llndk']), - symbolfile.Symbol('baz', ['llndk', 'apex']), - symbolfile.Symbol('qux', ['apex']), + symbolfile.Symbol('bar', [Tag('llndk')]), + symbolfile.Symbol('baz', [Tag('llndk'), Tag('apex')]), + symbolfile.Symbol('qux', [Tag('apex')]), ] self.assertEqual(expected_symbols, version.symbols) -def main(): +def main() -> None: suite = unittest.TestLoader().loadTestsFromName(__name__) unittest.TextTestRunner(verbosity=3).run(suite) |