blob: 1e663a4b79fe32aa8a4045b949129f4cc21e53e3 [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()
Brian Silverman452aaec2014-05-05 16:52:18 -0700565 elif platform_string == 'all':
Brian Silverman9b7a6842014-05-05 16:19:11 -0700566 return self.platforms()
Brian Silvermane6bada62014-05-04 16:16:54 -0700567 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700568 for part in platform_string.split(','):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500569 if part[0] == '+':
570 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')
627 if is_deploy:
628 packages.add('openssh-client')
629 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
630 packages.add('gcc-4.7')
631 packages.add('g++-4.7')
632
633 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700634
Brian Silvermana29ebf92014-04-23 13:08:49 -0500635def main():
636 class TryParsingAgain(Exception):
637 pass
638
639 class TryAgainArgumentParser(argparse.ArgumentParser):
640 def __init__(self, **kwargs):
641 super(TryAgainArgumentParser, self).__init__(**kwargs)
642
643 def error(self, message):
644 raise TryParsingAgain
645
Brian Silvermane6bada62014-05-04 16:16:54 -0700646 def set_up_parser(parser, args):
647 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500648 parser.add_argument(
649 'target',
650 help='target to build',
651 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700652 parser.add_argument(
653 '--jobs', '-j',
654 help='number of things to do at once',
655 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700656 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500657 parser.add_argument(
658 'platforms',
659 help='platform(s) to act on',
660 nargs='?')
661
662 parser.add_argument('--processor', required=True, help='prime or crio')
663 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
664 subparsers = parser.add_subparsers(dest='action_name')
665
666 build_parser = subparsers.add_parser(
667 'build',
668 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700669 add_common_args(build_parser)
670 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500671
672 clean_parser = subparsers.add_parser(
673 'clean',
674 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700675 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500676
677 deploy_parser = subparsers.add_parser(
678 'deploy',
679 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700680 add_common_args(deploy_parser)
681 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500682 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700683 '-n', '--dry-run',
684 help="don't actually download anything",
685 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500686
Brian Silvermane48c09a2014-04-30 18:04:58 -0700687 tests_parser = subparsers.add_parser(
688 'tests',
689 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700690 add_common_args(tests_parser)
691 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700692
Brian Silvermana29ebf92014-04-23 13:08:49 -0500693 return parser.parse_args(args)
694
695 try:
696 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700697 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500698 except TryParsingAgain:
699 parser = argparse.ArgumentParser()
700 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700701 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
702 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500703
704 if args.processor == 'crio':
705 processor = CRIOProcessor()
706 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500707 processor = PrimeProcessor(args.action_name == 'tests',
708 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500709 else:
710 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
711
712 if 'target' in args:
713 targets = args.target[:]
714 else:
715 targets = []
716 unknown_platform_error = None
717 try:
718 platforms = processor.parse_platforms(args.platforms)
719 except Processor.UnknownPlatform as e:
720 unknown_platform_error = e.message
721 targets.append(args.platforms)
722 platforms = processor.parse_platforms(None)
723 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700724 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500725 exit(1)
726
Brian Silverman9b7a6842014-05-05 16:19:11 -0700727 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700728 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500729
730 class ToolsConfig(object):
731 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500732 self.variables = {'AOS': aos_path()}
733 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500734 for line in f:
735 if line[0] == '#':
736 pass
737 elif line.isspace():
738 pass
739 else:
740 new_name, new_value = line.rstrip().split('=')
741 for name, value in self.variables.items():
742 new_value = new_value.replace('${%s}' % name, value)
743 self.variables[new_name] = new_value
744 def __getitem__(self, key):
745 return self.variables[key]
746
747 tools_config = ToolsConfig()
748
749 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700750 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500751 if issubclass(OSError, excinfo[0]):
752 if excinfo[1].errno == errno.ENOENT:
753 # Who cares if the file we're deleting isn't there?
754 return
755 raise excinfo[1]
756
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700757 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700758 """Determines if we need to run gyp again or not.
759
760 The generated build files are supposed to re-run gyp again themselves, but
761 that doesn't work (or at least it used to not) and we sometimes want to
762 modify the results anyways.
763
764 Args:
765 platform: The platform to check for.
766 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700767 if not os.path.exists(platform.build_ninja()):
768 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700769 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
770 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700771 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700772 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700773 if dirs.count('output'):
774 dirs.remove('output')
775 if dirs.count('.git'):
776 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700777 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700778 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700779 + ('-newer', platform.build_ninja(),
780 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700781 stdin=open(os.devnull, 'r'))
782
783 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700784 """Makes sure we pass through important environmental variables.
785
786 Returns:
787 An environment suitable for passing to subprocess.Popen and friends.
788 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700789 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700790 if not 'TERM' in build_env:
791 build_env['TERM'] = os.environ['TERM']
792 if not 'PATH' in build_env:
793 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700794 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700795
Brian Silverman47cd6f62014-05-03 10:35:52 -0700796 to_build = []
797 for platform in platforms:
798 to_build.append(str(platform))
799 if len(to_build) > 1:
800 to_build[-1] = 'and ' + to_build[-1]
801 user_output('Building %s...' % ', '.join(to_build))
802
803 if args.action_name == 'tests':
804 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
805 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
806 if warned_about:
807 user_output(warning[1])
808 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700809 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700810 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
811 exit(1)
812
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700813 num = 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500814 for platform in platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700815 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500816 if args.action_name == 'clean':
817 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
818 else:
819 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700820 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500821 gyp = subprocess.Popen(
822 (tools_config['GYP'],
823 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500824 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500825 '--no-circular-check',
826 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500827 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500828 '-I/dev/stdin', '-Goutput_dir=output',
829 '-DOS=%s' % platform.os(),
830 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700831 '-DARCHITECTURE=%s' % platform.architecture(),
832 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
833 '-DFULL_COMPILER=%s' % platform.compiler(),
834 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
835 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700836 '-DSANITIZER_FPIE=%s' %
837 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
838 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500839 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500840 stdin=subprocess.PIPE)
841 gyp.communicate(("""
842{
843 'target_defaults': {
844 'configurations': {
845 '%s': {}
846 }
847 }
848}""" % platform.outname()).encode())
849 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700850 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500851 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700852 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700853 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500854 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700855 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500856
857 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700858 call = (tools_config['NINJA'],
859 '-C', platform.outdir()) + tuple(targets)
860 if args.jobs:
861 call += ('-j', str(args.jobs))
862 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700863 stdin=open(os.devnull, 'r'),
864 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500865 except subprocess.CalledProcessError as e:
866 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700867 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500868 raise e
869
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500870 if args.action_name == 'deploy':
871 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700872 elif args.action_name == 'tests':
873 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700874 done_queue = queue.Queue()
875 running = []
876 if args.jobs:
877 number_jobs = args.jobs
878 else:
879 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
880 test_start_semaphore = threading.Semaphore(number_jobs)
881 if targets:
882 to_run = []
883 for target in targets:
884 if target.endswith('_test'):
885 to_run.append(target)
886 else:
887 to_run = os.listdir(dirname)
888 for f in to_run:
889 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
890 test_start_semaphore)
891 running.append(thread)
892 thread.start()
893 try:
894 while running:
895 done = done_queue.get()
896 running.remove(done)
897 with test_output_lock:
898 test_output('Output from test %s:' % done.name)
899 for line in os.fdopen(done.output):
900 if not sys.stdout.isatty():
901 # Remove color escape codes.
902 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
903 sys.stdout.write(line)
Brian Silvermane6bada62014-05-04 16:16:54 -0700904 if not done.returncode:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700905 test_output('Test %s succeeded' % done.name)
906 else:
907 test_output('Test %s failed' % done.name)
908 user_output('Aborting because of test failure.')
909 exit(1)
910 finally:
911 if running:
912 test_output('Killing other tests...')
913 for thread in running:
914 thread.terminate_process()
915 to_remove = []
916 for thread in running:
917 thread.join(5)
918 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -0700919 to_remove.append(thread)
920 for thread in to_remove:
921 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700922 for thread in running:
923 test_output(
924 'Test %s did not terminate. Killing it.' % thread.name)
925 thread.kill_process()
926 thread.join()
927 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500928
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700929 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
930 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500931
932if __name__ == '__main__':
933 main()