import threading
import urllib2
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+import pynacl.platform
+
class Error(Exception):
pass
# function that still has the path length limit. Instead, we'll cheat and
# normalize the path lexically.
path = os.path.normpath(os.path.join(os.getcwd(), path))
- if sys.platform in ['win32', 'cygwin']:
+ if pynacl.platform.IsWindows():
if len(path) > 255:
raise Error('Path "%s" is too long (%d characters), and will fail.' % (
path, len(path)))
flag_value = os.environ.get(flag_name)
if flag_value is None:
return default
- return bool(re.search(flag_value, r'^([tTyY]|1:?)'))
-
-
-def GetGomaConfig(gomadir, osname, arch, toolname, is_pnacl_toolchain):
- """Returns full-path of gomacc if goma is available or None."""
- # Start goma support from os/arch/toolname that have been tested.
- # Set NO_NACL_GOMA=true to force to avoid using goma.
- if (osname not in ['linux', 'mac'] or arch not in ['x86-32', 'x86-64']
- or toolname not in ['newlib', 'glibc']
- or IsEnvFlagTrue('NO_NACL_GOMA', default=False)):
- return {}
- # TODO(yyanagisawa): should fix ambiguous executable selection on pnacl-clang.
- if is_pnacl_toolchain:
- return {}
-
- goma_config = {}
- try:
- gomacc_base = 'gomacc.exe' if os.name == 'nt' 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(gomadir_env)
- # 3. Append PATH env.
- path_env = os.environ.get('PATH')
- if path_env:
- search_path.extend(path_env.split(os.path.pathsep))
+ return bool(re.search(r'^([tTyY]|1:?)', flag_value))
- for directory in search_path:
- gomacc = os.path.join(directory, gomacc_base)
- if os.path.isfile(gomacc):
- port = subprocess.Popen(
- [gomacc, 'port'], stdout=subprocess.PIPE).communicate()[0].strip()
- if port:
- status = urllib2.urlopen(
- 'http://127.0.0.1:%s/healthz' % port).read().strip()
- if status == 'ok':
- goma_config['gomacc'] = gomacc
- break
- except Exception:
- # Anyway, fallbacks to non-goma mode.
- pass
- if goma_config:
- default_value = False
- if osname == 'linux':
- default_value = True
- goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST',
- default=default_value)
- return goma_config
+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(object):
build_type = options.build.split('_')
toolname = build_type[0]
self.outtype = build_type[1]
-
- if sys.platform.startswith('linux'):
- self.osname = 'linux'
- elif sys.platform.startswith('win'):
- self.osname = 'win'
- elif sys.platform.startswith('darwin'):
- self.osname = 'mac'
- else:
- raise Error('Toolchain OS %s not supported.' % sys.platform)
+ 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_pnacl use pnacl
- # toolchain in arm-native mode (e.g. the arm IRT)
+ # 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.subarch = arch.split('-')[1]
self.tool_prefix = 'x86_64-nacl-'
elif arch == 'arm':
- self.subarch = ''
self.tool_prefix = 'arm-nacl-'
mainarch = 'arm'
elif arch == 'mips':
self.is_pnacl_toolchain = True
elif arch == 'pnacl':
- self.subarch = ''
self.is_pnacl_toolchain = True
else:
raise Error('Toolchain architecture %s not supported.' % arch)
if self.is_pnacl_toolchain:
self.tool_prefix = 'pnacl-'
- tooldir = '%s_pnacl' % self.osname
+ tool_subdir = 'pnacl_newlib'
else:
- tooldir = '%s_%s_%s' % (self.osname, mainarch, toolname)
+ tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
+
+ 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')
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 = GetGomaConfig(options.gomadir, self.osname, arch, toolname,
- self.is_pnacl_toolchain)
+ 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 == 'Release':
+ self.compile_options.append('-DNDEBUG')
# Use unoptimized native objects for debug IRT builds for faster compiles.
if (self.is_pnacl_toolchain
# Also use fast translation because we are still translating libc/libc++
self.link_options.append('-Wt,-O0')
+ self.irt_layout = options.irt_layout
+ if self.irt_layout:
+ # IRT constraints for auto layout.
+ # IRT text can only go up to 256MB. Addresses after that are for data.
+ # Reserve an extra page because:
+ # * sel_ldr requires a HLT sled at the end of the dynamic code area;
+ # * dynamic_load_test currently tests loading at the end of the dynamic
+ # code area.
+ self.irt_text_max = 0x10000000 - 0x10000
+ # Data can only go up to the sandbox_top - sizeof(stack).
+ # NaCl allocates 16MB for the initial thread's stack (see
+ # NACL_DEFAULT_STACK_MAX in sel_ldr.h).
+ # Assume sandbox_top is 1GB, since even on x86-64 the limit would
+ # only be 2GB (rip-relative references can only be +/- 2GB).
+ sandbox_top = 0x40000000
+ self.irt_data_max = sandbox_top - (16 << 20)
+ # Initialize layout flags with "too-close-to-max" flags so that
+ # we can relax this later and get a tight fit.
+ self.link_options += [
+ '-Wl,-Ttext-segment=0x%x' % (self.irt_text_max - 0x10000),
+ '-Wl,-Trodata-segment=0x%x' % (self.irt_data_max - 0x10000)]
self.Log('Compile options: %s' % self.compile_options)
self.Log('Linker options: %s' % self.link_options)
"""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
define.startswith('NACL_WINDOWS=') or
define.startswith('NACL_OSX=') or
define.startswith('NACL_LINUX=') or
+ define.startswith('NACL_ANDROID=') 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_LINUX=0',
+ 'NACL_ANDROID=0'])
options += ['-D' + define for define in define_list]
self.compile_options = options + ['-I' + name for name in self.inc_paths]
if self.verbose:
sys.stderr.write(str(msg) + '\n')
- def Run(self, cmd_line, out):
- """Helper which runs a command line."""
+ def Run(self, cmd_line, get_output=False, **kwargs):
+ """Helper which runs a command line.
+
+ Returns the error code if get_output is False.
+ Returns the output if get_output is True.
+ """
# For POSIX style path on windows for POSIX based toolchain
# (just for arguments, not for the path to the command itself)
self.Log(' '.join(cmd_line))
try:
- if self.is_pnacl_toolchain:
+ runner = subprocess.call
+ if get_output:
+ runner = subprocess.check_output
+ if self.is_pnacl_toolchain and pynacl.platform.IsWindows():
# PNaCl toolchain executable is a script, not a binary, so it doesn't
# want to run on Windows without a shell
- ecode = subprocess.call(' '.join(cmd_line), shell=True)
+ result = runner(' '.join(cmd_line), shell=True, **kwargs)
else:
- ecode = subprocess.call(cmd_line)
+ result = runner(cmd_line, **kwargs)
except Exception as err:
raise Error('%s\nFAILED: %s' % (' '.join(cmd_line), str(err)))
-
- if temp_file is not None:
- RemoveFile(temp_file.name)
- return ecode
+ finally:
+ if temp_file is not None:
+ RemoveFile(temp_file.name)
+ return result
def GetObjectName(self, src):
if self.strip:
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)):
+ 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 1
+ 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:
if rebuilt:
raise Error('Could not find output file %s.' % out)
return True
- stamp_tm = GetMTime(self.toolstamp)
- out_tm = GetMTime(out)
- outd_tm = GetMTime(outd)
- src_tm = GetMTime(src)
- if IsStale(out_tm, stamp_tm, rebuilt):
- if rebuilt:
- raise Error('Output %s is older than toolchain stamp %s' % (
- out, self.toolstamp))
- return True
- if IsStale(out_tm, src_tm, rebuilt):
+
+ inputs = [__file__, self.toolstamp, src]
+ 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('Output %s is older than source %s.' % (out, src))
+ raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
return True
- if IsStale(outd_tm, src_tm, rebuilt):
+ newest_input = max(input_times)
+ oldest_output = min(output_times)
+
+ if IsStale(oldest_output[0], newest_input[0], rebuilt):
if rebuilt:
- raise Error('Dependency file is older than source %s.' % src)
+ raise Error('Output %s is older than toolchain stamp %s' % (
+ oldest_output[1], newest_input[1]))
return True
# Decode emitted makefile.
deps = deps.replace('\\\n', ' ')
deps = deps.replace('\n', '')
# The dependencies are whitespace delimited following the first ':'
- deps = deps.split(':', 1)[1]
+ # (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 sys.platform in ['win32', 'cygwin']:
+ 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(out_tm, file_tm, rebuilt):
+ if IsStale(oldest_output[0], file_tm, rebuilt):
if rebuilt:
raise Error('Dependency %s is older than output %s.' % (
- filename, out))
- return True
-
- if IsStale(outd_tm, file_tm, rebuilt):
- if rebuilt:
- raise Error('Dependency %s is older than dep file %s.' % (
- filename, outd))
+ 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()
- extra = ['-std=gnu99']
+ compile_options.append('-std=gnu99')
if self.is_pnacl_toolchain and ext == '.S':
- extra.append('-arch')
- extra.append(self.arch)
+ compile_options.append('-arch')
+ compile_options.append(self.arch)
elif ext in ['.cc', '.cpp']:
bin_name = self.GetCXXCompiler()
- extra = []
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)
self.CleanOutput(out)
self.CleanOutput(outd)
cmd_line = [bin_name, '-c', src, '-o', out,
- '-MD', '-MF', outd] + extra + self.compile_options
+ '-MD', '-MF', outd] + compile_options
if self.gomacc:
cmd_line.insert(0, self.gomacc)
err = self.Run(cmd_line, out)
src, out, outd, ' '.join(cmd_line), e))
return out
+ def IRTLayoutFits(self, irt_file):
+ """Check if the IRT's data and text segment fit layout constraints.
+
+ Returns a tuple containing:
+ * whether the IRT data/text top addresses fit within the max limit
+ * current data/text top addrs
+ """
+ cmd_line = [self.GetReadElf(), '-W', '--segments', irt_file]
+ # Put LC_ALL=C in the environment for readelf, so that its messages
+ # will reliably match what we're looking for rather than being in some
+ # other language and/or character set.
+ env = dict(os.environ)
+ env['LC_ALL'] = 'C'
+ seginfo = self.Run(cmd_line, get_output=True, env=env)
+ lines = seginfo.splitlines()
+ ph_start = -1
+ for i, line in enumerate(lines):
+ if line == 'Program Headers:':
+ ph_start = i + 1
+ break
+ if ph_start == -1:
+ raise Error('Could not find Program Headers start: %s\n' % lines)
+ seg_lines = lines[ph_start:]
+ text_top = 0
+ data_top = 0
+ for line in seg_lines:
+ pieces = line.split()
+ # Type, Offset, Vaddr, Paddr, FileSz, MemSz, Flg(multiple), Align
+ if len(pieces) >= 8 and pieces[0] == 'LOAD':
+ # Vaddr + MemSz
+ segment_top = int(pieces[2], 16) + int(pieces[5], 16)
+ if pieces[6] == 'R' and pieces[7] == 'E':
+ text_top = max(segment_top, text_top)
+ continue
+ if pieces[6] == 'R' or pieces[6] == 'RW':
+ data_top = max(segment_top, data_top)
+ continue
+ if text_top == 0 or data_top == 0:
+ raise Error('Could not parse IRT Layout: text_top=0x%x data_top=0x%x\n'
+ 'readelf output: %s\n' % (text_top, data_top, lines))
+ return (text_top <= self.irt_text_max and
+ data_top <= self.irt_data_max), text_top, data_top
+
+ def FindOldIRTFlagPosition(self, cmd_line, flag_name):
+ """Search for a given IRT link flag's position and value."""
+ pos = -1
+ old_start = ''
+ for i, option in enumerate(cmd_line):
+ m = re.search('.*%s=(0x.*)' % flag_name, option)
+ if m:
+ if pos != -1:
+ raise Exception('Duplicate %s flag at position %d' % (flag_name, i))
+ pos = i
+ old_start = m.group(1)
+ if pos == -1:
+ raise Exception('Could not find IRT layout flag %s' % flag_name)
+ return pos, old_start
+
+ def AdjustIRTLinkToFit(self, cmd_line, text_top, data_top):
+ """Adjust the linker options so that the IRT's data and text segment fit."""
+ def RoundDownToAlign(x):
+ return x - (x % 0x10000)
+ def AdjustFlag(flag_name, orig_max, expected_max):
+ if orig_max < expected_max:
+ return
+ pos, old_start = self.FindOldIRTFlagPosition(cmd_line, flag_name)
+ size = orig_max - int(old_start, 16)
+ self.Log('IRT %s size is %s' % (flag_name, size))
+ new_start = RoundDownToAlign(expected_max - size)
+ self.Log('Adjusting link flag %s from %s to %s' % (flag_name,
+ old_start,
+ hex(new_start)))
+ cmd_line[pos] = cmd_line[pos].replace(old_start, hex(new_start))
+ AdjustFlag('-Ttext-segment', text_top, self.irt_text_max)
+ AdjustFlag('-Trodata-segment', data_top, self.irt_data_max)
+ self.Log('Adjusted link options to %s' % ' '.join(cmd_line))
+ return cmd_line
+
+ def RunLink(self, cmd_line, link_out):
+ self.CleanOutput(link_out)
+ err = self.Run(cmd_line, link_out)
+ 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()
- MakeDir(os.path.dirname(out))
- self.CleanOutput(out)
- cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
+ link_out = out
+ if self.tls_edit is not None:
+ link_out = out + '.raw'
+
+ MakeDir(os.path.dirname(link_out))
+
+ cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
if not self.empty:
cmd_line += srcs
cmd_line += self.link_options
- err = self.Run(cmd_line, out)
- if err:
- raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
+ self.RunLink(cmd_line, link_out)
+
+ if self.irt_layout:
+ fits, text_top, data_top = self.IRTLayoutFits(link_out)
+ if not fits:
+ self.Log('IRT layout does not fit: text_top=0x%x and data_top=0x%x' %
+ (text_top, data_top))
+ cmd_line = self.AdjustIRTLinkToFit(cmd_line, text_top, data_top)
+ self.RunLink(cmd_line, link_out)
+ fits, text_top, data_top = self.IRTLayoutFits(link_out)
+ if not fits:
+ raise Error('Already re-linked IRT and it still does not fit:\n'
+ 'text_top=0x%x and data_top=0x%x\n' % (
+ text_top, data_top))
+ self.Log('IRT layout fits: text_top=0x%x and data_top=0x%x' %
+ (text_top, data_top))
+
+ if self.tls_edit is not None:
+ tls_edit_cmd = [FixPath(self.tls_edit), link_out, out]
+ tls_edit_err = self.Run(tls_edit_cmd, out)
+ if tls_edit_err:
+ raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
+
return out
# For now, only support translating a pexe, and not .o file(s)
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-layout', dest='irt_layout', default=False,
+ help='Apply the IRT layout (pick data/text seg addresses)',
+ action='store_true')
parser.add_option('-a', '--arch', dest='arch',
help='Set target architecture')
parser.add_option('-c', '--compile', dest='compile_only', default=False,
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
build.Translate(list(files)[0])
return 0
- if build.gomacc and build.goma_burst: # execute gomacc as many as possible.
+ if build.gomacc and (build.goma_burst or build.goma_threads > 1):
returns = Queue.Queue()
- def CompileThread(filename, 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:
- queue.put(build.Compile(filename))
+ 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.
- queue.put(sys.exc_info())
- build_threads = []
+ 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.
- for filename in files:
- thr = threading.Thread(target=CompileThread, args=(filename, returns))
+ build_threads = []
+ for _ in xrange(num_threads):
+ thr = threading.Thread(target=CompileThread, args=(inputs, returns))
thr.start()
build_threads.append(thr)
- for thr in build_threads:
- thr.join()
+
+ # Wait for results.
+ for _ in files:
out = returns.get()
# An exception raised in the thread may come through the queue.
# Raise it again here.
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)