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.
15 # Done first to set up python module path
33 import pnacl_targetlibs
37 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
38 import pynacl.platform
40 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
41 NACL_DIR = os.path.dirname(SCRIPT_DIR)
42 # Use the argparse from third_party to ensure it's the same on all platorms
43 python_lib_dir = os.path.join(os.path.dirname(NACL_DIR), 'third_party',
44 'python_libs', 'argparse')
45 sys.path.insert(0, python_lib_dir)
48 # Scons tests can check this version number to decide whether to enable tests
49 # for toolchain bug fixes or new features. This allows tests to be enabled on
50 # the toolchain buildbots/trybots before the new toolchain version is rolled
51 # into TOOL_REVISIONS (i.e. before the tests would pass on the main NaCl
52 # buildbots/trybots). If you are adding a test that depends on a toolchain
53 # change, you can increment this version number manually.
56 # For backward compatibility, these key names match the directory names
57 # previously used with gclient
59 'binutils': 'nacl-binutils.git',
60 'clang': 'pnacl-clang.git',
61 'llvm': 'pnacl-llvm.git',
62 'gcc': 'pnacl-gcc.git',
63 'libcxx': 'pnacl-libcxx.git',
64 'libcxxabi': 'pnacl-libcxxabi.git',
65 'nacl-newlib': 'nacl-newlib.git',
66 'llvm-test-suite': 'pnacl-llvm-testsuite.git',
67 'compiler-rt': 'pnacl-compiler-rt.git',
70 GIT_BASE_URL = 'https://chromium.googlesource.com/native_client/'
71 GIT_DEPS_FILE = os.path.join(NACL_DIR, 'pnacl', 'COMPONENT_REVISIONS')
73 # TODO(dschuff): Some of this cygwin/mingw logic duplicates stuff in command.py
74 # and the mechanism for switching between cygwin and mingw is bad.
75 # Path to the hermetic cygwin
76 BUILD_CROSS_MINGW = False
77 CYGWIN_PATH = os.path.join(NACL_DIR, 'cygwin')
78 # Path to the mingw cross-compiler libs on Ubuntu
79 CROSS_MINGW_LIBPATH = '/usr/lib/gcc/i686-w64-mingw32/4.6'
80 # Path and version of the native mingw compiler to be installed on Windows hosts
81 MINGW_PATH = os.path.join(NACL_DIR, 'mingw32')
82 MINGW_VERSION = 'i686-w64-mingw32-4.8.1'
84 ALL_ARCHES = ('x86-32', 'x86-64', 'arm', 'mips32')
85 BITCODE_BIASES = ('portable', 'x86-64')
87 MAKE_DESTDIR_CMD = ['make', 'DESTDIR=%(abs_output)s']
90 if pynacl.platform.IsWindows():
91 if command.Runnable.use_cygwin:
92 return 'i686-pc-cygwin'
94 return 'i686-w64-mingw32'
95 elif pynacl.platform.IsMacOS():
96 return 'x86_64-apple-darwin'
97 elif pynacl.platform.IsLinux():
98 if pynacl.platform.Is64BitLinux():
103 raise Exception('Unknown build architecture %s' % sys.platform)
106 def TripleIsWindows(t):
107 return fnmatch.fnmatch(t, '*-mingw32*') or fnmatch.fnmatch(t, '*cygwin*')
110 def CompilersForHost(host):
112 # For now we only do native builds for linux and mac
113 'i686-linux': ('gcc', 'g++'), # treat 32-bit linux like a native build
114 'x86_64-linux': ('gcc', 'g++'),
115 # TODO(dschuff): switch to clang on mac
116 'x86_64-apple-darwin': ('gcc', 'g++'),
117 # Windows build should work for native and cross
118 'i686-w64-mingw32': ('i686-w64-mingw32-gcc', 'i686-w64-mingw32-g++'),
119 # TODO: add arm-hosted support
121 return compiler[host]
124 def ConfigureHostArchFlags(host):
128 native = NativeTriple()
129 is_cross = host != native
131 if (pynacl.platform.Is64BitLinux() and
132 fnmatch.fnmatch(host, '*-linux*')):
133 # 64 bit linux can build 32 bit linux binaries while still being a native
134 # build for our purposes. But it's not what config.guess will yield, so
135 # use --build to force it and make sure things build correctly.
136 configure_args.append('--build=' + host)
137 extra_cc_args = ['-m32']
139 configure_args.append('--host=' + host)
141 extra_cxx_args = list(extra_cc_args)
143 cc, cxx = CompilersForHost(host)
146 # LLVM's linux->mingw cross build needs this
147 configure_args.append('CC_FOR_BUILD=gcc')
149 configure_args.append('CC=' + ' '.join([cc] + extra_cc_args))
150 configure_args.append('CXX=' + ' '.join([cxx] + extra_cxx_args))
152 if TripleIsWindows(host):
153 # The i18n support brings in runtime dependencies on MinGW DLLs
154 # that we don't want to have to distribute alongside our binaries.
155 # So just disable it, and compiler messages will always be in US English.
156 configure_args.append('--disable-nls')
157 if not command.Runnable.use_cygwin:
158 configure_args.extend(['LDFLAGS=-L%(abs_libdl)s',
159 'CFLAGS=-isystem %(abs_libdl)s',
160 'CXXFLAGS=-isystem %(abs_libdl)s'])
161 return configure_args
164 def CmakeHostArchFlags(host, options):
167 cc ='%(abs_top_srcdir)s/../third_party/llvm-build/Release+Asserts/bin/clang'
170 cc, cxx = CompilersForHost(host)
171 cmake_flags.extend(['-DCMAKE_C_COMPILER='+cc, '-DCMAKE_CXX_COMPILER='+cxx])
173 if NativeTriple() != host and pynacl.platform.Is64BitLinux():
174 # Currently the only supported "cross" build is 64-bit Linux to 32-bit
175 # Linux. Enable it, and disable libxml because Ubuntu doesn't have a
176 # 32-bit libxml build.
177 cmake_flags.extend(['-DLLVM_BUILD_32_BITS=ON',
178 '-DLLVM_ENABLE_LIBXML=OFF'])
181 cmake_flags.extend(['-DCMAKE_%s_FLAGS=-fsanitize=%s' % (c, options.sanitize)
182 for c in ('C', 'CXX')])
183 cmake_flags.append('-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=%s' %
188 def MakeCommand(host):
189 make_command = ['make']
190 if (not pynacl.platform.IsWindows() or
191 command.Runnable.use_cygwin):
192 # The make that ships with msys sometimes hangs when run with -j.
193 # The ming32-make that comes with the compiler itself reportedly doesn't
194 # have this problem, but it has issues with pathnames with LLVM's build.
195 make_command.append('-j%(cores)s')
197 if TripleIsWindows(host):
198 # There appears to be nothing we can pass at top-level configure time
199 # that will prevent the configure scripts from finding MinGW's libiconv
200 # and using it. We have to force this variable into the environment
201 # of the sub-configure runs, which are run via make.
202 make_command.append('HAVE_LIBICONV=no')
206 def CopyWindowsHostLibs(host):
207 if not TripleIsWindows(host):
209 if command.Runnable.use_cygwin:
210 libs = ('cyggcc_s-1.dll', 'cygiconv-2.dll', 'cygwin1.dll', 'cygintl-8.dll',
211 'cygstdc++-6.dll', 'cygz.dll')
212 return [command.Copy(
213 os.path.join(CYGWIN_PATH, 'bin', lib),
214 os.path.join('%(output)s', 'bin', lib))
216 if pynacl.platform.IsWindows():
217 lib_path = os.path.join(MINGW_PATH, 'bin')
218 # The native minGW compiler uses winpthread, but the Ubuntu cross compiler
220 libs = ('libgcc_s_sjlj-1.dll', 'libstdc++-6.dll', 'libwinpthread-1.dll')
222 lib_path = os.path.join(CROSS_MINGW_LIBPATH)
223 libs = ('libgcc_s_sjlj-1.dll', 'libstdc++-6.dll')
224 return [command.Copy(
225 os.path.join(lib_path, lib),
226 os.path.join('%(output)s', 'bin', lib))
229 def GetGitSyncCmdCallback(revisions):
230 """Return a callback which returns the git sync command for a component.
232 This allows all the revision information to be processed here while giving
233 other modules like pnacl_targetlibs.py the ability to define their own
234 source targets with minimal boilerplate.
236 def GetGitSyncCmd(component):
237 return command.SyncGitRepo(GIT_BASE_URL + GIT_REPOS[component],
239 revisions[component])
242 def HostToolsSources(GetGitSyncCmd):
244 'binutils_pnacl_src': {
246 'output_dirname': 'binutils',
248 GetGitSyncCmd('binutils'),
251 # For some reason, the llvm build using --with-clang-srcdir chokes if the
252 # clang source directory is named something other than 'clang', so don't
253 # change output_dirname for clang.
256 'output_dirname': 'clang',
258 GetGitSyncCmd('clang'),
263 'output_dirname': 'llvm',
265 GetGitSyncCmd('llvm'),
274 if TripleIsWindows(host) and not command.Runnable.use_cygwin:
275 if pynacl.platform.IsWindows():
278 ar = 'i686-w64-mingw32-ar'
283 'inputs' : { 'src' : os.path.join(NACL_DIR, '..', 'third_party',
286 command.CopyTree('%(src)s', '.'),
287 command.Command(['i686-w64-mingw32-gcc',
288 '-o', 'dlfcn.o', '-c', 'dlfcn.c',
289 '-Wall', '-O3', '-fomit-frame-pointer']),
290 command.Command([ar, 'cru',
291 'libdl.a', 'dlfcn.o']),
292 command.Copy('libdl.a',
293 os.path.join('%(output)s', 'libdl.a')),
294 command.Copy('dlfcn.h',
295 os.path.join('%(output)s', 'dlfcn.h')),
302 def HostTools(host, options):
303 def H(component_name):
304 # Return a package name for a component name with a host triple.
305 return component_name + '_' + gsd_storage.LegalizeName(host)
307 return fnmatch.fnmatch(host, 'x86_64*')
308 def HostSubdir(host):
309 return 'host_x86_64' if IsHost64(host) else 'host_x86_32'
311 return 'bin64' if host == 'x86_64-linux' else 'bin'
313 H('binutils_pnacl'): {
314 'dependencies': ['binutils_pnacl_src'],
316 'output_subdir': HostSubdir(host),
318 command.SkipForIncrementalCommand([
320 '%(binutils_pnacl_src)s/configure'] +
321 ConfigureHostArchFlags(host) +
323 '--disable-silent-rules',
324 '--target=arm-pc-nacl',
325 '--program-prefix=le32-nacl-',
326 '--enable-targets=arm-pc-nacl,i686-pc-nacl,x86_64-pc-nacl,' +
328 '--enable-deterministic-archives',
329 '--enable-shared=no',
330 '--enable-gold=default',
335 '--with-sysroot=/arm-pc-nacl']),
336 command.Command(MakeCommand(host)),
337 command.Command(MAKE_DESTDIR_CMD + ['install-strip'])] +
338 [command.RemoveDirectory(os.path.join('%(output)s', dir))
339 for dir in ('arm-pc-nacl', 'lib', 'lib32')]
343 'output_subdir': BinSubdir(host),
344 'inputs': { 'src': os.path.join(NACL_DIR, 'pnacl', 'driver')},
346 command.Runnable(pnacl_commands.InstallDriverScripts,
347 '%(src)s', '%(output)s',
348 host_windows=TripleIsWindows(host),
349 host_64bit=IsHost64(host))
355 'dependencies': ['clang_src', 'llvm_src', 'binutils_pnacl_src'],
357 'output_subdir': HostSubdir(host),
359 command.SkipForIncrementalCommand([
360 'cmake', '-G', 'Ninja'] +
361 CmakeHostArchFlags(host, options) +
362 ['-DCMAKE_BUILD_TYPE=RelWithDebInfo',
363 '-DCMAKE_INSTALL_PREFIX=%(output)s',
364 '-DCMAKE_INSTALL_RPATH=$ORIGIN/../lib',
365 '-DBUILD_SHARED_LIBS=ON',
366 '-DLLVM_ENABLE_ASSERTIONS=ON',
367 '-DLLVM_ENABLE_ZLIB=OFF',
368 '-DLLVM_BUILD_TESTS=ON',
369 '-DLLVM_APPEND_VC_REV=ON',
370 '-DLLVM_BINUTILS_INCDIR=%(abs_binutils_pnacl_src)s/include',
371 '-DLLVM_EXTERNAL_CLANG_SOURCE_DIR=%(clang_src)s',
373 command.Command(['ninja', '-v']),
374 command.Command(['ninja', 'install']),
380 'dependencies': ['clang_src', 'llvm_src', 'binutils_pnacl_src'],
382 'output_subdir': HostSubdir(host),
384 command.SkipForIncrementalCommand([
386 '%(llvm_src)s/configure'] +
387 ConfigureHostArchFlags(host) +
392 '--disable-bindings', # ocaml is currently the only binding.
393 '--with-binutils-include=%(abs_binutils_pnacl_src)s/include',
394 '--enable-targets=x86,arm,mips',
396 '--enable-optimized',
397 '--with-clang-srcdir=%(abs_clang_src)s']),
398 command.Command(MakeCommand(host) + [
402 command.Command(MAKE_DESTDIR_CMD + ['install'])] +
403 CopyWindowsHostLibs(host),
407 tools.update(llvm_cmake)
409 tools.update(llvm_autoconf)
410 if TripleIsWindows(host) and not command.Runnable.use_cygwin:
411 tools[H('binutils_pnacl')]['dependencies'].append('libdl')
412 tools[H('llvm')]['dependencies'].append('libdl')
415 # TODO(dschuff): The REV file should probably go here rather than in the driver
421 'inputs': { 'readme': os.path.join(NACL_DIR, 'pnacl', 'README') },
423 command.Copy('%(readme)s', os.path.join('%(output)s', 'README')),
424 command.WriteData(str(FEATURE_VERSION),
425 os.path.join('%(output)s', 'FEATURE_VERSION')),
431 def ParseComponentRevisionsFile(filename):
432 ''' Parse a simple-format deps file, with fields of the form:
434 Keys should match the keys in GIT_REPOS above, which match the previous
435 directory names used by gclient (with the exception that '_' in the file is
436 replaced by '-' in the returned key name).
437 Values are the git hashes for each repo.
438 Empty lines or lines beginning with '#' are ignored.
439 This function returns a dictionary mapping the keys found in the file to their
442 with open(filename) as f:
445 stripped = line.strip()
446 if stripped.startswith('#') or len(stripped) == 0:
448 tokens = stripped.split('=')
450 raise Exception('Malformed component revisions file: ' + filename)
451 deps[tokens[0].replace('_', '-')] = tokens[1]
454 # This is to replace build.sh's use of gclient, which will eliminate the issues
455 # with msys vs cygwin git checkouts, and make testing easier. Note that the new
456 # build scripts will not share source directories with build.sh.
457 def SyncPNaClRepos(revisions):
459 for repo, revision in revisions.iteritems():
460 destination = os.path.join(NACL_DIR, 'pnacl', 'git', repo)
461 # This replaces build.sh's newlib-nacl-headers-clean step by cleaning the
462 # the newlib repo on checkout (while silently blowing away any local
463 # changes). TODO(dschuff): find a better way to handle nacl newlib headers.
464 is_newlib = repo == 'nacl-newlib'
465 repo_tools.SyncGitRepo(
466 GIT_BASE_URL + GIT_REPOS[repo],
471 # For testing LLVM, Clang, etc. changes on the trybots, look for a
472 # Git bundle file created by llvm_change_try_helper.sh.
473 bundle_file = os.path.join(NACL_DIR, 'pnacl', 'not_for_commit',
475 base64_file = '%s.b64' % bundle_file
476 if os.path.exists(base64_file):
477 input_fh = open(base64_file, 'r')
478 output_fh = open(bundle_file, 'wb')
479 base64.decode(input_fh, output_fh)
482 subprocess.check_call(repo_tools.GitCmd() + ['fetch'], cwd=destination)
483 subprocess.check_call(repo_tools.GitCmd() + ['bundle', 'unbundle',
486 commit_id_file = os.path.join(NACL_DIR, 'pnacl', 'not_for_commit',
487 '%s_commit_id' % repo)
488 commit_id = open(commit_id_file, 'r').readline().strip()
489 subprocess.check_call(repo_tools.GitCmd() + ['checkout', commit_id],
493 def InstallMinGWHostCompiler():
494 """Install the MinGW host compiler used to build the host tools on Windows.
496 We could use an ordinary source rule for this, but that would require hashing
497 hundreds of MB of toolchain files on every build. Instead, check for the
498 presence of the specially-named file <version>.installed in the install
499 directory. If it is absent, check for the presence of the zip file
500 <version>.zip. If it is absent, attempt to download it from Google Storage.
501 Then extract the zip file and create the install file.
503 if not os.path.isfile(os.path.join(MINGW_PATH, MINGW_VERSION + '.installed')):
504 downloader = gsd_storage.GSDStorage([], ['nativeclient-mingw'])
505 zipfilename = MINGW_VERSION + '.zip'
506 zipfilepath = os.path.join(NACL_DIR, zipfilename)
507 # If the zip file is not present, try to download it from Google Storage.
508 # If that fails, bail out.
509 if (not os.path.isfile(zipfilepath) and
510 not downloader.GetSecureFile(zipfilename, zipfilepath)):
511 print >>sys.stderr, 'Failed to install MinGW tools:'
512 print >>sys.stderr, 'could not find or download', zipfilename
514 logging.info('Extracting %s' % zipfilename)
515 zf = zipfile.ZipFile(zipfilepath)
516 if os.path.exists(MINGW_PATH):
517 shutil.rmtree(MINGW_PATH)
518 zf.extractall(NACL_DIR)
519 with open(os.path.join(MINGW_PATH, MINGW_VERSION + '.installed'), 'w') as _:
521 os.environ['MINGW'] = MINGW_PATH
523 if __name__ == '__main__':
524 # This sets the logging for gclient-alike repo sync. It will be overridden
525 # by the package builder based on the command-line flags.
526 logging.getLogger().setLevel(logging.DEBUG)
527 parser = argparse.ArgumentParser(add_help=False)
528 parser.add_argument('--legacy-repo-sync', action='store_true',
529 dest='legacy_repo_sync', default=False,
530 help='Sync the git repo directories used by build.sh')
531 parser.add_argument('--build-64bit-host', action='store_true',
532 dest='build_64bit_host', default=False,
533 help='Build 64-bit Linux host binaries in addition to 32')
534 parser.add_argument('--cmake', action='store_true', default=False,
535 help="Use LLVM's cmake ninja build instead of autoconf")
536 parser.add_argument('--clang', action='store_true', default=False,
537 help="Use clang instead of gcc with LLVM's cmake build")
538 parser.add_argument('--sanitize', choices=['address', 'thread', 'undefined'],
539 help="Use a sanitizer with LLVM's clang cmake build")
540 args, leftover_args = parser.parse_known_args()
541 if '-h' in leftover_args or '--help' in leftover_args:
542 print 'The following arguments are specific to toolchain_build_pnacl.py:'
544 print 'The rest of the arguments are generic, in toolchain_main.py'
546 if args.sanitize and not args.cmake:
547 print 'Use of sanitizers requires a cmake build'
549 if args.clang and not args.cmake:
550 print 'Use of clang is currently only supported with cmake/ninja'
553 revisions = ParseComponentRevisionsFile(GIT_DEPS_FILE)
554 if args.legacy_repo_sync:
555 SyncPNaClRepos(revisions)
558 if (pynacl.platform.IsWindows() and
559 not command.Runnable.use_cygwin):
560 InstallMinGWHostCompiler()
563 packages.update(HostToolsSources(GetGitSyncCmdCallback(revisions)))
565 if pynacl.platform.Is64BitLinux():
566 hosts = ['i686-linux']
567 if args.build_64bit_host:
568 hosts.append(NativeTriple())
570 hosts = [NativeTriple()]
571 if pynacl.platform.IsLinux() and BUILD_CROSS_MINGW:
572 hosts.append('i686-w64-mingw32')
574 packages.update(HostLibs(host))
575 packages.update(HostTools(host, args))
576 # Don't build the target libs on Windows because of pathname issues.
577 # Don't build the target libs on Mac because the gold plugin's rpaths
579 # On linux use the 32-bit compiler to build the target libs since that's what
580 # most developers will be using.
581 if pynacl.platform.IsLinux():
582 packages.update(pnacl_targetlibs.TargetLibsSrc(
583 GetGitSyncCmdCallback(revisions)))
584 for bias in BITCODE_BIASES:
585 packages.update(pnacl_targetlibs.BitcodeLibs(hosts[0], bias))
586 for arch in ALL_ARCHES:
587 packages.update(pnacl_targetlibs.NativeLibs(hosts[0], arch))
588 packages.update(pnacl_targetlibs.NativeLibsUnsandboxed('linux-x86-32'))
589 packages.update(Metadata())
591 tb = toolchain_main.PackageBuilder(packages,