OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium 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 """Python wrapper around gcc to make it behave a little |
| 7 more like cl.exe WRT to parallel building. |
| 8 """ |
| 9 |
| 10 import multiprocessing |
| 11 import os |
| 12 import Queue |
| 13 import shlex |
| 14 import subprocess |
| 15 import sys |
| 16 import threading |
| 17 import time |
| 18 |
| 19 verbose = int(os.environ.get('NACL_GCC_VERBOSE', '0')) |
| 20 show_commands = int(os.environ.get('NACL_GCC_SHOW_COMMANDS', '0')) |
| 21 stop_on_error = False |
| 22 |
| 23 |
| 24 def RunGCC(cmd, basename): |
| 25 """Run gcc and return the result along will the stdout/stderr.""" |
| 26 cmdstring = subprocess.list2cmdline(cmd) |
| 27 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 28 stdout, stderr = p.communicate() |
| 29 p.wait() |
| 30 if show_commands: |
| 31 logmsg = cmdstring |
| 32 else: |
| 33 logmsg = basename |
| 34 stderr = logmsg + '\n' + stderr |
| 35 return (p.returncode, stdout, stderr) |
| 36 |
| 37 |
| 38 def BuildSerial(base_cmd, outpath, files): |
| 39 final_result = 0 |
| 40 |
| 41 for filename in files: |
| 42 cmd, basename = MakeCommand(base_cmd, outpath, filename) |
| 43 rtn, stdout, stderr = RunGCC(cmd, basename) |
| 44 sys.stdout.write(stdout) |
| 45 sys.stdout.flush() |
| 46 sys.stderr.write(stderr) |
| 47 sys.stderr.flush() |
| 48 if rtn: |
| 49 final_result = rtn |
| 50 if stop_on_error: |
| 51 break |
| 52 |
| 53 return final_result |
| 54 |
| 55 |
| 56 def Worker(queue, out_queue): |
| 57 """Entry point got worker threads. |
| 58 |
| 59 Each thread will compiler jobs from the queue until |
| 60 there are no jobs left or until the main thread signals |
| 61 for the work to stop. |
| 62 """ |
| 63 while not queue.empty() and Worker.running: |
| 64 item = queue.get(False) |
| 65 if not item: |
| 66 break |
| 67 results = RunGCC(item[0], item[1]) |
| 68 out_queue.put(results) |
| 69 |
| 70 |
| 71 def MakeCommand(base_cmd, outpath, filename): |
| 72 """Build the full commandline given that output root |
| 73 and the intput filename. |
| 74 |
| 75 If VS passes an existing directory to -o, then we derive the |
| 76 actual object name by combining he directory name with the |
| 77 basename of the source file and andding ".obj" |
| 78 """ |
| 79 basename = os.path.basename(filename) |
| 80 out = os.path.join(outpath, os.path.splitext(basename)[0] + '.obj') |
| 81 return (base_cmd + ['-c', filename, '-o', out], basename) |
| 82 |
| 83 |
| 84 def BuildParallel(cores, base_cmd, outpath, files): |
| 85 Worker.running = True |
| 86 pool = [] |
| 87 job_queue = Queue.Queue() |
| 88 out_queue = Queue.Queue() |
| 89 |
| 90 for filename in files: |
| 91 cmd, basename = MakeCommand(base_cmd, outpath, filename) |
| 92 job_queue.put((cmd, basename)) |
| 93 |
| 94 # Create worker thread pool, passing job queue |
| 95 # and output queue to each worker. |
| 96 args = (job_queue, out_queue) |
| 97 for i in xrange(cores): |
| 98 t = threading.Thread(target=Worker, args=args) |
| 99 t.start() |
| 100 |
| 101 results = 0 |
| 102 Trace("waiting for %d results" % len(files)) |
| 103 final_result = 0 |
| 104 while results < len(files): |
| 105 results += 1 |
| 106 rtn, stdout, stderr = out_queue.get() |
| 107 # stdout seem to be completely ignored by visual studio |
| 108 # but GCC should output all useful information on stderr |
| 109 # anyway. |
| 110 sys.stdout.write(stdout) |
| 111 sys.stdout.flush() |
| 112 sys.stderr.write(stderr) |
| 113 sys.stderr.flush() |
| 114 if rtn: |
| 115 final_result = rtn |
| 116 if stop_on_error: |
| 117 # stop all workers |
| 118 Worker.running = False |
| 119 break |
| 120 |
| 121 return final_result |
| 122 |
| 123 |
| 124 def Log(msg): |
| 125 """Log message to stderr.""" |
| 126 # Since Visual Studio basically seems to completely ignore the stdout |
| 127 # of the compiler and only echo stderr we print everythign to stderr. |
| 128 sys.stderr.write(str(msg) + '\n') |
| 129 sys.stderr.flush() |
| 130 |
| 131 |
| 132 def Trace(msg): |
| 133 if verbose: |
| 134 Log("nacl_compiler:" + str(msg)) |
| 135 |
| 136 |
| 137 def main(args): |
| 138 if args[0][0] == '@': |
| 139 rspfile = args[0][1:] |
| 140 args = shlex.split(open(rspfile).read()) |
| 141 |
| 142 # find the last occurrence of '--' in the argument |
| 143 # list and use that to signify the start of the |
| 144 # list of sources |
| 145 index = list(reversed(args)).index('--') |
| 146 index = len(args) - index |
| 147 base_cmd = args[:index-1] |
| 148 files = args[index:] |
| 149 |
| 150 # remove -o <path> from base_cmd |
| 151 index = base_cmd.index('-o') |
| 152 outpath = base_cmd[index+1] |
| 153 del base_cmd[index+1] |
| 154 del base_cmd[index] |
| 155 |
| 156 cores = int(os.environ.get('NACL_GCC_CORES', '0')) |
| 157 if not cores: |
| 158 cores = multiprocessing.cpu_count() |
| 159 cores = min(cores, len(files)) |
| 160 |
| 161 Trace("compiling %d sources using %d threads" % (len(files), cores)) |
| 162 rtn = BuildParallel(cores, base_cmd, outpath, files) |
| 163 Trace("returning %d" % rtn) |
| 164 return rtn |
| 165 |
| 166 |
| 167 if __name__ == '__main__': |
| 168 sys.exit(main(sys.argv[1:])) |
OLD | NEW |