blob: 9c82bf01f3ed7c46545ff3a50183f419b83dc323 [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
Brian Silvermanf2bbe092014-05-13 16:55:03 -070014import pty
Brian Silvermanc3740c32014-05-04 12:42:47 -070015
16class TestThread(threading.Thread):
Brian Silvermane6bada62014-05-04 16:16:54 -070017 """Runs 1 test and keeps track of its current state.
18
19 A TestThread is either waiting to start the test, actually running it, done,
20 running it, or stopped. The first 3 always happen in that order and can
21 change to stopped at any time.
22
23 It will finish (ie join() will return) once the process has exited, at which
24 point accessing process to see the status is OK.
25
26 Attributes:
27 executable: The file path of the executable to run.
28 env: The environment variables to set.
29 done_queue: A queue.Queue to place self on once done running the test.
30 start_semaphore: A threading.Semaphore to wait on before starting.
31 process_lock: A lock around process.
32 process: The currently executing test process or None. Synchronized by
33 process_lock.
34 stopped: True if we're stopped.
35 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070036 def __init__(self, executable, env, done_queue, start_semaphore):
37 super(TestThread, self).__init__(
38 name=os.path.split(executable)[1])
39
40 self.executable = executable
41 self.env = env
42 self.done_queue = done_queue
43 self.start_semaphore = start_semaphore
44
45 self.process_lock = threading.Lock()
46 self.process = None
47 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070048 self.returncode = None
49 self.output = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070050
51 def run(self):
52 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070053 if self.stopped:
54 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070055 test_output('Starting test %s...' % self.name)
Brian Silvermanf2bbe092014-05-13 16:55:03 -070056 self.output, subprocess_output = pty.openpty()
57 try:
58 with self.process_lock:
59 self.process = subprocess.Popen(self.executable,
60 env=self.env,
61 stderr=subprocess.STDOUT,
62 stdout=subprocess_output,
63 stdin=open(os.devnull, 'r'))
64 finally:
65 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -070066 self.process.wait()
67 with self.process_lock:
68 self.returncode = self.process.returncode
69 self.process = None
70 if not self.stopped:
71 self.done_queue.put(self)
72
73 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070074 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070075 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070076 if not self.process:
77 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070078 self.process.terminate()
79 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070080 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070081 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070082 if not self.process:
83 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070084 self.process.kill()
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070085 def stop(self):
86 """Changes self to the stopped state."""
87 with self.process_lock:
88 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -050089
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050090def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -070091 """Returns:
92 A relative path to the aos directory.
93 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050094 return os.path.join(os.path.dirname(__file__), '..')
95
96def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -070097 """Retrieves the IP address for a given device."""
98 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -070099 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500100 if not os.access(FILENAME, os.R_OK):
101 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
102 with open(FILENAME, 'w') as f:
103 f.write('10.9.71')
104 with open(FILENAME, 'r') as f:
105 base = f.readline()
106 if device == 'prime':
107 return base + '.179'
108 elif device == 'robot':
109 return base + '.2'
110 else:
111 raise Exception('Unknown device %s to get an IP address for.' % device)
112
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700113def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700114 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700115 print('build.py: ' + message, file=sys.stderr)
116
Brian Silverman452aaec2014-05-05 16:52:18 -0700117# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700118test_output_lock = threading.RLock()
119def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700120 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700121 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700122 print('tests: ' + message, file=sys.stdout)
123
124def call_download_externals(argument):
125 """Calls download_externals.sh for a given set of externals.
126
127 Args:
128 argument: The argument to pass to the shell script to tell it what to
129 download.
130 """
131 subprocess.check_call(
132 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
133 argument),
134 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700135
Brian Silvermana29ebf92014-04-23 13:08:49 -0500136class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700137 """Represents a processor architecture we can build for."""
138
Brian Silvermana29ebf92014-04-23 13:08:49 -0500139 class UnknownPlatform(Exception):
140 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700141 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500142 self.message = message
143
Brian Silvermanb3d50542014-04-23 14:28:55 -0500144 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700145 """Represents a single way to build the code."""
146
Brian Silvermanb3d50542014-04-23 14:28:55 -0500147 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700148 """Returns:
149 The path of the directory build outputs get put in to.
150 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500151 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500152 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500153 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700154 """Returns:
155 The path of the build.ninja file.
156 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500157 return os.path.join(self.outdir(), 'build.ninja')
158
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500159 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700160 """Helper for subclasses to implement deploy.
161
162 Args:
163 dry_run: If True, prints the command instead of actually running it.
164 command: A tuple of command-line arguments.
165 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500166 real_command = (('echo',) + command) if dry_run else command
167 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500168
Brian Silvermane6bada62014-05-04 16:16:54 -0700169 def deploy(self, dry_run):
170 """Downloads the compiled code to the target computer."""
171 raise NotImplementedError('deploy should be overriden')
172 def outname(self):
173 """Returns:
174 The name of the directory the code will be compiled to.
175 """
176 raise NotImplementedError('outname should be overriden')
177 def os(self):
178 """Returns:
179 The name of the operating system this platform is for.
180
181 This will be used as the value of the OS gyp variable.
182 """
183 raise NotImplementedError('os should be overriden')
184 def gyp_platform(self):
185 """Returns:
186 The platform name the .gyp files know.
187
188 This will be used as the value of the PLATFORM gyp variable.
189 """
190 raise NotImplementedError('gyp_platform should be overriden')
191 def architecture(self):
192 """Returns:
193 The processor architecture for this platform.
194
195 This will be used as the value of the ARCHITECTURE gyp variable.
196 """
197 raise NotImplementedError('architecture should be overriden')
198 def compiler(self):
199 """Returns:
200 The compiler used for this platform.
201
202 Everything before the first _ will be used as the value of the
203 COMPILER gyp variable and the whole thing will be used as the value
204 of the FULL_COMPILER gyp variable.
205 """
206 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700207 def sanitizer(self):
208 """Returns:
209 The sanitizer used on this platform.
210
211 This will be used as the value of the SANITIZER gyp variable.
212
213 "none" if there isn't one.
214 """
215 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700216 def debug(self):
217 """Returns:
218 Whether or not this platform compiles with debugging information.
219
220 The DEBUG gyp variable will be set to "yes" or "no" based on this.
221 """
222 raise NotImplementedError('debug should be overriden')
223 def build_env(self):
224 """Returns:
225 A map of environment variables to set while building this platform.
226 """
227 raise NotImplementedError('build_env should be overriden')
228
Brian Silverman9b7a6842014-05-05 16:19:11 -0700229 def check_installed(self, platforms, is_deploy):
230 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700231 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700232 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700233 """Args:
234 string: A user-supplied string saying which platforms to select.
235
236 Returns:
237 A tuple of Platform objects.
238
239 Raises:
240 Processor.UnknownPlatform: Parsing string didn't work out.
241 """
242 raise NotImplementedError('parse_platforms should be overriden')
243 def extra_gyp_flags(self):
244 """Returns:
245 A tuple of extra flags to pass to gyp (if any).
246 """
247 return ()
248 def modify_ninja_file(self, ninja_file):
249 """Modifies a freshly generated ninja file as necessary.
250
251 Args:
252 ninja_file: Path to the file to modify.
253 """
254 pass
255 def download_externals(self, platforms):
256 """Calls download_externals as appropriate to build platforms.
257
258 Args:
259 platforms: A list of platforms to download external libraries for.
260 """
261 raise NotImplementedError('download_externals should be overriden')
262
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700263 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700264 """Helper for subclasses to implement check_installed.
265
266 Args:
267 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700268 all_packages = other_packages
269 # Necessary to build stuff.
270 all_packages += ('ccache', 'make')
271 # Necessary to download stuff to build.
272 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
273 # Necessary to build externals stuff.
274 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700275 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700276 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700277 result = subprocess.check_output(
278 ('dpkg-query', '--show') + all_packages,
279 stdin=open(os.devnull, 'r'),
280 stderr=subprocess.STDOUT)
281 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700282 output = e.output.decode('utf-8').rstrip()
283 not_found = []
284 for line in output.splitlines(True):
285 match = re.match(r'dpkg-query: no packages found matching (.*)',
286 line)
287 if match:
288 not_found.append(match.group(1))
289 user_output('Some packages not installed: %s.' % ', '.join(not_found))
290 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700291 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700292 exit(1)
293
Brian Silvermana29ebf92014-04-23 13:08:49 -0500294class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700295 """A Processor subclass for building cRIO code."""
296
Brian Silvermanb3d50542014-04-23 14:28:55 -0500297 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700298 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500299 super(CRIOProcessor.Platform, self).__init__()
300
Brian Silvermane6bada62014-05-04 16:16:54 -0700301 self.__debug = debug
302 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500303
304 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700305 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500306 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700307 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500308
309 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700310 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500311 def os(self):
312 return 'vxworks'
313 def gyp_platform(self):
314 return 'crio'
315 def architecture(self):
316 return 'ppc'
317 def compiler(self):
318 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700319 def sanitizer(self):
320 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700321 def debug(self):
322 return self.__debug
323 def wind_base(self):
324 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500325
Brian Silvermane48c09a2014-04-30 18:04:58 -0700326 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500327 def deploy(self, dry_run):
328 self.do_deploy(dry_run,
329 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700330 os.path.join(self.outdir(), 'lib',
331 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500332
Brian Silvermana4aff562014-05-02 17:43:50 -0700333 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700334 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700335
Brian Silvermana29ebf92014-04-23 13:08:49 -0500336 def __init__(self):
337 super(CRIOProcessor, self).__init__()
338
339 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700340 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500341 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700342 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500343
Brian Silverman452aaec2014-05-05 16:52:18 -0700344 def parse_platforms(self, platforms_string):
345 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700346 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700347 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700348 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500349 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700350 raise Processor.UnknownPlatform(
351 'Unknown cRIO platform "%s".' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500352
Brian Silvermane6bada62014-05-04 16:16:54 -0700353 def wind_base(self):
354 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500355
Brian Silvermane6bada62014-05-04 16:16:54 -0700356 def extra_gyp_flags(self):
357 return ('-DWIND_BASE=%s' % self.wind_base(),)
358
359 def modify_ninja_file(self, ninja_file):
360 subprocess.check_call(
361 ('sed', '-i',
362 's/nm -gD/nm/g', ninja_file),
363 stdin=open(os.devnull, 'r'))
364
365 def download_externals(self, _):
366 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500367
Brian Silverman9b7a6842014-05-05 16:19:11 -0700368 def check_installed(self, platforms, is_deploy):
369 packages = ('powerpc-wrs-vxworks', 'tcl')
370 if is_deploy:
371 packages += ('ncftp',)
372 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700373
Brian Silvermana29ebf92014-04-23 13:08:49 -0500374class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700375 """A Processor subclass for building prime code."""
376
Brian Silvermanb3d50542014-04-23 14:28:55 -0500377 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700378 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500379 super(PrimeProcessor.Platform, self).__init__()
380
Brian Silvermane6bada62014-05-04 16:16:54 -0700381 self.__architecture = architecture
382 self.__compiler = compiler
383 self.__debug = debug
384 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500385
386 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700387 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
388 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700389 % (self.architecture(), self.compiler(), self.debug(),
390 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500391 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700392 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700393 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500394
395 def os(self):
396 return 'linux'
397 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700398 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
399 def architecture(self):
400 return self.__architecture
401 def compiler(self):
402 return self.__compiler
403 def sanitizer(self):
404 return self.__sanitizer
405 def debug(self):
406 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500407
Brian Silvermana29ebf92014-04-23 13:08:49 -0500408 def outname(self):
409 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500410
Brian Silvermane48c09a2014-04-30 18:04:58 -0700411 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500412 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700413 # Downloads code to the prime in a way that avoids clashing too badly with
414 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500415 SUM = 'md5sum'
416 TARGET_DIR = '/home/driver/robot_code/bin'
417 TEMP_DIR = '/tmp/aos_downloader'
418 TARGET = 'driver@' + get_ip('prime')
419
420 from_dir = os.path.join(self.outdir(), 'outputs')
421 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
422 stdin=open(os.devnull, 'r'),
423 cwd=from_dir)
424 to_download = subprocess.check_output(
425 ('ssh', TARGET,
426 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
427 && echo '{SUMS}' | {SUM} --check --quiet
Brian Silverman452aaec2014-05-05 16:52:18 -0700428 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".
429 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500430 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700431 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500432 return
433 self.do_deploy(
434 dry_run,
435 ('scp', '-o', 'Compression yes') + to_download
436 + (('%s:%s' % (TARGET, TEMP_DIR)),))
437 if not dry_run:
438 subprocess.check_call(
439 ('ssh', TARGET,
440 """mv {TMPDIR}/* {TO_DIR}
441 && echo 'Done moving new executables into place'
442 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
443 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
444
Brian Silvermana4aff562014-05-02 17:43:50 -0700445 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700446 OTHER_SYSROOT = '/opt/clang-3.5/'
447 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700448 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700449 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
450 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
451 if self.sanitizer() == 'address':
452 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700453 r['ASAN_OPTIONS'] = \
454 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700455 elif self.sanitizer() == 'memory':
456 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
457 elif self.sanitizer() == 'thread':
458 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700459
460 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700461 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
462 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700463 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700464 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700465 # clang doesn't like being run directly on the preprocessed files.
466 r['CCACHE_CPP2'] = 'yes'
467 # Without this, ccache slows down because of the generated header files.
468 # The race condition that this opens up isn't a problem because the build
469 # system finishes modifying header files before compiling anything that
470 # uses them.
471 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700472
Brian Silvermane6bada62014-05-04 16:16:54 -0700473 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700474 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
475 ':' + os.environ['PATH']
476
Brian Silvermana4aff562014-05-02 17:43:50 -0700477 return r
478
Brian Silverman47cd6f62014-05-03 10:35:52 -0700479 ARCHITECTURES = ('arm', 'amd64')
480 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
481 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
482 SANITIZER_TEST_WARNINGS = {
483 'memory': (True,
484"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700485 errors with msan (especially stdlibc++).
486 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700487 'undefined': (False,
488"""There are several warnings in other people's code that ubsan catches.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700489 The following have been verified non-interesting:
Brian Silverman452aaec2014-05-05 16:52:18 -0700490 include/c++/4.8.2/array:*: runtime error: reference binding to null pointer
491 of type 'int'
492 This happens with ::std::array<T, 0> and it doesn't seem to cause any
493 issues.
494 output/downloaded/eigen-3.2.1/Eigen/src/Core/util/Memory.h:782:*: runtime
495 error: load of misaligned address 0x* for type 'const int', which
496 requires 4 byte alignment
Brian Silverman47cd6f62014-05-03 10:35:52 -0700497 That's in the CPUID detection code which only runs on x86."""),
498 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700499 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500500
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500501 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700502 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500503
504 platforms = []
505 for architecture in PrimeProcessor.ARCHITECTURES:
506 for compiler in PrimeProcessor.COMPILERS:
507 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700508 if architecture == 'arm' and compiler == 'gcc_4.8':
509 # We don't have a compiler to use here.
510 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500511 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700512 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
513 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700514 for compiler in ('gcc_4.8', 'clang'):
515 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
516 sanitizer == 'integer' or
517 sanitizer == 'memory'):
518 # GCC 4.8 doesn't support these sanitizers.
519 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700520 if sanitizer == 'none':
521 # We already added sanitizer == 'none' above.
522 continue
523 platforms.append(
524 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
525 self.__platforms = frozenset(platforms)
526
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500527 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700528 default_platforms = self.select_platforms(architecture='amd64',
529 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700530 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
531 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700532 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500533 elif is_deploy:
534 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700535 default_platforms = self.select_platforms(architecture='arm',
536 compiler='gcc',
537 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500538 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700539 default_platforms = self.select_platforms(debug=False)
540 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500541
Brian Silvermane6bada62014-05-04 16:16:54 -0700542 def platforms(self):
543 return self.__platforms
544 def default_platforms(self):
545 return self.__default_platforms
546
547 def download_externals(self, platforms):
548 to_download = set()
549 for architecture in PrimeProcessor.ARCHITECTURES:
550 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
551 if platforms & self.select_platforms(architecture=architecture,
552 sanitizer=sanitizer):
553 to_download.add(architecture + '-fPIE')
554 if platforms & self.select_platforms(architecture=architecture,
555 sanitizer='none'):
556 to_download.add(architecture)
557 for download_target in to_download:
558 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500559
Brian Silverman452aaec2014-05-05 16:52:18 -0700560 def parse_platforms(self, platform_string):
561 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700562 return self.default_platforms()
563 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700564 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700565 if part == 'all':
566 r = self.platforms()
567 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500568 r = r | self.select_platforms_string(part[1:])
569 elif part[0] == '-':
570 r = r - self.select_platforms_string(part[1:])
571 elif part[0] == '=':
572 r = self.select_platforms_string(part[1:])
573 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500574 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700575 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500576 if not r:
577 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500578 return r
579
Brian Silverman452aaec2014-05-05 16:52:18 -0700580 def select_platforms(self, architecture=None, compiler=None, debug=None,
581 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500582 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700583 for platform in self.platforms():
584 if architecture is None or platform.architecture() == architecture:
585 if compiler is None or platform.compiler() == compiler:
586 if debug is None or platform.debug() == debug:
587 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700588 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500589 return set(r)
590
Brian Silverman452aaec2014-05-05 16:52:18 -0700591 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700592 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700593 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500594 if part in PrimeProcessor.ARCHITECTURES:
595 architecture = part
596 elif part in PrimeProcessor.COMPILERS:
597 compiler = part
598 elif part in ['debug', 'dbg']:
599 debug = True
600 elif part in ['release', 'nodebug', 'ndb']:
601 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700602 elif part in PrimeProcessor.SANITIZERS:
603 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500604 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700605 raise Processor.UnknownPlatform(
606 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500607 return self.select_platforms(
608 architecture=architecture,
609 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700610 debug=debug,
611 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500612
Brian Silverman9b7a6842014-05-05 16:19:11 -0700613 def check_installed(self, platforms, is_deploy):
614 packages = set(('lzip', 'm4', 'realpath'))
615 packages.add('ruby')
616 # clang-format from here gets used for all versions.
617 packages.add('clang-3.5')
618 packages.add('arm-eabi-gcc')
619 for platform in platforms:
620 if platform.architecture() == 'arm':
621 packages.add('gcc-4.7-arm-linux-gnueabihf')
622 packages.add('g++-4.7-arm-linux-gnueabihf')
623 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
624 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700625 if platform.compiler() == 'gcc_4.8':
626 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700627 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)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700899 with os.fdopen(done.output) as output_file:
900 try:
901 for line in output_file:
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)
906 except IOError as e:
907# We want to just ignore EIOs from reading the master pty because that just
908# means we hit the end.
909 if e.errno != errno.EIO:
910 raise e
911 if not done.returncode:
912 test_output('Test %s succeeded' % done.name)
913 else:
914 test_output('Test %s failed' % done.name)
915 user_output('Aborting because of test failure.')
916 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700917 finally:
918 if running:
919 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700920# Stop all of them before killing processes because otherwise stopping some of
921# them tends to let other ones that are waiting to start go.
922 for thread in running:
923 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700924 for thread in running:
925 thread.terminate_process()
926 to_remove = []
927 for thread in running:
928 thread.join(5)
929 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -0700930 to_remove.append(thread)
931 for thread in to_remove:
932 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700933 for thread in running:
934 test_output(
935 'Test %s did not terminate. Killing it.' % thread.name)
936 thread.kill_process()
937 thread.join()
938 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500939
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700940 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
941 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500942
943if __name__ == '__main__':
944 main()