blob: 7bc09863f1aa80ccd0ef9362befa9e3ddca11856 [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 Silvermane6bada62014-05-04 16:16:54 -070072 """Asks any currently running process to stop.
73
74 Also changes this object to the stopped state.
75 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070076 with self.process_lock:
77 self.stopped = True
Brian Silverman452aaec2014-05-05 16:52:18 -070078 if not self.process:
79 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070080 self.process.terminate()
81 def kill_process(self):
Brian Silvermane6bada62014-05-04 16:16:54 -070082 """Forcibly terminates any running process.
83
84 Also changes this object to the stopped state.
85 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070086 with self.process_lock:
87 self.stopped = True
Brian Silverman452aaec2014-05-05 16:52:18 -070088 if not self.process:
89 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070090 self.process.kill()
Brian Silvermana29ebf92014-04-23 13:08:49 -050091
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050092def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -070093 """Returns:
94 A relative path to the aos directory.
95 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050096 return os.path.join(os.path.dirname(__file__), '..')
97
98def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -070099 """Retrieves the IP address for a given device."""
100 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700101 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500102 if not os.access(FILENAME, os.R_OK):
103 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
104 with open(FILENAME, 'w') as f:
105 f.write('10.9.71')
106 with open(FILENAME, 'r') as f:
107 base = f.readline()
108 if device == 'prime':
109 return base + '.179'
110 elif device == 'robot':
111 return base + '.2'
112 else:
113 raise Exception('Unknown device %s to get an IP address for.' % device)
114
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700115def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700116 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700117 print('build.py: ' + message, file=sys.stderr)
118
Brian Silverman452aaec2014-05-05 16:52:18 -0700119# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700120test_output_lock = threading.RLock()
121def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700122 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700123 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700124 print('tests: ' + message, file=sys.stdout)
125
126def call_download_externals(argument):
127 """Calls download_externals.sh for a given set of externals.
128
129 Args:
130 argument: The argument to pass to the shell script to tell it what to
131 download.
132 """
133 subprocess.check_call(
134 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
135 argument),
136 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700137
Brian Silvermana29ebf92014-04-23 13:08:49 -0500138class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700139 """Represents a processor architecture we can build for."""
140
Brian Silvermana29ebf92014-04-23 13:08:49 -0500141 class UnknownPlatform(Exception):
142 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700143 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500144 self.message = message
145
Brian Silvermanb3d50542014-04-23 14:28:55 -0500146 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700147 """Represents a single way to build the code."""
148
Brian Silvermanb3d50542014-04-23 14:28:55 -0500149 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700150 """Returns:
151 The path of the directory build outputs get put in to.
152 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500153 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500154 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500155 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700156 """Returns:
157 The path of the build.ninja file.
158 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500159 return os.path.join(self.outdir(), 'build.ninja')
160
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500161 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700162 """Helper for subclasses to implement deploy.
163
164 Args:
165 dry_run: If True, prints the command instead of actually running it.
166 command: A tuple of command-line arguments.
167 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500168 real_command = (('echo',) + command) if dry_run else command
169 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500170
Brian Silvermane6bada62014-05-04 16:16:54 -0700171 def deploy(self, dry_run):
172 """Downloads the compiled code to the target computer."""
173 raise NotImplementedError('deploy should be overriden')
174 def outname(self):
175 """Returns:
176 The name of the directory the code will be compiled to.
177 """
178 raise NotImplementedError('outname should be overriden')
179 def os(self):
180 """Returns:
181 The name of the operating system this platform is for.
182
183 This will be used as the value of the OS gyp variable.
184 """
185 raise NotImplementedError('os should be overriden')
186 def gyp_platform(self):
187 """Returns:
188 The platform name the .gyp files know.
189
190 This will be used as the value of the PLATFORM gyp variable.
191 """
192 raise NotImplementedError('gyp_platform should be overriden')
193 def architecture(self):
194 """Returns:
195 The processor architecture for this platform.
196
197 This will be used as the value of the ARCHITECTURE gyp variable.
198 """
199 raise NotImplementedError('architecture should be overriden')
200 def compiler(self):
201 """Returns:
202 The compiler used for this platform.
203
204 Everything before the first _ will be used as the value of the
205 COMPILER gyp variable and the whole thing will be used as the value
206 of the FULL_COMPILER gyp variable.
207 """
208 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700209 def sanitizer(self):
210 """Returns:
211 The sanitizer used on this platform.
212
213 This will be used as the value of the SANITIZER gyp variable.
214
215 "none" if there isn't one.
216 """
217 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700218 def debug(self):
219 """Returns:
220 Whether or not this platform compiles with debugging information.
221
222 The DEBUG gyp variable will be set to "yes" or "no" based on this.
223 """
224 raise NotImplementedError('debug should be overriden')
225 def build_env(self):
226 """Returns:
227 A map of environment variables to set while building this platform.
228 """
229 raise NotImplementedError('build_env should be overriden')
230
Brian Silverman9b7a6842014-05-05 16:19:11 -0700231 def check_installed(self, platforms, is_deploy):
232 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700233 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700234 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700235 """Args:
236 string: A user-supplied string saying which platforms to select.
237
238 Returns:
239 A tuple of Platform objects.
240
241 Raises:
242 Processor.UnknownPlatform: Parsing string didn't work out.
243 """
244 raise NotImplementedError('parse_platforms should be overriden')
245 def extra_gyp_flags(self):
246 """Returns:
247 A tuple of extra flags to pass to gyp (if any).
248 """
249 return ()
250 def modify_ninja_file(self, ninja_file):
251 """Modifies a freshly generated ninja file as necessary.
252
253 Args:
254 ninja_file: Path to the file to modify.
255 """
256 pass
257 def download_externals(self, platforms):
258 """Calls download_externals as appropriate to build platforms.
259
260 Args:
261 platforms: A list of platforms to download external libraries for.
262 """
263 raise NotImplementedError('download_externals should be overriden')
264
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700265 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700266 """Helper for subclasses to implement check_installed.
267
268 Args:
269 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700270 all_packages = other_packages
271 # Necessary to build stuff.
272 all_packages += ('ccache', 'make')
273 # Necessary to download stuff to build.
274 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
275 # Necessary to build externals stuff.
276 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700277 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700278 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700279 result = subprocess.check_output(
280 ('dpkg-query', '--show') + all_packages,
281 stdin=open(os.devnull, 'r'),
282 stderr=subprocess.STDOUT)
283 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700284 output = e.output.decode('utf-8').rstrip()
285 not_found = []
286 for line in output.splitlines(True):
287 match = re.match(r'dpkg-query: no packages found matching (.*)',
288 line)
289 if match:
290 not_found.append(match.group(1))
291 user_output('Some packages not installed: %s.' % ', '.join(not_found))
292 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700293 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700294 exit(1)
295
Brian Silvermana29ebf92014-04-23 13:08:49 -0500296class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700297 """A Processor subclass for building cRIO code."""
298
Brian Silvermanb3d50542014-04-23 14:28:55 -0500299 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700300 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500301 super(CRIOProcessor.Platform, self).__init__()
302
Brian Silvermane6bada62014-05-04 16:16:54 -0700303 self.__debug = debug
304 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500305
306 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700307 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500308 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700309 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500310
311 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700312 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500313 def os(self):
314 return 'vxworks'
315 def gyp_platform(self):
316 return 'crio'
317 def architecture(self):
318 return 'ppc'
319 def compiler(self):
320 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700321 def sanitizer(self):
322 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700323 def debug(self):
324 return self.__debug
325 def wind_base(self):
326 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500327
Brian Silvermane48c09a2014-04-30 18:04:58 -0700328 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500329 def deploy(self, dry_run):
330 self.do_deploy(dry_run,
331 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700332 os.path.join(self.outdir(), 'lib',
333 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500334
Brian Silvermana4aff562014-05-02 17:43:50 -0700335 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700336 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700337
Brian Silvermana29ebf92014-04-23 13:08:49 -0500338 def __init__(self):
339 super(CRIOProcessor, self).__init__()
340
341 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700342 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500343 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700344 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500345
Brian Silverman452aaec2014-05-05 16:52:18 -0700346 def parse_platforms(self, platforms_string):
347 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700348 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700349 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700350 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500351 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700352 raise Processor.UnknownPlatform(
353 'Unknown cRIO platform "%s".' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500354
Brian Silvermane6bada62014-05-04 16:16:54 -0700355 def wind_base(self):
356 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500357
Brian Silvermane6bada62014-05-04 16:16:54 -0700358 def extra_gyp_flags(self):
359 return ('-DWIND_BASE=%s' % self.wind_base(),)
360
361 def modify_ninja_file(self, ninja_file):
362 subprocess.check_call(
363 ('sed', '-i',
364 's/nm -gD/nm/g', ninja_file),
365 stdin=open(os.devnull, 'r'))
366
367 def download_externals(self, _):
368 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500369
Brian Silverman9b7a6842014-05-05 16:19:11 -0700370 def check_installed(self, platforms, is_deploy):
371 packages = ('powerpc-wrs-vxworks', 'tcl')
372 if is_deploy:
373 packages += ('ncftp',)
374 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700375
Brian Silvermana29ebf92014-04-23 13:08:49 -0500376class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700377 """A Processor subclass for building prime code."""
378
Brian Silvermanb3d50542014-04-23 14:28:55 -0500379 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700380 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500381 super(PrimeProcessor.Platform, self).__init__()
382
Brian Silvermane6bada62014-05-04 16:16:54 -0700383 self.__architecture = architecture
384 self.__compiler = compiler
385 self.__debug = debug
386 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500387
388 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700389 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
390 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700391 % (self.architecture(), self.compiler(), self.debug(),
392 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500393 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700394 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700395 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500396
397 def os(self):
398 return 'linux'
399 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700400 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
401 def architecture(self):
402 return self.__architecture
403 def compiler(self):
404 return self.__compiler
405 def sanitizer(self):
406 return self.__sanitizer
407 def debug(self):
408 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500409
Brian Silvermana29ebf92014-04-23 13:08:49 -0500410 def outname(self):
411 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500412
Brian Silvermane48c09a2014-04-30 18:04:58 -0700413 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500414 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700415 # Downloads code to the prime in a way that avoids clashing too badly with
416 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500417 SUM = 'md5sum'
418 TARGET_DIR = '/home/driver/robot_code/bin'
419 TEMP_DIR = '/tmp/aos_downloader'
420 TARGET = 'driver@' + get_ip('prime')
421
422 from_dir = os.path.join(self.outdir(), 'outputs')
423 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
424 stdin=open(os.devnull, 'r'),
425 cwd=from_dir)
426 to_download = subprocess.check_output(
427 ('ssh', TARGET,
428 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
429 && echo '{SUMS}' | {SUM} --check --quiet
Brian Silverman452aaec2014-05-05 16:52:18 -0700430 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".
431 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500432 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700433 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500434 return
435 self.do_deploy(
436 dry_run,
437 ('scp', '-o', 'Compression yes') + to_download
438 + (('%s:%s' % (TARGET, TEMP_DIR)),))
439 if not dry_run:
440 subprocess.check_call(
441 ('ssh', TARGET,
442 """mv {TMPDIR}/* {TO_DIR}
443 && echo 'Done moving new executables into place'
444 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
445 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
446
Brian Silvermana4aff562014-05-02 17:43:50 -0700447 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700448 OTHER_SYSROOT = '/opt/clang-3.5/'
449 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700450 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700451 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
452 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
453 if self.sanitizer() == 'address':
454 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700455 r['ASAN_OPTIONS'] = \
456 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700457 elif self.sanitizer() == 'memory':
458 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
459 elif self.sanitizer() == 'thread':
460 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700461
462 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700463 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
464 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700465 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700466 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700467 # clang doesn't like being run directly on the preprocessed files.
468 r['CCACHE_CPP2'] = 'yes'
469 # Without this, ccache slows down because of the generated header files.
470 # The race condition that this opens up isn't a problem because the build
471 # system finishes modifying header files before compiling anything that
472 # uses them.
473 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700474
Brian Silvermane6bada62014-05-04 16:16:54 -0700475 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700476 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
477 ':' + os.environ['PATH']
478
Brian Silvermana4aff562014-05-02 17:43:50 -0700479 return r
480
Brian Silverman47cd6f62014-05-03 10:35:52 -0700481 ARCHITECTURES = ('arm', 'amd64')
482 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
483 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
484 SANITIZER_TEST_WARNINGS = {
485 'memory': (True,
486"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700487 errors with msan (especially stdlibc++).
488 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700489 'undefined': (False,
490"""There are several warnings in other people's code that ubsan catches.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700491 The following have been verified non-interesting:
Brian Silverman452aaec2014-05-05 16:52:18 -0700492 include/c++/4.8.2/array:*: runtime error: reference binding to null pointer
493 of type 'int'
494 This happens with ::std::array<T, 0> and it doesn't seem to cause any
495 issues.
496 output/downloaded/eigen-3.2.1/Eigen/src/Core/util/Memory.h:782:*: runtime
497 error: load of misaligned address 0x* for type 'const int', which
498 requires 4 byte alignment
Brian Silverman47cd6f62014-05-03 10:35:52 -0700499 That's in the CPUID detection code which only runs on x86."""),
500 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700501 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500502
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500503 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700504 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500505
506 platforms = []
507 for architecture in PrimeProcessor.ARCHITECTURES:
508 for compiler in PrimeProcessor.COMPILERS:
509 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700510 if architecture == 'arm' and compiler == 'gcc_4.8':
511 # We don't have a compiler to use here.
512 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500513 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700514 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
515 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700516 for compiler in ('gcc_4.8', 'clang'):
517 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
518 sanitizer == 'integer' or
519 sanitizer == 'memory'):
520 # GCC 4.8 doesn't support these sanitizers.
521 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700522 if sanitizer == 'none':
523 # We already added sanitizer == 'none' above.
524 continue
525 platforms.append(
526 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
527 self.__platforms = frozenset(platforms)
528
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500529 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700530 default_platforms = self.select_platforms(architecture='amd64',
531 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700532 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
533 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700534 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500535 elif is_deploy:
536 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700537 default_platforms = self.select_platforms(architecture='arm',
538 compiler='gcc',
539 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500540 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700541 default_platforms = self.select_platforms(debug=False)
542 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500543
Brian Silvermane6bada62014-05-04 16:16:54 -0700544 def platforms(self):
545 return self.__platforms
546 def default_platforms(self):
547 return self.__default_platforms
548
549 def download_externals(self, platforms):
550 to_download = set()
551 for architecture in PrimeProcessor.ARCHITECTURES:
552 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
553 if platforms & self.select_platforms(architecture=architecture,
554 sanitizer=sanitizer):
555 to_download.add(architecture + '-fPIE')
556 if platforms & self.select_platforms(architecture=architecture,
557 sanitizer='none'):
558 to_download.add(architecture)
559 for download_target in to_download:
560 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500561
Brian Silverman452aaec2014-05-05 16:52:18 -0700562 def parse_platforms(self, platform_string):
563 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700564 return self.default_platforms()
565 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700566 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700567 if part == 'all':
568 r = self.platforms()
569 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500570 r = r | self.select_platforms_string(part[1:])
571 elif part[0] == '-':
572 r = r - self.select_platforms_string(part[1:])
573 elif part[0] == '=':
574 r = self.select_platforms_string(part[1:])
575 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500576 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700577 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500578 if not r:
579 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500580 return r
581
Brian Silverman452aaec2014-05-05 16:52:18 -0700582 def select_platforms(self, architecture=None, compiler=None, debug=None,
583 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500584 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700585 for platform in self.platforms():
586 if architecture is None or platform.architecture() == architecture:
587 if compiler is None or platform.compiler() == compiler:
588 if debug is None or platform.debug() == debug:
589 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700590 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500591 return set(r)
592
Brian Silverman452aaec2014-05-05 16:52:18 -0700593 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700594 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700595 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500596 if part in PrimeProcessor.ARCHITECTURES:
597 architecture = part
598 elif part in PrimeProcessor.COMPILERS:
599 compiler = part
600 elif part in ['debug', 'dbg']:
601 debug = True
602 elif part in ['release', 'nodebug', 'ndb']:
603 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700604 elif part in PrimeProcessor.SANITIZERS:
605 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500606 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700607 raise Processor.UnknownPlatform(
608 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500609 return self.select_platforms(
610 architecture=architecture,
611 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700612 debug=debug,
613 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500614
Brian Silverman9b7a6842014-05-05 16:19:11 -0700615 def check_installed(self, platforms, is_deploy):
616 packages = set(('lzip', 'm4', 'realpath'))
617 packages.add('ruby')
618 # clang-format from here gets used for all versions.
619 packages.add('clang-3.5')
620 packages.add('arm-eabi-gcc')
621 for platform in platforms:
622 if platform.architecture() == 'arm':
623 packages.add('gcc-4.7-arm-linux-gnueabihf')
624 packages.add('g++-4.7-arm-linux-gnueabihf')
625 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
626 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700627 if platform.compiler() == 'gcc_4.8':
628 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700629 if is_deploy:
630 packages.add('openssh-client')
631 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
632 packages.add('gcc-4.7')
633 packages.add('g++-4.7')
634
635 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700636
Brian Silvermana29ebf92014-04-23 13:08:49 -0500637def main():
638 class TryParsingAgain(Exception):
639 pass
640
641 class TryAgainArgumentParser(argparse.ArgumentParser):
642 def __init__(self, **kwargs):
643 super(TryAgainArgumentParser, self).__init__(**kwargs)
644
645 def error(self, message):
646 raise TryParsingAgain
647
Brian Silvermane6bada62014-05-04 16:16:54 -0700648 def set_up_parser(parser, args):
649 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500650 parser.add_argument(
651 'target',
652 help='target to build',
653 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700654 parser.add_argument(
655 '--jobs', '-j',
656 help='number of things to do at once',
657 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700658 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500659 parser.add_argument(
660 'platforms',
661 help='platform(s) to act on',
662 nargs='?')
663
664 parser.add_argument('--processor', required=True, help='prime or crio')
665 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
666 subparsers = parser.add_subparsers(dest='action_name')
667
668 build_parser = subparsers.add_parser(
669 'build',
670 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700671 add_common_args(build_parser)
672 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500673
674 clean_parser = subparsers.add_parser(
675 'clean',
676 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700677 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500678
679 deploy_parser = subparsers.add_parser(
680 'deploy',
681 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700682 add_common_args(deploy_parser)
683 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500684 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700685 '-n', '--dry-run',
686 help="don't actually download anything",
687 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500688
Brian Silvermane48c09a2014-04-30 18:04:58 -0700689 tests_parser = subparsers.add_parser(
690 'tests',
691 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700692 add_common_args(tests_parser)
693 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700694
Brian Silvermana29ebf92014-04-23 13:08:49 -0500695 return parser.parse_args(args)
696
697 try:
698 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700699 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500700 except TryParsingAgain:
701 parser = argparse.ArgumentParser()
702 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700703 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
704 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500705
706 if args.processor == 'crio':
707 processor = CRIOProcessor()
708 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500709 processor = PrimeProcessor(args.action_name == 'tests',
710 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500711 else:
712 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
713
714 if 'target' in args:
715 targets = args.target[:]
716 else:
717 targets = []
718 unknown_platform_error = None
719 try:
720 platforms = processor.parse_platforms(args.platforms)
721 except Processor.UnknownPlatform as e:
722 unknown_platform_error = e.message
723 targets.append(args.platforms)
724 platforms = processor.parse_platforms(None)
725 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700726 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500727 exit(1)
728
Brian Silverman9b7a6842014-05-05 16:19:11 -0700729 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700730 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500731
732 class ToolsConfig(object):
733 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500734 self.variables = {'AOS': aos_path()}
735 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500736 for line in f:
737 if line[0] == '#':
738 pass
739 elif line.isspace():
740 pass
741 else:
742 new_name, new_value = line.rstrip().split('=')
743 for name, value in self.variables.items():
744 new_value = new_value.replace('${%s}' % name, value)
745 self.variables[new_name] = new_value
746 def __getitem__(self, key):
747 return self.variables[key]
748
749 tools_config = ToolsConfig()
750
751 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700752 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500753 if issubclass(OSError, excinfo[0]):
754 if excinfo[1].errno == errno.ENOENT:
755 # Who cares if the file we're deleting isn't there?
756 return
757 raise excinfo[1]
758
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700759 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700760 """Determines if we need to run gyp again or not.
761
762 The generated build files are supposed to re-run gyp again themselves, but
763 that doesn't work (or at least it used to not) and we sometimes want to
764 modify the results anyways.
765
766 Args:
767 platform: The platform to check for.
768 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700769 if not os.path.exists(platform.build_ninja()):
770 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700771 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
772 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700773 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700774 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700775 if dirs.count('output'):
776 dirs.remove('output')
777 if dirs.count('.git'):
778 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700779 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700780 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700781 + ('-newer', platform.build_ninja(),
782 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700783 stdin=open(os.devnull, 'r'))
784
785 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700786 """Makes sure we pass through important environmental variables.
787
788 Returns:
789 An environment suitable for passing to subprocess.Popen and friends.
790 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700791 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700792 if not 'TERM' in build_env:
793 build_env['TERM'] = os.environ['TERM']
794 if not 'PATH' in build_env:
795 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700796 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700797
Brian Silverman47cd6f62014-05-03 10:35:52 -0700798 to_build = []
799 for platform in platforms:
800 to_build.append(str(platform))
801 if len(to_build) > 1:
802 to_build[-1] = 'and ' + to_build[-1]
803 user_output('Building %s...' % ', '.join(to_build))
804
805 if args.action_name == 'tests':
806 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
807 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
808 if warned_about:
809 user_output(warning[1])
810 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700811 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700812 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
813 exit(1)
814
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700815 num = 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500816 for platform in platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700817 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500818 if args.action_name == 'clean':
819 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
820 else:
821 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700822 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500823 gyp = subprocess.Popen(
824 (tools_config['GYP'],
825 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500826 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500827 '--no-circular-check',
828 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500829 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500830 '-I/dev/stdin', '-Goutput_dir=output',
831 '-DOS=%s' % platform.os(),
832 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700833 '-DARCHITECTURE=%s' % platform.architecture(),
834 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
835 '-DFULL_COMPILER=%s' % platform.compiler(),
836 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
837 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700838 '-DSANITIZER_FPIE=%s' %
839 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
840 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500841 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500842 stdin=subprocess.PIPE)
843 gyp.communicate(("""
844{
845 'target_defaults': {
846 'configurations': {
847 '%s': {}
848 }
849 }
850}""" % platform.outname()).encode())
851 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700852 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500853 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700854 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700855 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500856 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700857 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500858
859 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700860 call = (tools_config['NINJA'],
861 '-C', platform.outdir()) + tuple(targets)
862 if args.jobs:
863 call += ('-j', str(args.jobs))
864 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700865 stdin=open(os.devnull, 'r'),
866 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500867 except subprocess.CalledProcessError as e:
868 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700869 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500870 raise e
871
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500872 if args.action_name == 'deploy':
873 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700874 elif args.action_name == 'tests':
875 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700876 done_queue = queue.Queue()
877 running = []
878 if args.jobs:
879 number_jobs = args.jobs
880 else:
881 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
882 test_start_semaphore = threading.Semaphore(number_jobs)
883 if targets:
884 to_run = []
885 for target in targets:
886 if target.endswith('_test'):
887 to_run.append(target)
888 else:
889 to_run = os.listdir(dirname)
890 for f in to_run:
891 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
892 test_start_semaphore)
893 running.append(thread)
894 thread.start()
895 try:
896 while running:
897 done = done_queue.get()
898 running.remove(done)
899 with test_output_lock:
900 test_output('Output from test %s:' % done.name)
901 for line in os.fdopen(done.output):
902 if not sys.stdout.isatty():
903 # Remove color escape codes.
904 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
905 sys.stdout.write(line)
Brian Silvermane6bada62014-05-04 16:16:54 -0700906 if not done.returncode:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700907 test_output('Test %s succeeded' % done.name)
908 else:
909 test_output('Test %s failed' % done.name)
910 user_output('Aborting because of test failure.')
911 exit(1)
912 finally:
913 if running:
914 test_output('Killing other tests...')
915 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()