diff --git a/Android.bp b/Android.bp index 488146c78..273f87e8c 100644 --- a/Android.bp +++ b/Android.bp @@ -134,6 +134,10 @@ bootstrap_go_package { "cc/toolchain.go", "cc/util.go", + "cc/ndk_headers.go", + "cc/ndk_library.go", + "cc/ndk_sysroot.go", + "cc/arm_device.go", "cc/arm64_device.go", "cc/mips_device.go", diff --git a/cc/androidmk.go b/cc/androidmk.go index 4f76dc9e3..df44a4c21 100644 --- a/cc/androidmk.go +++ b/cc/androidmk.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "path/filepath" + "strconv" "strings" "android/soong/android" @@ -183,3 +184,22 @@ func (installer *baseInstaller) AndroidMk(ctx AndroidMkContext, ret *android.And return nil }) } + +func (c *stubCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) { + ret.SubName = "." + strconv.Itoa(c.properties.ApiLevel) +} + +func (installer *stubInstaller) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) { + ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error { + path, file := filepath.Split(installer.installPath) + stem := strings.TrimSuffix(file, filepath.Ext(file)) + fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path) + fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem) + + // Prevent make from installing the libraries to obj/lib (since we have + // dozens of libraries with the same name, they'll clobber each other + // and the real versions of the libraries from the platform). + fmt.Fprintln(w, "LOCAL_COPY_TO_INTERMEDIATE_LIBRARIES := false") + return nil + }) +} diff --git a/cc/cc.go b/cc/cc.go index 60bc4d472..12d9ed713 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -58,6 +58,7 @@ func init() { // the Go initialization order because this package depends on common, so common's init // functions will run first. android.RegisterBottomUpMutator("link", linkageMutator) + android.RegisterBottomUpMutator("ndk_api", ndkApiMutator) android.RegisterBottomUpMutator("test_per_src", testPerSrcMutator) android.RegisterBottomUpMutator("deps", depsMutator) @@ -70,6 +71,10 @@ func init() { var ( HostPrebuiltTag = pctx.VariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS) + + // These libraries have migrated over to the new ndk_library, which is added + // as a variation dependency via depsMutator. + ndkMigratedLibs = []string{} ) // Flags used by lots of devices. Putting them in package static variables will save bytes in @@ -555,6 +560,8 @@ var ( crtBeginDepTag = dependencyTag{name: "crtbegin"} crtEndDepTag = dependencyTag{name: "crtend"} reuseObjTag = dependencyTag{name: "reuse objects"} + ndkStubDepTag = dependencyTag{name: "ndk stub", library: true} + ndkLateStubDepTag = dependencyTag{name: "ndk late stub", library: true} ) // Module contains the properties and members used by all C/C++ module types, and implements @@ -883,20 +890,40 @@ func (c *Module) depsMutator(actx android.BottomUpMutatorContext) { c.Properties.AndroidMkSharedLibs = append(c.Properties.AndroidMkSharedLibs, deps.SharedLibs...) c.Properties.AndroidMkSharedLibs = append(c.Properties.AndroidMkSharedLibs, deps.LateSharedLibs...) + variantNdkLibs := []string{} + variantLateNdkLibs := []string{} if ctx.sdk() { - version := "." + ctx.sdkVersion() + version := ctx.sdkVersion() - rewriteNdkLibs := func(list []string) []string { - for i, entry := range list { + // Rewrites the names of shared libraries into the names of the NDK + // libraries where appropriate. This returns two slices. + // + // The first is a list of non-variant shared libraries (either rewritten + // NDK libraries to the modules in prebuilts/ndk, or not rewritten + // because they are not NDK libraries). + // + // The second is a list of ndk_library modules. These need to be + // separated because they are a variation dependency and must be added + // in a different manner. + rewriteNdkLibs := func(list []string) ([]string, []string) { + variantLibs := []string{} + nonvariantLibs := []string{} + for _, entry := range list { if inList(entry, ndkPrebuiltSharedLibraries) { - list[i] = "ndk_" + entry + version + if !inList(entry, ndkMigratedLibs) { + nonvariantLibs = append(nonvariantLibs, entry+".ndk."+version) + } else { + variantLibs = append(variantLibs, entry+ndkLibrarySuffix) + } + } else { + nonvariantLibs = append(variantLibs, entry) } } - return list + return nonvariantLibs, variantLibs } - deps.SharedLibs = rewriteNdkLibs(deps.SharedLibs) - deps.LateSharedLibs = rewriteNdkLibs(deps.LateSharedLibs) + deps.SharedLibs, variantNdkLibs = rewriteNdkLibs(deps.SharedLibs) + deps.LateSharedLibs, variantLateNdkLibs = rewriteNdkLibs(deps.LateSharedLibs) } actx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, wholeStaticDepTag, @@ -935,6 +962,12 @@ func (c *Module) depsMutator(actx android.BottomUpMutatorContext) { if deps.CrtEnd != "" { actx.AddDependency(c, crtEndDepTag, deps.CrtEnd) } + + version := ctx.sdkVersion() + actx.AddVariationDependencies([]blueprint.Variation{ + {"ndk_api", version}, {"link", "shared"}}, ndkStubDepTag, variantNdkLibs...) + actx.AddVariationDependencies([]blueprint.Variation{ + {"ndk_api", version}, {"link", "shared"}}, ndkLateStubDepTag, variantLateNdkLibs...) } func depsMutator(ctx android.BottomUpMutatorContext) { @@ -990,6 +1023,11 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { // These are allowed, but don't set sdk_version return true } + if _, ok := to.linker.(*stubLinker); ok { + // These aren't real libraries, but are the stub shared libraries that are included in + // the NDK. + return true + } return to.Properties.Sdk_version != "" } @@ -1073,9 +1111,9 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { var depPtr *android.Paths switch tag { - case sharedDepTag, sharedExportDepTag: + case ndkStubDepTag, sharedDepTag, sharedExportDepTag: depPtr = &depPaths.SharedLibs - case lateSharedDepTag: + case lateSharedDepTag, ndkLateStubDepTag: depPtr = &depPaths.LateSharedLibs case staticDepTag, staticExportDepTag: depPtr = &depPaths.StaticLibs @@ -1190,6 +1228,30 @@ func (compiler *baseCompiler) flags(ctx ModuleContext, flags Flags) Flags { }...) } + if ctx.sdk() { + // The NDK headers are installed to a common sysroot. While a more + // typical Soong approach would be to only make the headers for the + // library you're using available, we're trying to emulate the NDK + // behavior here, and the NDK always has all the NDK headers available. + flags.GlobalFlags = append(flags.GlobalFlags, + "-isystem "+getCurrentIncludePath(ctx).String(), + "-isystem "+getCurrentIncludePath(ctx).Join(ctx, toolchain.ClangTriple()).String()) + + // Traditionally this has come from android/api-level.h, but with the + // libc headers unified it must be set by the build system since we + // don't have per-API level copies of that header now. + flags.GlobalFlags = append(flags.GlobalFlags, + "-D__ANDROID_API__="+ctx.sdkVersion()) + + // Until the full NDK has been migrated to using ndk_headers, we still + // need to add the legacy sysroot includes to get the full set of + // headers. + legacyIncludes := fmt.Sprintf( + "prebuilts/ndk/current/platforms/android-%s/arch-%s/usr/include", + ctx.sdkVersion(), ctx.Arch().ArchType.String()) + flags.GlobalFlags = append(flags.GlobalFlags, "-isystem "+legacyIncludes) + } + instructionSet := compiler.Properties.Instruction_set if flags.RequiredInstructionSet != "" { instructionSet = flags.RequiredInstructionSet @@ -1310,11 +1372,22 @@ func (compiler *baseCompiler) flags(ctx ModuleContext, flags Flags) Flags { return flags } +func ndkPathDeps(ctx ModuleContext) android.Paths { + if ctx.sdk() { + // The NDK sysroot timestamp file depends on all the NDK sysroot files + // (headers and libraries). + return android.Paths{getNdkSysrootTimestampFile(ctx)} + } + return nil +} + func (compiler *baseCompiler) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Paths { + pathDeps := deps.GeneratedHeaders + pathDeps = append(pathDeps, ndkPathDeps(ctx)...) // Compile files listed in c.Properties.Srcs into objects objFiles := compiler.compileObjs(ctx, flags, "", compiler.Properties.Srcs, compiler.Properties.Exclude_srcs, - deps.GeneratedSources, deps.GeneratedHeaders) + deps.GeneratedSources, pathDeps) if ctx.Failed() { return nil @@ -1595,14 +1668,17 @@ func (library *libraryCompiler) compile(ctx ModuleContext, flags Flags, deps Pat objFiles = library.baseCompiler.compile(ctx, flags, deps) library.reuseObjFiles = objFiles + pathDeps := deps.GeneratedHeaders + pathDeps = append(pathDeps, ndkPathDeps(ctx)...) + if library.linker.static() { objFiles = append(objFiles, library.compileObjs(ctx, flags, android.DeviceStaticLibrary, library.Properties.Static.Srcs, library.Properties.Static.Exclude_srcs, - nil, deps.GeneratedHeaders)...) + nil, pathDeps)...) } else { objFiles = append(objFiles, library.compileObjs(ctx, flags, android.DeviceSharedLibrary, library.Properties.Shared.Srcs, library.Properties.Shared.Exclude_srcs, - nil, deps.GeneratedHeaders)...) + nil, pathDeps)...) } return objFiles @@ -1626,6 +1702,10 @@ type libraryLinker struct { // For whole_static_libs objFiles android.Paths + + // Uses the module's name if empty, but can be overridden. Does not include + // shlib suffix. + libName string } var _ linker = (*libraryLinker)(nil) @@ -1646,7 +1726,10 @@ func (library *libraryLinker) props() []interface{} { } func (library *libraryLinker) getLibName(ctx ModuleContext) string { - name := ctx.ModuleName() + name := library.libName + if name == "" { + name = ctx.ModuleName() + } if ctx.Host() && Bool(library.Properties.Unique_host_soname) { if !strings.HasSuffix(name, "-host") { @@ -2634,10 +2717,6 @@ func ndkPrebuiltLibraryFactory() (blueprint.Module, []interface{}) { func (ndk *ndkPrebuiltLibraryLinker) link(ctx ModuleContext, flags Flags, deps PathDeps, objFiles android.Paths) android.Path { // A null build step, but it sets up the output path. - if !strings.HasPrefix(ctx.ModuleName(), "ndk_lib") { - ctx.ModuleErrorf("NDK prebuilts must have an ndk_lib prefixed name") - } - ndk.exportIncludes(ctx, "-isystem") return ndkPrebuiltModuleToPath(ctx, flags.Toolchain, flags.Toolchain.ShlibSuffix(), diff --git a/cc/gen_stub_libs.py b/cc/gen_stub_libs.py new file mode 100755 index 000000000..a4a042141 --- /dev/null +++ b/cc/gen_stub_libs.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 The Android Open Source Project +# +# 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. +# +"""Generates source for stub shared libraries for the NDK.""" +import argparse +import os +import re + + +ALL_ARCHITECTURES = ( + 'arm', + 'arm64', + 'mips', + 'mips64', + 'x86', + 'x86_64', +) + + +class Scope(object): + """Enum for version script scope. + + Top: Top level of the file. + Global: In a version and visibility section where symbols should be visible + to the NDK. + Local: In a visibility section of a public version where symbols should be + hidden to the NDK. + Private: In a version where symbols should not be visible to the NDK. + """ + Top = 1 + Global = 2 + Local = 3 + Private = 4 + + +class Stack(object): + """Basic stack implementation.""" + def __init__(self): + self.stack = [] + + def push(self, obj): + """Push an item on to the stack.""" + self.stack.append(obj) + + def pop(self): + """Remove and return the item on the top of the stack.""" + return self.stack.pop() + + @property + def top(self): + """Return the top of the stack.""" + return self.stack[-1] + + +def version_is_private(version): + """Returns True if the version name should be treated as private.""" + return version.endswith('_PRIVATE') or version.endswith('_PLATFORM') + + +def enter_version(scope, line, version_file): + """Enters a new version block scope.""" + if scope.top != Scope.Top: + raise RuntimeError('Encountered nested version block.') + + # Entering a new version block. By convention symbols with versions ending + # with "_PRIVATE" or "_PLATFORM" are not included in the NDK. + version_name = line.split('{')[0].strip() + if version_is_private(version_name): + scope.push(Scope.Private) + else: + scope.push(Scope.Global) # By default symbols are visible. + version_file.write(line) + + +def leave_version(scope, line, version_file): + """Leave a version block scope.""" + # There is no close to a visibility section, just the end of the version or + # a new visiblity section. + assert scope.top in (Scope.Global, Scope.Local, Scope.Private) + if scope.top != Scope.Private: + version_file.write(line) + scope.pop() + assert scope.top == Scope.Top + + +def enter_visibility(scope, line, version_file): + """Enters a new visibility block scope.""" + leave_visibility(scope) + version_file.write(line) + visibility = line.split(':')[0].strip() + if visibility == 'local': + scope.push(Scope.Local) + elif visibility == 'global': + scope.push(Scope.Global) + else: + raise RuntimeError('Unknown visiblity label: ' + visibility) + + +def leave_visibility(scope): + """Leaves a visibility block scope.""" + assert scope.top in (Scope.Global, Scope.Local) + scope.pop() + assert scope.top == Scope.Top + + +def handle_top_scope(scope, line, version_file): + """Processes a line in the top level scope.""" + if '{' in line: + enter_version(scope, line, version_file) + else: + raise RuntimeError('Unexpected contents at top level: ' + line) + + +def handle_private_scope(scope, line, version_file): + """Eats all input.""" + if '}' in line: + leave_version(scope, line, version_file) + + +def handle_local_scope(scope, line, version_file): + """Passes through input.""" + if ':' in line: + enter_visibility(scope, line, version_file) + elif '}' in line: + leave_version(scope, line, version_file) + else: + version_file.write(line) + + +def symbol_in_arch(tags, arch): + """Returns true if the symbol is present for the given architecture.""" + has_arch_tags = False + for tag in tags: + if tag == arch: + return True + if tag in ALL_ARCHITECTURES: + has_arch_tags = True + + # If there were no arch tags, the symbol is available for all + # architectures. If there were any arch tags, the symbol is only available + # for the tagged architectures. + return not has_arch_tags + + +def symbol_in_version(tags, arch, version): + """Returns true if the symbol is present for the given version.""" + introduced_tag = None + arch_specific = False + for tag in tags: + # If there is an arch-specific tag, it should override the common one. + if tag.startswith('introduced=') and not arch_specific: + introduced_tag = tag + elif tag.startswith('introduced-' + arch + '='): + introduced_tag = tag + arch_specific = True + + if introduced_tag is None: + # We found no "introduced" tags, so the symbol has always been + # available. + return True + + # The tag is a key=value pair, and we only care about the value now. + _, _, version_str = introduced_tag.partition('=') + return version >= int(version_str) + + +def handle_global_scope(scope, line, src_file, version_file, arch, api): + """Emits present symbols to the version file and stub source file.""" + if ':' in line: + enter_visibility(scope, line, version_file) + return + if '}' in line: + leave_version(scope, line, version_file) + return + + if ';' not in line: + raise RuntimeError('Expected ; to terminate symbol: ' + line) + if '*' in line: + raise RuntimeError('Wildcard global symbols are not permitted.') + + # Line is now in the format "; # tags" + # Tags are whitespace separated. + symbol_name, _, rest = line.strip().partition(';') + _, _, all_tags = rest.partition('#') + tags = re.split(r'\s+', all_tags) + + if not symbol_in_arch(tags, arch): + return + if not symbol_in_version(tags, arch, api): + return + + if 'var' in tags: + src_file.write('int {} = 0;\n'.format(symbol_name)) + else: + src_file.write('void {}() {{}}\n'.format(symbol_name)) + version_file.write(line) + + +def generate(symbol_file, src_file, version_file, arch, api): + """Generates the stub source file and version script.""" + scope = Stack() + scope.push(Scope.Top) + for line in symbol_file: + if line.strip() == '' or line.strip().startswith('#'): + version_file.write(line) + elif scope.top == Scope.Top: + handle_top_scope(scope, line, version_file) + elif scope.top == Scope.Private: + handle_private_scope(scope, line, version_file) + elif scope.top == Scope.Local: + handle_local_scope(scope, line, version_file) + elif scope.top == Scope.Global: + handle_global_scope(scope, line, src_file, version_file, arch, api) + + +def parse_args(): + """Parses and returns command line arguments.""" + parser = argparse.ArgumentParser() + + parser.add_argument('--api', type=int, help='API level being targeted.') + parser.add_argument( + '--arch', choices=ALL_ARCHITECTURES, + help='Architecture being targeted.') + + parser.add_argument( + 'symbol_file', type=os.path.realpath, help='Path to symbol file.') + parser.add_argument( + 'stub_src', type=os.path.realpath, + help='Path to output stub source file.') + parser.add_argument( + 'version_script', type=os.path.realpath, + help='Path to output version script.') + + return parser.parse_args() + + +def main(): + """Program entry point.""" + args = parse_args() + + with open(args.symbol_file) as symbol_file: + with open(args.stub_src, 'w') as src_file: + with open(args.version_script, 'w') as version_file: + generate(symbol_file, src_file, version_file, args.arch, + args.api) + + +if __name__ == '__main__': + main() diff --git a/cc/makevars.go b/cc/makevars.go index 0e75329ef..ae95a189d 100644 --- a/cc/makevars.go +++ b/cc/makevars.go @@ -50,6 +50,8 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.StrictRaw("SRC_HEADERS", strings.Join(includes, " ")) ctx.StrictRaw("SRC_SYSTEM_HEADERS", strings.Join(systemIncludes, " ")) + ctx.Strict("NDK_MIGRATED_LIBS", strings.Join(ndkMigratedLibs, " ")) + hostTargets := ctx.Config().Targets[android.Host] makeVarsToolchain(ctx, "", hostTargets[0]) if len(hostTargets) > 1 { @@ -198,6 +200,7 @@ func makeVarsToolchain(ctx android.MakeVarsContext, secondPrefix string, ctx.Strict(makePrefix+"STRIP", gccCmd(toolchain, "strip")) ctx.Strict(makePrefix+"GCC_VERSION", toolchain.GccVersion()) ctx.Strict(makePrefix+"NDK_GCC_VERSION", toolchain.GccVersion()) + ctx.Strict(makePrefix+"NDK_TRIPLE", toolchain.ClangTriple()) } ctx.Strict(makePrefix+"TOOLCHAIN_ROOT", toolchain.GccRoot()) diff --git a/cc/ndk_headers.go b/cc/ndk_headers.go new file mode 100644 index 000000000..5d70b8916 --- /dev/null +++ b/cc/ndk_headers.go @@ -0,0 +1,102 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cc + +import ( + "path/filepath" + + "github.com/google/blueprint" + + "android/soong/android" +) + +// Returns the NDK base include path for use with sdk_version current. Usable with -I. +func getCurrentIncludePath(ctx android.ModuleContext) android.OutputPath { + return getNdkSysrootBase(ctx).Join(ctx, "usr/include") +} + +type headerProperies struct { + // Base directory of the headers being installed. As an example: + // + // ndk_headers { + // name: "foo", + // from: "include", + // to: "", + // srcs: ["include/foo/bar/baz.h"], + // } + // + // Will install $SYSROOT/usr/include/foo/bar/baz.h. If `from` were instead + // "include/foo", it would have installed $SYSROOT/usr/include/bar/baz.h. + From string + + // Install path within the sysroot. This is relative to usr/include. + To string + + // List of headers to install. Glob compatible. Common case is "include/**/*.h". + Srcs []string +} + +type headerModule struct { + android.ModuleBase + + properties headerProperies + + installPaths []string +} + +func (m *headerModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { + srcFiles := ctx.ExpandSources(m.properties.Srcs, nil) + for _, header := range srcFiles { + // Output path is the sysroot base + "usr/include" + to directory + directory component + // of the file without the leading from directory stripped. + // + // Given: + // sysroot base = "ndk/sysroot" + // from = "include/foo" + // to = "bar" + // header = "include/foo/woodly/doodly.h" + // output path = "ndk/sysroot/usr/include/bar/woodly/doodly.h" + + // full/platform/path/to/include/foo + fullFromPath := android.PathForModuleSrc(ctx, m.properties.From) + + // full/platform/path/to/include/foo/woodly + headerDir := filepath.Dir(header.String()) + + // woodly + strippedHeaderDir, err := filepath.Rel(fullFromPath.String(), headerDir) + if err != nil { + ctx.ModuleErrorf("filepath.Rel(%q, %q) failed: %s", headerDir, + fullFromPath.String(), err) + } + + // full/platform/path/to/sysroot/usr/include/bar/woodly + installDir := getCurrentIncludePath(ctx).Join(ctx, m.properties.To, strippedHeaderDir) + + // full/platform/path/to/sysroot/usr/include/bar/woodly/doodly.h + installPath := ctx.InstallFile(installDir, header) + m.installPaths = append(m.installPaths, installPath.String()) + } + + if len(m.installPaths) == 0 { + ctx.ModuleErrorf("srcs %q matched zero files", m.properties.Srcs) + } +} + +func ndkHeadersFactory() (blueprint.Module, []interface{}) { + module := &headerModule{} + return android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst, + &module.properties) +} diff --git a/cc/ndk_library.go b/cc/ndk_library.go new file mode 100644 index 000000000..562186eaf --- /dev/null +++ b/cc/ndk_library.go @@ -0,0 +1,250 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cc + +import ( + "fmt" + "strconv" + "strings" + + "github.com/google/blueprint" + + "android/soong/android" +) + +var ( + toolPath = pctx.SourcePathVariable("toolPath", "build/soong/cc/gen_stub_libs.py") + + genStubSrc = pctx.StaticRule("genStubSrc", + blueprint.RuleParams{ + Command: "$toolPath --arch $arch --api $apiLevel $in $out", + Description: "genStubSrc $out", + CommandDeps: []string{"$toolPath"}, + }, "arch", "apiLevel") + + ndkLibrarySuffix = ".ndk" +) + +// Creates a stub shared library based on the provided version file. +// +// The name of the generated file will be based on the module name by stripping +// the ".ndk" suffix from the module name. Module names must end with ".ndk" +// (as a convention to allow soong to guess the NDK name of a dependency when +// needed). "libfoo.ndk" will generate "libfoo.so. +// +// Example: +// +// ndk_library { +// name: "libfoo.ndk", +// symbol_file: "libfoo.map.txt", +// first_version: "9", +// } +// +type libraryProperties struct { + // Relative path to the symbol map. + // An example file can be seen here: TODO(danalbert): Make an example. + Symbol_file string + + // The first API level a library was available. A library will be generated + // for every API level beginning with this one. + First_version string + + // Private property for use by the mutator that splits per-API level. + ApiLevel int `blueprint:"mutated"` +} + +type stubCompiler struct { + baseCompiler + + properties libraryProperties +} + +// OMG GO +func intMin(a int, b int) int { + if a < b { + return a + } else { + return b + } +} + +func generateStubApiVariants(mctx android.BottomUpMutatorContext, c *stubCompiler) { + minVersion := 9 // Minimum version supported by the NDK. + // TODO(danalbert): Use PlatformSdkVersion when possible. + // This is an interesting case because for the moment we actually need 24 + // even though the latest released version in aosp is 23. prebuilts/ndk/r11 + // has android-24 versions of libraries, and as platform libraries get + // migrated the libraries in prebuilts will need to depend on them. + // + // Once everything is all moved over to the new stuff (when there isn't a + // prebuilts/ndk any more) then this should be fixable, but for now I think + // it needs to remain as-is. + maxVersion := 24 + firstArchVersions := map[string]int{ + "arm": 9, + "arm64": 21, + "mips": 9, + "mips64": 21, + "x86": 9, + "x86_64": 21, + } + + // 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 minimum version. Clip to the + // supported version here instead. + firstVersion, err := strconv.Atoi(c.properties.First_version) + if err != nil { + mctx.ModuleErrorf("Invalid first_version value (must be int): %q", + c.properties.First_version) + } + if firstVersion < minVersion { + firstVersion = minVersion + } + + arch := mctx.Arch().ArchType.String() + firstArchVersion, ok := firstArchVersions[arch] + if !ok { + panic(fmt.Errorf("Arch %q not found in firstArchVersions", arch)) + } + firstGenVersion := intMin(firstVersion, firstArchVersion) + versionStrs := make([]string, maxVersion-firstGenVersion+1) + for version := firstGenVersion; version <= maxVersion; version++ { + versionStrs[version-firstGenVersion] = strconv.Itoa(version) + } + + modules := mctx.CreateVariations(versionStrs...) + for i, module := range modules { + module.(*Module).compiler.(*stubCompiler).properties.ApiLevel = firstGenVersion + i + } +} + +func ndkApiMutator(mctx android.BottomUpMutatorContext) { + if m, ok := mctx.Module().(*Module); ok { + if compiler, ok := m.compiler.(*stubCompiler); ok { + generateStubApiVariants(mctx, compiler) + } + } +} + +func (c *stubCompiler) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Paths { + arch := ctx.Arch().ArchType.String() + + if !strings.HasSuffix(ctx.ModuleName(), ndkLibrarySuffix) { + ctx.ModuleErrorf("ndk_library modules names must be suffixed with %q\n", + ndkLibrarySuffix) + } + libName := strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix) + fileBase := fmt.Sprintf("%s.%s.%d", libName, arch, c.properties.ApiLevel) + stubSrcName := fileBase + ".c" + stubSrcPath := android.PathForModuleGen(ctx, stubSrcName) + versionScriptName := fileBase + ".map" + versionScriptPath := android.PathForModuleGen(ctx, versionScriptName) + symbolFilePath := android.PathForModuleSrc(ctx, c.properties.Symbol_file) + ctx.ModuleBuild(pctx, android.ModuleBuildParams{ + Rule: genStubSrc, + Outputs: []android.WritablePath{stubSrcPath, versionScriptPath}, + Input: symbolFilePath, + Args: map[string]string{ + "arch": arch, + "apiLevel": strconv.Itoa(c.properties.ApiLevel), + }, + }) + + flags.CFlags = append(flags.CFlags, + // We're knowingly doing some otherwise unsightly things with builtin + // functions here. We're just generating stub libraries, so ignore it. + "-Wno-incompatible-library-redeclaration", + "-Wno-builtin-requires-header", + "-Wno-invalid-noreturn", + + // These libraries aren't actually used. Don't worry about unwinding + // (avoids the need to link an unwinder into a fake library). + "-fno-unwind-tables", + ) + + subdir := "" + srcs := []string{} + excludeSrcs := []string{} + extraSrcs := []android.Path{stubSrcPath} + extraDeps := []android.Path{} + return c.baseCompiler.compileObjs(ctx, flags, subdir, srcs, excludeSrcs, + extraSrcs, extraDeps) +} + +type stubLinker struct { + libraryLinker +} + +func (linker *stubLinker) deps(ctx BaseModuleContext, deps Deps) Deps { + return Deps{} +} + +func (linker *stubLinker) flags(ctx ModuleContext, flags Flags) Flags { + linker.libraryLinker.libName = strings.TrimSuffix(ctx.ModuleName(), + ndkLibrarySuffix) + return linker.libraryLinker.flags(ctx, flags) +} + +type stubInstaller struct { + baseInstaller + + compiler *stubCompiler + + installPath string +} + +var _ installer = (*stubInstaller)(nil) + +func (installer *stubInstaller) install(ctx ModuleContext, path android.Path) { + arch := ctx.Target().Arch.ArchType.Name + apiLevel := installer.compiler.properties.ApiLevel + + // arm64 isn't actually a multilib toolchain, so unlike the other LP64 + // architectures it's just installed to lib. + libDir := "lib" + if ctx.toolchain().Is64Bit() && arch != "arm64" { + libDir = "lib64" + } + + installDir := getNdkInstallBase(ctx).Join(ctx, fmt.Sprintf( + "platforms/android-%d/arch-%s/usr/%s", apiLevel, arch, libDir)) + installer.installPath = ctx.InstallFile(installDir, path).String() +} + +func newStubLibrary() *Module { + module := newModule(android.DeviceSupported, android.MultilibBoth) + module.stl = nil + + linker := &stubLinker{} + linker.dynamicProperties.BuildShared = true + linker.dynamicProperties.BuildStatic = false + linker.stripper.StripProperties.Strip.None = true + module.linker = linker + + compiler := &stubCompiler{} + module.compiler = compiler + module.installer = &stubInstaller{baseInstaller{ + dir: "lib", + dir64: "lib64", + }, compiler, ""} + + return module +} + +func ndkLibraryFactory() (blueprint.Module, []interface{}) { + module := newStubLibrary() + return android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth, + &module.compiler.(*stubCompiler).properties) +} diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go new file mode 100644 index 000000000..2eae360ca --- /dev/null +++ b/cc/ndk_sysroot.go @@ -0,0 +1,112 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cc + +// The platform needs to provide the following artifacts for the NDK: +// 1. Bionic headers. +// 2. Platform API headers. +// 3. NDK stub shared libraries. +// 4. Bionic static libraries. +// +// TODO(danalbert): All of the above need to include NOTICE files. +// +// Components 1 and 2: Headers +// The bionic and platform API headers are generalized into a single +// `ndk_headers` rule. This rule has a `from` property that indicates a base +// directory from which headers are to be taken, and a `to` property that +// indicates where in the sysroot they should reside relative to usr/include. +// There is also a `srcs` property that is glob compatible for specifying which +// headers to include. +// +// Component 3: Stub Libraries +// The shared libraries in the NDK are not the actual shared libraries they +// refer to (to prevent people from accidentally loading them), but stub +// libraries with dummy implementations of everything for use at build time +// only. +// +// Since we don't actually need to know anything about the stub libraries aside +// from a list of functions and globals to be exposed, we can create these for +// every platform level in the current tree. This is handled by the +// ndk_library rule. +// +// Component 4: Static Libraries +// The NDK only provides static libraries for bionic, not the platform APIs. +// Since these need to be the actual implementation, we can't build old versions +// in the current platform tree. As such, legacy versions are checked in +// prebuilt to development/ndk, and a current version is built and archived as +// part of the platform build. The platfrom already builds these libraries, our +// NDK build rules only need to archive them for retrieval so they can be added +// to the prebuilts. +// +// TODO(danalbert): Write `ndk_static_library` rule. + +import ( + "github.com/google/blueprint" + + "android/soong" + "android/soong/android" +) + +func init() { + soong.RegisterModuleType("ndk_headers", ndkHeadersFactory) + soong.RegisterModuleType("ndk_library", ndkLibraryFactory) + soong.RegisterSingletonType("ndk", NdkSingleton) + + pctx.Import("android/soong/common") +} + +func getNdkInstallBase(ctx android.ModuleContext) android.OutputPath { + return android.PathForOutput(ctx, "ndk") +} + +// Returns the main install directory for the NDK sysroot. Usable with --sysroot. +func getNdkSysrootBase(ctx android.ModuleContext) android.OutputPath { + return getNdkInstallBase(ctx).Join(ctx, "sysroot") +} + +func getNdkSysrootTimestampFile(ctx android.PathContext) android.Path { + return android.PathForOutput(ctx, "ndk.timestamp") +} + +func NdkSingleton() blueprint.Singleton { + return &ndkSingleton{} +} + +type ndkSingleton struct{} + +func (n *ndkSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { + installPaths := []string{} + ctx.VisitAllModules(func(module blueprint.Module) { + if m, ok := module.(*headerModule); ok { + installPaths = append(installPaths, m.installPaths...) + } + }) + + ctx.VisitAllModules(func(module blueprint.Module) { + if m, ok := module.(*Module); ok { + if installer, ok := m.installer.(*stubInstaller); ok { + installPaths = append(installPaths, installer.installPath) + } + } + }) + + // There's a dummy "ndk" rule defined in ndk/Android.mk that depends on + // this. `m ndk` will build the sysroots. + ctx.Build(pctx, blueprint.BuildParams{ + Rule: android.Touch, + Outputs: []string{getNdkSysrootTimestampFile(ctx).String()}, + Implicits: installPaths, + }) +} diff --git a/cc/pylintrc b/cc/pylintrc new file mode 100644 index 000000000..5d1aa9af9 --- /dev/null +++ b/cc/pylintrc @@ -0,0 +1,280 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=design + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct attribute names in class +# bodies +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception