blob: f0daa33cc108d9ea637512b3041dedd87c06e329 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file
"""Python wrapper around gcc to make it behave a little
more like cl.exe WRT to parallel building.
"""
import multiprocessing
import os
import Queue
import shlex
import subprocess
import sys
import threading
import time
verbose = int(os.environ.get('NACL_GCC_VERBOSE', '0'))
show_commands = int(os.environ.get('NACL_GCC_SHOW_COMMANDS', '0'))
stop_on_error = False
def RunGCC(cmd, basename):
"""Run gcc and return the result along will the stdout/stderr."""
cmdstring = subprocess.list2cmdline(cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
p.wait()
if show_commands:
logmsg = cmdstring
else:
logmsg = basename
stderr = logmsg + '\n' + stderr
return (p.returncode, stdout, stderr)
def BuildSerial(base_cmd, outpath, files):
final_result = 0
for filename in files:
cmd, basename = MakeCommand(base_cmd, outpath, filename)
rtn, stdout, stderr = RunGCC(cmd, basename)
sys.stdout.write(stdout)
sys.stdout.flush()
sys.stderr.write(stderr)
sys.stderr.flush()
if rtn:
final_result = rtn
if stop_on_error:
break
return final_result
def Worker(queue, out_queue):
"""Entry point got worker threads.
Each thread will compiler jobs from the queue until
there are no jobs left or until the main thread signals
for the work to stop.
"""
while not queue.empty() and Worker.running:
item = queue.get(False)
if not item:
break
results = RunGCC(item[0], item[1])
out_queue.put(results)
def MakeCommand(base_cmd, outpath, filename):
"""Build the full commandline given that output root
and the intput filename.
If VS passes an existing directory to -o, then we derive the
actual object name by combining he directory name with the
basename of the source file and andding ".obj"
"""
basename = os.path.basename(filename)
out = os.path.join(outpath, os.path.splitext(basename)[0] + '.obj')
return (base_cmd + ['-c', filename, '-o', out], basename)
def BuildParallel(cores, base_cmd, outpath, files):
Worker.running = True
pool = []
job_queue = Queue.Queue()
out_queue = Queue.Queue()
for filename in files:
cmd, basename = MakeCommand(base_cmd, outpath, filename)
job_queue.put((cmd, basename))
# Create worker thread pool, passing job queue
# and output queue to each worker.
args = (job_queue, out_queue)
for i in xrange(cores):
t = threading.Thread(target=Worker, args=args)
t.start()
results = 0
Trace("waiting for %d results" % len(files))
final_result = 0
while results < len(files):
results += 1
rtn, stdout, stderr = out_queue.get()
# stdout seem to be completely ignored by visual studio
# but GCC should output all useful information on stderr
# anyway.
sys.stdout.write(stdout)
sys.stdout.flush()
sys.stderr.write(stderr)
sys.stderr.flush()
if rtn:
final_result = rtn
if stop_on_error:
# stop all workers
Worker.running = False
break
return final_result
def Log(msg):
"""Log message to stderr."""
# Since Visual Studio basically seems to completely ignore the stdout
# of the compiler and only echo stderr we print everythign to stderr.
sys.stderr.write(str(msg) + '\n')
sys.stderr.flush()
def Trace(msg):
if verbose:
Log("nacl_compiler:" + str(msg))
def main(args):
if args[0][0] == '@':
rspfile = args[0][1:]
args = shlex.split(open(rspfile).read())
# find the last occurrence of '--' in the argument
# list and use that to signify the start of the
# list of sources
index = list(reversed(args)).index('--')
index = len(args) - index
base_cmd = args[:index-1]
files = args[index:]
# remove -o <path> from base_cmd
index = base_cmd.index('-o')
outpath = base_cmd[index+1]
del base_cmd[index+1]
del base_cmd[index]
cores = int(os.environ.get('NACL_GCC_CORES', '0'))
if not cores:
cores = multiprocessing.cpu_count()
cores = min(cores, len(files))
Trace("compiling %d sources using %d threads" % (len(files), cores))
rtn = BuildParallel(cores, base_cmd, outpath, files)
Trace("returning %d" % rtn)
return rtn
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))