blob: 2a7c4b7d806388f57b63cdd5dc26d846ffcd0913 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script to build binary components of the SDK.
This script builds binary components of the Native Client SDK, create tarballs
for them, and uploads them to Google Cloud Storage.
This prevents a source dependency on the Chromium/NaCl tree in the Native
Client SDK repo.
"""
import argparse
import datetime
import glob
import hashlib
import json
import os
import sys
import tempfile
if sys.version_info < (2, 7, 0):
sys.stderr.write("python 2.7 or later is required run this script\n")
sys.exit(1)
import buildbot_common
import build_version
from build_paths import NACL_DIR, OUT_DIR, SRC_DIR, SDK_SRC_DIR
from build_paths import BUILD_ARCHIVE_DIR
sys.path.append(os.path.join(SDK_SRC_DIR, 'tools'))
import getos
import oshelpers
BUILD_DIR = os.path.join(NACL_DIR, 'build')
NACL_TOOLCHAIN_DIR = os.path.join(NACL_DIR, 'toolchain')
NACL_TOOLCHAINTARS_DIR = os.path.join(NACL_TOOLCHAIN_DIR, '.tars')
CYGTAR = os.path.join(BUILD_DIR, 'cygtar.py')
PKGVER = os.path.join(BUILD_DIR, 'package_version', 'package_version.py')
VERSION_JSON = os.path.join(BUILD_ARCHIVE_DIR, 'version.json')
PLATFORM = getos.GetPlatform()
TAR = oshelpers.FindExeInPath('tar')
options = None
all_archives = []
# Mapping from toolchain name to the equivalent package_version.py directory
# name.
TOOLCHAIN_PACKAGE_MAP = {
'glibc_x86': 'nacl_x86_glibc',
'glibc_arm': 'nacl_arm_glibc',
'pnacl': 'pnacl_newlib'}
def Tar(archive_path, root, files):
if os.path.exists(TAR):
cmd = [TAR]
else:
cmd = [sys.executable, CYGTAR]
cmd.extend(['-cjf', archive_path])
cmd.extend(files)
buildbot_common.Run(cmd, cwd=root)
def ComputeSha(filename):
with open(filename) as f:
return hashlib.sha1(f.read()).hexdigest()
class TempDir(object):
def __init__(self, prefix=None, dont_remove=False):
self.prefix = prefix
self.name = None
self.dont_remove = dont_remove
self.created = False
self.destroyed = False
def Create(self):
assert not self.created
self.name = tempfile.mkdtemp(prefix=self.prefix)
return self
def Destroy(self):
assert not self.destroyed
if not self.dont_remove:
buildbot_common.RemoveDir(self.name)
def __enter__(self):
self.Create()
return self.name
def __exit__(self, exc, value, tb):
return self.Destroy()
class Archive(object):
def __init__(self, name):
self.name = '%s_%s' % (PLATFORM, name)
self.archive_name = self.name + '.tar.bz2'
self.archive_path = os.path.join(BUILD_ARCHIVE_DIR, self.archive_name)
self.dirname = os.path.join(BUILD_ARCHIVE_DIR, self.name)
self._MakeDirname()
def _MakeDirname(self):
if os.path.exists(self.dirname):
buildbot_common.RemoveDir(self.dirname)
buildbot_common.MakeDir(self.dirname)
def Copy(self, src_root, file_list):
if type(file_list) is not list:
file_list = [file_list]
for file_spec in file_list:
# The list of files to install can be a simple list of
# strings or a list of pairs, where each pair corresponds
# to a mapping from source to destination names.
if type(file_spec) is str:
src_file = dest_file = file_spec
else:
src_file, dest_file = file_spec
src_file = os.path.join(src_root, src_file)
# Expand sources files using glob.
sources = glob.glob(src_file)
if not sources:
sources = [src_file]
if len(sources) > 1:
if not (dest_file.endswith('/') or dest_file == ''):
buildbot_common.ErrorExit(
"Target file %r must end in '/' or be empty when "
"using globbing to install files %r" % (dest_file, sources))
for source in sources:
if dest_file.endswith('/'):
dest = os.path.join(dest_file, os.path.basename(source))
else:
dest = dest_file
dest = os.path.join(self.dirname, dest)
if not os.path.isdir(os.path.dirname(dest)):
buildbot_common.MakeDir(os.path.dirname(dest))
if os.path.isdir(source):
buildbot_common.CopyDir(source, dest)
else:
buildbot_common.CopyFile(source, dest)
def CreateArchiveShaFile(self):
sha1 = ComputeSha(self.archive_path)
sha1_filename = self.archive_path + '.sha1'
with open(sha1_filename, 'w') as f:
f.write(sha1)
def Tar(self):
Tar(self.archive_path, BUILD_ARCHIVE_DIR, [
self.name,
os.path.basename(VERSION_JSON)])
self.CreateArchiveShaFile()
all_archives.append(self.archive_name)
def MakeToolchainArchive(toolchain):
archive = Archive(toolchain)
build_platform = '%s_x86' % PLATFORM
with TempDir('tc_%s_' % toolchain) as tmpdir:
package_name = os.path.join(build_platform,
TOOLCHAIN_PACKAGE_MAP.get(toolchain))
# Extract all of the packages into the temp directory.
buildbot_common.Run([sys.executable, PKGVER,
'--packages', package_name,
'--tar-dir', NACL_TOOLCHAINTARS_DIR,
'--dest-dir', tmpdir,
'extract'])
# Copy all the files we extracted to the correct destination.
archive.Copy(os.path.join(tmpdir, package_name), ('*', ''))
archive.Tar()
def MakeNinjaRelPath(path):
return os.path.join(os.path.relpath(OUT_DIR, SRC_DIR), path)
def NinjaBuild(targets, out_dir):
if type(targets) is not list:
targets = [targets]
out_config_dir = os.path.join(out_dir, 'Release')
buildbot_common.Run(['ninja', '-C', out_config_dir] + targets, cwd=SRC_DIR)
def GypNinjaBuild(arch, gyp_py_script, gyp_file, targets, out_dir):
gyp_env = dict(os.environ)
gyp_defines = []
if options.mac_sdk:
gyp_defines.append('mac_sdk=%s' % options.mac_sdk)
if arch:
gyp_defines.append('target_arch=%s' % arch)
if arch == 'arm':
gyp_env['GYP_CROSSCOMPILE'] = '1'
if options.no_arm_trusted:
gyp_defines.append('disable_cross_trusted=1')
gyp_env['GYP_DEFINES'] = ' '.join(gyp_defines)
generator_flags = ['-G', 'output_dir=%s' % out_dir]
depth = '--depth=.'
cmd = [sys.executable, gyp_py_script, gyp_file, depth] + generator_flags
buildbot_common.Run(cmd, cwd=SRC_DIR, env=gyp_env)
NinjaBuild(targets, out_dir)
def GetToolsFiles():
files = [
('sel_ldr', 'sel_ldr_x86_32'),
('ncval_new', 'ncval'),
('irt_core_newlib_x32.nexe', 'irt_core_x86_32.nexe'),
('irt_core_newlib_x64.nexe', 'irt_core_x86_64.nexe'),
]
if PLATFORM == 'linux':
files.append(['nacl_helper_bootstrap', 'nacl_helper_bootstrap_x86_32'])
files.append(['nonsfi_loader_newlib_x32_nonsfi.nexe',
'nonsfi_loader_x86_32'])
files.append(['nonsfi_loader_newlib_arm_nonsfi.nexe',
'nonsfi_loader_arm'])
# Add .exe extensions to all windows tools
for pair in files:
if PLATFORM == 'win' and not pair[0].endswith('.nexe'):
pair[0] += '.exe'
pair[1] += '.exe'
return files
def GetTools64Files():
files = []
if PLATFORM == 'win':
files.append('sel_ldr64')
else:
files.append(('sel_ldr', 'sel_ldr_x86_64'))
if PLATFORM == 'linux':
files.append(('nacl_helper_bootstrap', 'nacl_helper_bootstrap_x86_64'))
return files
def GetToolsArmFiles():
assert PLATFORM == 'linux'
return [('irt_core_newlib_arm.nexe', 'irt_core_arm.nexe'),
('sel_ldr', 'sel_ldr_arm'),
('nacl_helper_bootstrap', 'nacl_helper_bootstrap_arm')]
def GetNewlibToolchainLibs():
return ['crti.o',
'crtn.o',
'libminidump_generator.a',
'libnacl.a',
'libnacl_dyncode.a',
'libnacl_exception.a',
'libnacl_list_mappings.a',
'libnosys.a',
'libppapi.a',
'libppapi_stub.a',
'libpthread.a']
def GetGlibcToolchainLibs():
return ['libminidump_generator.a',
'libminidump_generator.so',
'libnacl.a',
'libnacl_dyncode.a',
'libnacl_dyncode.so',
'libnacl_exception.a',
'libnacl_exception.so',
'libnacl_list_mappings.a',
'libnacl_list_mappings.so',
'libppapi.a',
'libppapi.so',
'libppapi_stub.a']
def GetPNaClToolchainLibs():
return ['libminidump_generator.a',
'libnacl.a',
'libnacl_dyncode.a',
'libnacl_exception.a',
'libnacl_list_mappings.a',
'libnosys.a',
'libppapi.a',
'libppapi_stub.a',
'libpthread.a']
def GetBionicToolchainLibs():
return ['libminidump_generator.a',
'libnacl_dyncode.a',
'libnacl_exception.a',
'libnacl_list_mappings.a',
'libppapi.a']
def GetToolchainNaClLib(tcname, tcpath, xarch):
if tcname == 'pnacl':
return os.path.join(tcpath, 'le32-nacl', 'lib')
elif xarch == 'x86_32':
return os.path.join(tcpath, 'x86_64-nacl', 'lib32')
elif xarch == 'x86_64':
return os.path.join(tcpath, 'x86_64-nacl', 'lib')
elif xarch == 'arm':
return os.path.join(tcpath, 'arm-nacl', 'lib')
def GetGypBuiltLib(root, tcname, xarch=None):
if tcname == 'pnacl':
tcname = 'pnacl_newlib'
if tcname in ('glibc_arm', 'glibc_x86'):
tcname = 'glibc'
if xarch == 'x86_32':
xarch = '32'
elif xarch == 'x86_64':
xarch = '64'
elif not xarch:
xarch = ''
return os.path.join(root, 'Release', 'gen', 'tc_' + tcname, 'lib' + xarch)
def GetGypToolchainLib(root, tcname, xarch):
tcpath = os.path.join(root, 'Release', 'gen', 'sdk', '%s_x86' % PLATFORM,
TOOLCHAIN_PACKAGE_MAP[tcname])
return GetToolchainNaClLib(tcname, tcpath, xarch)
def MakeGypArchives():
join = os.path.join
gyp_chromium = join(SRC_DIR, 'build', 'gyp_chromium')
# TODO(binji): gyp_nacl doesn't build properly on Windows anymore; it only
# can use VS2010, not VS2013 which is now required by the Chromium repo. NaCl
# needs to be updated to perform the same logic as Chromium in detecting VS,
# which can now exist in the depot_tools directory.
# See https://code.google.com/p/nativeclient/issues/detail?id=4022
#
# For now, let's use gyp_chromium to build these components.
# gyp_nacl = join(NACL_DIR, 'build', 'gyp_nacl')
gyp_nacl = gyp_chromium
nacl_core_sdk_gyp = join(NACL_DIR, 'build', 'nacl_core_sdk.gyp')
all_gyp = join(NACL_DIR, 'build', 'all.gyp')
breakpad_gyp = join(SRC_DIR, 'breakpad', 'breakpad.gyp')
ppapi_gyp = join(SRC_DIR, 'ppapi', 'native_client', 'native_client.gyp')
breakpad_targets = ['dump_syms', 'minidump_dump', 'minidump_stackwalk']
# Build
tmpdir_obj = TempDir('nacl_core_sdk_', dont_remove=True).Create()
tmpdir = tmpdir_obj.name
GypNinjaBuild('ia32', gyp_nacl, nacl_core_sdk_gyp, 'nacl_core_sdk', tmpdir)
GypNinjaBuild('ia32', gyp_nacl, all_gyp, 'ncval_new', tmpdir)
GypNinjaBuild('ia32', gyp_chromium, breakpad_gyp, breakpad_targets, tmpdir)
GypNinjaBuild('ia32', gyp_chromium, ppapi_gyp, 'ppapi_lib', tmpdir)
GypNinjaBuild('x64', gyp_chromium, ppapi_gyp, 'ppapi_lib', tmpdir)
tmpdir64_obj = TempDir('nacl_core_sdk_64_', dont_remove=True).Create()
tmpdir_64 = tmpdir64_obj.name
if PLATFORM == 'win':
GypNinjaBuild('ia32', gyp_nacl, nacl_core_sdk_gyp, 'sel_ldr64', tmpdir_64)
else:
GypNinjaBuild('x64', gyp_nacl, nacl_core_sdk_gyp, 'sel_ldr', tmpdir_64)
tmpdirarm_obj = TempDir('nacl_core_sdk_arm_', dont_remove=True).Create()
tmpdir_arm = tmpdirarm_obj.name
GypNinjaBuild('arm', gyp_nacl, nacl_core_sdk_gyp, 'nacl_core_sdk', tmpdir_arm)
GypNinjaBuild('arm', gyp_chromium, ppapi_gyp, 'ppapi_lib', tmpdir_arm)
# Tools archive
archive = Archive('tools')
archive.Copy(join(tmpdir, 'Release'), GetToolsFiles())
archive.Copy(join(tmpdir_64, 'Release'), GetTools64Files())
if PLATFORM == 'linux':
archive.Copy(join(tmpdir_arm, 'Release'), GetToolsArmFiles())
# TODO(binji): dump_syms doesn't currently build on Windows. See
# http://crbug.com/245456
if PLATFORM != 'win':
archive.Copy(join(tmpdir, 'Release'), breakpad_targets)
archive.Tar()
# glibc x86 libs archives
for arch in ('x86_32', 'x86_64'):
archive = Archive('glibc_%s_libs' % arch)
archive.Copy(GetGypBuiltLib(tmpdir, 'glibc', arch), GetGlibcToolchainLibs())
archive.Copy(GetGypToolchainLib(tmpdir, 'glibc', arch), 'crt1.o')
archive.Tar()
# pnacl libs archive
archive = Archive('pnacl_libs')
archive.Copy(GetGypBuiltLib(tmpdir, 'pnacl'), GetPNaClToolchainLibs())
archive.Tar()
# Destroy the temporary directories
tmpdir_obj.Destroy()
tmpdirarm_obj.Destroy()
tmpdir64_obj.Destroy()
def MakePNaClArchives():
join = os.path.join
gyp_chromium = join(SRC_DIR, 'build', 'gyp_chromium')
pnacl_irt_shim_gyp = join(SRC_DIR, 'ppapi', 'native_client', 'src',
'untrusted', 'pnacl_irt_shim', 'pnacl_irt_shim.gyp')
with TempDir('pnacl_irt_shim_ia32_') as tmpdir:
GypNinjaBuild('ia32', gyp_chromium, pnacl_irt_shim_gyp, 'aot', tmpdir)
archive = Archive('pnacl_translator_x86_32_libs')
libdir = join(tmpdir, 'Release', 'gen', 'tc_pnacl_translate', 'lib-x86-32')
archive.Copy(libdir, 'libpnacl_irt_shim.a')
archive.Tar()
archive = Archive('pnacl_translator_x86_64_libs')
libdir = join(tmpdir, 'Release', 'gen', 'tc_pnacl_translate', 'lib-x86-32')
archive.Copy(libdir, 'libpnacl_irt_shim.a')
archive.Tar()
with TempDir('pnacl_irt_shim_arm_') as tmpdir:
GypNinjaBuild('arm', gyp_chromium, pnacl_irt_shim_gyp, 'aot', tmpdir)
archive = Archive('pnacl_translator_arm_libs')
libdir = join(tmpdir, 'Release', 'gen', 'tc_pnacl_translate', 'lib-arm')
archive.Copy(libdir, 'libpnacl_irt_shim.a')
archive.Tar()
def GetNewlibHeaders():
return [
('native_client/src/include/nacl/nacl_exception.h', 'nacl/'),
('native_client/src/include/nacl/nacl_minidump.h', 'nacl/'),
('native_client/src/untrusted/irt/irt.h', ''),
('native_client/src/untrusted/irt/irt_dev.h', ''),
('native_client/src/untrusted/irt/irt_extension.h', ''),
('native_client/src/untrusted/nacl/nacl_dyncode.h', 'nacl/'),
('native_client/src/untrusted/nacl/nacl_startup.h', 'nacl/'),
('native_client/src/untrusted/pthread/pthread.h', ''),
('native_client/src/untrusted/pthread/semaphore.h', ''),
('native_client/src/untrusted/valgrind/dynamic_annotations.h', 'nacl/'),
('ppapi/nacl_irt/public/irt_ppapi.h', '')]
def GetGlibcHeaders():
return [
('native_client/src/include/nacl/nacl_exception.h', 'nacl/'),
('native_client/src/include/nacl/nacl_minidump.h', 'nacl/'),
('native_client/src/untrusted/irt/irt.h', ''),
('native_client/src/untrusted/irt/irt_dev.h', ''),
('native_client/src/untrusted/nacl/nacl_dyncode.h', 'nacl/'),
('native_client/src/untrusted/nacl/nacl_startup.h', 'nacl/'),
('native_client/src/untrusted/valgrind/dynamic_annotations.h', 'nacl/'),
('ppapi/nacl_irt/public/irt_ppapi.h', '')]
def GetBionicHeaders():
return [('ppapi/nacl_irt/public/irt_ppapi.h', '')]
def MakeToolchainHeaderArchives():
archive = Archive('newlib_headers')
archive.Copy(SRC_DIR, GetNewlibHeaders())
archive.Tar()
archive = Archive('glibc_headers')
archive.Copy(SRC_DIR, GetGlibcHeaders())
archive.Tar()
def MakePepperArchive():
archive = Archive('ppapi')
archive.Copy(os.path.join(SRC_DIR, 'ppapi'), ['c', 'cpp', 'lib', 'utility'])
archive.Tar()
def UploadArchives():
major_version = build_version.ChromeMajorVersion()
chrome_revision = build_version.ChromeRevision()
commit_position = build_version.ChromeCommitPosition()
git_sha = build_version.ParseCommitPosition(commit_position)[0]
short_sha = git_sha[:9]
archive_version = '%s-%s-%s' % (major_version, chrome_revision, short_sha)
bucket_path = 'native-client-sdk/archives/%s' % archive_version
for archive_name in all_archives:
buildbot_common.Archive(archive_name, bucket_path,
cwd=BUILD_ARCHIVE_DIR, step_link=False)
sha1_filename = archive_name + '.sha1'
buildbot_common.Archive(sha1_filename, bucket_path,
cwd=BUILD_ARCHIVE_DIR, step_link=False)
def MakeVersionJson():
time_format = '%Y/%m/%d %H:%M:%S'
data = {
'chrome_version': build_version.ChromeVersionNoTrunk(),
'chrome_revision': build_version.ChromeRevision(),
'chrome_commit_position': build_version.ChromeCommitPosition(),
'nacl_revision': build_version.NaClRevision(),
'build_date': datetime.datetime.now().strftime(time_format)}
dirname = os.path.dirname(VERSION_JSON)
if not os.path.exists(dirname):
buildbot_common.MakeDir(dirname)
with open(VERSION_JSON, 'w') as outf:
json.dump(data, outf, indent=2, separators=(',', ': '))
def main(args):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--mac-sdk',
help='Set the mac-sdk (e.g. 10.6) to use when building with ninja.')
parser.add_argument('--no-arm-trusted', action='store_true',
help='Disable building of ARM trusted components (sel_ldr, etc).')
parser.add_argument('--upload', action='store_true',
help='Upload tarballs to GCS.')
global options
options = parser.parse_args(args)
toolchains = ['pnacl', 'glibc_x86', 'glibc_arm']
MakeVersionJson()
for tc in toolchains:
MakeToolchainArchive(tc)
MakeGypArchives()
MakePNaClArchives()
MakeToolchainHeaderArchives()
MakePepperArchive()
if options.upload:
UploadArchives()
return 0
if __name__ == '__main__':
try:
sys.exit(main(sys.argv[1:]))
except KeyboardInterrupt:
buildbot_common.ErrorExit('build_artifacts: interrupted')