blob: 80bd6685b0be925a7c94fb5a018e4696a20b0fe3 [file] [log] [blame]
Brian Silvermana29ebf92014-04-23 13:08:49 -05001#!/usr/bin/python3
2
3import argparse
4import sys
5import subprocess
6import re
7import os
8import os.path
9import string
10import shutil
11import errno
Brian Silvermanc3740c32014-05-04 12:42:47 -070012import queue
13import threading
14
15class TestThread(threading.Thread):
Brian Silvermane6bada62014-05-04 16:16:54 -070016 """Runs 1 test and keeps track of its current state.
17
18 A TestThread is either waiting to start the test, actually running it, done,
19 running it, or stopped. The first 3 always happen in that order and can
20 change to stopped at any time.
21
22 It will finish (ie join() will return) once the process has exited, at which
23 point accessing process to see the status is OK.
24
25 Attributes:
26 executable: The file path of the executable to run.
27 env: The environment variables to set.
28 done_queue: A queue.Queue to place self on once done running the test.
29 start_semaphore: A threading.Semaphore to wait on before starting.
30 process_lock: A lock around process.
31 process: The currently executing test process or None. Synchronized by
32 process_lock.
33 stopped: True if we're stopped.
34 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070035 def __init__(self, executable, env, done_queue, start_semaphore):
36 super(TestThread, self).__init__(
37 name=os.path.split(executable)[1])
38
39 self.executable = executable
40 self.env = env
41 self.done_queue = done_queue
42 self.start_semaphore = start_semaphore
43
44 self.process_lock = threading.Lock()
45 self.process = None
46 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070047 self.returncode = None
48 self.output = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070049
50 def run(self):
51 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070052 if self.stopped:
53 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070054 test_output('Starting test %s...' % self.name)
55 self.output, subprocess_output = os.pipe()
56 with self.process_lock:
57 self.process = subprocess.Popen((self.executable,
58 '--gtest_color=yes'),
59 env=self.env,
60 stderr=subprocess.STDOUT,
61 stdout=subprocess_output,
62 stdin=open(os.devnull, 'r'))
63 os.close(subprocess_output)
64 self.process.wait()
65 with self.process_lock:
66 self.returncode = self.process.returncode
67 self.process = None
68 if not self.stopped:
69 self.done_queue.put(self)
70
71 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070072 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070073 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070074 if not self.process:
75 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070076 self.process.terminate()
77 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070078 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070079 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070080 if not self.process:
81 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070082 self.process.kill()
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070083 def stop(self):
84 """Changes self to the stopped state."""
85 with self.process_lock:
86 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -050087
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050088def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -070089 """Returns:
90 A relative path to the aos directory.
91 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050092 return os.path.join(os.path.dirname(__file__), '..')
93
94def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -070095 """Retrieves the IP address for a given device."""
96 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -070097 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050098 if not os.access(FILENAME, os.R_OK):
99 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
100 with open(FILENAME, 'w') as f:
101 f.write('10.9.71')
102 with open(FILENAME, 'r') as f:
103 base = f.readline()
104 if device == 'prime':
105 return base + '.179'
106 elif device == 'robot':
107 return base + '.2'
108 else:
109 raise Exception('Unknown device %s to get an IP address for.' % device)
110
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700111def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700112 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700113 print('build.py: ' + message, file=sys.stderr)
114
Brian Silverman452aaec2014-05-05 16:52:18 -0700115# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700116test_output_lock = threading.RLock()
117def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700118 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700119 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700120 print('tests: ' + message, file=sys.stdout)
121
122def call_download_externals(argument):
123 """Calls download_externals.sh for a given set of externals.
124
125 Args:
126 argument: The argument to pass to the shell script to tell it what to
127 download.
128 """
129 subprocess.check_call(
130 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
131 argument),
132 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700133
Brian Silvermana29ebf92014-04-23 13:08:49 -0500134class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700135 """Represents a processor architecture we can build for."""
136
Brian Silvermana29ebf92014-04-23 13:08:49 -0500137 class UnknownPlatform(Exception):
138 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700139 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500140 self.message = message
141
Brian Silvermanb3d50542014-04-23 14:28:55 -0500142 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700143 """Represents a single way to build the code."""
144
Brian Silvermanb3d50542014-04-23 14:28:55 -0500145 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700146 """Returns:
147 The path of the directory build outputs get put in to.
148 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500149 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500150 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500151 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700152 """Returns:
153 The path of the build.ninja file.
154 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500155 return os.path.join(self.outdir(), 'build.ninja')
156
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500157 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700158 """Helper for subclasses to implement deploy.
159
160 Args:
161 dry_run: If True, prints the command instead of actually running it.
162 command: A tuple of command-line arguments.
163 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500164 real_command = (('echo',) + command) if dry_run else command
165 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500166
Brian Silvermane6bada62014-05-04 16:16:54 -0700167 def deploy(self, dry_run):
168 """Downloads the compiled code to the target computer."""
169 raise NotImplementedError('deploy should be overriden')
170 def outname(self):
171 """Returns:
172 The name of the directory the code will be compiled to.
173 """
174 raise NotImplementedError('outname should be overriden')
175 def os(self):
176 """Returns:
177 The name of the operating system this platform is for.
178
179 This will be used as the value of the OS gyp variable.
180 """
181 raise NotImplementedError('os should be overriden')
182 def gyp_platform(self):
183 """Returns:
184 The platform name the .gyp files know.
185
186 This will be used as the value of the PLATFORM gyp variable.
187 """
188 raise NotImplementedError('gyp_platform should be overriden')
189 def architecture(self):
190 """Returns:
191 The processor architecture for this platform.
192
193 This will be used as the value of the ARCHITECTURE gyp variable.
194 """
195 raise NotImplementedError('architecture should be overriden')
196 def compiler(self):
197 """Returns:
198 The compiler used for this platform.
199
200 Everything before the first _ will be used as the value of the
201 COMPILER gyp variable and the whole thing will be used as the value
202 of the FULL_COMPILER gyp variable.
203 """
204 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700205 def sanitizer(self):
206 """Returns:
207 The sanitizer used on this platform.
208
209 This will be used as the value of the SANITIZER gyp variable.
210
211 "none" if there isn't one.
212 """
213 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700214 def debug(self):
215 """Returns:
216 Whether or not this platform compiles with debugging information.
217
218 The DEBUG gyp variable will be set to "yes" or "no" based on this.
219 """
220 raise NotImplementedError('debug should be overriden')
221 def build_env(self):
222 """Returns:
223 A map of environment variables to set while building this platform.
224 """
225 raise NotImplementedError('build_env should be overriden')
226
Brian Silverman9b7a6842014-05-05 16:19:11 -0700227 def check_installed(self, platforms, is_deploy):
228 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700229 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700230 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700231 """Args:
232 string: A user-supplied string saying which platforms to select.
233
234 Returns:
235 A tuple of Platform objects.
236
237 Raises:
238 Processor.UnknownPlatform: Parsing string didn't work out.
239 """
240 raise NotImplementedError('parse_platforms should be overriden')
241 def extra_gyp_flags(self):
242 """Returns:
243 A tuple of extra flags to pass to gyp (if any).
244 """
245 return ()
246 def modify_ninja_file(self, ninja_file):
247 """Modifies a freshly generated ninja file as necessary.
248
249 Args:
250 ninja_file: Path to the file to modify.
251 """
252 pass
253 def download_externals(self, platforms):
254 """Calls download_externals as appropriate to build platforms.
255
256 Args:
257 platforms: A list of platforms to download external libraries for.
258 """
259 raise NotImplementedError('download_externals should be overriden')
260
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700261 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700262 """Helper for subclasses to implement check_installed.
263
264 Args:
265 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700266 all_packages = other_packages
267 # Necessary to build stuff.
268 all_packages += ('ccache', 'make')
269 # Necessary to download stuff to build.
270 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
271 # Necessary to build externals stuff.
272 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700273 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700274 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700275 result = subprocess.check_output(
276 ('dpkg-query', '--show') + all_packages,
277 stdin=open(os.devnull, 'r'),
278 stderr=subprocess.STDOUT)
279 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700280 output = e.output.decode('utf-8').rstrip()
281 not_found = []
282 for line in output.splitlines(True):
283 match = re.match(r'dpkg-query: no packages found matching (.*)',
284 line)
285 if match:
286 not_found.append(match.group(1))
287 user_output('Some packages not installed: %s.' % ', '.join(not_found))
288 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700289 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700290 exit(1)
291
Brian Silvermana29ebf92014-04-23 13:08:49 -0500292class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700293 """A Processor subclass for building cRIO code."""
294
Brian Silvermanb3d50542014-04-23 14:28:55 -0500295 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700296 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500297 super(CRIOProcessor.Platform, self).__init__()
298
Brian Silvermane6bada62014-05-04 16:16:54 -0700299 self.__debug = debug
300 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500301
302 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700303 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500304 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700305 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500306
307 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700308 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500309 def os(self):
310 return 'vxworks'
311 def gyp_platform(self):
312 return 'crio'
313 def architecture(self):
314 return 'ppc'
315 def compiler(self):
316 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700317 def sanitizer(self):
318 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700319 def debug(self):
320 return self.__debug
321 def wind_base(self):
322 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500323
Brian Silvermane48c09a2014-04-30 18:04:58 -0700324 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500325 def deploy(self, dry_run):
326 self.do_deploy(dry_run,
327 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700328 os.path.join(self.outdir(), 'lib',
329 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500330
Brian Silvermana4aff562014-05-02 17:43:50 -0700331 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700332 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700333
Brian Silvermana29ebf92014-04-23 13:08:49 -0500334 def __init__(self):
335 super(CRIOProcessor, self).__init__()
336
337 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700338 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500339 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700340 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500341
Brian Silverman452aaec2014-05-05 16:52:18 -0700342 def parse_platforms(self, platforms_string):
343 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700344 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700345 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700346 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500347 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700348 raise Processor.UnknownPlatform(
349 'Unknown cRIO platform "%s".' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500350
Brian Silvermane6bada62014-05-04 16:16:54 -0700351 def wind_base(self):
352 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500353
Brian Silvermane6bada62014-05-04 16:16:54 -0700354 def extra_gyp_flags(self):
355 return ('-DWIND_BASE=%s' % self.wind_base(),)
356
357 def modify_ninja_file(self, ninja_file):
358 subprocess.check_call(
359 ('sed', '-i',
360 's/nm -gD/nm/g', ninja_file),
361 stdin=open(os.devnull, 'r'))
362
363 def download_externals(self, _):
364 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500365
Brian Silverman9b7a6842014-05-05 16:19:11 -0700366 def check_installed(self, platforms, is_deploy):
367 packages = ('powerpc-wrs-vxworks', 'tcl')
368 if is_deploy:
369 packages += ('ncftp',)
370 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700371
Brian Silvermana29ebf92014-04-23 13:08:49 -0500372class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700373 """A Processor subclass for building prime code."""
374
Brian Silvermanb3d50542014-04-23 14:28:55 -0500375 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700376 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500377 super(PrimeProcessor.Platform, self).__init__()
378
Brian Silvermane6bada62014-05-04 16:16:54 -0700379 self.__architecture = architecture
380 self.__compiler = compiler
381 self.__debug = debug
382 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500383
384 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700385 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
386 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700387 % (self.architecture(), self.compiler(), self.debug(),
388 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500389 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700390 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700391 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500392
393 def os(self):
394 return 'linux'
395 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700396 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
397 def architecture(self):
398 return self.__architecture
399 def compiler(self):
400 return self.__compiler
401 def sanitizer(self):
402 return self.__sanitizer
403 def debug(self):
404 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500405
Brian Silvermana29ebf92014-04-23 13:08:49 -0500406 def outname(self):
407 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500408
Brian Silvermane48c09a2014-04-30 18:04:58 -0700409 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500410 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700411 # Downloads code to the prime in a way that avoids clashing too badly with
412 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500413 SUM = 'md5sum'
414 TARGET_DIR = '/home/driver/robot_code/bin'
415 TEMP_DIR = '/tmp/aos_downloader'
416 TARGET = 'driver@' + get_ip('prime')
417
418 from_dir = os.path.join(self.outdir(), 'outputs')
419 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
420 stdin=open(os.devnull, 'r'),
421 cwd=from_dir)
422 to_download = subprocess.check_output(
423 ('ssh', TARGET,
424 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
425 && echo '{SUMS}' | {SUM} --check --quiet
Brian Silverman452aaec2014-05-05 16:52:18 -0700426 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".
427 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500428 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700429 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500430 return
431 self.do_deploy(
432 dry_run,
433 ('scp', '-o', 'Compression yes') + to_download
434 + (('%s:%s' % (TARGET, TEMP_DIR)),))
435 if not dry_run:
436 subprocess.check_call(
437 ('ssh', TARGET,
438 """mv {TMPDIR}/* {TO_DIR}
439 && echo 'Done moving new executables into place'
440 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
441 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
442
Brian Silvermana4aff562014-05-02 17:43:50 -0700443 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700444 OTHER_SYSROOT = '/opt/clang-3.5/'
445 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700446 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700447 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
448 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
449 if self.sanitizer() == 'address':
450 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700451 r['ASAN_OPTIONS'] = \
452 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700453 elif self.sanitizer() == 'memory':
454 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
455 elif self.sanitizer() == 'thread':
456 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700457
458 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700459 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
460 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700461 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700462 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700463 # clang doesn't like being run directly on the preprocessed files.
464 r['CCACHE_CPP2'] = 'yes'
465 # Without this, ccache slows down because of the generated header files.
466 # The race condition that this opens up isn't a problem because the build
467 # system finishes modifying header files before compiling anything that
468 # uses them.
469 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700470
Brian Silvermane6bada62014-05-04 16:16:54 -0700471 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700472 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
473 ':' + os.environ['PATH']
474
Brian Silvermana4aff562014-05-02 17:43:50 -0700475 return r
476
Brian Silverman47cd6f62014-05-03 10:35:52 -0700477 ARCHITECTURES = ('arm', 'amd64')
478 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
479 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
480 SANITIZER_TEST_WARNINGS = {
481 'memory': (True,
482"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700483 errors with msan (especially stdlibc++).
484 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700485 'undefined': (False,
486"""There are several warnings in other people's code that ubsan catches.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700487 The following have been verified non-interesting:
Brian Silverman452aaec2014-05-05 16:52:18 -0700488 include/c++/4.8.2/array:*: runtime error: reference binding to null pointer
489 of type 'int'
490 This happens with ::std::array<T, 0> and it doesn't seem to cause any
491 issues.
492 output/downloaded/eigen-3.2.1/Eigen/src/Core/util/Memory.h:782:*: runtime
493 error: load of misaligned address 0x* for type 'const int', which
494 requires 4 byte alignment
Brian Silverman47cd6f62014-05-03 10:35:52 -0700495 That's in the CPUID detection code which only runs on x86."""),
496 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700497 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500498
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500499 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700500 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500501
502 platforms = []
503 for architecture in PrimeProcessor.ARCHITECTURES:
504 for compiler in PrimeProcessor.COMPILERS:
505 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700506 if architecture == 'arm' and compiler == 'gcc_4.8':
507 # We don't have a compiler to use here.
508 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500509 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700510 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
511 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700512 for compiler in ('gcc_4.8', 'clang'):
513 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
514 sanitizer == 'integer' or
515 sanitizer == 'memory'):
516 # GCC 4.8 doesn't support these sanitizers.
517 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700518 if sanitizer == 'none':
519 # We already added sanitizer == 'none' above.
520 continue
521 platforms.append(
522 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
523 self.__platforms = frozenset(platforms)
524
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500525 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700526 default_platforms = self.select_platforms(architecture='amd64',
527 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700528 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
529 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700530 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500531 elif is_deploy:
532 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700533 default_platforms = self.select_platforms(architecture='arm',
534 compiler='gcc',
535 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500536 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700537 default_platforms = self.select_platforms(debug=False)
538 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500539
Brian Silvermane6bada62014-05-04 16:16:54 -0700540 def platforms(self):
541 return self.__platforms
542 def default_platforms(self):
543 return self.__default_platforms
544
545 def download_externals(self, platforms):
546 to_download = set()
547 for architecture in PrimeProcessor.ARCHITECTURES:
548 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
549 if platforms & self.select_platforms(architecture=architecture,
550 sanitizer=sanitizer):
551 to_download.add(architecture + '-fPIE')
552 if platforms & self.select_platforms(architecture=architecture,
553 sanitizer='none'):
554 to_download.add(architecture)
555 for download_target in to_download:
556 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500557
Brian Silverman452aaec2014-05-05 16:52:18 -0700558 def parse_platforms(self, platform_string):
559 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700560 return self.default_platforms()
561 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700562 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700563 if part == 'all':
564 r = self.platforms()
565 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500566 r = r | self.select_platforms_string(part[1:])
567 elif part[0] == '-':
568 r = r - self.select_platforms_string(part[1:])
569 elif part[0] == '=':
570 r = self.select_platforms_string(part[1:])
571 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500572 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700573 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500574 if not r:
575 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500576 return r
577
Brian Silverman452aaec2014-05-05 16:52:18 -0700578 def select_platforms(self, architecture=None, compiler=None, debug=None,
579 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500580 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700581 for platform in self.platforms():
582 if architecture is None or platform.architecture() == architecture:
583 if compiler is None or platform.compiler() == compiler:
584 if debug is None or platform.debug() == debug:
585 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700586 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500587 return set(r)
588
Brian Silverman452aaec2014-05-05 16:52:18 -0700589 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700590 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700591 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500592 if part in PrimeProcessor.ARCHITECTURES:
593 architecture = part
594 elif part in PrimeProcessor.COMPILERS:
595 compiler = part
596 elif part in ['debug', 'dbg']:
597 debug = True
598 elif part in ['release', 'nodebug', 'ndb']:
599 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700600 elif part in PrimeProcessor.SANITIZERS:
601 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500602 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700603 raise Processor.UnknownPlatform(
604 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500605 return self.select_platforms(
606 architecture=architecture,
607 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700608 debug=debug,
609 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500610
Brian Silverman9b7a6842014-05-05 16:19:11 -0700611 def check_installed(self, platforms, is_deploy):
612 packages = set(('lzip', 'm4', 'realpath'))
613 packages.add('ruby')
614 # clang-format from here gets used for all versions.
615 packages.add('clang-3.5')
616 packages.add('arm-eabi-gcc')
617 for platform in platforms:
618 if platform.architecture() == 'arm':
619 packages.add('gcc-4.7-arm-linux-gnueabihf')
620 packages.add('g++-4.7-arm-linux-gnueabihf')
621 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
622 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700623 if platform.compiler() == 'gcc_4.8':
624 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700625 if is_deploy:
626 packages.add('openssh-client')
627 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
628 packages.add('gcc-4.7')
629 packages.add('g++-4.7')
630
631 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700632
Brian Silvermana29ebf92014-04-23 13:08:49 -0500633def main():
634 class TryParsingAgain(Exception):
635 pass
636
637 class TryAgainArgumentParser(argparse.ArgumentParser):
638 def __init__(self, **kwargs):
639 super(TryAgainArgumentParser, self).__init__(**kwargs)
640
641 def error(self, message):
642 raise TryParsingAgain
643
Brian Silvermane6bada62014-05-04 16:16:54 -0700644 def set_up_parser(parser, args):
645 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500646 parser.add_argument(
647 'target',
648 help='target to build',
649 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700650 parser.add_argument(
651 '--jobs', '-j',
652 help='number of things to do at once',
653 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700654 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500655 parser.add_argument(
656 'platforms',
657 help='platform(s) to act on',
658 nargs='?')
659
660 parser.add_argument('--processor', required=True, help='prime or crio')
661 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
662 subparsers = parser.add_subparsers(dest='action_name')
663
664 build_parser = subparsers.add_parser(
665 'build',
666 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700667 add_common_args(build_parser)
668 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500669
670 clean_parser = subparsers.add_parser(
671 'clean',
672 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700673 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500674
675 deploy_parser = subparsers.add_parser(
676 'deploy',
677 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700678 add_common_args(deploy_parser)
679 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500680 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700681 '-n', '--dry-run',
682 help="don't actually download anything",
683 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500684
Brian Silvermane48c09a2014-04-30 18:04:58 -0700685 tests_parser = subparsers.add_parser(
686 'tests',
687 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700688 add_common_args(tests_parser)
689 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700690
Brian Silvermana29ebf92014-04-23 13:08:49 -0500691 return parser.parse_args(args)
692
693 try:
694 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700695 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500696 except TryParsingAgain:
697 parser = argparse.ArgumentParser()
698 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700699 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
700 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500701
702 if args.processor == 'crio':
703 processor = CRIOProcessor()
704 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500705 processor = PrimeProcessor(args.action_name == 'tests',
706 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500707 else:
708 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
709
710 if 'target' in args:
711 targets = args.target[:]
712 else:
713 targets = []
714 unknown_platform_error = None
715 try:
716 platforms = processor.parse_platforms(args.platforms)
717 except Processor.UnknownPlatform as e:
718 unknown_platform_error = e.message
719 targets.append(args.platforms)
720 platforms = processor.parse_platforms(None)
721 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700722 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500723 exit(1)
724
Brian Silverman9b7a6842014-05-05 16:19:11 -0700725 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700726 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500727
728 class ToolsConfig(object):
729 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500730 self.variables = {'AOS': aos_path()}
731 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500732 for line in f:
733 if line[0] == '#':
734 pass
735 elif line.isspace():
736 pass
737 else:
738 new_name, new_value = line.rstrip().split('=')
739 for name, value in self.variables.items():
740 new_value = new_value.replace('${%s}' % name, value)
741 self.variables[new_name] = new_value
742 def __getitem__(self, key):
743 return self.variables[key]
744
745 tools_config = ToolsConfig()
746
747 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700748 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500749 if issubclass(OSError, excinfo[0]):
750 if excinfo[1].errno == errno.ENOENT:
751 # Who cares if the file we're deleting isn't there?
752 return
753 raise excinfo[1]
754
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700755 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700756 """Determines if we need to run gyp again or not.
757
758 The generated build files are supposed to re-run gyp again themselves, but
759 that doesn't work (or at least it used to not) and we sometimes want to
760 modify the results anyways.
761
762 Args:
763 platform: The platform to check for.
764 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700765 if not os.path.exists(platform.build_ninja()):
766 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700767 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
768 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700769 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700770 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700771 if dirs.count('output'):
772 dirs.remove('output')
773 if dirs.count('.git'):
774 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700775 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700776 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700777 + ('-newer', platform.build_ninja(),
778 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700779 stdin=open(os.devnull, 'r'))
780
781 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700782 """Makes sure we pass through important environmental variables.
783
784 Returns:
785 An environment suitable for passing to subprocess.Popen and friends.
786 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700787 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700788 if not 'TERM' in build_env:
789 build_env['TERM'] = os.environ['TERM']
790 if not 'PATH' in build_env:
791 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700792 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700793
Brian Silverman47cd6f62014-05-03 10:35:52 -0700794 to_build = []
795 for platform in platforms:
796 to_build.append(str(platform))
797 if len(to_build) > 1:
798 to_build[-1] = 'and ' + to_build[-1]
799 user_output('Building %s...' % ', '.join(to_build))
800
801 if args.action_name == 'tests':
802 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
803 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
804 if warned_about:
805 user_output(warning[1])
806 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700807 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700808 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
809 exit(1)
810
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700811 num = 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500812 for platform in platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700813 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500814 if args.action_name == 'clean':
815 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
816 else:
817 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700818 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500819 gyp = subprocess.Popen(
820 (tools_config['GYP'],
821 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500822 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500823 '--no-circular-check',
824 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500825 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500826 '-I/dev/stdin', '-Goutput_dir=output',
827 '-DOS=%s' % platform.os(),
828 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700829 '-DARCHITECTURE=%s' % platform.architecture(),
830 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
831 '-DFULL_COMPILER=%s' % platform.compiler(),
832 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
833 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700834 '-DSANITIZER_FPIE=%s' %
835 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
836 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500837 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500838 stdin=subprocess.PIPE)
839 gyp.communicate(("""
840{
841 'target_defaults': {
842 'configurations': {
843 '%s': {}
844 }
845 }
846}""" % platform.outname()).encode())
847 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700848 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500849 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700850 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700851 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500852 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700853 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500854
855 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700856 call = (tools_config['NINJA'],
857 '-C', platform.outdir()) + tuple(targets)
858 if args.jobs:
859 call += ('-j', str(args.jobs))
860 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700861 stdin=open(os.devnull, 'r'),
862 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500863 except subprocess.CalledProcessError as e:
864 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700865 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500866 raise e
867
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500868 if args.action_name == 'deploy':
869 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700870 elif args.action_name == 'tests':
871 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700872 done_queue = queue.Queue()
873 running = []
874 if args.jobs:
875 number_jobs = args.jobs
876 else:
877 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
878 test_start_semaphore = threading.Semaphore(number_jobs)
879 if targets:
880 to_run = []
881 for target in targets:
882 if target.endswith('_test'):
883 to_run.append(target)
884 else:
885 to_run = os.listdir(dirname)
886 for f in to_run:
887 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
888 test_start_semaphore)
889 running.append(thread)
890 thread.start()
891 try:
892 while running:
893 done = done_queue.get()
894 running.remove(done)
895 with test_output_lock:
896 test_output('Output from test %s:' % done.name)
897 for line in os.fdopen(done.output):
898 if not sys.stdout.isatty():
899 # Remove color escape codes.
900 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
901 sys.stdout.write(line)
Brian Silvermane6bada62014-05-04 16:16:54 -0700902 if not done.returncode:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700903 test_output('Test %s succeeded' % done.name)
904 else:
905 test_output('Test %s failed' % done.name)
906 user_output('Aborting because of test failure.')
907 exit(1)
908 finally:
909 if running:
910 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700911# Stop all of them before killing processes because otherwise stopping some of
912# them tends to let other ones that are waiting to start go.
913 for thread in running:
914 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700915 for thread in running:
916 thread.terminate_process()
917 to_remove = []
918 for thread in running:
919 thread.join(5)
920 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -0700921 to_remove.append(thread)
922 for thread in to_remove:
923 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700924 for thread in running:
925 test_output(
926 'Test %s did not terminate. Killing it.' % thread.name)
927 thread.kill_process()
928 thread.join()
929 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500930
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700931 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
932 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500933
934if __name__ == '__main__':
935 main()