2 # Copyright (c) 2013 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Recipes for PNaCl toolchain packages.
8 Recipes consist of specially-structured dictionaries, with keys for package
9 name, type, commands to execute, etc. The structure is documented in the
10 PackageBuilder docstring in toolchain_main.py.
12 The real entry plumbing and CLI flags are also in toolchain_main.py.
26 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
27 import pynacl.gsd_storage
28 import pynacl.platform
29 import pynacl.repo_tools
33 import pnacl_targetlibs
36 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
37 NACL_DIR = os.path.dirname(SCRIPT_DIR)
38 # Use the argparse from third_party to ensure it's the same on all platorms
39 python_lib_dir = os.path.join(os.path.dirname(NACL_DIR), 'third_party',
40 'python_libs', 'argparse')
41 sys.path.insert(0, python_lib_dir)
44 # Scons tests can check this version number to decide whether to enable tests
45 # for toolchain bug fixes or new features. This allows tests to be enabled on
46 # the toolchain buildbots/trybots before the new toolchain version is rolled
47 # into TOOL_REVISIONS (i.e. before the tests would pass on the main NaCl
48 # buildbots/trybots). If you are adding a test that depends on a toolchain
49 # change, you can increment this version number manually.
52 # For backward compatibility, these key names match the directory names
53 # previously used with gclient
55 'binutils': 'nacl-binutils.git',
56 'clang': 'pnacl-clang.git',
57 'llvm': 'pnacl-llvm.git',
58 'gcc': 'pnacl-gcc.git',
59 'libcxx': 'pnacl-libcxx.git',
60 'libcxxabi': 'pnacl-libcxxabi.git',
61 'nacl-newlib': 'nacl-newlib.git',
62 'llvm-test-suite': 'pnacl-llvm-testsuite.git',
63 'compiler-rt': 'pnacl-compiler-rt.git',
66 GIT_BASE_URL = 'https://chromium.googlesource.com/native_client/'
67 GIT_DEPS_FILE = os.path.join(NACL_DIR, 'pnacl', 'COMPONENT_REVISIONS')
69 # TODO(dschuff): Some of this cygwin/mingw logic duplicates stuff in command.py
70 # and the mechanism for switching between cygwin and mingw is bad.
71 # Path to the hermetic cygwin
72 BUILD_CROSS_MINGW = False
73 CYGWIN_PATH = os.path.join(NACL_DIR, 'cygwin')
74 # Path to the mingw cross-compiler libs on Ubuntu
75 CROSS_MINGW_LIBPATH = '/usr/lib/gcc/i686-w64-mingw32/4.6'
76 # Path and version of the native mingw compiler to be installed on Windows hosts
77 MINGW_PATH = os.path.join(NACL_DIR, 'mingw32')
78 MINGW_VERSION = 'i686-w64-mingw32-4.8.1'
80 ALL_ARCHES = ('x86-32', 'x86-64', 'arm', 'mips32', 'x86-32-nonsfi')
81 # MIPS32 doesn't use biased bitcode, and nonsfi targets don't need it.
82 BITCODE_BIASES = tuple(bias for bias in ('portable', 'x86-32', 'x86-64', 'arm'))
84 MAKE_DESTDIR_CMD = ['make', 'DESTDIR=%(abs_output)s']
86 def TripleIsWindows(t):
87 return fnmatch.fnmatch(t, '*-mingw32*') or fnmatch.fnmatch(t, '*cygwin*')
90 def CompilersForHost(host):
92 # For now we only do native builds for linux and mac
93 'i686-linux': ('gcc', 'g++'), # treat 32-bit linux like a native build
94 'x86_64-linux': ('gcc', 'g++'),
95 # TODO(dschuff): switch to clang on mac
96 'x86_64-apple-darwin': ('gcc', 'g++'),
97 # Windows build should work for native and cross
98 'i686-w64-mingw32': ('i686-w64-mingw32-gcc', 'i686-w64-mingw32-g++'),
99 # TODO: add arm-hosted support
101 return compiler[host]
104 def ConfigureHostArchFlags(host):
108 native = pynacl.platform.PlatformTriple()
109 is_cross = host != native
111 if (pynacl.platform.IsLinux64() and
112 fnmatch.fnmatch(host, '*-linux*')):
113 # 64 bit linux can build 32 bit linux binaries while still being a native
114 # build for our purposes. But it's not what config.guess will yield, so
115 # use --build to force it and make sure things build correctly.
116 configure_args.append('--build=' + host)
117 extra_cc_args = ['-m32']
119 configure_args.append('--host=' + host)
121 extra_cxx_args = list(extra_cc_args)
123 cc, cxx = CompilersForHost(host)
126 # LLVM's linux->mingw cross build needs this
127 configure_args.append('CC_FOR_BUILD=gcc')
129 configure_args.append('CC=' + ' '.join([cc] + extra_cc_args))
130 configure_args.append('CXX=' + ' '.join([cxx] + extra_cxx_args))
132 if TripleIsWindows(host):
133 # The i18n support brings in runtime dependencies on MinGW DLLs
134 # that we don't want to have to distribute alongside our binaries.
135 # So just disable it, and compiler messages will always be in US English.
136 configure_args.append('--disable-nls')
137 if not command.Runnable.use_cygwin:
138 configure_args.extend(['LDFLAGS=-L%(abs_libdl)s',
139 'CFLAGS=-isystem %(abs_libdl)s',
140 'CXXFLAGS=-isystem %(abs_libdl)s'])
141 return configure_args
144 def CmakeHostArchFlags(host, options):
147 cc ='%(abs_top_srcdir)s/../third_party/llvm-build/Release+Asserts/bin/clang'
150 cc, cxx = CompilersForHost(host)
151 cmake_flags.extend(['-DCMAKE_C_COMPILER='+cc, '-DCMAKE_CXX_COMPILER='+cxx])
153 if pynacl.platform.IsLinux64() and pynacl.platform.PlatformTriple() != host:
154 # Currently the only supported "cross" build is 64-bit Linux to 32-bit
155 # Linux. Enable it, and disable libxml because Ubuntu doesn't have a
156 # 32-bit libxml build.
157 cmake_flags.extend(['-DLLVM_BUILD_32_BITS=ON',
158 '-DLLVM_ENABLE_LIBXML=OFF'])
161 cmake_flags.extend(['-DCMAKE_%s_FLAGS=-fsanitize=%s' % (c, options.sanitize)
162 for c in ('C', 'CXX')])
163 cmake_flags.append('-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=%s' %
168 def MakeCommand(host):
169 make_command = ['make']
170 if (not pynacl.platform.IsWindows() or
171 command.Runnable.use_cygwin):
172 # The make that ships with msys sometimes hangs when run with -j.
173 # The ming32-make that comes with the compiler itself reportedly doesn't
174 # have this problem, but it has issues with pathnames with LLVM's build.
175 make_command.append('-j%(cores)s')
177 if TripleIsWindows(host):
178 # There appears to be nothing we can pass at top-level configure time
179 # that will prevent the configure scripts from finding MinGW's libiconv
180 # and using it. We have to force this variable into the environment
181 # of the sub-configure runs, which are run via make.
182 make_command.append('HAVE_LIBICONV=no')
186 def CopyWindowsHostLibs(host):
187 if not TripleIsWindows(host):
189 if command.Runnable.use_cygwin:
190 libs = ('cyggcc_s-1.dll', 'cygiconv-2.dll', 'cygwin1.dll', 'cygintl-8.dll',
191 'cygstdc++-6.dll', 'cygz.dll')
192 return [command.Copy(
193 os.path.join(CYGWIN_PATH, 'bin', lib),
194 os.path.join('%(output)s', 'bin', lib))
196 if pynacl.platform.IsWindows():
197 lib_path = os.path.join(MINGW_PATH, 'bin')
198 # The native minGW compiler uses winpthread, but the Ubuntu cross compiler
200 libs = ('libgcc_s_sjlj-1.dll', 'libstdc++-6.dll', 'libwinpthread-1.dll')
202 lib_path = os.path.join(CROSS_MINGW_LIBPATH)
203 libs = ('libgcc_s_sjlj-1.dll', 'libstdc++-6.dll')
204 return [command.Copy(
205 os.path.join(lib_path, lib),
206 os.path.join('%(output)s', 'bin', lib))
209 def GetGitSyncCmdCallback(revisions):
210 """Return a callback which returns the git sync command for a component.
212 This allows all the revision information to be processed here while giving
213 other modules like pnacl_targetlibs.py the ability to define their own
214 source targets with minimal boilerplate.
216 def GetGitSyncCmd(component):
217 return command.SyncGitRepo(GIT_BASE_URL + GIT_REPOS[component],
219 revisions[component])
222 def HostToolsSources(GetGitSyncCmd):
224 'binutils_pnacl_src': {
226 'output_dirname': 'binutils',
228 GetGitSyncCmd('binutils'),
231 # For some reason, the llvm build using --with-clang-srcdir chokes if the
232 # clang source directory is named something other than 'clang', so don't
233 # change output_dirname for clang.
236 'output_dirname': 'clang',
238 GetGitSyncCmd('clang'),
243 'output_dirname': 'llvm',
245 GetGitSyncCmd('llvm'),
254 if TripleIsWindows(host) and not command.Runnable.use_cygwin:
255 if pynacl.platform.IsWindows():
258 ar = 'i686-w64-mingw32-ar'
263 'inputs' : { 'src' : os.path.join(NACL_DIR, '..', 'third_party',
266 command.CopyTree('%(src)s', '.'),
267 command.Command(['i686-w64-mingw32-gcc',
268 '-o', 'dlfcn.o', '-c', 'dlfcn.c',
269 '-Wall', '-O3', '-fomit-frame-pointer']),
270 command.Command([ar, 'cru',
271 'libdl.a', 'dlfcn.o']),
272 command.Copy('libdl.a',
273 os.path.join('%(output)s', 'libdl.a')),
274 command.Copy('dlfcn.h',
275 os.path.join('%(output)s', 'dlfcn.h')),
282 def HostTools(host, options):
283 def H(component_name):
284 # Return a package name for a component name with a host triple.
285 return component_name + '_' + pynacl.gsd_storage.LegalizeName(host)
287 return fnmatch.fnmatch(host, 'x86_64*')
288 def HostSubdir(host):
289 return 'host_x86_64' if IsHost64(host) else 'host_x86_32'
291 return 'bin64' if host == 'x86_64-linux' else 'bin'
293 H('binutils_pnacl'): {
294 'dependencies': ['binutils_pnacl_src'],
296 'output_subdir': HostSubdir(host),
298 command.SkipForIncrementalCommand([
300 '%(binutils_pnacl_src)s/configure'] +
301 ConfigureHostArchFlags(host) +
303 '--disable-silent-rules',
304 '--target=arm-pc-nacl',
305 '--program-prefix=le32-nacl-',
306 '--enable-targets=arm-pc-nacl,i686-pc-nacl,x86_64-pc-nacl,' +
308 '--enable-deterministic-archives',
309 '--enable-shared=no',
310 '--enable-gold=default',
315 '--with-sysroot=/arm-pc-nacl']),
316 command.Command(MakeCommand(host)),
317 command.Command(MAKE_DESTDIR_CMD + ['install-strip'])] +
318 [command.RemoveDirectory(os.path.join('%(output)s', dir))
319 for dir in ('arm-pc-nacl', 'lib', 'lib32')]
323 'output_subdir': BinSubdir(host),
324 'inputs': { 'src': os.path.join(NACL_DIR, 'pnacl', 'driver')},
326 command.Runnable(pnacl_commands.InstallDriverScripts,
327 '%(src)s', '%(output)s',
328 host_windows=TripleIsWindows(host),
329 host_64bit=IsHost64(host))
335 'dependencies': ['clang_src', 'llvm_src', 'binutils_pnacl_src'],
337 'output_subdir': HostSubdir(host),
339 command.SkipForIncrementalCommand([
340 'cmake', '-G', 'Ninja'] +
341 CmakeHostArchFlags(host, options) +
342 ['-DCMAKE_BUILD_TYPE=RelWithDebInfo',
343 '-DCMAKE_INSTALL_PREFIX=%(output)s',
344 '-DCMAKE_INSTALL_RPATH=$ORIGIN/../lib',
345 '-DBUILD_SHARED_LIBS=ON',
346 '-DLLVM_ENABLE_ASSERTIONS=ON',
347 '-DLLVM_ENABLE_ZLIB=OFF',
348 '-DLLVM_BUILD_TESTS=ON',
349 '-DLLVM_APPEND_VC_REV=ON',
350 '-DLLVM_BINUTILS_INCDIR=%(abs_binutils_pnacl_src)s/include',
351 '-DLLVM_EXTERNAL_CLANG_SOURCE_DIR=%(clang_src)s',
353 command.Command(['ninja', '-v']),
354 command.Command(['ninja', 'install']),
360 'dependencies': ['clang_src', 'llvm_src', 'binutils_pnacl_src'],
362 'output_subdir': HostSubdir(host),
364 command.SkipForIncrementalCommand([
366 '%(llvm_src)s/configure'] +
367 ConfigureHostArchFlags(host) +
371 '--disable-terminfo',
373 '--disable-bindings', # ocaml is currently the only binding.
374 '--with-binutils-include=%(abs_binutils_pnacl_src)s/include',
375 '--enable-targets=x86,arm,mips',
377 '--enable-optimized',
378 '--with-clang-srcdir=%(abs_clang_src)s']),
379 command.Command(MakeCommand(host) + [
383 command.Command(MAKE_DESTDIR_CMD + ['install'])] +
384 CopyWindowsHostLibs(host),
388 tools.update(llvm_cmake)
390 tools.update(llvm_autoconf)
391 if TripleIsWindows(host) and not command.Runnable.use_cygwin:
392 tools[H('binutils_pnacl')]['dependencies'].append('libdl')
393 tools[H('llvm')]['dependencies'].append('libdl')
396 # TODO(dschuff): The REV file should probably go here rather than in the driver
402 'inputs': { 'readme': os.path.join(NACL_DIR, 'pnacl', 'README') },
404 command.Copy('%(readme)s', os.path.join('%(output)s', 'README')),
405 command.WriteData(str(FEATURE_VERSION),
406 os.path.join('%(output)s', 'FEATURE_VERSION')),
412 def ParseComponentRevisionsFile(filename):
413 ''' Parse a simple-format deps file, with fields of the form:
415 Keys should match the keys in GIT_REPOS above, which match the previous
416 directory names used by gclient (with the exception that '_' in the file is
417 replaced by '-' in the returned key name).
418 Values are the git hashes for each repo.
419 Empty lines or lines beginning with '#' are ignored.
420 This function returns a dictionary mapping the keys found in the file to their
423 with open(filename) as f:
426 stripped = line.strip()
427 if stripped.startswith('#') or len(stripped) == 0:
429 tokens = stripped.split('=')
431 raise Exception('Malformed component revisions file: ' + filename)
432 deps[tokens[0].replace('_', '-')] = tokens[1]
435 # This is to replace build.sh's use of gclient, which will eliminate the issues
436 # with msys vs cygwin git checkouts, and make testing easier. Note that the new
437 # build scripts will not share source directories with build.sh.
438 def SyncPNaClRepos(revisions):
440 for repo, revision in revisions.iteritems():
441 destination = os.path.join(NACL_DIR, 'pnacl', 'git', repo)
442 # This replaces build.sh's newlib-nacl-headers-clean step by cleaning the
443 # the newlib repo on checkout (while silently blowing away any local
444 # changes). TODO(dschuff): find a better way to handle nacl newlib headers.
445 is_newlib = repo == 'nacl-newlib'
446 pynacl.repo_tools.SyncGitRepo(
447 GIT_BASE_URL + GIT_REPOS[repo],
453 # For testing LLVM, Clang, etc. changes on the trybots, look for a
454 # Git bundle file created by llvm_change_try_helper.sh.
455 bundle_file = os.path.join(NACL_DIR, 'pnacl', 'not_for_commit',
457 base64_file = '%s.b64' % bundle_file
458 if os.path.exists(base64_file):
459 input_fh = open(base64_file, 'r')
460 output_fh = open(bundle_file, 'wb')
461 base64.decode(input_fh, output_fh)
464 subprocess.check_call(
465 pynacl.repo_tools.GitCmd() + ['fetch'],
468 subprocess.check_call(
469 pynacl.repo_tools.GitCmd() + ['bundle', 'unbundle', bundle_file],
472 commit_id_file = os.path.join(NACL_DIR, 'pnacl', 'not_for_commit',
473 '%s_commit_id' % repo)
474 commit_id = open(commit_id_file, 'r').readline().strip()
475 subprocess.check_call(
476 pynacl.repo_tools.GitCmd() + ['checkout', commit_id],
481 def InstallMinGWHostCompiler():
482 """Install the MinGW host compiler used to build the host tools on Windows.
484 We could use an ordinary source rule for this, but that would require hashing
485 hundreds of MB of toolchain files on every build. Instead, check for the
486 presence of the specially-named file <version>.installed in the install
487 directory. If it is absent, check for the presence of the zip file
488 <version>.zip. If it is absent, attempt to download it from Google Storage.
489 Then extract the zip file and create the install file.
491 if not os.path.isfile(os.path.join(MINGW_PATH, MINGW_VERSION + '.installed')):
492 downloader = pynacl.gsd_storage.GSDStorage([], ['nativeclient-mingw'])
493 zipfilename = MINGW_VERSION + '.zip'
494 zipfilepath = os.path.join(NACL_DIR, zipfilename)
495 # If the zip file is not present, try to download it from Google Storage.
496 # If that fails, bail out.
497 if (not os.path.isfile(zipfilepath) and
498 not downloader.GetSecureFile(zipfilename, zipfilepath)):
499 print >>sys.stderr, 'Failed to install MinGW tools:'
500 print >>sys.stderr, 'could not find or download', zipfilename
502 logging.info('Extracting %s' % zipfilename)
503 zf = zipfile.ZipFile(zipfilepath)
504 if os.path.exists(MINGW_PATH):
505 shutil.rmtree(MINGW_PATH)
506 zf.extractall(NACL_DIR)
507 with open(os.path.join(MINGW_PATH, MINGW_VERSION + '.installed'), 'w') as _:
509 os.environ['MINGW'] = MINGW_PATH
511 if __name__ == '__main__':
512 # This sets the logging for gclient-alike repo sync. It will be overridden
513 # by the package builder based on the command-line flags.
514 logging.getLogger().setLevel(logging.DEBUG)
515 parser = argparse.ArgumentParser(add_help=False)
516 parser.add_argument('--legacy-repo-sync', action='store_true',
517 dest='legacy_repo_sync', default=False,
518 help='Sync the git repo directories used by build.sh')
519 parser.add_argument('--build-64bit-host', action='store_true',
520 dest='build_64bit_host', default=False,
521 help='Build 64-bit Linux host binaries in addition to 32')
522 parser.add_argument('--cmake', action='store_true', default=False,
523 help="Use LLVM's cmake ninja build instead of autoconf")
524 parser.add_argument('--clang', action='store_true', default=False,
525 help="Use clang instead of gcc with LLVM's cmake build")
526 parser.add_argument('--sanitize', choices=['address', 'thread', 'undefined'],
527 help="Use a sanitizer with LLVM's clang cmake build")
528 args, leftover_args = parser.parse_known_args()
529 if '-h' in leftover_args or '--help' in leftover_args:
530 print 'The following arguments are specific to toolchain_build_pnacl.py:'
532 print 'The rest of the arguments are generic, in toolchain_main.py'
534 if args.sanitize and not args.cmake:
535 print 'Use of sanitizers requires a cmake build'
537 if args.clang and not args.cmake:
538 print 'Use of clang is currently only supported with cmake/ninja'
541 revisions = ParseComponentRevisionsFile(GIT_DEPS_FILE)
542 if args.legacy_repo_sync:
543 SyncPNaClRepos(revisions)
546 if pynacl.platform.IsWindows() and not command.Runnable.use_cygwin:
547 InstallMinGWHostCompiler()
550 packages.update(HostToolsSources(GetGitSyncCmdCallback(revisions)))
552 if pynacl.platform.IsLinux64():
553 hosts = ['i686-linux']
554 if args.build_64bit_host:
555 hosts.append(pynacl.platform.PlatformTriple())
557 hosts = [pynacl.platform.PlatformTriple()]
558 if pynacl.platform.IsLinux() and BUILD_CROSS_MINGW:
559 hosts.append('i686-w64-mingw32')
561 packages.update(HostLibs(host))
562 packages.update(HostTools(host, args))
563 # Don't build the target libs on Windows because of pathname issues.
564 # Don't build the target libs on Mac because the gold plugin's rpaths
566 # On linux use the 32-bit compiler to build the target libs since that's what
567 # most developers will be using.
568 if pynacl.platform.IsLinux():
569 packages.update(pnacl_targetlibs.TargetLibsSrc(
570 GetGitSyncCmdCallback(revisions)))
571 for bias in BITCODE_BIASES:
572 packages.update(pnacl_targetlibs.BitcodeLibs(hosts[0], bias))
573 for arch in ALL_ARCHES:
574 packages.update(pnacl_targetlibs.NativeLibs(hosts[0], arch))
575 packages.update(pnacl_targetlibs.NativeLibsUnsandboxed('x86-32-linux'))
576 packages.update(Metadata())
578 tb = toolchain_main.PackageBuilder(packages,