blob: de059e42fee20b9a4330403d85cab7054fea005a [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (C) 2015 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.
#
"""Verifies that the build is sane.
Cleans old build artifacts, configures the required environment, determines
build goals, and invokes the build scripts.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import contextlib
import copy
import errno
import glob
import inspect
import json
import logging
import multiprocessing
import os
import re
import shutil
import site
import subprocess
import sys
import tempfile
import textwrap
import traceback
import build.lib.build_support as build_support
import ndk.ansi
import ndk.builds
import ndk.config
import ndk.deps
import ndk.ext.shutil
import ndk.file
import ndk.notify
import ndk.paths
import ndk.test.builder
import ndk.test.printers
import ndk.test.spec
import ndk.timer
import ndk.ui
import ndk.workqueue
def _make_tar_package(package_path, base_dir, path):
"""Creates a tarball package for distribution.
Args:
package_path (string): Path (without extention) to the output archive.
base_dir (string): Path to the directory from which to perform the
packaging (identical to tar's -C).
path (string): Path to the directory to package.
"""
has_pbzip2 = ndk.ext.shutil.which('pbzip2') is not None
if has_pbzip2:
compress_arg = '--use-compress-prog=pbzip2'
else:
compress_arg = '-j'
package_path = package_path + '.tar.bz2'
cmd = ['tar', compress_arg, '-cf', package_path, '-C', base_dir, path]
subprocess.check_call(cmd)
return package_path
def _make_zip_package(package_path, base_dir, path):
"""Creates a zip package for distribution.
Args:
package_path (string): Path (without extention) to the output archive.
base_dir (string): Path to the directory from which to perform the
packaging (identical to tar's -C).
path (string): Path to the directory to package.
"""
cwd = os.getcwd()
package_path = os.path.realpath(package_path) + '.zip'
os.chdir(base_dir)
try:
subprocess.check_call(['zip', '-9qr', package_path, path])
return package_path
finally:
os.chdir(cwd)
def package_ndk(ndk_dir, dist_dir, host_tag, build_number):
"""Packages the built NDK for distribution.
Args:
ndk_dir (string): Path to the built NDK.
dist_dir (string): Path to place the built package in.
host_tag (string): Host tag to use in the package name,
build_number (printable): Build number to use in the package name. Will
be 'dev' if the argument evaluates to False.
"""
package_name = 'android-ndk-{}-{}'.format(build_number, host_tag)
package_path = os.path.join(dist_dir, package_name)
for path, _dirs, files in os.walk(ndk_dir):
for file_name in files:
if file_name.endswith('.pyc'):
os.remove(os.path.join(path, file_name))
base_dir = os.path.dirname(ndk_dir)
files = os.path.basename(ndk_dir)
if host_tag.startswith('windows'):
return _make_zip_package(package_path, base_dir, files)
else:
return _make_tar_package(package_path, base_dir, files)
def build_ndk_tests(out_dir, dist_dir, args):
"""Builds the NDK tests.
Args:
out_dir: Build output directory.
dist_dir: Preserved artifact directory.
args: Parsed command line arguments.
Returns:
True if all tests pass, else False.
"""
# The packaging step extracts all the modules to a known directory for
# packaging. This directory is not cleaned up after packaging, so we can
# reuse that for testing.
ndk_dir = ndk.paths.get_install_path(out_dir)
test_src_dir = build_support.ndk_path('tests')
test_out_dir = os.path.join(out_dir, 'tests')
site.addsitedir(os.path.join(ndk_dir, 'python-packages'))
test_options = ndk.test.spec.TestOptions(
test_src_dir, ndk_dir, test_out_dir, clean=True)
printer = ndk.test.printers.StdoutPrinter()
with open(os.path.realpath('qa_config.json')) as config_file:
test_config = json.load(config_file)
if args.arch is not None:
test_config['abis'] = build_support.arch_to_abis(args.arch)
test_spec = ndk.test.builder.test_spec_from_config(test_config)
builder = ndk.test.builder.TestBuilder(
test_spec, test_options, printer)
report = builder.build()
printer.print_summary(report)
if report.successful:
print('Packaging tests...')
package_path = os.path.join(dist_dir, 'ndk-tests')
_make_tar_package(package_path, out_dir, 'tests/dist')
else:
# Write out the result to logs/build_error.log so we can find the
# failure easily on the build server.
log_path = os.path.join(dist_dir, 'logs/build_error.log')
with open(log_path, 'a') as error_log:
error_log_printer = ndk.test.printers.FilePrinter(error_log)
error_log_printer.print_summary(report)
return report.successful
def install_file(file_name, src_dir, dst_dir):
src_file = os.path.join(src_dir, file_name)
dst_file = os.path.join(dst_dir, file_name)
print('Copying {} to {}...'.format(src_file, dst_file))
if os.path.isdir(src_file):
_install_dir(src_file, dst_file)
elif os.path.islink(src_file):
_install_symlink(src_file, dst_file)
else:
_install_file(src_file, dst_file)
def _install_dir(src_dir, dst_dir):
parent_dir = os.path.normpath(os.path.join(dst_dir, '..'))
if not os.path.exists(parent_dir):
os.makedirs(parent_dir)
shutil.copytree(src_dir, dst_dir, symlinks=True)
def _install_symlink(src_file, dst_file):
dirname = os.path.dirname(dst_file)
if not os.path.exists(dirname):
os.makedirs(dirname)
link_target = os.readlink(src_file)
os.symlink(link_target, dst_file)
def _install_file(src_file, dst_file):
dirname = os.path.dirname(dst_file)
if not os.path.exists(dirname):
os.makedirs(dirname)
# copy2 is just copy followed by copystat (preserves file metadata).
shutil.copy2(src_file, dst_file)
class Clang(ndk.builds.Module):
name = 'clang'
path = 'toolchains/llvm/prebuilt/{host}'
version = 'clang-r328903'
notice_group = ndk.builds.NoticeGroup.TOOLCHAIN
@property
def notices(self):
return [
os.path.join(self.get_prebuilt_path('darwin'), 'NOTICE'),
os.path.join(self.get_prebuilt_path('linux'), 'NOTICE'),
os.path.join(self.get_prebuilt_path('windows'), 'NOTICE'),
os.path.join(self.get_prebuilt_path('windows64'), 'NOTICE'),
]
def get_prebuilt_path(self, host):
# The 32-bit Windows Clang is a part of the 64-bit Clang package in
# prebuilts/clang.
if host == 'windows':
platform_host_tag = 'windows-x86_32'
elif host == 'windows64':
platform_host_tag = 'windows-x86'
else:
platform_host_tag = host + '-x86'
rel_prebuilt_path = 'prebuilts/clang/host/{}'.format(platform_host_tag)
prebuilt_path = os.path.join(build_support.android_path(),
rel_prebuilt_path, self.version)
if not os.path.isdir(prebuilt_path):
raise RuntimeError(
'Could not find prebuilt LLVM at {}'.format(prebuilt_path))
return prebuilt_path
def build(self, _build_dir, _dist_dir, _args):
pass
def install(self, out_dir, _dist_dir, args):
prebuilt_path = self.get_prebuilt_path(args.system)
install_path = self.get_install_path(out_dir, args.system)
install_parent = os.path.dirname(install_path)
if os.path.exists(install_path):
shutil.rmtree(install_path)
if not os.path.exists(install_parent):
os.makedirs(install_parent)
shutil.copytree(prebuilt_path, install_path)
# clang-4053586 was patched in the prebuilts directory to add the
# libc++ includes. These are almost certainly a different revision than
# the NDK libc++, and may contain local changes that the NDK's don't
# and vice versa. Best to just remove them for the time being since
# that returns to the previous behavior.
# https://github.com/android-ndk/ndk/issues/564#issuecomment-342307128
cxx_includes_path = os.path.join(install_path, 'include')
shutil.rmtree(cxx_includes_path)
if args.system in ('darwin', 'linux'):
# The Linux and Darwin toolchains have Python compiler wrappers
# that currently do nothing. We don't have these for Windows and we
# want to make sure Windows behavior is consistent with the other
# platforms, so just unwrap the compilers until they do something
# useful and are available on Windows.
os.rename(os.path.join(install_path, 'bin/clang.real'),
os.path.join(install_path, 'bin/clang'))
os.rename(os.path.join(install_path, 'bin/clang++.real'),
os.path.join(install_path, 'bin/clang++'))
# The prebuilts have symlinks pointing at a clang-MAJ.MIN binary,
# but we replace symlinks with standalone copies, so remove this
# copy to save space.
bin_dir = os.path.join(install_path, 'bin')
(clang_maj_min,) = glob.glob(os.path.join(bin_dir, 'clang-?'))
os.remove(clang_maj_min)
# Remove LLD duplicates. We only need ld.lld.
# http://b/74250510
#
# Note that lld is experimental in the NDK. It is not the default for
# any architecture and has received only minimal testing in the NDK.
bin_ext = '.exe' if args.system.startswith('windows') else ''
os.remove(os.path.join(install_path, 'bin/ld64.lld' + bin_ext))
os.remove(os.path.join(install_path, 'bin/lld' + bin_ext))
os.remove(os.path.join(install_path, 'bin/lld-link' + bin_ext))
libdir_name = 'lib' if args.system == 'windows' else 'lib64'
if args.system.startswith('windows'):
# The toolchain prebuilts have LLVMgold.dll in the bin directory
# rather than the lib directory that will actually be searched.
bin_dir = os.path.join(install_path, 'bin')
lib_dir = os.path.join(install_path, libdir_name)
os.rename(os.path.join(bin_dir, 'LLVMgold.dll'),
os.path.join(lib_dir, 'LLVMgold.dll'))
# Windows doesn't support rpath, so we need to copy
# libwinpthread-1.dll too.
shutil.copy2(os.path.join(bin_dir, 'libwinpthread-1.dll'),
os.path.join(lib_dir, 'libwinpthread-1.dll'))
install_clanglib = os.path.join(install_path, libdir_name, 'clang')
linux_prebuilt_path = self.get_prebuilt_path('linux')
if args.system != 'linux':
# We don't build target binaries as part of the Darwin or Windows
# build. These toolchains need to get these from the Linux
# prebuilts.
#
# The headers and libraries we care about are all in lib64/clang
# for both toolchains, and those two are intended to be identical
# between each host, so we can just replace them with the one from
# the Linux toolchain.
linux_clanglib = os.path.join(linux_prebuilt_path, 'lib64/clang')
shutil.rmtree(install_clanglib)
shutil.copytree(linux_clanglib, install_clanglib)
# The Clang prebuilts have the platform toolchain libraries in
# lib64/clang. The libraries we want are in runtimes_ndk_cxx.
ndk_runtimes = os.path.join(linux_prebuilt_path, 'runtimes_ndk_cxx')
runtime_arches = ['aarch64', 'arm', 'i386', 'x86_64']
versions = os.listdir(install_clanglib)
for version in versions:
version_dir = os.path.join(install_clanglib, version)
dst_lib_dir = os.path.join(version_dir, 'lib/linux')
for arch in runtime_arches:
src_arch_dir = os.path.join(ndk_runtimes, arch)
dst_arch_dir = os.path.join(dst_lib_dir, arch)
# The install directory currently contains the platform
# libraries with the wrong arch name. We need to remove the
# wrongly named wrong libraries before we fix the arch name.
shutil.rmtree(dst_arch_dir)
shutil.copytree(src_arch_dir, dst_arch_dir)
# Also remove the other libraries that we installed, but they were only
# installed on Linux.
if args.system == 'linux':
shutil.rmtree(os.path.join(install_path, 'runtimes_ndk_cxx'))
def get_gcc_prebuilt_path(host, arch):
host_tag = ndk.hosts.host_to_tag(host)
toolchain = ndk.abis.arch_to_toolchain(arch) + '-4.9'
rel_prebuilt_path = os.path.join(
'prebuilts/ndk/current/toolchains', host_tag, toolchain)
prebuilt_path = build_support.android_path(rel_prebuilt_path)
if not os.path.isdir(prebuilt_path):
raise RuntimeError(
'Could not find prebuilt GCC at {}'.format(prebuilt_path))
return prebuilt_path
def get_binutils_prebuilt_path(host, arch):
if host == 'windows':
host = 'win'
elif host == 'windows64':
host = 'win64'
binutils_name = 'binutils-{}-{}'.format(arch, host)
prebuilt_path = ndk.paths.android_path(
'prebuilts/ndk', 'binutils', host, binutils_name)
if not os.path.isdir(prebuilt_path):
raise RuntimeError(
'Could not find prebuilt binutils at {}'.format(prebuilt_path))
return prebuilt_path
def versioned_so(host, lib, version):
if host == 'darwin':
return '{}.{}.dylib'.format(lib, version)
elif host == 'linux':
return '{}.so.{}'.format(lib, version)
else:
raise ValueError('Unsupported host: {}'.format(host))
def install_gcc_lib(install_path, host, arch, subarch, lib_subdir, libname):
gcc_prebuilt = get_gcc_prebuilt_path(host, arch)
lib_install_dir = os.path.join(install_path, lib_subdir, subarch)
if not os.path.exists(lib_install_dir):
os.makedirs(lib_install_dir)
shutil.copy2(
os.path.join(gcc_prebuilt, lib_subdir, subarch, libname),
os.path.join(lib_install_dir, libname))
def install_gcc_crtbegin(install_path, host, arch, subarch):
triple = ndk.abis.arch_to_triple(arch)
subdir = os.path.join('lib/gcc', triple, '4.9.x')
install_gcc_lib(install_path, host, arch, subarch, subdir, 'crtbegin.o')
def install_libgcc(install_path, host, arch, subarch):
triple = ndk.abis.arch_to_triple(arch)
subdir = os.path.join('lib/gcc', triple, '4.9.x')
install_gcc_lib(install_path, host, arch, subarch, subdir, 'libgcc.a')
def install_libatomic(install_path, host, arch, subarch):
triple = ndk.abis.arch_to_triple(arch)
subdir = os.path.join(triple, 'lib64' if arch.endswith('64') else 'lib')
install_gcc_lib(install_path, host, arch, subarch, subdir, 'libatomic.a')
def get_subarches(arch):
if arch != 'arm':
return ['']
return [
'',
'thumb',
'armv7-a',
'armv7-a/thumb'
]
class Binutils(ndk.builds.Module):
name = 'binutils'
path = 'toolchains/{toolchain}-4.9/prebuilt/{host}'
notice_group = ndk.builds.NoticeGroup.TOOLCHAIN
@property
def notices(self):
notices = []
for host in ndk.hosts.ALL_HOSTS:
for arch in ndk.abis.ALL_ARCHITECTURES:
prebuilt_path = get_gcc_prebuilt_path(host, arch)
notices.append(os.path.join(prebuilt_path, 'NOTICE'))
return notices
def build(self, _build_dir, _dist_dir, _args):
pass
def install(self, out_dir, _dist_dir, args):
arches = build_support.ALL_ARCHITECTURES
if args.arch is not None:
arches = [args.arch]
for arch in arches:
self.install_arch(out_dir, args.system, arch)
def install_arch(self, out_dir, host, arch):
install_path = self.get_install_path(out_dir, host, arch)
toolchain_path = get_binutils_prebuilt_path(host, arch)
ndk.builds.install_directory(toolchain_path, install_path)
# We still need libgcc/libatomic. Copy them from the old GCC prebuilts.
for subarch in get_subarches(arch):
install_libgcc(install_path, host, arch, subarch)
install_libatomic(install_path, host, arch, subarch)
# We don't actually want this, but Clang won't recognize a
# -gcc-toolchain without it.
install_gcc_crtbegin(install_path, host, arch, subarch)
# Copy the LLVMgold plugin into the binutils plugin directory so ar can
# use it.
if host == 'linux':
so = '.so'
elif host == 'darwin':
so = '.dylib'
else:
so = '.dll'
is_win = host.startswith('windows')
libdir_name = 'lib' if host == 'windows' else 'lib64'
host_tag = build_support.host_to_tag(host)
clang_prebuilts = build_support.android_path(
'prebuilts/ndk/current/toolchains', host_tag, 'llvm')
clang_bin = os.path.join(clang_prebuilts, 'bin')
clang_libs = os.path.join(clang_prebuilts, libdir_name)
if is_win:
llvmgold = os.path.join(clang_bin, 'LLVMgold' + so)
else:
llvmgold = os.path.join(clang_libs, 'LLVMgold' + so)
bfd_plugins = os.path.join(install_path, 'lib/bfd-plugins')
os.makedirs(bfd_plugins)
shutil.copy2(llvmgold, bfd_plugins)
if not is_win:
libcxx_1 = os.path.join(
clang_libs, versioned_so(host, 'libc++', '1'))
# The rpath on LLVMgold.so is ../lib64, so we have to install to
# lib/lib64 to have it be in the right place :(
lib_dir = os.path.join(install_path, 'lib/lib64')
os.makedirs(lib_dir)
shutil.copy2(libcxx_1, lib_dir)
else:
libwinpthread = os.path.join(clang_bin, 'libwinpthread-1.dll')
shutil.copy2(libwinpthread, bfd_plugins)
class ShaderTools(ndk.builds.InvokeBuildModule):
name = 'shader-tools'
path = 'shader-tools/{host}'
script = 'build-shader-tools.py'
notice_group = ndk.builds.NoticeGroup.TOOLCHAIN
@property
def notices(self):
base = ndk.paths.android_path('external/shaderc')
shaderc_dir = os.path.join(base, 'shaderc')
spirv_dir = os.path.join(base, 'spirv-headers')
return [
os.path.join(shaderc_dir, 'LICENSE'),
os.path.join(shaderc_dir, 'third_party', 'LICENSE.spirv-tools'),
os.path.join(shaderc_dir, 'third_party', 'LICENSE.glslang'),
os.path.join(spirv_dir, 'LICENSE')
]
class HostTools(ndk.builds.Module):
name = 'host-tools'
path = 'prebuilt/{host}'
notice_group = ndk.builds.NoticeGroup.TOOLCHAIN
@property
def notices(self):
return [
ndk.paths.android_path('toolchain/gdb/gdb-7.11/COPYING'),
ndk.paths.android_path('toolchain/python/Python-2.7.5/LICENSE'),
ndk.paths.android_path('toolchain/yasm/Artistic.txt'),
ndk.paths.android_path('toolchain/yasm/BSD.txt'),
ndk.paths.android_path('toolchain/yasm/COPYING'),
ndk.paths.android_path('toolchain/yasm/GNU_GPL-2.0'),
ndk.paths.android_path('toolchain/yasm/GNU_LGPL-2.0'),
ndk.paths.ndk_path('sources/host-tools/make-3.81/COPYING'),
ndk.paths.ndk_path('sources/host-tools/toolbox/NOTICE'),
]
def build(self, build_dir, dist_dir, args):
build_args = ndk.builds.common_build_args(build_dir, dist_dir, args)
print('Building make...')
ndk.builds.invoke_external_build(
'ndk/sources/host-tools/make-3.81/build.py', build_args)
if args.system in ('windows', 'windows64'):
print('Building toolbox...')
ndk.builds.invoke_external_build(
'ndk/sources/host-tools/toolbox/build.py', build_args)
print('Building Python...')
ndk.builds.invoke_external_build(
'toolchain/python/build.py', build_args)
print('Building GDB...')
ndk.builds.invoke_external_build('toolchain/gdb/build.py', build_args)
print('Building YASM...')
ndk.builds.invoke_external_build('toolchain/yasm/build.py', build_args)
def install(self, out_dir, _dist_dir, args):
install_dir = self.get_install_path(out_dir, args.system)
try:
os.makedirs(install_dir)
except OSError as ex:
# Another build might be trying to create this simultaneously,
# which we can safely ignore.
if ex.errno != errno.EEXIST:
raise
packages = [
'gdb-multiarch-7.11',
'ndk-make',
'ndk-python',
'ndk-yasm',
]
files = [
'ndk-gdb',
'ndk-gdb.py',
'ndk-which',
]
if args.system in ('windows', 'windows64'):
packages.append('toolbox')
files.append('ndk-gdb.cmd')
host_tag = build_support.host_to_tag(args.system)
package_names = [p + '-' + host_tag + '.tar.bz2' for p in packages]
for package_name in package_names:
package_path = os.path.join(out_dir, package_name)
subprocess.check_call(
['tar', 'xf', package_path, '-C', install_dir,
'--strip-components=1'])
for f in files:
shutil.copy2(f, os.path.join(install_dir, 'bin'))
ndk.builds.make_repo_prop(install_dir)
def install_exe(out_dir, install_dir, name, system):
is_win = system.startswith('windows')
ext = '.exe' if is_win else ''
exe_name = name + ext
src = os.path.join(out_dir, exe_name)
dst = os.path.join(install_dir, exe_name)
try:
os.makedirs(install_dir)
except OSError as ex:
# Another build might be trying to create this simultaneously,
# which we can safely ignore.
if ex.errno != errno.EEXIST:
raise
shutil.copy2(src, dst)
class NdkDepends(ndk.builds.InvokeExternalBuildModule):
name = 'ndk-depends'
path = 'prebuilt/{host}/bin'
script = 'ndk/sources/host-tools/ndk-depends/build.py'
notice = ndk.paths.ndk_path('sources/host-tools/ndk-depends/NOTICE')
def install(self, out_dir, _dist_dir, args):
src = os.path.join(out_dir, self.name)
install_dir = self.get_install_path(out_dir, args.system)
install_exe(src, install_dir, self.name, args.system)
class NdkStack(ndk.builds.InvokeExternalBuildModule):
name = 'ndk-stack'
path = 'prebuilt/{host}/bin'
script = 'ndk/sources/host-tools/ndk-stack/build.py'
notice = ndk.paths.ndk_path('sources/host-tools/ndk-stack/NOTICE')
def install(self, out_dir, _dist_dir, args):
src = os.path.join(out_dir, self.name)
install_dir = self.get_install_path(out_dir, args.system)
install_exe(src, install_dir, self.name, args.system)
class GdbServer(ndk.builds.InvokeBuildModule):
name = 'gdbserver'
path = 'prebuilt/android-{arch}/gdbserver'
script = 'build-gdbserver.py'
notice = ndk.paths.android_path('toolchain/gdb/gdb-7.11/gdb/COPYING')
notice_group = ndk.builds.NoticeGroup.TOOLCHAIN
arch_specific = True
split_build_by_arch = True
def install(self, out_dir, dist_dir, args):
src_dir = os.path.join(out_dir, self.name, self.build_arch, 'install')
install_path = self.get_install_path(
out_dir, args.system, self.build_arch)
if os.path.exists(install_path):
shutil.rmtree(install_path)
shutil.copytree(src_dir, install_path)
def make_linker_script(path, libs):
ndk.file.write_file(path, 'INPUT({})\n'.format(' '.join(libs)))
def create_libcxx_linker_scripts(lib_dir, abi, api):
static_libs = ['-lc++_static', '-lc++abi']
is_arm = abi == 'armeabi-v7a'
needs_android_support = api < 21
if needs_android_support:
static_libs.append('-landroid_support')
if is_arm:
static_libs.extend(['-lunwind', '-latomic'])
make_linker_script(
os.path.join(lib_dir, 'libc++.a.{}'.format(api)), static_libs)
shared_libs = []
if needs_android_support:
shared_libs.append('-landroid_support')
if is_arm:
shared_libs.extend(['-lunwind', '-latomic'])
shared_libs.append('-lc++_shared')
make_linker_script(
os.path.join(lib_dir, 'libc++.so.{}'.format(api)), shared_libs)
class Libcxx(ndk.builds.Module):
name = 'libc++'
path = 'sources/cxx-stl/llvm-libc++'
script = 'ndk/sources/cxx-stl/llvm-libc++/build.py'
notice = ndk.paths.android_path('external/libcxx/NOTICE')
notice_group = ndk.builds.NoticeGroup.TOOLCHAIN
arch_specific = True
deps = {
'libandroid_support',
'ndk-build',
'ndk-build-shortcut',
'platforms',
'sysroot',
}
def __init__(self):
super(Libcxx, self).__init__()
self.abis = None
self.obj_out = None
self.lib_out = None
self.libcxx_path = ndk.paths.android_path('external/libcxx')
def set_abis(self, arch):
if arch is None:
self.abis = ndk.abis.ALL_ABIS
else:
self.abis = ndk.abis.arch_to_abis(arch)
def build(self, build_dir, _dist_dir, args):
ndk_path = ndk.paths.get_install_path(build_dir)
ndk_build = os.path.join(ndk_path, 'build/ndk-build')
bionic_path = ndk.paths.android_path('bionic')
self.obj_out = os.path.join(build_dir, 'libcxx/obj')
self.lib_out = os.path.join(build_dir, 'libcxx/libs')
self.set_abis(args.arch)
android_mk = os.path.join(self.libcxx_path, 'Android.mk')
application_mk = os.path.join(self.libcxx_path, 'Application.mk')
prebuilt_ndk = build_support.android_path('prebuilts/ndk')
platforms_root = os.path.join(ndk_path, 'platforms')
unified_sysroot_path = os.path.join(ndk_path, 'sysroot')
toolchains_root = os.path.join(prebuilt_ndk, 'current/toolchains')
build_cmd = [
'bash', ndk_build, build_support.jobs_arg(), 'V=1',
'APP_ABI=' + ' '.join(self.abis),
'BIONIC_PATH=' + bionic_path,
# Use the prebuilt platforms and toolchains.
'NDK_UNIFIED_SYSROOT_PATH=' + unified_sysroot_path,
'NDK_PLATFORMS_ROOT=' + platforms_root,
'NDK_TOOLCHAINS_ROOT=' + toolchains_root,
'NDK_NEW_TOOLCHAINS_LAYOUT=true',
# Tell ndk-build where all of our makefiles are and where outputs
# should go. The defaults in ndk-build are only valid if we have a
# typical ndk-build layout with a jni/{Android,Application}.mk.
'NDK_PROJECT_PATH=null',
'APP_BUILD_SCRIPT=' + android_mk,
'NDK_APPLICATION_MK=' + application_mk,
'NDK_OUT=' + self.obj_out,
'NDK_LIBS_OUT=' + self.lib_out,
# Make sure we don't pick up a cached copy.
'LIBCXX_FORCE_REBUILD=true',
]
print('Running: ' + ' '.join(build_cmd))
subprocess.check_call(build_cmd)
def install(self, out_dir, dist_dir, args):
ndk_path = ndk.paths.get_install_path(out_dir)
install_root = os.path.join(ndk_path, self.path)
if os.path.exists(install_root):
shutil.rmtree(install_root)
os.makedirs(install_root)
shutil.copy2(
os.path.join(self.libcxx_path, 'Android.mk'), install_root)
shutil.copy2(
os.path.join(self.libcxx_path, 'NOTICE'), install_root)
shutil.copytree(
os.path.join(self.libcxx_path, 'include'),
os.path.join(install_root, 'include'))
shutil.copytree(self.lib_out, os.path.join(install_root, 'libs'))
# TODO(danalbert): Fix the test runner to work with a separated test
# source and build output tree. The test runner itself works with this,
# but we need to do some work to separate the two when we invoke it.
shutil.copytree(
os.path.join(self.libcxx_path, 'test'),
os.path.join(install_root, 'test'),
symlinks=True)
shutil.copytree(
os.path.join(self.libcxx_path, 'utils'),
os.path.join(install_root, 'utils'))
for abi in self.abis:
lib_dir = os.path.join(install_root, 'libs', abi)
# The static libraries installed to the obj dir, not the lib dir.
self.install_static_libs(lib_dir, abi)
# Create linker scripts for the libraries we use so that we link
# things properly even when we're not using ndk-build. The linker
# will read the script in place of the library so that we link the
# unwinder and other support libraries appropriately.
platforms_meta = json.loads(
ndk.file.read_file(ndk.paths.ndk_path('meta/platforms.json')))
for api in range(platforms_meta['min'], platforms_meta['max'] + 1):
if api < ndk.abis.min_api_for_abi(abi):
continue
create_libcxx_linker_scripts(lib_dir, abi, api)
def install_static_libs(self, lib_dir, abi):
static_lib_dir = os.path.join(self.obj_out, 'local', abi)
shutil.copy2(os.path.join(static_lib_dir, 'libc++abi.a'), lib_dir)
shutil.copy2(os.path.join(static_lib_dir, 'libc++_static.a'), lib_dir)
if abi == 'armeabi-v7a':
shutil.copy2(os.path.join(static_lib_dir, 'libunwind.a'), lib_dir)
if abi in ndk.abis.LP32_ABIS:
shutil.copy2(
os.path.join(static_lib_dir, 'libandroid_support.a'), lib_dir)
class Platforms(ndk.builds.Module):
name = 'platforms'
path = 'platforms'
min_supported_api = 16
# These API levels had no new native APIs. The contents of these platforms
# directories would be identical to the previous extant API level, so they
# are not included in the NDK to save space.
skip_apis = (20, 25)
# We still need a numeric API level for codenamed API levels because
# ABI_ANDROID_API in crtbrand is an integer. We start counting the
# codenamed releases from 9000 and increment for each additional release.
# This is filled by get_apis.
codename_api_map = {}
# Shared with the sysroot, though the sysroot NOTICE actually includes a
# lot more licenses. Platforms and Sysroot are essentially a single
# component that is split into two directories only temporarily, so this
# will be the end state when we merge the two anyway.
notice = ndk.paths.android_path('prebuilts/ndk/platform/sysroot/NOTICE')
def prebuilt_path(self, *args): # pylint: disable=no-self-use
return build_support.android_path('prebuilts/ndk/platform', *args)
def src_path(self, *args): # pylint: disable=no-self-use
return build_support.android_path('development/ndk/platforms', *args)
def gcc_toolchain(self, arch): # pylint: disable=no-self-use
host_tag = build_support.host_to_tag(build_support.get_default_host())
toolchain = build_support.arch_to_toolchain(arch) + '-4.9'
return build_support.android_path(
'prebuilts/ndk/current/toolchains', host_tag, toolchain)
def gcc_tool(self, tool, arch):
gcc_toolchain = self.gcc_toolchain(arch)
triple = build_support.arch_to_triple(arch)
return os.path.join(gcc_toolchain, 'bin', triple + '-' + tool)
def libdir_name(self, arch): # pylint: disable=no-self-use
if arch == 'x86_64':
return 'lib64'
else:
return 'lib'
def get_apis(self):
codenamed_apis = []
apis = []
for name in os.listdir(self.prebuilt_path('platforms')):
if not name.startswith('android-'):
continue
_, api_str = name.split('-')
try:
api = int(api_str)
if api >= self.min_supported_api:
apis.append(api)
except ValueError:
# Codenamed release like android-O, android-O-MR1, etc.
apis.append(api_str)
codenamed_apis.append(api_str)
for api_num, api_str in enumerate(sorted(codenamed_apis), start=9000):
self.codename_api_map[api_str] = api_num
return sorted(apis)
def get_arches(self, api): # pylint: disable=no-self-use
arches = ['arm', 'x86']
if api >= 21:
arches.extend(['arm64', 'x86_64'])
return arches
def get_build_cmd(self, dst, srcs, api, arch, build_number):
bionic_includes = build_support.android_path(
'bionic/libc/arch-common/bionic')
cc = ndk.paths.android_path(
'prebuilts/clang/host',
build_support.get_default_host() + '-x86',
Clang.version, 'bin/clang')
args = [
cc,
'-target', build_support.arch_to_triple(arch),
'--sysroot', self.prebuilt_path('sysroot'),
'-gcc-toolchain', self.gcc_toolchain(arch),
'-I', bionic_includes,
'-D__ANDROID_API__={}'.format(api),
'-DPLATFORM_SDK_VERSION={}'.format(api),
'-DABI_NDK_VERSION="{}"'.format(ndk.config.release),
'-DABI_NDK_BUILD_NUMBER="{}"'.format(build_number),
'-O2', '-fpic', '-Wl,-r', '-no-pie', '-nostdlib', '-o', dst,
] + srcs
return args
def check_elf_note(self, obj_file):
# readelf is a cross platform tool, so arch doesn't matter.
readelf = self.gcc_tool('readelf', 'arm')
out = subprocess.check_output([readelf, '--notes', obj_file])
if 'Android' not in out.decode('utf-8'):
raise RuntimeError(
'{} does not contain NDK ELF note'.format(obj_file))
def build_crt_object(self, dst, srcs, api, arch, build_number, defines):
try:
# No-op for stable releases.
api_num = int(api)
except ValueError:
# ValueError means this was a codenamed release. We need the
# integer matching this release for ABI_ANDROID_API in crtbrand.
api_num = self.codename_api_map[api]
cc_args = self.get_build_cmd(dst, srcs, api_num, arch, build_number)
cc_args.extend(defines)
subprocess.check_call(cc_args)
def build_crt_objects(self, dst_dir, api, arch, build_number):
src_dir = ndk.paths.android_path('bionic/libc/arch-common/bionic')
crt_brand = ndk.paths.ndk_path('sources/crt/crtbrand.S')
objects = {
'crtbegin_dynamic.o': [
os.path.join(src_dir, 'crtbegin.c'),
crt_brand,
],
'crtbegin_so.o': [
os.path.join(src_dir, 'crtbegin_so.c'),
crt_brand,
],
'crtbegin_static.o': [
os.path.join(src_dir, 'crtbegin.c'),
crt_brand,
],
'crtend_android.o': [
os.path.join(src_dir, 'crtend.S'),
],
'crtend_so.o': [
os.path.join(src_dir, 'crtend_so.S'),
],
}
for name, srcs in objects.items():
dst_path = os.path.join(dst_dir, name)
defs = []
if name == 'crtbegin_static.o':
# libc.a is always the latest version, so ignore the API level
# setting for crtbegin_static.
defs.append('-D_FORCE_CRT_ATFORK')
self.build_crt_object(
dst_path, srcs, api, arch, build_number, defs)
if name.startswith('crtbegin'):
self.check_elf_note(dst_path)
def build(self, build_dir, _dist_dir, args):
build_dir = os.path.join(build_dir, self.path)
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
for api in self.get_apis():
if api in self.skip_apis:
continue
platform = 'android-{}'.format(api)
for arch in self.get_arches(api):
arch_name = 'arch-{}'.format(arch)
dst_dir = os.path.join(build_dir, platform, arch_name)
os.makedirs(dst_dir)
self.build_crt_objects(dst_dir, api, arch, args.build_number)
def install(self, out_dir, dist_dir, args):
build_dir = os.path.join(out_dir, self.path)
install_dir = os.path.join(
ndk.paths.get_install_path(out_dir), self.path)
if os.path.exists(install_dir):
shutil.rmtree(install_dir)
os.makedirs(install_dir)
for api in self.get_apis():
if api in self.skip_apis:
continue
# Copy shared libraries from prebuilts/ndk/platform/platforms.
platform = 'android-{}'.format(api)
platform_src = self.prebuilt_path('platforms', platform)
platform_dst = os.path.join(install_dir, 'android-{}'.format(api))
shutil.copytree(platform_src, platform_dst)
for arch in self.get_arches(api):
arch_name = 'arch-{}'.format(arch)
triple = ndk.abis.arch_to_triple(arch)
# Install static libraries from prebuilts/ndk/platform/sysroot.
# TODO: Determine if we can change the build system to use the
# libraries directly from the sysroot directory rather than
# duplicating all the libraries in platforms.
lib_dir = self.prebuilt_path('sysroot/usr/lib', triple)
libdir_name = self.libdir_name(arch)
lib_dir_dst = os.path.join(
install_dir, platform, arch_name, 'usr', libdir_name)
for name in os.listdir(lib_dir):
lib_src = os.path.join(lib_dir, name)
lib_dst = os.path.join(lib_dir_dst, name)
shutil.copy2(lib_src, lib_dst)
if libdir_name == 'lib64':
# The Clang driver won't accept a sysroot that contains
# only a lib64. An empty lib dir is enough to convince it.
os.makedirs(os.path.join(
install_dir, platform, arch_name, 'usr/lib'))
# Install the CRT objects that we just built.
obj_dir = os.path.join(build_dir, platform, arch_name)
for name in os.listdir(obj_dir):
obj_src = os.path.join(obj_dir, name)
obj_dst = os.path.join(lib_dir_dst, name)
shutil.copy2(obj_src, obj_dst)
# https://github.com/android-ndk/ndk/issues/372
for root, dirs, files in os.walk(install_dir):
if len(files) == 0 and len(dirs) == 0:
with open(os.path.join(root, '.keep_dir'), 'w') as keep_file:
keep_file.write(
'This file forces git to keep the directory.')
class LibShaderc(ndk.builds.Module):
name = 'libshaderc'
path = 'sources/third_party/shaderc'
src = ndk.paths.android_path('external/shaderc')
notice_group = ndk.builds.NoticeGroup.TOOLCHAIN
@property
def notices(self):
shaderc_dir = os.path.join(self.src, 'shaderc')
return [
os.path.join(shaderc_dir, 'LICENSE'),
os.path.join(shaderc_dir, 'third_party', 'LICENSE.glslang'),
os.path.join(shaderc_dir, 'third_party', 'LICENSE.spirv-tools'),
]
def build(self, _build_dir, dist_dir, _args):
copies = [
{
'source_dir': os.path.join(self.src, 'shaderc'),
'dest_dir': 'shaderc',
'files': [
'Android.mk', 'libshaderc/Android.mk',
'libshaderc_util/Android.mk',
'third_party/Android.mk',
'utils/update_build_version.py',
'CHANGES',
],
'dirs': [
'libshaderc/include', 'libshaderc/src',
'libshaderc_util/include', 'libshaderc_util/src',
],
},
{
'source_dir': os.path.join(self.src, 'spirv-tools'),
'dest_dir': 'shaderc/third_party/spirv-tools',
'files': [
'utils/generate_grammar_tables.py',
'utils/generate_language_headers.py',
'utils/generate_registry_tables.py',
'utils/update_build_version.py',
'Android.mk',
'CHANGES',
],
'dirs': ['include', 'source'],
},
{
'source_dir': os.path.join(self.src, 'spirv-headers'),
'dest_dir':
'shaderc/third_party/spirv-tools/external/spirv-headers',
'dirs': ['include'],
'files': [
'include/spirv/1.0/spirv.py',
'include/spirv/1.1/spirv.py',
'include/spirv/1.2/spirv.py'
],
},
{
'source_dir': os.path.join(self.src, 'glslang'),
'dest_dir': 'shaderc/third_party/glslang',
'files': ['glslang/OSDependent/osinclude.h'],
'dirs': [
'SPIRV',
'OGLCompilersDLL',
'glslang/GenericCodeGen',
'hlsl',
'glslang/Include',
'glslang/MachineIndependent',
'glslang/OSDependent/Unix',
'glslang/Public',
],
},
]
default_ignore_patterns = shutil.ignore_patterns(
"*CMakeLists.txt",
"*.py",
"*test.h",
"*test.cc")
temp_dir = tempfile.mkdtemp()
shaderc_path = os.path.join(temp_dir, 'shaderc')
try:
for properties in copies:
source_dir = properties['source_dir']
dest_dir = os.path.join(temp_dir, properties['dest_dir'])
for d in properties['dirs']:
src = os.path.join(source_dir, d)
dst = os.path.join(dest_dir, d)
print(src, " -> ", dst)
shutil.copytree(src, dst,
ignore=default_ignore_patterns)
for f in properties['files']:
print(source_dir, ':', dest_dir, ":", f)
# Only copy if the source file exists. That way
# we can update this script in anticipation of
# source files yet-to-come.
if os.path.exists(os.path.join(source_dir, f)):
install_file(f, source_dir, dest_dir)
else:
print(source_dir, ':', dest_dir, ":", f, "SKIPPED")
build_support.make_package('libshaderc', shaderc_path, dist_dir)
finally:
shutil.rmtree(temp_dir)
class CpuFeatures(ndk.builds.PackageModule):
name = 'cpufeatures'
path = 'sources/android/cpufeatures'
src = build_support.ndk_path('sources/android/cpufeatures')
class NativeAppGlue(ndk.builds.PackageModule):
name = 'native_app_glue'
path = 'sources/android/native_app_glue'
src = build_support.ndk_path('sources/android/native_app_glue')
class NdkHelper(ndk.builds.PackageModule):
name = 'ndk_helper'
path = 'sources/android/ndk_helper'
src = build_support.ndk_path('sources/android/ndk_helper')
class Gtest(ndk.builds.PackageModule):
name = 'gtest'
path = 'sources/third_party/googletest'
src = ndk.paths.android_path('external/googletest/googletest')
class Sysroot(ndk.builds.Module):
name = 'sysroot'
path = 'sysroot'
notice = ndk.paths.android_path('prebuilts/ndk/platform/sysroot/NOTICE')
def build(self, _out_dir, dist_dir, args):
temp_dir = tempfile.mkdtemp()
try:
path = build_support.android_path('prebuilts/ndk/platform/sysroot')
install_path = os.path.join(temp_dir, 'sysroot')
shutil.copytree(path, install_path)
if args.system != 'linux':
# linux/netfilter has some headers with names that differ only
# by case, which can't be extracted to a case-insensitive
# filesystem, which are the defaults for Darwin and Windows :(
#
# There isn't really a good way to decide which of these to
# keep and which to remove. The capitalized versions expose
# different APIs, but we can't keep both. So far no one has
# filed bugs about needing either API, so let's just dedup them
# consistently and we can change that if we hear otherwise.
remove_paths = [
'usr/include/linux/netfilter_ipv4/ipt_ECN.h',
'usr/include/linux/netfilter_ipv4/ipt_TTL.h',
'usr/include/linux/netfilter_ipv6/ip6t_HL.h',
'usr/include/linux/netfilter/xt_CONNMARK.h',
'usr/include/linux/netfilter/xt_DSCP.h',
'usr/include/linux/netfilter/xt_MARK.h',
'usr/include/linux/netfilter/xt_RATEEST.h',
'usr/include/linux/netfilter/xt_TCPMSS.h',
]
for remove_path in remove_paths:
os.remove(os.path.join(install_path, remove_path))
ndk_version_h_path = os.path.join(
install_path, 'usr/include/android/ndk-version.h')
with open(ndk_version_h_path, 'w') as ndk_version_h:
major = ndk.config.major
minor = ndk.config.hotfix
beta = ndk.config.beta
canary = '1' if ndk.config.canary else '0'
build = args.build_number
if build == 'dev':
build = '0'
ndk_version_h.write(textwrap.dedent("""\
#ifndef ANDROID_NDK_VERSION_H
#define ANDROID_NDK_VERSION_H
/**
* Major version of this NDK.
*
* For example: 16 for r16.
*/
#define __NDK_MAJOR__ {major}
/**
* Minor version of this NDK.
*
* For example: 0 for r16 and 1 for r16b.
*/
#define __NDK_MINOR__ {minor}
/**
* Set to 0 if this is a release build, or 1 for beta 1,
* 2 for beta 2, and so on.
*/
#define __NDK_BETA__ {beta}
/**
* Build number for this NDK.
*
* For a local development build of the NDK, this is -1.
*/
#define __NDK_BUILD__ {build}
/**
* Set to 1 if this is a canary build, 0 if not.
*/
#define __NDK_CANARY__ {canary}
#endif /* ANDROID_NDK_VERSION_H */
""".format(
major=major,
minor=minor,
beta=beta,
build=build,
canary=canary)))
build_support.make_package('sysroot', install_path, dist_dir)
finally:
shutil.rmtree(temp_dir)
class Vulkan(ndk.builds.Module):
name = 'vulkan'
path = 'sources/third_party/vulkan'
notice = ndk.paths.android_path(
'external/vulkan-validation-layers/LICENSE.txt')
def build(self, build_dir, dist_dir, args):
print('Constructing Vulkan validation layer source...')
vulkan_root_dir = ndk.paths.android_path(
'external/vulkan-validation-layers')
copies = [
{
'source_dir': vulkan_root_dir,
'dest_dir': 'vulkan/src',
'files': [
],
'dirs': [
'layers', 'include', 'tests', 'common', 'libs', 'scripts'
],
},
{
'source_dir': vulkan_root_dir + '/loader',
'dest_dir': 'vulkan/src/loader',
'files': [
'vk_loader_platform.h',
'vk_loader_layer.h'
],
'dirs': [],
}
]
default_ignore_patterns = shutil.ignore_patterns(
"*CMakeLists.txt",
"*test.cc",
"linux",
"windows")
base_vulkan_path = os.path.join(build_dir, 'vulkan')
vulkan_path = os.path.join(base_vulkan_path, 'src')
for properties in copies:
source_dir = properties['source_dir']
dest_dir = os.path.join(build_dir, properties['dest_dir'])
for d in properties['dirs']:
src = os.path.join(source_dir, d)
dst = os.path.join(dest_dir, d)
shutil.rmtree(dst, True)
shutil.copytree(src, dst,
ignore=default_ignore_patterns)
for f in properties['files']:
install_file(f, source_dir, dest_dir)
# Copy Android build components
print('Copying Vulkan build components...')
src = os.path.join(vulkan_root_dir, 'build-android')
dst = os.path.join(vulkan_path, 'build-android')
shutil.rmtree(dst, True)
shutil.copytree(src, dst, ignore=default_ignore_patterns)
print('Copying finished')
# Copy binary validation layer libraries
print('Copying Vulkan binary validation layers...')
src = build_support.android_path(
'prebuilts/ndk/vulkan-validation-layers')
dst = os.path.join(vulkan_path, 'build-android/jniLibs')
shutil.rmtree(dst, True)
shutil.copytree(src, dst, ignore=default_ignore_patterns)
print('Copying finished')
build_cmd = [
'bash', vulkan_path + '/build-android/android-generate.sh'
]
print('Generating generated layers...')
subprocess.check_call(build_cmd)
print('Generation finished')
build_args = ndk.builds.common_build_args(build_dir, dist_dir, args)
if args.arch is not None:
build_args.append('--arch={}'.format(args.arch))
build_args.append('--no-symbols')
# TODO: Verify source packaged properly
print('Packaging Vulkan source...')
src = os.path.join(build_dir, 'vulkan')
build_support.make_package('vulkan', src, dist_dir)
print('Packaging Vulkan source finished')
def make_format_value(value):
if isinstance(value, list):
return ' '.join(value)
return value
def var_dict_to_make(var_dict):
lines = []
for name, value in var_dict.items():
lines.append('{} := {}'.format(name, make_format_value(value)))
return os.linesep.join(lines)
def cmake_format_value(value):
if isinstance(value, list):
return ';'.join(value)
return value
def var_dict_to_cmake(var_dict):
lines = []
for name, value in var_dict.items():
lines.append('set({} "{}")'.format(name, cmake_format_value(value)))
return os.linesep.join(lines)
def generate_language_specific_metadata(name, install_path, json_path, func):
meta = json.loads(ndk.file.read_file(json_path))
meta_vars = func(meta)
ndk.file.write_file(
os.path.join(install_path, 'core/{}.mk'.format(name)),
var_dict_to_make(meta_vars))
ndk.file.write_file(
os.path.join(install_path, 'cmake/{}.cmake'.format(name)),
var_dict_to_cmake(meta_vars))
def abis_meta_transform(metadata):
default_abis = []
deprecated_abis = []
lp32_abis = []
lp64_abis = []
for abi, abi_data in metadata.items():
bitness = abi_data['bitness']
if bitness == 32:
lp32_abis.append(abi)
elif bitness == 64:
lp64_abis.append(abi)
else:
raise ValueError('{} bitness is unsupported value: {}'.format(
abi, bitness))
if abi_data['default']:
default_abis.append(abi)
if abi_data['deprecated']:
deprecated_abis.append(abi)
meta_vars = {
'NDK_DEFAULT_ABIS': sorted(default_abis),
'NDK_DEPRECATED_ABIS': sorted(deprecated_abis),
'NDK_KNOWN_DEVICE_ABI32S': sorted(lp32_abis),
'NDK_KNOWN_DEVICE_ABI64S': sorted(lp64_abis),
}
return meta_vars
def platforms_meta_transform(metadata):
meta_vars = {
'NDK_MIN_PLATFORM_LEVEL': metadata['min'],
'NDK_MAX_PLATFORM_LEVEL': metadata['max'],
}
for src, dst in metadata['aliases'].items():
name = 'NDK_PLATFORM_ALIAS_{}'.format(src)
value = 'android-{}'.format(dst)
meta_vars[name] = value
return meta_vars
class NdkBuild(ndk.builds.PackageModule):
name = 'ndk-build'
path = 'build'
src = ndk.paths.ndk_path('build')
notice = ndk.paths.ndk_path('NOTICE')
def install(self, out_dir, dist_dir, args):
super(NdkBuild, self).install(out_dir, dist_dir, args)
install_path = self.get_install_path(out_dir, args.system)
abis_json = os.path.join(Meta.path, 'abis.json')
generate_language_specific_metadata(
'abis', install_path, abis_json, abis_meta_transform)
platforms_json = os.path.join(Meta.path, 'platforms.json')
generate_language_specific_metadata(
'platforms', install_path, platforms_json,
platforms_meta_transform)
class PythonPackages(ndk.builds.PackageModule):
name = 'python-packages'
path = 'python-packages'
src = ndk.paths.android_path('development/python-packages')
class SystemStl(ndk.builds.PackageModule):
name = 'system-stl'
path = 'sources/cxx-stl/system'
src = build_support.ndk_path('sources/cxx-stl/system')
class LibAndroidSupport(ndk.builds.PackageModule):
name = 'libandroid_support'
path = 'sources/android/support'
src = build_support.ndk_path('sources/android/support')
class Libcxxabi(ndk.builds.PackageModule):
name = 'libc++abi'
path = 'sources/cxx-stl/llvm-libc++abi'
src = build_support.android_path('external/libcxxabi')
class SimplePerf(ndk.builds.Module):
name = 'simpleperf'
path = 'simpleperf'
notice = ndk.paths.android_path('prebuilts/simpleperf/NOTICE')
def build(self, build_dir, dist_dir, args):
print('Building simpleperf...')
install_dir = os.path.join(build_dir, 'simpleperf')
if os.path.exists(install_dir):
shutil.rmtree(install_dir)
os.makedirs(install_dir)
simpleperf_path = ndk.paths.android_path('prebuilts/simpleperf')
dirs = ['doc', 'inferno', 'bin/android']
is_win = args.system.startswith('windows')
host_bin_dir = 'windows' if is_win else args.system
dirs.append(os.path.join('bin/', host_bin_dir))
for d in dirs:
shutil.copytree(os.path.join(simpleperf_path, d),
os.path.join(install_dir, d))
for item in os.listdir(simpleperf_path):
should_copy = False
if item.endswith('.py') and item not in ['update.py', 'test.py']:
should_copy = True
elif item == 'report_html.js':
should_copy = True
elif item == 'inferno.sh' and not is_win:
should_copy = True
elif item == 'inferno.bat' and is_win:
should_copy = True
if should_copy:
shutil.copy2(os.path.join(simpleperf_path, item), install_dir)
shutil.copy2(os.path.join(simpleperf_path, 'ChangeLog'), install_dir)
build_support.make_package('simpleperf', install_dir, dist_dir)
class RenderscriptLibs(ndk.builds.PackageModule):
name = 'renderscript-libs'
path = 'sources/android/renderscript'
src = build_support.ndk_path('sources/android/renderscript')
class RenderscriptToolchain(ndk.builds.InvokeBuildModule):
name = 'renderscript-toolchain'
path = 'toolchains/renderscript/prebuilt/{host}'
script = 'build-renderscript.py'
@property
def notices(self):
base = ndk.paths.android_path('prebuilts/renderscript/host')
return [
os.path.join(base, 'darwin-x86/current/NOTICE'),
os.path.join(base, 'linux-x86/current/NOTICE'),
os.path.join(base, 'windows-x86/current/NOTICE'),
]
class Changelog(ndk.builds.FileModule):
name = 'changelog'
path = 'CHANGELOG.md'
src = build_support.ndk_path('CHANGELOG.md')
no_notice = True
class NdkGdbShortcut(ndk.builds.ScriptShortcutModule):
name = 'ndk-gdb-shortcut'
path = 'ndk-gdb'
script = 'prebuilt/{host}/bin/ndk-gdb'
windows_ext = '.cmd'
class NdkWhichShortcut(ndk.builds.ScriptShortcutModule):
name = 'ndk-which-shortcut'
path = 'ndk-which'
script = 'prebuilt/{host}/bin/ndk-which'
windows_ext = '' # There isn't really a Windows ndk-which.
class NdkDependsShortcut(ndk.builds.ScriptShortcutModule):
name = 'ndk-depends-shortcut'
path = 'ndk-depends'
script = 'prebuilt/{host}/bin/ndk-depends'
windows_ext = '.exe'
class NdkStackShortcut(ndk.builds.ScriptShortcutModule):
name = 'ndk-stack-shortcut'
path = 'ndk-stack'
script = 'prebuilt/{host}/bin/ndk-stack'
windows_ext = '.exe'
class NdkBuildShortcut(ndk.builds.ScriptShortcutModule):
name = 'ndk-build-shortcut'
path = 'ndk-build'
script = 'build/ndk-build'
windows_ext = '.cmd'
class Readme(ndk.builds.FileModule):
name = 'readme'
path = 'README.md'
src = build_support.ndk_path('UserReadme.md')
CANARY_TEXT = textwrap.dedent("""\
This is a canary build of the Android NDK. It's updated almost every day.
Canary builds are designed for early adopters and can be prone to breakage.
Sometimes they can break completely. To aid development and testing, this
distribution can be installed side-by-side with your existing, stable NDK
release.
""")
class CanaryReadme(ndk.builds.Module):
name = 'canary-readme'
path = 'README.canary'
no_notice = True
def build(self, _out_dir, _dist_dir, _args):
pass
def install(self, out_dir, _dist_dir, _args):
if ndk.config.canary:
extract_dir = ndk.paths.get_install_path(out_dir)
canary_path = os.path.join(extract_dir, self.path)
with open(canary_path, 'w') as canary_file:
canary_file.write(CANARY_TEXT)
class Meta(ndk.builds.PackageModule):
name = 'meta'
path = 'meta'
src = build_support.ndk_path('meta')
no_notice = True
class WrapSh(ndk.builds.PackageModule):
name = 'wrap.sh'
path = 'wrap.sh'
src = build_support.ndk_path('wrap.sh')
no_notice = True
class SourceProperties(ndk.builds.Module):
name = 'source.properties'
path = 'source.properties'
no_notice = True
def build(self, _out_dir, _dist_dir, _args):
pass
def install(self, out_dir, _dist_dir, args):
install_dir = ndk.paths.get_install_path(out_dir)
path = os.path.join(install_dir, self.path)
with open(path, 'w') as source_properties:
build = args.build_number
if build == 'dev':
build = '0'
version = '{}.{}.{}'.format(
ndk.config.major, ndk.config.hotfix, build)
if ndk.config.beta > 0:
version += '-beta{}'.format(ndk.config.beta)
source_properties.writelines([
'Pkg.Desc = Android NDK\n',
'Pkg.Revision = {}\n'.format(version)
])
class AdbPy(ndk.builds.PythonPackage):
name = 'adb.py'
path = ndk.paths.android_path('development/python-packages/adb/setup.py')
notice = ndk.paths.android_path('development/python-packages/NOTICE')
class Lit(ndk.builds.PythonPackage):
name = 'lit'
path = build_support.android_path('external/llvm/utils/lit/setup.py')
notice = ndk.paths.android_path('external/llvm/NOTICE')
class NdkPy(ndk.builds.PythonPackage):
name = 'ndk.py'
path = build_support.ndk_path('setup.py')
def create_notice_file(path, for_group):
# Using sets here so we can perform some amount of duplicate reduction. In
# a lot of cases there will be minor differences that cause lots of
# "duplicates", but might as well catch what we can.
notice_files = set()
for module in ALL_MODULES:
if module.notice_group == for_group:
for notice in module.notices:
notice_files.add(notice)
licenses = set()
for notice_path in notice_files:
with open(notice_path) as notice_file:
licenses.add(notice_file.read())
with open(path, 'w') as output_file:
# Sorting the contents here to try to make things deterministic.
output_file.write(os.linesep.join(sorted(list(licenses))))
def launch_build(worker, module, out_dir, dist_dir, args, log_dir):
result = do_build(worker, module, out_dir, dist_dir, args, log_dir)
if not result:
return result, module
do_install(worker, module, out_dir, dist_dir, args)
return True, module
def do_build(worker, module, out_dir, dist_dir, args, log_dir):
with open(module.log_path(log_dir), 'w') as log_file:
os.dup2(log_file.fileno(), sys.stdout.fileno())
os.dup2(log_file.fileno(), sys.stderr.fileno())
try:
worker.status = 'Building {}...'.format(module)
module.build(out_dir, dist_dir, args)
return True
except Exception: # pylint: disable=broad-except
traceback.print_exc()
return False
def do_install(worker, module, out_dir, dist_dir, args):
worker.status = 'Installing {}...'.format(module)
module.install(out_dir, dist_dir, args)
def split_module_by_arch(module, arches):
if module.split_build_by_arch:
for arch in arches:
build_module = copy.deepcopy(module)
build_module.build_arch = arch
yield build_module
else:
yield module
def _get_transitive_module_deps(module, deps, unknown_deps, seen):
seen.add(module)
for name in module.deps:
if name not in NAMES_TO_MODULES:
unknown_deps.add(name)
continue
dep = NAMES_TO_MODULES[name]
if dep in seen:
# Cycle detection is already handled by ndk.deps.DependencyManager.
# Just avoid falling into an infinite loop here and let that do the
# work.
continue
deps.add(dep)
_get_transitive_module_deps(dep, deps, unknown_deps, seen)
def get_transitive_module_deps(module):
seen = set()
deps = set()
unknown_deps = set()
_get_transitive_module_deps(module, deps, unknown_deps, seen)
return deps, unknown_deps
def get_modules_to_build(module_names, arches):
"""Returns a list of modules to be built given a list of module names.
The module names are those given explicitly by the user or the full list.
In the event that the user has passed a subset of modules, we need to also
return the dependencies of that module.
"""
unknown_modules = set()
modules = set()
deps_only = set()
for name in module_names:
if name not in NAMES_TO_MODULES:
# Build a list of all the unknown modules rather than error out
# immediately so we can provide a complete error message.
unknown_modules.add(name)
module = NAMES_TO_MODULES[name]
modules.add(module)
deps, unknown_deps = get_transitive_module_deps(module)
modules.update(deps)
# --skip-deps may be passed if the user wants to avoid rebuilding a
# costly dependency. It's up to the user to guarantee that the
# dependency has actually been built. Modules are skipped by
# immediately completing them rather than sending them to the
# workqueue. As such, we need to return a list of which modules are
# *only* in the list because they are dependencies rather than being a
# part of the requested set.
for dep in deps:
if dep.name not in module_names:
deps_only.add(dep)
unknown_modules.update(unknown_deps)
if unknown_modules:
sys.exit('Unknown modules: {}'.format(
', '.join(sorted(list(unknown_modules)))))
build_modules = []
for module in modules:
for build_module in split_module_by_arch(module, arches):
build_modules.append(build_module)
return sorted(list(build_modules)), deps_only
ALL_MODULES = [
AdbPy(),
Binutils(),
CanaryReadme(),
Changelog(),
Clang(),
CpuFeatures(),
GdbServer(),
Gtest(),
HostTools(),
LibAndroidSupport(),
LibShaderc(),
Libcxx(),
Libcxxabi(),
Lit(),
Meta(),
NativeAppGlue(),
NdkBuild(),
NdkBuildShortcut(),
NdkDepends(),
NdkDependsShortcut(),
NdkGdbShortcut(),
NdkHelper(),
NdkPy(),
NdkStack(),
NdkStackShortcut(),
NdkWhichShortcut(),
Platforms(),
PythonPackages(),
Readme(),
RenderscriptLibs(),
RenderscriptToolchain(),
ShaderTools(),
SimplePerf(),
SourceProperties(),
Sysroot(),
SystemStl(),
Vulkan(),
WrapSh(),
]
NAMES_TO_MODULES = {m.name: m for m in ALL_MODULES}
def get_all_module_names():
return [m.name for m in ALL_MODULES]
def build_number_arg(value):
if value.startswith('P'):
# Treehugger build. Treat as a local development build.
return '0'
return value
def parse_args():
parser = argparse.ArgumentParser(
description=inspect.getdoc(sys.modules[__name__]))
parser.add_argument(
'--arch',
choices=('arm', 'arm64', 'x86', 'x86_64'),
help='Build for the given architecture. Build all by default.')
parser.add_argument(
'-j', '--jobs', type=int, default=multiprocessing.cpu_count(),
help=('Number of parallel builds to run. Note that this will not '
'affect the -j used for make; this just parallelizes '
'checkbuild.py. Defaults to the number of CPUs available.'))
parser.add_argument(
'--skip-deps', action='store_true',
help=('Assume that dependencies have been built and only build '
'explicitly named modules.'))
package_group = parser.add_mutually_exclusive_group()
package_group.add_argument(
'--package', action='store_true', dest='package', default=True,
help='Package the NDK when done building (default).')
package_group.add_argument(
'--no-package', action='store_false', dest='package',
help='Do not package the NDK when done building.')
package_group.add_argument(
'--force-package', action='store_true', dest='force_package',
help='Force a package even if only building a subset of modules.')
test_group = parser.add_mutually_exclusive_group()
test_group.add_argument(
'--build-tests', action='store_true', dest='build_tests', default=True,
help=textwrap.dedent("""\
Build tests when finished. --package is required. Not supported
when targeting Windows.
"""))
test_group.add_argument(
'--no-build-tests', action='store_false', dest='build_tests',
help='Skip building tests after building the NDK.')
parser.add_argument(
'--build-number', default='0', type=build_number_arg,
help='Build number for use in version files.')
parser.add_argument(
'--release', help='Ignored. Temporarily compatibility.')
parser.add_argument(
'--system', choices=('darwin', 'linux', 'windows', 'windows64'),
default=build_support.get_default_host(),
help='Build for the given OS.')
module_group = parser.add_mutually_exclusive_group()
module_group.add_argument(
'--module', dest='modules', action='append', default=[],
choices=get_all_module_names(), help='NDK modules to build.')
module_group.add_argument(
'--host-only', action='store_true',
help='Skip building target components.')
return parser.parse_known_args()
def log_build_failure(log_path, dist_dir):
with open(log_path, 'r') as log_file:
contents = log_file.read()
print(contents)
# The build server has a build_error.log file that is supposed to be
# the short log of the failure that stopped the build. Append our
# failing log to that.
build_error_log = os.path.join(dist_dir, 'logs/build_error.log')
with open(build_error_log, 'a') as error_log:
error_log.write('\n')
error_log.write(contents)
def launch_buildable(deps, workqueue, out_dir, dist_dir, log_dir, args,
skip_modules):
# If args.skip_deps is true, we could get into a case where we just
# dequeued the only module that was still building and the only
# items in get_buildable() are modules that will be skipped.
# Without this outer while loop, we'd mark the skipped dependencies
# as complete and then complete the outer loop. The workqueue
# would be out of work and we'd exit.
#
# Avoid this by making sure that we queue all possible buildable
# modules before we complete the loop.
while deps.buildable_modules:
for module in deps.get_buildable():
if args.skip_deps and module in skip_modules:
deps.complete(module)
continue
workqueue.add_task(
launch_build, module, out_dir, dist_dir, args, log_dir)
def wait_for_build(deps, workqueue, out_dir, dist_dir, log_dir, args,
skip_modules):
console = ndk.ansi.get_console()
ui = ndk.ui.get_build_progress_ui(console, workqueue)
with ndk.ansi.disable_terminal_echo(sys.stdin):
with console.cursor_hide_context():
while not workqueue.finished():
result, module = workqueue.get_result()
if not result:
ui.clear()
print('Build failed: {}'.format(module))
log_build_failure(
module.log_path(log_dir), dist_dir)
sys.exit(1)
elif not console.smart_console:
ui.clear()
print('Build succeeded: {}'.format(module))
deps.complete(module)
launch_buildable(
deps, workqueue, out_dir, dist_dir, log_dir, args,
skip_modules)
ui.draw()
ui.clear()
print('Build finished')
def main():
logging.basicConfig()
total_timer = ndk.timer.Timer()
total_timer.start()
args, module_names = parse_args()
module_names.extend(args.modules)
if not module_names:
module_names = get_all_module_names()
if args.host_only:
module_names = [
'clang',
'gcc',
'host-tools',
'ndk-build',
'python-packages',
'renderscript-toolchain',
'shader-tools',
'simpleperf',
]
required_package_modules = set(get_all_module_names())
have_required_modules = required_package_modules <= set(module_names)
do_package = have_required_modules if args.package else False
if args.force_package:
do_package = True
# TODO(danalbert): wine?
# We're building the Windows packages from Linux, so we can't actually run
# any of the tests from here.
if args.system.startswith('windows') or not do_package:
args.build_tests = False
os.chdir(os.path.dirname(os.path.realpath(__file__)))
# Set ANDROID_BUILD_TOP.
if 'ANDROID_BUILD_TOP' in os.environ:
sys.exit(textwrap.dedent("""\
Error: ANDROID_BUILD_TOP is already set in your environment.
This typically means you are running in a shell that has lunched a
target in a platform build. The platform environment interferes
with the NDK build environment, so the build cannot continue.
Launch a new shell before building the NDK."""))
os.environ['ANDROID_BUILD_TOP'] = os.path.realpath('..')
out_dir = build_support.get_out_dir()
dist_dir = build_support.get_dist_dir(out_dir)
print('Cleaning up...')
ndk.builds.invoke_build('dev-cleanup.sh')
arches = build_support.ALL_ARCHITECTURES
if args.arch is not None:
arches = [args.arch]
modules, deps_only = get_modules_to_build(module_names, arches)
print('Building modules: {}'.format(' '.join(
[str(m) for m in modules
if not args.skip_deps or m not in deps_only])))
print('Machine has {} CPUs'.format(multiprocessing.cpu_count()))
deps = ndk.deps.DependencyManager(modules)
log_dir = os.path.join(dist_dir, 'logs')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
build_timer = ndk.timer.Timer()
workqueue = ndk.workqueue.WorkQueue(args.jobs)
try:
with build_timer:
ndk_dir = ndk.paths.get_install_path(out_dir)
if not os.path.exists(ndk_dir):
os.makedirs(ndk_dir)
launch_buildable(
deps, workqueue, out_dir, dist_dir, log_dir, args, deps_only)
wait_for_build(
deps, workqueue, out_dir, dist_dir, log_dir, args, deps_only)
if deps.get_buildable():
raise RuntimeError(
'Builder stopped early. Modules are still '
'buildable: {}'.format(', '.join(deps.get_buildable())))
install_dir = ndk.paths.get_install_path(out_dir)
create_notice_file(
os.path.join(install_dir, 'NOTICE'),
ndk.builds.NoticeGroup.BASE)
create_notice_file(
os.path.join(install_dir, 'NOTICE.toolchain'),
ndk.builds.NoticeGroup.TOOLCHAIN)
du_str = subprocess.check_output(['du', '-sm', install_dir])
match = re.match(r'^(\d+)', du_str.decode('utf-8'))
size_str = match.group(1)
installed_size = int(size_str)
finally:
workqueue.terminate()
workqueue.join()
package_timer = ndk.timer.Timer()
with package_timer:
if do_package:
print('Packaging NDK...')
host_tag = build_support.host_to_tag(args.system)
package_path = package_ndk(
ndk_dir, dist_dir, host_tag, args.build_number)
packaged_size_bytes = os.path.getsize(package_path)
packaged_size = packaged_size_bytes // (2 ** 20)
good = True
test_timer = ndk.timer.Timer()
with test_timer:
if args.build_tests:
good = build_ndk_tests(out_dir, dist_dir, args)
print() # Blank line between test results and timing data.
total_timer.finish()
print('')
print('Installed size: {} MiB'.format(installed_size))
if do_package:
print('Package size: {} MiB'.format(packaged_size))
print('Finished {}'.format('successfully' if good else 'unsuccessfully'))
print('Build: {}'.format(build_timer.duration))
print('Packaging: {}'.format(package_timer.duration))
print('Testing: {}'.format(test_timer.duration))
print('Total: {}'.format(total_timer.duration))
subject = 'NDK Build {}!'.format('Passed' if good else 'Failed')
body = 'Build finished in {}'.format(total_timer.duration)
ndk.notify.toast(subject, body)
sys.exit(not good)
@contextlib.contextmanager
def _assign_self_to_new_process_group(fd):
# It seems the build servers run us in our own session, in which case we
# get EPERM from `setpgrp`. No need to call this in that case because we
# will already be the process group leader.
if os.getpid() == os.getsid(os.getpid()):
yield
return
if ndk.ansi.is_self_in_tty_foreground_group(fd):
old_pgrp = os.tcgetpgrp(fd.fileno())
os.tcsetpgrp(fd.fileno(), os.getpid())
os.setpgrp()
try:
yield
finally:
os.tcsetpgrp(fd.fileno(), old_pgrp)
else:
os.setpgrp()
yield
def _run_main_in_new_process_group():
with _assign_self_to_new_process_group(sys.stdin):
main()
if __name__ == '__main__':
_run_main_in_new_process_group()