Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / native_client / toolchain_build / toolchain_build_pnacl.py
1 #!/usr/bin/python
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.
5
6 """Recipes for PNaCl toolchain packages.
7
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.
11
12    The real entry plumbing and CLI flags are also in toolchain_main.py.
13 """
14
15 import fnmatch
16 import logging
17 import os
18 import shutil
19 import sys
20 import zipfile
21
22 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
23 import pynacl.gsd_storage
24 import pynacl.platform
25 import pynacl.repo_tools
26
27 import command
28 import pnacl_commands
29 import pnacl_targetlibs
30 import toolchain_main
31
32 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
33 NACL_DIR = os.path.dirname(SCRIPT_DIR)
34 # Use the argparse from third_party to ensure it's the same on all platorms
35 python_lib_dir = os.path.join(os.path.dirname(NACL_DIR), 'third_party',
36                               'python_libs', 'argparse')
37 sys.path.insert(0, python_lib_dir)
38 import argparse
39
40 PNACL_DRIVER_DIR = os.path.join(NACL_DIR, 'pnacl', 'driver')
41
42 # Scons tests can check this version number to decide whether to enable tests
43 # for toolchain bug fixes or new features.  This allows tests to be enabled on
44 # the toolchain buildbots/trybots before the new toolchain version is pinned
45 # (i.e. before the tests would pass on the main NaCl buildbots/trybots).
46 # If you are adding a test that depends on a toolchain change, you can
47 # increment this version number manually.
48 FEATURE_VERSION = 8
49
50 # For backward compatibility, these key names match the directory names
51 # previously used with gclient
52 GIT_REPOS = {
53     'binutils': 'nacl-binutils.git',
54     'clang': 'pnacl-clang.git',
55     'llvm': 'pnacl-llvm.git',
56     'gcc': 'pnacl-gcc.git',
57     'libcxx': 'pnacl-libcxx.git',
58     'libcxxabi': 'pnacl-libcxxabi.git',
59     'nacl-newlib': 'nacl-newlib.git',
60     'llvm-test-suite': 'pnacl-llvm-testsuite.git',
61     'compiler-rt': 'pnacl-compiler-rt.git',
62     'subzero': 'pnacl-subzero.git',
63     'binutils-x86': 'nacl-binutils.git',
64     }
65
66 GIT_BASE_URL = 'https://chromium.googlesource.com/native_client/'
67 GIT_PUSH_URL = 'ssh://gerrit.chromium.org/native_client/'
68 GIT_DEPS_FILE = os.path.join(NACL_DIR, 'pnacl', 'COMPONENT_REVISIONS')
69
70 ALT_GIT_BASE_URL = 'https://chromium.googlesource.com/a/native_client/'
71
72 KNOWN_MIRRORS = [('http://git.chromium.org/native_client/', GIT_BASE_URL)]
73 PUSH_MIRRORS = [('http://git.chromium.org/native_client/', GIT_PUSH_URL),
74                 (ALT_GIT_BASE_URL, GIT_PUSH_URL),
75                 (GIT_BASE_URL, GIT_PUSH_URL)]
76
77 PACKAGE_NAME = 'Native Client SDK [%(build_signature)s]'
78 BUG_URL = 'http://gonacl.com/reportissue'
79
80 # TODO(dschuff): Some of this mingw logic duplicates stuff in command.py
81 BUILD_CROSS_MINGW = False
82 # Path to the mingw cross-compiler libs on Ubuntu
83 CROSS_MINGW_LIBPATH = '/usr/lib/gcc/i686-w64-mingw32/4.6'
84 # Path and version of the native mingw compiler to be installed on Windows hosts
85 MINGW_PATH = os.path.join(NACL_DIR, 'mingw32')
86 MINGW_VERSION = 'i686-w64-mingw32-4.8.1'
87
88 CHROME_CLANG = os.path.join(os.path.dirname(NACL_DIR), 'third_party',
89                             'llvm-build', 'Release+Asserts', 'bin', 'clang')
90 CHROME_CLANGXX = CHROME_CLANG + '++'
91
92 ALL_ARCHES = ('x86-32', 'x86-64', 'arm', 'mips32',
93               'x86-32-nonsfi', 'arm-nonsfi')
94 # MIPS32 doesn't use biased bitcode, and nonsfi targets don't need it.
95 BITCODE_BIASES = tuple(bias for bias in ('le32', 'x86-32', 'x86-64', 'arm'))
96
97 MAKE_DESTDIR_CMD = ['make', 'DESTDIR=%(abs_output)s']
98
99 def TripleIsWindows(t):
100   return fnmatch.fnmatch(t, '*-mingw32*')
101
102 def TripleIsCygWin(t):
103   return fnmatch.fnmatch(t, '*-cygwin*')
104
105 def TripleIsLinux(t):
106   return fnmatch.fnmatch(t, '*-linux*')
107
108 def TripleIsMac(t):
109   return fnmatch.fnmatch(t, '*-darwin*')
110
111 def TripleIsX8664(t):
112   return fnmatch.fnmatch(t, 'x86_64*')
113
114
115 # Return a tuple (C compiler, C++ compiler) of the compilers to compile the host
116 # toolchains
117 def CompilersForHost(host):
118   compiler = {
119       # For now we only do native builds for linux and mac
120       # treat 32-bit linux like a native build
121       'i686-linux': (CHROME_CLANG, CHROME_CLANGXX),
122       'x86_64-linux': (CHROME_CLANG, CHROME_CLANGXX),
123       'x86_64-apple-darwin': (CHROME_CLANG, CHROME_CLANGXX),
124       # Windows build should work for native and cross
125       'i686-w64-mingw32': ('i686-w64-mingw32-gcc', 'i686-w64-mingw32-g++'),
126       # TODO: add arm-hosted support
127       'i686-pc-cygwin': ('gcc', 'g++'),
128   }
129   return compiler[host]
130
131
132 def GSDJoin(*args):
133   return '_'.join([pynacl.gsd_storage.LegalizeName(arg) for arg in args])
134
135
136 def ConfigureHostArchFlags(host, extra_cflags, options):
137   """ Return flags passed to LLVM and binutils configure for compilers and
138   compile flags. """
139   configure_args = []
140   extra_cc_args = []
141
142   native = pynacl.platform.PlatformTriple()
143   is_cross = host != native
144   if is_cross:
145     if (pynacl.platform.IsLinux64() and
146         fnmatch.fnmatch(host, '*-linux*')):
147       # 64 bit linux can build 32 bit linux binaries while still being a native
148       # build for our purposes. But it's not what config.guess will yield, so
149       # use --build to force it and make sure things build correctly.
150       configure_args.append('--build=' + host)
151     else:
152       configure_args.append('--host=' + host)
153   if TripleIsLinux(host) and not TripleIsX8664(host):
154     # Chrome clang defaults to 64-bit builds, even when run on 32-bit Linux.
155     extra_cc_args = ['-m32']
156
157   extra_cxx_args = list(extra_cc_args)
158
159   if not options.gcc:
160     cc, cxx = CompilersForHost(host)
161
162     configure_args.append('CC=' + ' '.join([cc] + extra_cc_args))
163     configure_args.append('CXX=' + ' '.join([cxx] + extra_cxx_args))
164
165   if TripleIsWindows(host):
166     # The i18n support brings in runtime dependencies on MinGW DLLs
167     # that we don't want to have to distribute alongside our binaries.
168     # So just disable it, and compiler messages will always be in US English.
169     configure_args.append('--disable-nls')
170     configure_args.extend(['LDFLAGS=-L%(abs_libdl)s -ldl',
171                            'CFLAGS=-isystem %(abs_libdl)s',
172                            'CXXFLAGS=-isystem %(abs_libdl)s'])
173     if is_cross:
174       # LLVM's linux->mingw cross build needs this
175       configure_args.append('CC_FOR_BUILD=gcc')
176   else:
177     if options.gcc:
178       configure_args.extend(['CFLAGS=' + ' '.join(extra_cflags),
179                              'CXXFLAGS=' + ' '.join(extra_cflags)])
180     else:
181       configure_args.extend(
182        ['CFLAGS=' + ' '.join(extra_cflags),
183         'LDFLAGS=-L%(' + GSDJoin('abs_libcxx', host) + ')s/lib',
184         'CXXFLAGS=-stdlib=libc++ -I%(' + GSDJoin('abs_libcxx', host) +
185         ')s/include/c++/v1 ' + ' '.join(extra_cflags)])
186
187   return configure_args
188
189
190 def LibCxxHostArchFlags(host):
191   cc, cxx = CompilersForHost(host)
192   cmake_flags = []
193   cmake_flags.extend(['-DCMAKE_C_COMPILER='+cc, '-DCMAKE_CXX_COMPILER='+cxx])
194   if TripleIsLinux(host) and not TripleIsX8664(host):
195     # Chrome clang defaults to 64-bit builds, even when run on 32-bit Linux
196     cmake_flags.extend(['-DCMAKE_C_FLAGS=-m32',
197                         '-DCMAKE_CXX_FLAGS=-m32'])
198   return cmake_flags
199
200 def CmakeHostArchFlags(host, options):
201   """ Set flags passed to LLVM cmake for compilers and compile flags. """
202   cmake_flags = []
203   cc, cxx = CompilersForHost(host)
204
205   cmake_flags.extend(['-DCMAKE_C_COMPILER='+cc, '-DCMAKE_CXX_COMPILER='+cxx])
206
207   # There seems to be a bug in chrome clang where it exposes the msan interface
208   # (even when compiling without msan) but then does not link with an
209   # msan-enabled compiler_rt, leaving references to __msan_allocated_memory
210   # undefined.
211   cmake_flags.append('-DHAVE_SANITIZER_MSAN_INTERFACE_H=FALSE')
212
213   if pynacl.platform.IsLinux64() and pynacl.platform.PlatformTriple() != host:
214     # Currently the only supported "cross" build is 64-bit Linux to 32-bit
215     # Linux. Enable it.  Also disable libxml and libtinfo because our Ubuntu
216     # doesn't have 32-bit libxml or libtinfo build, and users may not have them
217     # either.
218     cmake_flags.extend(['-DLLVM_BUILD_32_BITS=ON',
219                         '-DLLVM_ENABLE_LIBXML=OFF',
220                         '-DLLVM_ENABLE_TERMINFO=OFF'])
221
222   if options.sanitize:
223     cmake_flags.extend(['-DCMAKE_%s_FLAGS=-fsanitize=%s' % (c, options.sanitize)
224                         for c in ('C', 'CXX')])
225     cmake_flags.append('-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=%s' %
226                        options.sanitize)
227
228   return cmake_flags
229
230
231 def ConfigureBinutilsCommon():
232   return ['--with-pkgversion=' + PACKAGE_NAME,
233           '--with-bugurl=' + BUG_URL,
234           '--without-zlib',
235           '--prefix=',
236           '--disable-silent-rules',
237           '--enable-deterministic-archives',
238          ]
239
240 def LLVMConfigureAssertionsFlags(options):
241   if options.enable_llvm_assertions:
242     return []
243   else:
244     return ['--disable-debug', '--disable-assertions']
245
246
247 def MakeCommand(host):
248   make_command = ['make']
249   if not pynacl.platform.IsWindows() or pynacl.platform.IsCygWin():
250     # The make that ships with msys sometimes hangs when run with -j.
251     # The ming32-make that comes with the compiler itself reportedly doesn't
252     # have this problem, but it has issues with pathnames with LLVM's build.
253     make_command.append('-j%(cores)s')
254
255   if TripleIsWindows(host):
256     # There appears to be nothing we can pass at top-level configure time
257     # that will prevent the configure scripts from finding MinGW's libiconv
258     # and using it.  We have to force this variable into the environment
259     # of the sub-configure runs, which are run via make.
260     make_command.append('HAVE_LIBICONV=no')
261   return make_command
262
263
264 def CopyWindowsHostLibs(host):
265   if not TripleIsWindows(host) and not TripleIsCygWin(host):
266     return []
267
268   if TripleIsCygWin(host):
269     lib_path = '/bin'
270     libs = ('cyggcc_s-1.dll', 'cygiconv-2.dll', 'cygwin1.dll',
271             'cygintl-8.dll', 'cygstdc++-6.dll', 'cygz.dll')
272   elif pynacl.platform.IsWindows():
273     lib_path = os.path.join(MINGW_PATH, 'bin')
274     # The native minGW compiler uses winpthread, but the Ubuntu cross compiler
275     # does not.
276     libs = ('libgcc_s_sjlj-1.dll', 'libstdc++-6.dll', 'libwinpthread-1.dll')
277   else:
278     lib_path = os.path.join(CROSS_MINGW_LIBPATH)
279     libs = ('libgcc_s_sjlj-1.dll', 'libstdc++-6.dll')
280   return [command.Copy(
281                   os.path.join(lib_path, lib),
282                   os.path.join('%(output)s', 'bin', lib))
283                for lib in libs]
284
285 def GetGitSyncCmdsCallback(revisions):
286   """Return a callback which returns the git sync commands for a component.
287
288      This allows all the revision information to be processed here while giving
289      other modules like pnacl_targetlibs.py the ability to define their own
290      source targets with minimal boilerplate.
291   """
292   def GetGitSyncCmds(component):
293     git_url = GIT_BASE_URL + GIT_REPOS[component]
294     git_push_url = GIT_PUSH_URL + GIT_REPOS[component]
295
296     # This replaces build.sh's newlib-nacl-headers-clean step by cleaning the
297     # the newlib repo on checkout (while silently blowing away any local
298     # changes). TODO(dschuff): find a better way to handle nacl newlib headers.
299     is_newlib = component == 'nacl-newlib'
300     return (command.SyncGitRepoCmds(git_url, '%(output)s', revisions[component],
301                                     clean=is_newlib,
302                                     git_cache='%(git_cache_dir)s',
303                                     push_url=git_push_url,
304                                     known_mirrors=KNOWN_MIRRORS,
305                                     push_mirrors=PUSH_MIRRORS) +
306             [command.Runnable(None,
307                               pnacl_commands.CmdCheckoutGitBundleForTrybot,
308                               component, '%(output)s')])
309
310   return GetGitSyncCmds
311
312
313 def HostToolsSources(GetGitSyncCmds):
314   sources = {
315       'libcxx_src': {
316           'type': 'source',
317           'output_dirname': 'libcxx',
318           'commands': GetGitSyncCmds('libcxx'),
319       },
320       'libcxxabi_src': {
321           'type': 'source',
322           'output_dirname': 'libcxxabi',
323           'commands': GetGitSyncCmds('libcxxabi'),
324       },
325       'binutils_pnacl_src': {
326           'type': 'source',
327           'output_dirname': 'binutils',
328           'commands': GetGitSyncCmds('binutils'),
329       },
330       # For some reason, the llvm build using --with-clang-srcdir chokes if the
331       # clang source directory is named something other than 'clang', so don't
332       # change output_dirname for clang.
333       'clang_src': {
334           'type': 'source',
335           'output_dirname': 'clang',
336           'commands': GetGitSyncCmds('clang'),
337       },
338       'llvm_src': {
339           'type': 'source',
340           'output_dirname': 'llvm',
341           'commands': GetGitSyncCmds('llvm'),
342       },
343       'subzero_src': {
344           'type': 'source',
345           'output_dirname': 'subzero',
346           'commands': GetGitSyncCmds('subzero'),
347       },
348       'binutils_x86_src': {
349           'type': 'source',
350           'output_dirname': 'binutils-x86',
351           'commands': GetGitSyncCmds('binutils-x86'),
352       },
353   }
354   return sources
355
356 def TestsuiteSources(GetGitSyncCmds):
357   sources = {
358       'llvm_testsuite_src': {
359           'type': 'source',
360           'output_dirname': 'llvm-test-suite',
361           'commands': GetGitSyncCmds('llvm-test-suite'),
362       },
363   }
364   return sources
365
366
367 def CopyHostLibcxxForLLVMBuild(host, dest, options):
368   """Copy libc++ to the working directory for build tools."""
369   if options.gcc:
370     return []
371   if TripleIsLinux(host):
372     libname = 'libc++.so.1'
373   elif TripleIsMac(host):
374     libname = 'libc++.1.dylib'
375   else:
376     return []
377   return [command.Mkdir(dest, parents=True),
378           command.Copy('%(' + GSDJoin('abs_libcxx', host) +')s/lib/' + libname,
379                        os.path.join(dest, libname))]
380
381 def HostLibs(host, options):
382   def H(component_name):
383     # Return a package name for a component name with a host triple.
384     return GSDJoin(component_name, host)
385   libs = {}
386   if TripleIsWindows(host):
387     if pynacl.platform.IsWindows():
388       ar = 'ar'
389     else:
390       ar = 'i686-w64-mingw32-ar'
391
392     libs.update({
393       'libdl': {
394           'type': 'build',
395           'inputs' : { 'src' : os.path.join(NACL_DIR, '..', 'third_party',
396                                             'dlfcn-win32') },
397           'commands': [
398               command.CopyTree('%(src)s', 'src'),
399               command.Command(['i686-w64-mingw32-gcc',
400                                '-o', 'dlfcn.o', '-c',
401                                os.path.join('src', 'dlfcn.c'),
402                                '-Wall', '-O3', '-fomit-frame-pointer']),
403               command.Command([ar, 'cru',
404                                'libdl.a', 'dlfcn.o']),
405               command.Copy('libdl.a',
406                            os.path.join('%(output)s', 'libdl.a')),
407               command.Copy(os.path.join('src', 'dlfcn.h'),
408                            os.path.join('%(output)s', 'dlfcn.h')),
409           ],
410       },
411     })
412   elif not options.gcc:
413     # Libc++ is only tested with the clang build
414     libs.update({
415         H('libcxx'): {
416             'dependencies': ['libcxx_src', 'libcxxabi_src'],
417             'type': 'build',
418             'commands': [
419                 command.SkipForIncrementalCommand([
420                     'cmake', '-G', 'Unix Makefiles'] +
421                      LibCxxHostArchFlags(host) +
422                      ['-DLIBCXX_CXX_ABI=libcxxabi',
423                       '-DLIBCXX_LIBCXXABI_INCLUDE_PATHS=' + command.path.join(
424                           '%(abs_libcxxabi_src)s', 'include'),
425                       '-DLIBCXX_ENABLE_SHARED=ON',
426                       '-DCMAKE_INSTALL_PREFIX=',
427                       '-DCMAKE_INSTALL_NAME_DIR=@executable_path/../lib',
428                       '%(libcxx_src)s']),
429                 command.Command(MakeCommand(host) + ['VERBOSE=1']),
430                 command.Command(MAKE_DESTDIR_CMD + ['VERBOSE=1', 'install']),
431             ],
432         },
433     })
434   return libs
435
436
437 def HostTools(host, options):
438   def H(component_name):
439     # Return a package name for a component name with a host triple.
440     return GSDJoin(component_name, host)
441   # Return the file name with the appropriate suffix for an executable file.
442   def Exe(file):
443     if TripleIsWindows(host):
444       return file + '.exe'
445     else:
446       return file
447   # Binutils still has some warnings when building with clang
448   warning_flags = ['-Wno-extended-offsetof', '-Wno-absolute-value',
449                    '-Wno-unused-function', '-Wno-unused-const-variable',
450                    '-Wno-unneeded-internal-declaration',
451                    '-Wno-unused-private-field', '-Wno-format-security']
452   tools = {
453       H('binutils_pnacl'): {
454           'dependencies': ['binutils_pnacl_src'],
455           'type': 'build',
456           'commands': [
457               command.SkipForIncrementalCommand([
458                   'sh',
459                   '%(binutils_pnacl_src)s/configure'] +
460                   ConfigureBinutilsCommon() +
461                   ConfigureHostArchFlags(host, warning_flags, options) +
462                   ['--target=arm-pc-nacl',
463                   '--program-prefix=le32-nacl-',
464                   '--enable-targets=arm-pc-nacl,i686-pc-nacl,x86_64-pc-nacl,' +
465                   'mipsel-pc-nacl',
466                   '--enable-shared=no',
467                   '--enable-gold=default',
468                   '--enable-ld=no',
469                   '--enable-plugins',
470                   '--without-gas',
471                   '--with-sysroot=/le32-nacl']),
472               command.Command(MakeCommand(host)),
473               command.Command(MAKE_DESTDIR_CMD + ['install-strip'])] +
474               [command.RemoveDirectory(os.path.join('%(output)s', dir))
475                for dir in ('arm-pc-nacl', 'lib', 'lib32')]
476       },
477       H('driver'): {
478         'type': 'build',
479         'output_subdir': 'bin',
480         'inputs': { 'src': PNACL_DRIVER_DIR },
481         'commands': [
482             command.Runnable(
483                 None,
484                 pnacl_commands.InstallDriverScripts,
485                 '%(src)s', '%(output)s',
486                 host_windows=TripleIsWindows(host) or TripleIsCygWin(host),
487                 host_64bit=TripleIsX8664(host))
488         ],
489       },
490   }
491
492   llvm_cmake = {
493       H('llvm'): {
494           'dependencies': ['clang_src', 'llvm_src', 'binutils_pnacl_src'],
495           'type': 'build',
496           'commands': [
497               command.SkipForIncrementalCommand([
498                   'cmake', '-G', 'Ninja'] +
499                   CmakeHostArchFlags(host, options) +
500                   ['-DCMAKE_BUILD_TYPE=RelWithDebInfo',
501                   '-DCMAKE_INSTALL_PREFIX=%(output)s',
502                   '-DCMAKE_INSTALL_RPATH=$ORIGIN/../lib',
503                   '-DLLVM_ENABLE_LIBCXX=ON',
504                   '-DBUILD_SHARED_LIBS=ON',
505                   '-DLLVM_TARGETS_TO_BUILD=X86;ARM;Mips',
506                   '-DLLVM_ENABLE_ASSERTIONS=ON',
507                   '-DLLVM_ENABLE_ZLIB=OFF',
508                   '-DLLVM_BUILD_TESTS=ON',
509                   '-DLLVM_APPEND_VC_REV=ON',
510                   '-DLLVM_BINUTILS_INCDIR=%(abs_binutils_pnacl_src)s/include',
511                   '-DLLVM_EXTERNAL_CLANG_SOURCE_DIR=%(clang_src)s',
512                   '%(llvm_src)s']),
513               command.Command(['ninja', '-v']),
514               command.Command(['ninja', 'install']),
515         ],
516       },
517   }
518   llvm_autoconf = {
519       H('llvm'): {
520           'dependencies': ['clang_src', 'llvm_src', 'binutils_pnacl_src',
521                            'subzero_src'],
522           'type': 'build',
523           'commands': [
524               command.SkipForIncrementalCommand([
525                   'sh',
526                   '%(llvm_src)s/configure'] +
527                   ConfigureHostArchFlags(host, [], options) +
528                   LLVMConfigureAssertionsFlags(options) +
529                   ['--prefix=/',
530                    '--enable-shared',
531                    '--disable-zlib',
532                    '--disable-terminfo',
533                    '--disable-jit',
534                    '--disable-bindings', # ocaml is currently the only binding.
535                    '--with-binutils-include=%(abs_binutils_pnacl_src)s/include',
536                    '--enable-targets=x86,arm,mips',
537                    '--program-prefix=',
538                    '--enable-optimized',
539                    '--with-clang-srcdir=%(abs_clang_src)s'])] +
540               CopyHostLibcxxForLLVMBuild(
541                   host,
542                   os.path.join('Release+Asserts', 'lib'),
543                   options) +
544               [command.Command(MakeCommand(host) + [
545                   'VERBOSE=1',
546                   'NACL_SANDBOX=0',
547                   'SUBZERO_SRC_ROOT=%(abs_subzero_src)s',
548                   'all']),
549               command.Command(MAKE_DESTDIR_CMD + ['install']),
550               command.Remove(*[os.path.join('%(output)s', 'lib', f) for f in
551                               '*.a', '*Hello.*', 'BugpointPasses.*']),
552               command.Remove(*[os.path.join('%(output)s', 'bin', f) for f in
553                                Exe('clang-format'), Exe('clang-check'),
554                                Exe('c-index-test'), Exe('clang-tblgen'),
555                                Exe('llvm-tblgen')])] +
556               [command.Command(['ln', '-f',
557                                 command.path.join('%(output)s', 'bin','clang'),
558                                 command.path.join('%(output)s', 'bin',
559                                                   arch + '-nacl-clang')])
560                for arch in ['i686', 'x86_64']] +
561               [command.Command(['ln', '-f',
562                                 command.path.join('%(output)s', 'bin','clang'),
563                                 command.path.join('%(output)s', 'bin',
564                                                   arch + '-nacl-clang++')])
565                for arch in ['i686', 'x86_64']] +
566               CopyWindowsHostLibs(host),
567       },
568   }
569   if options.cmake:
570     tools.update(llvm_cmake)
571   else:
572     tools.update(llvm_autoconf)
573   if TripleIsWindows(host):
574     tools[H('binutils_pnacl')]['dependencies'].append('libdl')
575     tools[H('llvm')]['dependencies'].append('libdl')
576   elif not options.gcc:
577     tools[H('binutils_pnacl')]['dependencies'].append(H('libcxx'))
578     tools[H('llvm')]['dependencies'].append(H('libcxx'))
579   return tools
580
581
582 def TargetLibCompiler(host, options):
583   def H(component_name):
584     return GSDJoin(component_name, host)
585   compiler = {
586       # Because target_lib_compiler is not a memoized target, its name doesn't
587       # need to have the host appended to it (it can be different on different
588       # hosts), which means that target library build rules don't have to care
589       # what host they run on; they can just depend on 'target_lib_compiler'
590       'target_lib_compiler': {
591           'type': 'work',
592           'output_subdir': 'target_lib_compiler',
593           'dependencies': [ H('binutils_pnacl'), H('llvm') ],
594           'inputs': { 'driver': PNACL_DRIVER_DIR },
595           'commands': [
596               command.CopyRecursive('%(' + t + ')s', '%(output)s')
597               for t in [H('llvm'), H('binutils_pnacl')]] + [
598               command.Runnable(
599                   None, pnacl_commands.InstallDriverScripts,
600                   '%(driver)s', os.path.join('%(output)s', 'bin'),
601                   host_windows=TripleIsWindows(host) or TripleIsCygWin(host),
602                   host_64bit=TripleIsX8664(host))
603           ]
604       },
605   }
606
607   if TripleIsWindows(host) or not options.gcc:
608     host_lib = 'libdl' if TripleIsWindows(host) else H('libcxx')
609     compiler['target_lib_compiler']['dependencies'].append(host_lib)
610     compiler['target_lib_compiler']['commands'].append(
611         command.CopyRecursive('%(' + host_lib + ')s', '%(output)s'))
612   return compiler
613
614
615 def Metadata(revisions):
616   data = {
617       'metadata': {
618           'type': 'build',
619           'inputs': { 'readme': os.path.join(NACL_DIR, 'pnacl', 'README'),
620                       'COMPONENT_REVISIONS': GIT_DEPS_FILE,
621                       'driver': PNACL_DRIVER_DIR },
622           'commands': [
623               command.Copy('%(readme)s', os.path.join('%(output)s', 'README')),
624               command.WriteData(str(FEATURE_VERSION),
625                                 os.path.join('%(output)s', 'FEATURE_VERSION')),
626               command.Runnable(None, pnacl_commands.WriteREVFile,
627                                os.path.join('%(output)s', 'REV'),
628                                GIT_BASE_URL,
629                                GIT_REPOS,
630                                revisions),
631           ],
632       }
633   }
634   return data
635
636
637 def HostToolsDirectToNacl(host):
638   def H(component_name):
639     return GSDJoin(component_name, host)
640   tools = {
641       H('binutils_x86'): {
642           'type': 'build',
643           'dependencies': [H('llvm'), 'binutils_x86_src'],
644           'commands': [
645               command.SkipForIncrementalCommand(
646                   ['sh', '%(binutils_x86_src)s/configure'] +
647                   ConfigureBinutilsCommon() +
648                   ['--target=x86_64-nacl',
649                    '--enable-targets=x86_64-nacl,i686-nacl',
650                    '--disable-werror']),
651               command.Command(MakeCommand(host)),
652               command.Command(MAKE_DESTDIR_CMD + ['install-strip'])] +
653               # Remove the share dir from this binutils build and leave the one
654               # from the newer version used for bitcode linking. Always remove
655               # the lib dirs, which have unneeded host libs.
656               [command.RemoveDirectory(os.path.join('%(output)s', dir))
657                for dir in ('lib', 'lib32', 'lib64', 'share')] +
658               # Create the set of directories for host libs and includes, for
659               # experimentation before we actually build them.
660               # Libc includes (libs dir is created by binutils)
661               [command.Mkdir(command.path.join(
662                   '%(output)s', target, 'include'), parents=True)
663                 for target in ['i686-nacl', 'x86_64-nacl']] +
664               # Compiler libs (includes are shared)
665               [command.Mkdir(command.path.join('%(output)s',
666                   'lib', 'clang', '3.4', 'lib', target), parents=True)
667                for target in ['i686-nacl', 'x86_64-nacl']]
668       }
669   }
670   return tools
671
672 def ParseComponentRevisionsFile(filename):
673   ''' Parse a simple-format deps file, with fields of the form:
674 key=value
675 Keys should match the keys in GIT_REPOS above, which match the previous
676 directory names used by gclient (with the exception that '_' in the file is
677 replaced by '-' in the returned key name).
678 Values are the git hashes for each repo.
679 Empty lines or lines beginning with '#' are ignored.
680 This function returns a dictionary mapping the keys found in the file to their
681 values.
682 '''
683   with open(filename) as f:
684     deps = {}
685     for line in f:
686       stripped = line.strip()
687       if stripped.startswith('#') or len(stripped) == 0:
688         continue
689       tokens = stripped.split('=')
690       if len(tokens) != 2:
691         raise Exception('Malformed component revisions file: ' + filename)
692       deps[tokens[0].replace('_', '-')] = tokens[1]
693   return deps
694
695
696 def GetSyncPNaClReposSource(revisions, GetGitSyncCmds):
697   sources = {}
698   for repo, revision in revisions.iteritems():
699     sources['legacy_pnacl_%s_src' % repo] = {
700         'type': 'source',
701         'output_dirname': os.path.join(NACL_DIR, 'pnacl', 'git', repo),
702         'commands': GetGitSyncCmds(repo),
703     }
704   return sources
705
706
707 def InstallMinGWHostCompiler():
708   """Install the MinGW host compiler used to build the host tools on Windows.
709
710   We could use an ordinary source rule for this, but that would require hashing
711   hundreds of MB of toolchain files on every build. Instead, check for the
712   presence of the specially-named file <version>.installed in the install
713   directory. If it is absent, check for the presence of the zip file
714   <version>.zip. If it is absent, attempt to download it from Google Storage.
715   Then extract the zip file and create the install file.
716   """
717   if not os.path.isfile(os.path.join(MINGW_PATH, MINGW_VERSION + '.installed')):
718     downloader = pynacl.gsd_storage.GSDStorage([], ['nativeclient-mingw'])
719     zipfilename = MINGW_VERSION + '.zip'
720     zipfilepath = os.path.join(NACL_DIR, zipfilename)
721     # If the zip file is not present, try to download it from Google Storage.
722     # If that fails, bail out.
723     if (not os.path.isfile(zipfilepath) and
724         not downloader.GetSecureFile(zipfilename, zipfilepath)):
725         print >>sys.stderr, 'Failed to install MinGW tools:'
726         print >>sys.stderr, 'could not find or download', zipfilename
727         sys.exit(1)
728     logging.info('Extracting %s' % zipfilename)
729     zf = zipfile.ZipFile(zipfilepath)
730     if os.path.exists(MINGW_PATH):
731       shutil.rmtree(MINGW_PATH)
732     zf.extractall(NACL_DIR)
733     with open(os.path.join(MINGW_PATH, MINGW_VERSION + '.installed'), 'w') as _:
734       pass
735   os.environ['MINGW'] = MINGW_PATH
736
737
738 def GetUploadPackageTargets():
739   """Package Targets describes all the archived package targets.
740
741   This build can be built among many build bots, but eventually all things
742   will be combined together. This package target dictionary describes the final
743   output of the entire build.
744   """
745   package_targets = {}
746
747   common_packages = ['metadata']
748
749   # Target native libraries
750   for arch in ALL_ARCHES:
751     legal_arch = pynacl.gsd_storage.LegalizeName(arch)
752     common_packages.append('libs_support_native_%s' % legal_arch)
753     common_packages.append('compiler_rt_%s' % legal_arch)
754     if not 'nonsfi' in arch:
755       common_packages.append('libgcc_eh_%s' % legal_arch)
756
757   # Target bitcode libraries
758   for bias in BITCODE_BIASES:
759     legal_bias = pynacl.gsd_storage.LegalizeName(bias)
760     common_packages.append('newlib_%s' % legal_bias)
761     common_packages.append('libcxx_%s' % legal_bias)
762     common_packages.append('libstdcxx_%s' % legal_bias)
763     common_packages.append('libs_support_bitcode_%s' % legal_bias)
764
765   # Host components
766   host_packages = {}
767   for os_name, arch in (('win', 'x86-32'),
768                         ('mac', 'x86-64'),
769                         ('linux', 'x86-64')):
770     triple = pynacl.platform.PlatformTriple(os_name, arch)
771     legal_triple = pynacl.gsd_storage.LegalizeName(triple)
772     host_packages.setdefault(os_name, []).extend(
773         ['binutils_pnacl_%s' % legal_triple,
774          'llvm_%s' % legal_triple,
775          'driver_%s' % legal_triple])
776     if os_name != 'win':
777       host_packages[os_name].append('libcxx_%s' % legal_triple)
778
779   # Unsandboxed target IRT libraries
780   for os_name in ('linux', 'mac'):
781     legal_triple = pynacl.gsd_storage.LegalizeName('x86-32-' + os_name)
782     host_packages[os_name].append('unsandboxed_irt_%s' % legal_triple)
783
784   for os_name, os_packages in host_packages.iteritems():
785     package_target = '%s_x86' % pynacl.platform.GetOS(os_name)
786     package_targets[package_target] = {}
787     package_name = 'pnacl_newlib'
788     combined_packages = os_packages + common_packages
789     package_targets[package_target][package_name] = combined_packages
790
791   return package_targets
792
793 if __name__ == '__main__':
794   # This sets the logging for gclient-alike repo sync. It will be overridden
795   # by the package builder based on the command-line flags.
796   logging.getLogger().setLevel(logging.DEBUG)
797   parser = argparse.ArgumentParser(add_help=False)
798   parser.add_argument('--legacy-repo-sync', action='store_true',
799                       dest='legacy_repo_sync', default=False,
800                       help='Sync the git repo directories used by build.sh')
801   parser.add_argument('--disable-llvm-assertions', action='store_false',
802                       dest='enable_llvm_assertions', default=True)
803   parser.add_argument('--cmake', action='store_true', default=False,
804                       help="Use LLVM's cmake ninja build instead of autoconf")
805   parser.add_argument('--gcc', action='store_true', default=False,
806                       help="Use the default compiler 'cc' instead of clang")
807   parser.add_argument('--sanitize', choices=['address', 'thread', 'memory',
808                                              'undefined'],
809                       help="Use a sanitizer with LLVM's clang cmake build")
810   parser.add_argument('--testsuite-sync', action='store_true', default=False,
811                       help=('Sync the sources for the LLVM testsuite. '
812                       'Only useful if --sync/ is also enabled'))
813   args, leftover_args = parser.parse_known_args()
814   if '-h' in leftover_args or '--help' in leftover_args:
815     print 'The following arguments are specific to toolchain_build_pnacl.py:'
816     parser.print_help()
817     print 'The rest of the arguments are generic, in toolchain_main.py'
818
819   if args.sanitize and not args.cmake:
820     print 'Use of sanitizers requires a cmake build'
821     sys.exit(1)
822
823   if args.gcc and args.cmake:
824     print 'gcc build is not supported with cmake'
825     sys.exit(1)
826
827   packages = {}
828   upload_packages = {}
829
830   rev = ParseComponentRevisionsFile(GIT_DEPS_FILE)
831   if args.legacy_repo_sync:
832     packages = GetSyncPNaClReposSource(rev, GetGitSyncCmdsCallback(rev))
833
834     # Make sure sync is inside of the args to toolchain_main.
835     if not set(['-y', '--sync', '--sync-only']).intersection(leftover_args):
836       leftover_args.append('--sync-only')
837   else:
838     upload_packages = GetUploadPackageTargets()
839     if pynacl.platform.IsWindows():
840       InstallMinGWHostCompiler()
841
842     packages.update(HostToolsSources(GetGitSyncCmdsCallback(rev)))
843     if args.testsuite_sync:
844       packages.update(TestsuiteSources(GetGitSyncCmdsCallback(rev)))
845
846     hosts = [pynacl.platform.PlatformTriple()]
847     if pynacl.platform.IsLinux() and BUILD_CROSS_MINGW:
848       hosts.append(pynacl.platform.PlatformTriple('win', 'x86-32'))
849     for host in hosts:
850       packages.update(HostLibs(host, args))
851       packages.update(HostTools(host, args))
852       packages.update(HostToolsDirectToNacl(host))
853     packages.update(TargetLibCompiler(pynacl.platform.PlatformTriple(), args))
854     # Don't build the target libs on Windows because of pathname issues.
855     # Only the linux64 bot is canonical (i.e. it will upload its packages).
856     # The other bots will use a 'work' target instead of a 'build' target for
857     # the target libs, so they will not be memoized, but can be used for tests.
858     # TODO(dschuff): Even better would be if we could memoize non-canonical
859     # build targets without doing things like mangling their names (and for e.g.
860     # scons tests, skip running them if their dependencies haven't changed, like
861     # build targets)
862     is_canonical = pynacl.platform.IsLinux64()
863     if pynacl.platform.IsLinux() or pynacl.platform.IsMac():
864       packages.update(pnacl_targetlibs.TargetLibsSrc(
865         GetGitSyncCmdsCallback(rev)))
866       for bias in BITCODE_BIASES:
867         packages.update(pnacl_targetlibs.BitcodeLibs(bias, is_canonical))
868       for arch in ALL_ARCHES:
869         packages.update(pnacl_targetlibs.NativeLibs(arch, is_canonical))
870       packages.update(Metadata(rev))
871     if pynacl.platform.IsLinux() or pynacl.platform.IsMac():
872       packages.update(pnacl_targetlibs.UnsandboxedIRT(
873           'x86-32-%s' % pynacl.platform.GetOS()))
874
875
876   tb = toolchain_main.PackageBuilder(packages,
877                                      upload_packages,
878                                      leftover_args)
879   tb.Main()