blob: 27e11d5c1225ea68bcad3d65263c28ba4ff3c330 [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):
16 def __init__(self, executable, env, done_queue, start_semaphore):
17 super(TestThread, self).__init__(
18 name=os.path.split(executable)[1])
19
20 self.executable = executable
21 self.env = env
22 self.done_queue = done_queue
23 self.start_semaphore = start_semaphore
24
25 self.process_lock = threading.Lock()
26 self.process = None
27 self.stopped = False
28
29 def run(self):
30 with self.start_semaphore:
31 if self.stopped: return
32 test_output('Starting test %s...' % self.name)
33 self.output, subprocess_output = os.pipe()
34 with self.process_lock:
35 self.process = subprocess.Popen((self.executable,
36 '--gtest_color=yes'),
37 env=self.env,
38 stderr=subprocess.STDOUT,
39 stdout=subprocess_output,
40 stdin=open(os.devnull, 'r'))
41 os.close(subprocess_output)
42 self.process.wait()
43 with self.process_lock:
44 self.returncode = self.process.returncode
45 self.process = None
46 if not self.stopped:
47 self.done_queue.put(self)
48
49 def terminate_process(self):
50 with self.process_lock:
51 self.stopped = True
52 if not self.process: return
53 self.process.terminate()
54 def kill_process(self):
55 with self.process_lock:
56 self.stopped = True
57 if not self.process: return
58 self.process.kill()
Brian Silvermana29ebf92014-04-23 13:08:49 -050059
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050060def aos_path():
61 return os.path.join(os.path.dirname(__file__), '..')
62
63def get_ip(device):
64 FILENAME = os.path.normpath(os.path.join(aos_path(), '..', 'output', 'ip_base.txt'))
65 if not os.access(FILENAME, os.R_OK):
66 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
67 with open(FILENAME, 'w') as f:
68 f.write('10.9.71')
69 with open(FILENAME, 'r') as f:
70 base = f.readline()
71 if device == 'prime':
72 return base + '.179'
73 elif device == 'robot':
74 return base + '.2'
75 else:
76 raise Exception('Unknown device %s to get an IP address for.' % device)
77
Brian Silvermana9b1e5c2014-04-30 18:08:04 -070078def user_output(message):
79 print('build.py: ' + message, file=sys.stderr)
80
Brian Silvermanc3740c32014-05-04 12:42:47 -070081test_output_lock = threading.RLock()
82def test_output(message):
83 with test_output_lock:
84 print('build.py: ' + message, file=sys.stdout)
85
Brian Silvermana29ebf92014-04-23 13:08:49 -050086class Processor(object):
87 class UnknownPlatform(Exception):
88 def __init__(self, message):
89 self.message = message
90
Brian Silvermanb3d50542014-04-23 14:28:55 -050091 class Platform(object):
92 def outdir(self):
93 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050094 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -050095 def build_ninja(self):
96 return os.path.join(self.outdir(), 'build.ninja')
97
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050098 def do_deploy(self, dry_run, command):
99 real_command = (('echo',) + command) if dry_run else command
100 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500101
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700102 # TODO(brians): Verify that this (and its callers) catch everything from a
103 # fresh install.
104 def do_check_installed(self, other_packages):
105 all_packages = () + other_packages
106 try:
107 result = subprocess.check_output(
108 ('dpkg-query', '--show') + all_packages,
109 stdin=open(os.devnull, 'r'),
110 stderr=subprocess.STDOUT)
111 except subprocess.CalledProcessError as e:
112 user_output('Some packages not installed:\n'
113 + e.output.decode('utf-8').rstrip())
114 exit(1)
115
Brian Silvermana29ebf92014-04-23 13:08:49 -0500116class CRIOProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500117 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700118 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500119 super(CRIOProcessor.Platform, self).__init__()
120
121 self.debug = debug
Brian Silvermana4aff562014-05-02 17:43:50 -0700122 self.wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500123
124 def __repr__(self):
125 return 'CRIOProcessor.Platform(debug=%s)' % self.debug
126 def __str__(self):
127 return 'crio%s' % ('-debug' if self.debug else '')
128
129 def outname(self):
130 return 'crio-debug' if self.debug else 'crio'
131 def os(self):
132 return 'vxworks'
133 def gyp_platform(self):
134 return 'crio'
135 def architecture(self):
136 return 'ppc'
137 def compiler(self):
138 return 'gcc'
139
Brian Silvermane48c09a2014-04-30 18:04:58 -0700140 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500141 def deploy(self, dry_run):
142 self.do_deploy(dry_run,
143 ('ncftpput', get_ip('robot'), '/',
144 os.path.join(self.outdir(), 'lib', 'FRC_UserProgram.out')))
145
Brian Silvermana4aff562014-05-02 17:43:50 -0700146 def build_env(self):
147 return {'WIND_BASE': self.wind_base}
148
Brian Silvermana29ebf92014-04-23 13:08:49 -0500149 def __init__(self):
150 super(CRIOProcessor, self).__init__()
151
152 if 'WIND_BASE' in os.environ:
153 self.wind_base = os.environ['WIND_BASE']
154 else:
155 self.wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
156
157 def parse_platforms(self, string):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500158 if string is None or string == 'crio':
Brian Silvermana4aff562014-05-02 17:43:50 -0700159 return (CRIOProcessor.Platform(False, self.wind_base),)
160 elif string == 'crio-debug' or string == 'debug':
161 return (CRIOProcessor.Platform(True, self.wind_base),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500162 else:
163 raise Processor.UnknownPlatform('Unknown cRIO platform "%s".' % string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500164
Brian Silvermanb3d50542014-04-23 14:28:55 -0500165 def extra_gyp_flags(self):
166 return ('-DWIND_BASE=%s' % self.wind_base,)
167
Brian Silvermana29ebf92014-04-23 13:08:49 -0500168 def is_crio(self): return True
169
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700170 def check_installed(self):
171 # TODO(brians): Add powerpc-wrs-vxworks (a new enough version too).
172 self.do_check_installed(
173 ('ncftp',))
174
Brian Silvermana29ebf92014-04-23 13:08:49 -0500175class PrimeProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500176 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700177 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500178 super(PrimeProcessor.Platform, self).__init__()
179
Brian Silvermana29ebf92014-04-23 13:08:49 -0500180 self.architecture = architecture
181 self.compiler = compiler
182 self.debug = debug
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700183 self.sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500184
185 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700186 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
187 ', sanitizer=%s)' \
188 % (self.architecture, self.compiler, self.debug, self.sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500189 def __str__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700190 return '%s-%s%s-%s' % (self.architecture, self.compiler,
191 '-debug' if self.debug else '', self.sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500192
193 def os(self):
194 return 'linux'
195 def gyp_platform(self):
196 return '%s-%s-%s' % (self.os(), self.architecture, self.compiler)
197
Brian Silvermana29ebf92014-04-23 13:08:49 -0500198 def outname(self):
199 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500200
Brian Silvermane48c09a2014-04-30 18:04:58 -0700201 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500202 def deploy(self, dry_run):
203 """Downloads code to the prime in a way that avoids clashing too badly with starter
204 """
205 SUM = 'md5sum'
206 TARGET_DIR = '/home/driver/robot_code/bin'
207 TEMP_DIR = '/tmp/aos_downloader'
208 TARGET = 'driver@' + get_ip('prime')
209
210 from_dir = os.path.join(self.outdir(), 'outputs')
211 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
212 stdin=open(os.devnull, 'r'),
213 cwd=from_dir)
214 to_download = subprocess.check_output(
215 ('ssh', TARGET,
216 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
217 && echo '{SUMS}' | {SUM} --check --quiet
218 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".format(
219 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
220 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700221 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500222 return
223 self.do_deploy(
224 dry_run,
225 ('scp', '-o', 'Compression yes') + to_download
226 + (('%s:%s' % (TARGET, TEMP_DIR)),))
227 if not dry_run:
228 subprocess.check_call(
229 ('ssh', TARGET,
230 """mv {TMPDIR}/* {TO_DIR}
231 && echo 'Done moving new executables into place'
232 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
233 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
234
Brian Silvermana4aff562014-05-02 17:43:50 -0700235 def build_env(self):
236 r = {}
Brian Silverman47cd6f62014-05-03 10:35:52 -0700237 if self.compiler == 'clang' or self.compiler == 'gcc_4.8':
Brian Silvermana4aff562014-05-02 17:43:50 -0700238 r['LD_LIBRARY_PATH'] = '/opt/clang-3.5/lib64'
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700239 if self.sanitizer == 'address':
Brian Silvermana4aff562014-05-02 17:43:50 -0700240 r['ASAN_SYMBOLIZER_PATH'] = '/opt/clang-3.5/bin/llvm-symbolizer'
241 r['ASAN_OPTIONS'] = 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700242 elif self.sanitizer == 'memory':
243 r['MSAN_SYMBOLIZER_PATH'] = '/opt/clang-3.5/bin/llvm-symbolizer'
Brian Silverman266811f2014-05-03 22:48:04 -0700244 elif self.sanitizer == 'thread':
245 r['TSAN_OPTIONS'] = 'external_symbolizer_path=/opt/clang-3.5/bin/llvm-symbolizer'
Brian Silvermand3fac732014-05-03 16:03:46 -0700246
247 r['CCACHE_COMPRESS'] = 'yes'
248 r['CCACHE_DIR'] = \
249 os.path.abspath(os.path.join(aos_path(), '..', 'output', 'ccache_dir'))
250 r['CCACHE_HASHDIR'] = 'yes'
251 if self.compiler == 'clang':
252 # clang doesn't like being run directly on the preprocessed files.
253 r['CCACHE_CPP2'] = 'yes'
254 # Without this, ccache slows down because of the generated header files.
255 # The race condition that this opens up isn't a problem because the build
256 # system finishes modifying header files before compiling anything that
257 # uses them.
258 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700259
260 if self.architecture == 'amd64':
261 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
262 ':' + os.environ['PATH']
263
Brian Silvermana4aff562014-05-02 17:43:50 -0700264 return r
265
Brian Silverman47cd6f62014-05-03 10:35:52 -0700266 ARCHITECTURES = ('arm', 'amd64')
267 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
268 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
269 SANITIZER_TEST_WARNINGS = {
270 'memory': (True,
271"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700272 errors with msan (especially stdlibc++).
273 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700274 'undefined': (False,
275"""There are several warnings in other people's code that ubsan catches.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700276 The following have been verified non-interesting:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700277 include/c++/4.8.2/array:*: runtime error: reference binding to null pointer of type 'int'
278 This happens with ::std::array<T, 0> and it doesn't seem to cause any issues.
279 output/downloaded/eigen-3.2.1/Eigen/src/Core/util/Memory.h:782:*: runtime error: load of misaligned address 0x* for type 'const int', which requires 4 byte alignment
280 That's in the CPUID detection code which only runs on x86."""),
281 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700282 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500283
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500284 def __init__(self, is_test, is_deploy):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500285 super(Processor, self).__init__()
286
287 platforms = []
288 for architecture in PrimeProcessor.ARCHITECTURES:
289 for compiler in PrimeProcessor.COMPILERS:
290 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700291 if architecture == 'arm' and compiler == 'gcc_4.8':
292 # We don't have a compiler to use here.
293 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500294 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700295 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
296 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700297 for compiler in ('gcc_4.8', 'clang'):
298 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
299 sanitizer == 'integer' or
300 sanitizer == 'memory'):
301 # GCC 4.8 doesn't support these sanitizers.
302 continue
303 # We already added sanitizer == 'none' above.
304 if sanitizer != 'none':
305 platforms.append(
306 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500307 self.platforms = frozenset(platforms)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500308 if is_test:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700309 self.default_platforms = self.select_platforms(architecture='amd64',
310 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700311 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
312 if warning[0]:
313 self.default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500314 elif is_deploy:
315 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700316 self.default_platforms = self.select_platforms(architecture='arm',
317 compiler='gcc',
318 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500319 else:
320 self.default_platforms = self.select_platforms(debug=False)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500321
Brian Silvermanb3d50542014-04-23 14:28:55 -0500322 def extra_gyp_flags(self):
323 return ()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500324 def is_crio(self): return False
325
326 def parse_platforms(self, string):
327 if string is None:
328 return self.default_platforms
329 r = self.default_platforms
330 for part in string.split(','):
331 if part[0] == '+':
332 r = r | self.select_platforms_string(part[1:])
333 elif part[0] == '-':
334 r = r - self.select_platforms_string(part[1:])
335 elif part[0] == '=':
336 r = self.select_platforms_string(part[1:])
337 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500338 selected = self.select_platforms_string(part)
339 r = r - (self.platforms - selected)
340 if not r:
341 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500342 return r
343
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700344 def select_platforms(self, architecture=None, compiler=None, debug=None, sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500345 r = []
346 for platform in self.platforms:
347 if architecture is None or platform.architecture == architecture:
348 if compiler is None or platform.compiler == compiler:
349 if debug is None or platform.debug == debug:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700350 if sanitizer is None or platform.sanitizer == sanitizer:
351 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500352 return set(r)
353
354 def select_platforms_string(self, string):
355 r = []
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700356 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500357 for part in string.split('-'):
358 if part in PrimeProcessor.ARCHITECTURES:
359 architecture = part
360 elif part in PrimeProcessor.COMPILERS:
361 compiler = part
362 elif part in ['debug', 'dbg']:
363 debug = True
364 elif part in ['release', 'nodebug', 'ndb']:
365 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700366 elif part in PrimeProcessor.SANITIZERS:
367 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500368 else:
369 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
370 return self.select_platforms(
371 architecture=architecture,
372 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700373 debug=debug,
374 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500375
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700376 def check_installed(self):
377 self.do_check_installed(
378 ('clang-3.5', 'gcc-4.7-arm-linux-gnueabihf',
379 'g++-4.7-arm-linux-gnueabihf', 'openssh-client'))
380
Brian Silvermana29ebf92014-04-23 13:08:49 -0500381def main():
382 class TryParsingAgain(Exception):
383 pass
384
385 class TryAgainArgumentParser(argparse.ArgumentParser):
386 def __init__(self, **kwargs):
387 super(TryAgainArgumentParser, self).__init__(**kwargs)
388
389 def error(self, message):
390 raise TryParsingAgain
391
392 def SetUpParser(parser, args):
393 def AddBuildArgs(parser):
394 parser.add_argument(
395 'target',
396 help='target to build',
397 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700398 parser.add_argument(
399 '--jobs', '-j',
400 help='number of things to do at once',
401 type=int)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500402 def AddCommonArgs(parser):
403 parser.add_argument(
404 'platforms',
405 help='platform(s) to act on',
406 nargs='?')
407
408 parser.add_argument('--processor', required=True, help='prime or crio')
409 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
410 subparsers = parser.add_subparsers(dest='action_name')
411
412 build_parser = subparsers.add_parser(
413 'build',
414 help='build the code (default)')
415 AddCommonArgs(build_parser)
416 AddBuildArgs(build_parser)
417
418 clean_parser = subparsers.add_parser(
419 'clean',
420 help='remove all output directories')
421 AddCommonArgs(clean_parser)
422
423 deploy_parser = subparsers.add_parser(
424 'deploy',
425 help='build and download the code')
426 AddCommonArgs(deploy_parser)
427 AddBuildArgs(deploy_parser)
428 deploy_parser.add_argument(
429 '-n', '--dry-run',
430 help="don't actually download anything",
431 action='store_true')
432
Brian Silvermane48c09a2014-04-30 18:04:58 -0700433 tests_parser = subparsers.add_parser(
434 'tests',
435 help='run tests')
436 AddCommonArgs(tests_parser)
437 AddBuildArgs(tests_parser)
438
Brian Silvermana29ebf92014-04-23 13:08:49 -0500439 return parser.parse_args(args)
440
441 try:
442 parser = TryAgainArgumentParser()
443 args = SetUpParser(parser, sys.argv[1:])
444 except TryParsingAgain:
445 parser = argparse.ArgumentParser()
446 REQUIRED_ARGS_END = 5
447 args = SetUpParser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
448 sys.argv[(REQUIRED_ARGS_END):])
449
450 if args.processor == 'crio':
451 processor = CRIOProcessor()
452 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500453 processor = PrimeProcessor(args.action_name == 'tests',
454 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500455 else:
456 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700457 processor.check_installed()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500458
459 if 'target' in args:
460 targets = args.target[:]
461 else:
462 targets = []
463 unknown_platform_error = None
464 try:
465 platforms = processor.parse_platforms(args.platforms)
466 except Processor.UnknownPlatform as e:
467 unknown_platform_error = e.message
468 targets.append(args.platforms)
469 platforms = processor.parse_platforms(None)
470 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700471 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500472 exit(1)
473
474 def download_externals(argument):
475 subprocess.check_call(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500476 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500477 argument),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500478 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500479
480 if processor.is_crio():
481 download_externals('crio')
482 else:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700483 to_download = set()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500484 for architecture in PrimeProcessor.ARCHITECTURES:
Brian Silvermana5301e32014-05-03 10:51:49 -0700485 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700486 if platforms & processor.select_platforms(architecture=architecture,
487 sanitizer=sanitizer):
Brian Silvermana5301e32014-05-03 10:51:49 -0700488 to_download.add(architecture + '-fPIE')
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700489 if platforms & processor.select_platforms(architecture=architecture,
490 sanitizer='none'):
491 to_download.add(architecture)
492 for download_target in to_download:
493 download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500494
495 class ToolsConfig(object):
496 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500497 self.variables = {'AOS': aos_path()}
498 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500499 for line in f:
500 if line[0] == '#':
501 pass
502 elif line.isspace():
503 pass
504 else:
505 new_name, new_value = line.rstrip().split('=')
506 for name, value in self.variables.items():
507 new_value = new_value.replace('${%s}' % name, value)
508 self.variables[new_name] = new_value
509 def __getitem__(self, key):
510 return self.variables[key]
511
512 tools_config = ToolsConfig()
513
514 def handle_clean_error(function, path, excinfo):
515 if issubclass(OSError, excinfo[0]):
516 if excinfo[1].errno == errno.ENOENT:
517 # Who cares if the file we're deleting isn't there?
518 return
519 raise excinfo[1]
520
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700521 def need_to_run_gyp(platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700522 if not os.path.exists(platform.build_ninja()):
523 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700524 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700525 # Looking through these folders takes a long time and isn't useful.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700526 dirs.remove('output')
Brian Silvermana4aff562014-05-02 17:43:50 -0700527 dirs.remove('.git')
528 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700529 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
530 + ('-newer', platform.build_ninja(),
531 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700532 stdin=open(os.devnull, 'r'))
533
534 def env(platform):
535 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700536 if not 'TERM' in build_env:
537 build_env['TERM'] = os.environ['TERM']
538 if not 'PATH' in build_env:
539 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700540 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700541
Brian Silverman47cd6f62014-05-03 10:35:52 -0700542 to_build = []
543 for platform in platforms:
544 to_build.append(str(platform))
545 if len(to_build) > 1:
546 to_build[-1] = 'and ' + to_build[-1]
547 user_output('Building %s...' % ', '.join(to_build))
548
549 if args.action_name == 'tests':
550 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
551 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
552 if warned_about:
553 user_output(warning[1])
554 if warning[0]:
555 # TODO(brians): Add a --force flag or something?
556 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
557 exit(1)
558
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700559 num = 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500560 for platform in platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700561 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500562 if args.action_name == 'clean':
563 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
564 else:
565 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700566 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500567 gyp = subprocess.Popen(
568 (tools_config['GYP'],
569 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500570 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500571 '--no-circular-check',
572 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500573 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500574 '-I/dev/stdin', '-Goutput_dir=output',
575 '-DOS=%s' % platform.os(),
576 '-DPLATFORM=%s' % platform.gyp_platform(),
577 '-DARCHITECTURE=%s' % platform.architecture,
Brian Silverman47cd6f62014-05-03 10:35:52 -0700578 '-DCOMPILER=%s' % platform.compiler.split('_')[0],
579 '-DFULL_COMPILER=%s' % platform.compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700580 '-DDEBUG=%s' % ('yes' if platform.debug else 'no'),
581 '-DSANITIZER=%s' % platform.sanitizer,
Brian Silvermana5301e32014-05-03 10:51:49 -0700582 '-DSANITIZER_FPIE=%s' % ('-fPIE'
583 if platform.sanitizer in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700584 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500585 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500586 stdin=subprocess.PIPE)
587 gyp.communicate(("""
588{
589 'target_defaults': {
590 'configurations': {
591 '%s': {}
592 }
593 }
594}""" % platform.outname()).encode())
595 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700596 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500597 exit(1)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500598 if processor.is_crio():
599 subprocess.check_call(
600 ('sed', '-i',
601 's/nm -gD/nm/g', platform.build_ninja()),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500602 stdin=open(os.devnull, 'r'))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700603 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500604 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700605 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500606
607 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700608 call = (tools_config['NINJA'],
609 '-C', platform.outdir()) + tuple(targets)
610 if args.jobs:
611 call += ('-j', str(args.jobs))
612 subprocess.check_call(call,
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500613 stdin=open(os.devnull, 'r'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700614 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500615 except subprocess.CalledProcessError as e:
616 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700617 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500618 raise e
619
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500620 if args.action_name == 'deploy':
621 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700622 elif args.action_name == 'tests':
623 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700624 done_queue = queue.Queue()
625 running = []
626 if args.jobs:
627 number_jobs = args.jobs
628 else:
629 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
630 test_start_semaphore = threading.Semaphore(number_jobs)
631 if targets:
632 to_run = []
633 for target in targets:
634 if target.endswith('_test'):
635 to_run.append(target)
636 else:
637 to_run = os.listdir(dirname)
638 for f in to_run:
639 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
640 test_start_semaphore)
641 running.append(thread)
642 thread.start()
643 try:
644 while running:
645 done = done_queue.get()
646 running.remove(done)
647 with test_output_lock:
648 test_output('Output from test %s:' % done.name)
649 for line in os.fdopen(done.output):
650 if not sys.stdout.isatty():
651 # Remove color escape codes.
652 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
653 sys.stdout.write(line)
654 if done.returncode == 0:
655 test_output('Test %s succeeded' % done.name)
656 else:
657 test_output('Test %s failed' % done.name)
658 user_output('Aborting because of test failure.')
659 exit(1)
660 finally:
661 if running:
662 test_output('Killing other tests...')
663 for thread in running:
664 thread.terminate_process()
665 to_remove = []
666 for thread in running:
667 thread.join(5)
668 if not thread.is_alive():
669 to_remove.append(thread);
670 for thread in to_remove: running.remove(thread)
671 for thread in running:
672 test_output(
673 'Test %s did not terminate. Killing it.' % thread.name)
674 thread.kill_process()
675 thread.join()
676 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500677
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700678 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
679 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500680
681if __name__ == '__main__':
682 main()