1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
|
#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""NEXE building script
This module will take a set of source files, include paths, library paths, and
additional arguments, and use them to build.
"""
import hashlib
import json
from optparse import OptionParser
import os
import re
import Queue
import shutil
import StringIO
import subprocess
import sys
import tempfile
import threading
import urllib2
from build_nexe_tools import (CommandRunner, Error, FixPath,
IsFile, MakeDir, RemoveFile)
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import pynacl.platform
# When a header file defining NACL_BUILD_SUBARCH is introduced,
# we can simply remove this map.
# cf) https://code.google.com/p/chromium/issues/detail?id=440012.
NACL_BUILD_ARCH_MAP = {
'x86-32': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=32'],
'x86-32-nonsfi': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=32'],
'x86-64': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=64'],
'arm': ['NACL_BUILD_ARCH=arm', 'NACL_BUILD_SUBARCH=32'],
'arm-nonsfi': ['NACL_BUILD_ARCH=arm', 'NACL_BUILD_SUBARCH=32'],
'mips': ['NACL_BUILD_ARCH=mips', 'NACL_BUILD_SUBARCH=32'],
'pnacl': ['NACL_BUILD_ARCH=pnacl'],
}
def RemoveQuotes(opt):
if opt and opt[0] == '"':
assert opt[-1] == '"', opt
return opt[1:-1].replace('\\"', '"')
return opt
def ArgToList(opt):
outlist = []
if opt is None:
return outlist
optlist = opt.split(' ')
for optitem in optlist:
optitem = RemoveQuotes(optitem)
if optitem:
outlist.append(optitem)
return outlist
def GetMTime(filepath):
"""GetMTime returns the last modification time of the file or None."""
try:
return os.path.getmtime(FixPath(filepath))
except OSError:
return None
def IsStale(out_ts, src_ts, rebuilt=False):
# If either source or output timestamp was not available, assume stale.
if not out_ts or not src_ts:
return True
# If just rebuilt timestamps may be equal due to time granularity.
if rebuilt:
return out_ts < src_ts
# If about to build, be conservative and rebuilt just in case.
return out_ts <= src_ts
def IsEnvFlagTrue(flag_name, default=False):
"""Return true when the given flag is true.
Note:
Any values that do not match the true pattern are False.
Args:
flag_name: a string name of a flag.
default: default return value if the flag is not set.
Returns:
True if the flag is in the true pattern. Otherwise False.
"""
flag_value = os.environ.get(flag_name)
if flag_value is None:
return default
return bool(re.search(r'^([tTyY]|1:?)', flag_value))
def GetIntegerEnv(flag_name, default=0):
"""Parses and returns integer environment variable.
Args:
flag_name: a string name of a flag.
default: default return value if the flag is not set.
Returns:
Integer value of the flag.
"""
flag_value = os.environ.get(flag_name)
if flag_value is None:
return default
try:
return int(flag_value)
except ValueError:
raise Error('Invalid ' + flag_name + ': ' + flag_value)
class Builder(CommandRunner):
"""Builder object maintains options and generates build command-lines.
The Builder object takes a set of script command-line options, and generates
a set of paths, and command-line options for the NaCl toolchain.
"""
def __init__(self, options):
super(Builder, self).__init__(options)
arch = options.arch
self.arch = arch
build_type = options.build.split('_')
toolname = build_type[0]
self.outtype = build_type[1]
self.osname = pynacl.platform.GetOS()
# pnacl toolchain can be selected in three different ways
# 1. by specifying --arch=pnacl directly to generate
# pexe targets.
# 2. by specifying --build=newlib_translate to generated
# nexe via translation
# 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
# toolchain in native mode (e.g. the IRT shim)
self.is_pnacl_toolchain = False
if self.outtype == 'translate':
self.is_pnacl_toolchain = True
if len(build_type) > 2 and build_type[2] == 'pnacl':
self.is_pnacl_toolchain = True
if arch.endswith('-nonsfi'):
arch = arch[:-len('-nonsfi')]
if arch in ['x86-32', 'x86-64']:
mainarch = 'x86'
self.tool_prefix = 'x86_64-nacl-'
elif arch == 'arm':
self.tool_prefix = 'arm-nacl-'
mainarch = 'arm'
elif arch == 'mips':
self.is_pnacl_toolchain = True
elif arch == 'pnacl':
self.is_pnacl_toolchain = True
else:
raise Error('Toolchain architecture %s not supported.' % arch)
if toolname not in ['newlib', 'glibc']:
raise Error('Toolchain of type %s not supported.' % toolname)
if arch == 'arm' and toolname == 'glibc':
raise Error('arm glibc not yet supported.')
if arch == 'mips' and toolname == 'glibc':
raise Error('mips glibc not supported.')
if arch == 'pnacl' and toolname == 'glibc':
raise Error('pnacl glibc not yet supported.')
if self.is_pnacl_toolchain:
self.tool_prefix = 'pnacl-'
tool_subdir = 'pnacl_newlib'
else:
tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
# The pnacl-clang, etc. tools are scripts. Note that for the CommandRunner
# so that it can know if a shell is needed or not.
self.SetCommandsAreScripts(self.is_pnacl_toolchain)
build_arch = pynacl.platform.GetArch()
tooldir = os.path.join('%s_%s' % (self.osname, build_arch), tool_subdir)
self.root_path = options.root
self.nacl_path = os.path.join(self.root_path, 'native_client')
project_path, project_name = os.path.split(options.name)
self.outdir = options.objdir
# Set the toolchain directories
self.toolchain = os.path.join(options.toolpath, tooldir)
self.toolbin = os.path.join(self.toolchain, 'bin')
self.toolstamp = os.path.join(self.toolchain, 'stamp.prep')
if not IsFile(self.toolstamp):
raise Error('Could not find toolchain prep stamp file: ' + self.toolstamp)
self.inc_paths = ArgToList(options.incdirs)
self.lib_paths = ArgToList(options.libdirs)
self.define_list = ArgToList(options.defines)
self.name = options.name
self.cmd_file = options.cmd_file
self.BuildCompileOptions(
options.compile_flags, self.define_list, options.arch)
self.BuildLinkOptions(options.link_flags)
self.BuildArchiveOptions()
self.strip = options.strip
self.empty = options.empty
self.strip_all = options.strip_all
self.strip_debug = options.strip_debug
self.tls_edit = options.tls_edit
self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
goma_config = self.GetGomaConfig(options.gomadir, arch, toolname)
self.gomacc = goma_config.get('gomacc', '')
self.goma_burst = goma_config.get('burst', False)
self.goma_threads = goma_config.get('threads', 1)
# Define NDEBUG for Release builds.
if options.build_config.startswith('Release'):
self.compile_options.append('-DNDEBUG')
# Use unoptimized native objects for debug IRT builds for faster compiles.
if (self.is_pnacl_toolchain
and (self.outtype == 'nlib'
or self.outtype == 'nexe')
and self.arch != 'pnacl'):
if (options.build_config is not None
and options.build_config.startswith('Debug')):
self.compile_options.extend(['--pnacl-allow-translate',
'--pnacl-allow-native',
'-arch', self.arch])
# Also use fast translation because we are still translating libc/libc++
self.link_options.append('-Wt,-O0')
self.irt_linker = options.irt_linker
self.Log('Compile options: %s' % self.compile_options)
self.Log('Linker options: %s' % self.link_options)
def GenNaClPath(self, path):
"""Helper which prepends path with the native client source directory."""
return os.path.join(self.root_path, 'native_client', path)
def GetBinName(self, name):
"""Helper which prepends executable with the toolchain bin directory."""
return os.path.join(self.toolbin, self.tool_prefix + name)
def GetCCompiler(self):
"""Helper which returns C compiler path."""
if self.is_pnacl_toolchain:
return self.GetBinName('clang')
else:
return self.GetBinName('gcc')
def GetCXXCompiler(self):
"""Helper which returns C++ compiler path."""
if self.is_pnacl_toolchain:
return self.GetBinName('clang++')
else:
return self.GetBinName('g++')
def GetAr(self):
"""Helper which returns ar path."""
return self.GetBinName('ar')
def GetStrip(self):
"""Helper which returns strip path."""
return self.GetBinName('strip')
def GetObjCopy(self):
"""Helper which returns objcopy path."""
return self.GetBinName('objcopy')
def GetReadElf(self):
"""Helper which returns readelf path."""
return self.GetBinName('readelf')
def GetPnaclFinalize(self):
"""Helper which returns pnacl-finalize path."""
assert self.is_pnacl_toolchain
return self.GetBinName('finalize')
def BuildAssembleOptions(self, options):
options = ArgToList(options)
self.assemble_options = options + ['-I' + name for name in self.inc_paths]
def DebugName(self):
return self.name + '.debug'
def UntaggedName(self):
return self.name + '.untagged'
def LinkOutputName(self):
if (self.is_pnacl_toolchain and self.finalize_pexe or
self.strip_all or self.strip_debug):
return self.DebugName()
else:
return self.name
def ArchiveOutputName(self):
if self.strip_debug:
return self.DebugName()
else:
return self.name
def StripOutputName(self):
return self.name
def TranslateOutputName(self):
return self.name
def Soname(self):
return self.name
def BuildCompileOptions(self, options, define_list, arch):
"""Generates compile options, called once by __init__."""
options = ArgToList(options)
# We want to shared gyp 'defines' with other targets, but not
# ones that are host system dependent. Filtering them out.
# This really should be better.
# See: http://code.google.com/p/nativeclient/issues/detail?id=2936
define_list = [define for define in define_list
if not (define.startswith('NACL_WINDOWS=') or
define.startswith('NACL_OSX=') or
define.startswith('NACL_LINUX=') or
define.startswith('NACL_ANDROID=') or
define.startswith('NACL_BUILD_ARCH=') or
define.startswith('NACL_BUILD_SUBARCH=') or
define == 'COMPONENT_BUILD' or
'WIN32' in define or
'WINDOWS' in define or
'WINVER' in define)]
define_list.extend(['NACL_WINDOWS=0',
'NACL_OSX=0',
'NACL_LINUX=0',
'NACL_ANDROID=0'])
define_list.extend(NACL_BUILD_ARCH_MAP[arch])
options += ['-D' + define for define in define_list]
self.compile_options = options + ['-I' + name for name in self.inc_paths]
def BuildLinkOptions(self, options):
"""Generates link options, called once by __init__."""
options = ArgToList(options)
if self.outtype == 'nso':
options += ['-Wl,-rpath-link,' + name for name in self.lib_paths]
options += ['-shared']
options += ['-Wl,-soname,' + os.path.basename(self.Soname())]
self.link_options = options + ['-L' + name for name in self.lib_paths]
def BuildArchiveOptions(self):
"""Generates link options, called once by __init__."""
self.archive_options = []
def GetObjectName(self, src):
if self.strip:
src = src.replace(self.strip,'')
# Hash the full path of the source file and add 32 bits of that hash onto
# the end of the object file name. This helps disambiguate files with the
# same name, because all of the object files are placed into the same
# directory. Technically, the correct solution would be to preserve the
# directory structure of the input source files inside the object file
# directory, but doing that runs the risk of running into filename length
# issues on Windows.
h = hashlib.sha1()
h.update(src)
wart = h.hexdigest()[:8]
_, filename = os.path.split(src)
filename, _ = os.path.splitext(filename)
return os.path.join(self.outdir, filename + '_' + wart + '.o')
def FixWindowsPath(self, path):
# The windows version of the nacl toolchain returns badly
# formed system header paths. As we do want changes in the
# toolchain to trigger rebuilds, compensate by detecting
# malformed paths (starting with /libexec/) and assume these
# are actually toolchain relative.
#
# Additionally, in some cases the toolchains emit cygwin paths
# which don't work in a win32 python.
# Assume they are all /cygdrive/ relative and convert to a
# drive letter.
cygdrive = '/cygdrive/'
if path.startswith('/cygdrive/'):
path = os.path.normpath(
path[len(cygdrive)] + ':' + path[len(cygdrive)+1:])
elif path.startswith('/libexec/'):
path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
return path
def GetGomaConfig(self, gomadir, arch, toolname):
"""Returns a goma config dictionary if goma is available or {}."""
# Start goma support from os/arch/toolname that have been tested.
# Set NO_NACL_GOMA=true to force to avoid using goma.
default_no_nacl_goma = True if pynacl.platform.IsWindows() else False
if (arch not in ['x86-32', 'x86-64', 'pnacl']
or toolname not in ['newlib', 'glibc']
or IsEnvFlagTrue('NO_NACL_GOMA', default=default_no_nacl_goma)
or IsEnvFlagTrue('GOMA_DISABLED')):
return {}
goma_config = {}
gomacc_base = 'gomacc.exe' if pynacl.platform.IsWindows() else 'gomacc'
# Search order of gomacc:
# --gomadir command argument -> GOMA_DIR env. -> PATH env.
search_path = []
# 1. --gomadir in the command argument.
if gomadir:
search_path.append(gomadir)
# 2. Use GOMA_DIR environment variable if exist.
goma_dir_env = os.environ.get('GOMA_DIR')
if goma_dir_env:
search_path.append(goma_dir_env)
# 3. Append PATH env.
path_env = os.environ.get('PATH')
if path_env:
search_path.extend(path_env.split(os.path.pathsep))
for directory in search_path:
gomacc = os.path.join(directory, gomacc_base)
if os.path.isfile(gomacc):
try:
port = int(subprocess.Popen(
[gomacc, 'port'],
stdout=subprocess.PIPE).communicate()[0].strip())
status = urllib2.urlopen(
'http://127.0.0.1:%d/healthz' % port).read().strip()
if status == 'ok':
goma_config['gomacc'] = gomacc
break
except (OSError, ValueError, urllib2.URLError) as e:
# Try another gomacc in the search path.
self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))
if goma_config:
goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST')
default_threads = 100 if pynacl.platform.IsLinux() else 10
goma_config['threads'] = GetIntegerEnv('NACL_GOMA_THREADS',
default=default_threads)
return goma_config
def NeedsRebuild(self, outd, out, src, rebuilt=False):
if not IsFile(self.toolstamp):
if rebuilt:
raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
return True
if not IsFile(self.cmd_file):
if rebuilt:
raise Error('Could not find cmd file %s.' % self.cmd_file)
return True
if not IsFile(outd):
if rebuilt:
raise Error('Could not find dependency file %s.' % outd)
return True
if not IsFile(out):
if rebuilt:
raise Error('Could not find output file %s.' % out)
return True
inputs = [__file__, self.toolstamp, src, self.cmd_file]
outputs = [out, outd]
# Find their timestamps if any.
input_times = [(GetMTime(f), f) for f in inputs]
output_times = [(GetMTime(f), f) for f in outputs]
# All inputs must exist.
missing_inputs = [p[1] for p in input_times if p[0] is None]
if missing_inputs:
raise Error('Missing inputs: %s' % str(missing_inputs))
# Rebuild if any outputs are missing.
missing_outputs = [p[1] for p in output_times if p[0] is None]
if missing_outputs:
if rebuilt:
raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
return True
newest_input = max(input_times)
oldest_output = min(output_times)
if IsStale(oldest_output[0], newest_input[0], rebuilt):
if rebuilt:
raise Error('Output %s is older than toolchain stamp %s' % (
oldest_output[1], newest_input[1]))
return True
# Decode emitted makefile.
with open(FixPath(outd), 'r') as fh:
deps = fh.read()
# Remove line continuations
deps = deps.replace('\\\n', ' ')
deps = deps.replace('\n', '')
# The dependencies are whitespace delimited following the first ':'
# (that is not part of a windows drive letter)
deps = deps.split(':', 1)
if pynacl.platform.IsWindows() and len(deps[0]) == 1:
# The path has a drive letter, find the next ':'
deps = deps[1].split(':', 1)[1]
else:
deps = deps[1]
deps = deps.split()
if pynacl.platform.IsWindows():
deps = [self.FixWindowsPath(d) for d in deps]
# Check if any input has changed.
for filename in deps:
file_tm = GetMTime(filename)
if IsStale(oldest_output[0], file_tm, rebuilt):
if rebuilt:
raise Error('Dependency %s is older than output %s.' % (
filename, oldest_output[1]))
return True
return False
def Compile(self, src):
"""Compile the source with pre-determined options."""
compile_options = self.compile_options[:]
_, ext = os.path.splitext(src)
if ext in ['.c', '.S']:
bin_name = self.GetCCompiler()
compile_options.append('-std=gnu99')
if self.is_pnacl_toolchain and ext == '.S':
compile_options.append('-arch')
compile_options.append(self.arch)
elif ext in ['.cc', '.cpp']:
compile_options.append('-std=gnu++0x')
compile_options.append('-Wno-deprecated-register')
bin_name = self.GetCXXCompiler()
else:
if ext != '.h':
self.Log('Skipping unknown type %s for %s.' % (ext, src))
return None
# This option is only applicable to C, and C++ compilers warn if
# it is present, so remove it for C++ to avoid the warning.
if ext != '.c' and '-Wstrict-prototypes' in compile_options:
compile_options.remove('-Wstrict-prototypes')
self.Log('\nCompile %s' % src)
out = self.GetObjectName(src)
outd = out + '.d'
# Don't rebuild unneeded.
if not self.NeedsRebuild(outd, out, src):
return out
MakeDir(os.path.dirname(out))
self.CleanOutput(out)
self.CleanOutput(outd)
cmd_line = [bin_name, '-c', src, '-o', out,
'-MD', '-MF', outd] + compile_options
if self.gomacc:
cmd_line.insert(0, self.gomacc)
err = self.Run(cmd_line)
if err:
self.CleanOutput(outd)
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
else:
try:
self.NeedsRebuild(outd, out, src, True)
except Error as e:
raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
'\nNeedsRebuild returned error: %s' % (
src, out, outd, ' '.join(cmd_line), e))
return out
def RunLink(self, cmd_line, link_out):
self.CleanOutput(link_out)
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
def Link(self, srcs):
"""Link these objects with predetermined options and output name."""
out = self.LinkOutputName()
self.Log('\nLink %s' % out)
bin_name = self.GetCXXCompiler()
srcs_flags = []
if not self.empty:
srcs_flags += srcs
srcs_flags += self.link_options
# Handle an IRT link specially, using a separate script.
if self.irt_linker:
if self.tls_edit is None:
raise Error('Linking the IRT requires tls_edit')
irt_link_cmd = [sys.executable, self.irt_linker,
'--output=' + out,
'--tls-edit=' + self.tls_edit,
'--link-cmd=' + bin_name,
'--readelf-cmd=' + self.GetReadElf()]
if self.commands_are_scripts:
irt_link_cmd += ['--commands-are-scripts']
irt_link_cmd += srcs_flags
err = self.Run(irt_link_cmd, normalize_slashes=False)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(irt_link_cmd)))
return out
MakeDir(os.path.dirname(out))
cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
cmd_line += srcs_flags
self.RunLink(cmd_line, out)
return out
# For now, only support translating a pexe, and not .o file(s)
def Translate(self, src):
"""Translate a pexe to a nexe."""
out = self.TranslateOutputName()
self.Log('\nTranslate %s' % out)
bin_name = self.GetBinName('translate')
cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
cmd_line += self.link_options
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
return out
def Archive(self, srcs):
"""Archive these objects with predetermined options and output name."""
out = self.ArchiveOutputName()
self.Log('\nArchive %s' % out)
if '-r' in self.link_options:
bin_name = self.GetCXXCompiler()
cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
if not self.empty:
cmd_line += srcs
cmd_line += self.link_options
else:
bin_name = self.GetAr()
cmd_line = [bin_name, '-rc', out]
if not self.empty:
cmd_line += srcs
MakeDir(os.path.dirname(out))
self.CleanOutput(out)
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
return out
def Strip(self, src):
"""Strip the NEXE"""
self.Log('\nStrip %s' % src)
out = self.StripOutputName()
pre_debug_tagging = self.UntaggedName()
self.CleanOutput(out)
self.CleanOutput(pre_debug_tagging)
# Strip from foo.debug to foo.untagged.
strip_name = self.GetStrip()
strip_option = '--strip-all' if self.strip_all else '--strip-debug'
# pnacl does not have an objcopy so there are no way to embed a link
if self.is_pnacl_toolchain:
cmd_line = [strip_name, strip_option, src, '-o', out]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
else:
cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
# Tag with a debug link to foo.debug copying from foo.untagged to foo.
objcopy_name = self.GetObjCopy()
cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
pre_debug_tagging, out]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
# Drop the untagged intermediate.
self.CleanOutput(pre_debug_tagging)
return out
def Finalize(self, src):
"""Finalize the PEXE"""
self.Log('\nFinalize %s' % src)
out = self.StripOutputName()
self.CleanOutput(out)
bin_name = self.GetPnaclFinalize()
cmd_line = [bin_name, src, '-o', out]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
return out
def Generate(self, srcs):
"""Generate final output file.
Link or Archive the final output file, from the compiled sources.
"""
if self.outtype in ['nexe', 'pexe', 'nso']:
out = self.Link(srcs)
if self.is_pnacl_toolchain and self.finalize_pexe:
# Note: pnacl-finalize also does stripping.
self.Finalize(out)
elif self.strip_all or self.strip_debug:
self.Strip(out)
elif self.outtype in ['nlib', 'plib']:
out = self.Archive(srcs)
if self.strip_debug:
self.Strip(out)
elif self.strip_all:
raise Error('FAILED: --strip-all on libs will result in unusable libs.')
else:
raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
def UpdateBuildArgs(args, filename):
new_cmd = json.dumps(args)
try:
with open(filename, 'r') as fileobj:
old_cmd = fileobj.read()
except:
old_cmd = None
if old_cmd == new_cmd:
return False
with open(filename, 'w') as fileobj:
fileobj.write(new_cmd)
return True
def Main(argv):
parser = OptionParser()
parser.add_option('--empty', dest='empty', default=False,
help='Do not pass sources to library.', action='store_true')
parser.add_option('--no-suffix', dest='suffix', default=True,
help='Do not append arch suffix.', action='store_false')
parser.add_option('--strip-debug', dest='strip_debug', default=False,
help='Strip the NEXE for debugging', action='store_true')
parser.add_option('--strip-all', dest='strip_all', default=False,
help='Strip the NEXE for production', action='store_true')
parser.add_option('--strip', dest='strip', default='',
help='Strip the filename')
parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
help='Do not finalize pnacl bitcode for ABI stability',
action='store_false')
parser.add_option('--source-list', dest='source_list',
help='Filename to load a source list from')
parser.add_option('--tls-edit', dest='tls_edit', default=None,
help='tls_edit location if TLS should be modified for IRT')
parser.add_option('--irt-linker', dest='irt_linker', default=None,
help='linker tool to use if linking the IRT')
parser.add_option('-a', '--arch', dest='arch',
help='Set target architecture')
parser.add_option('-c', '--compile', dest='compile_only', default=False,
help='Compile only.', action='store_true')
parser.add_option('-i', '--include-dirs', dest='incdirs',
help='Set include directories.')
parser.add_option('-l', '--lib-dirs', dest='libdirs',
help='Set library directories.')
parser.add_option('-n', '--name', dest='name',
help='Base path and name of the nexe.')
parser.add_option('-o', '--objdir', dest='objdir',
help='Base path of the object output dir.')
parser.add_option('-r', '--root', dest='root',
help='Set the root directory of the sources')
parser.add_option('--product-directory', dest='product_directory',
help='Set the root directory of the build')
parser.add_option('-b', '--build', dest='build',
help='Set build type (<toolchain>_<outtype>, ' +
'where toolchain is newlib or glibc and outtype is ' +
'one of nexe, nlib, nso, pexe, or translate)')
parser.add_option('--compile_flags', dest='compile_flags',
help='Set compile flags.')
parser.add_option('--defines', dest='defines',
help='Set defines')
parser.add_option('--link_flags', dest='link_flags',
help='Set link flags.')
parser.add_option('-v', '--verbose', dest='verbose', default=False,
help='Enable verbosity', action='store_true')
parser.add_option('-t', '--toolpath', dest='toolpath',
help='Set the path for of the toolchains.')
parser.add_option('--config-name', dest='build_config',
help='GYP build configuration name (Release/Debug)')
parser.add_option('--gomadir', dest='gomadir',
help='Path of the goma directory.')
options, files = parser.parse_args(argv[1:])
if options.name is None:
parser.error('--name is required!')
if options.build_config is None:
parser.error('--config-name is required!')
if options.root is None:
parser.error('--root is required!')
if options.arch is None:
parser.error('--arch is required!')
if options.build is None:
parser.error('--build is required!')
if not argv:
parser.print_help()
return 1
# Compare command-line options to last run, and force a rebuild if they
# have changed.
options.cmd_file = options.name + '.cmd'
UpdateBuildArgs(argv, options.cmd_file)
try:
if options.source_list:
source_list_handle = open(options.source_list, 'r')
source_list = source_list_handle.read().splitlines()
source_list_handle.close()
for file_name in source_list:
file_name = RemoveQuotes(file_name)
if "$" in file_name:
# Only require product directory if we need to interpolate it. This
# provides backwards compatibility in the cases where we don't need to
# interpolate. The downside is this creates a subtle landmine.
if options.product_directory is None:
parser.error('--product-dir is required')
product_dir = options.product_directory
# Normalize to forward slashes because re.sub interprets backslashes
# as escape characters. This also simplifies the subsequent regexes.
product_dir = product_dir.replace('\\', '/')
# Remove fake child that may be apended to the path.
# See untrusted.gypi.
product_dir = re.sub(r'/+xyz$', '', product_dir)
# The "make" backend can have an "obj" interpolation variable.
file_name = re.sub(r'\$!?[({]?obj[)}]?', product_dir + '/obj',
file_name)
# Expected patterns:
# $!PRODUCT_DIR in ninja.
# $(builddir) in make.
# $(OutDir) in MSVC.
# $(BUILT_PRODUCTS_DIR) in xcode.
# Also strip off and re-add the trailing directory seperator because
# different platforms are inconsistent on if it's there or not.
# HACK assume only the product directory is the only var left.
file_name = re.sub(r'\$!?[({]?\w+[)}]?/?', product_dir + '/',
file_name)
assert "$" not in file_name, file_name
files.append(file_name)
# Use set instead of list not to compile the same file twice.
# To keep in mind that the order of files may differ from the .gypcmd file,
# the set is not converted to a list.
# Having duplicated files can cause race condition of compiling during
# parallel build using goma.
# TODO(sbc): remove the duplication and turn it into an error.
files = set(files)
# Fix slash style to insulate invoked toolchains.
options.toolpath = os.path.normpath(options.toolpath)
build = Builder(options)
objs = []
if build.outtype == 'translate':
# Just translate a pexe to a nexe
if len(files) != 1:
parser.error('Pexe translation requires exactly one input file.')
build.Translate(list(files)[0])
return 0
if build.gomacc and (build.goma_burst or build.goma_threads > 1):
returns = Queue.Queue()
# Push all files into the inputs queue
inputs = Queue.Queue()
for filename in files:
inputs.put(filename)
def CompileThread(input_queue, output_queue):
try:
while True:
try:
filename = input_queue.get_nowait()
except Queue.Empty:
return
output_queue.put(build.Compile(filename))
except Exception:
# Put current exception info to the queue.
output_queue.put(sys.exc_info())
# Don't limit number of threads in the burst mode.
if build.goma_burst:
num_threads = len(files)
else:
num_threads = min(build.goma_threads, len(files))
# Start parallel build.
build_threads = []
for _ in xrange(num_threads):
thr = threading.Thread(target=CompileThread, args=(inputs, returns))
thr.start()
build_threads.append(thr)
# Wait for results.
for _ in files:
out = returns.get()
# An exception raised in the thread may come through the queue.
# Raise it again here.
if (isinstance(out, tuple) and len(out) == 3 and
isinstance(out[1], Exception)):
raise out[0], None, out[2]
elif out:
objs.append(out)
assert inputs.empty()
# Wait until all threads have stopped and verify that there are no more
# results.
for thr in build_threads:
thr.join()
assert returns.empty()
else: # slow path.
for filename in files:
out = build.Compile(filename)
if out:
objs.append(out)
# Do not link if building an object. However we still want the output file
# to be what was specified in options.name
if options.compile_only:
if len(objs) > 1:
raise Error('--compile mode cannot be used with multiple sources')
shutil.copy(objs[0], options.name)
else:
build.Generate(objs)
return 0
except Error as e:
sys.stderr.write('%s\n' % e)
return 1
except:
build.EmitDeferredLog()
raise
if __name__ == '__main__':
sys.exit(Main(sys.argv))
|