blob: 4ad502103e80d8a3d478799d19f1f9f22b0c0be9 [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
47
48 def run(self):
49 with self.start_semaphore:
50 if self.stopped: return
51 test_output('Starting test %s...' % self.name)
52 self.output, subprocess_output = os.pipe()
53 with self.process_lock:
54 self.process = subprocess.Popen((self.executable,
55 '--gtest_color=yes'),
56 env=self.env,
57 stderr=subprocess.STDOUT,
58 stdout=subprocess_output,
59 stdin=open(os.devnull, 'r'))
60 os.close(subprocess_output)
61 self.process.wait()
62 with self.process_lock:
63 self.returncode = self.process.returncode
64 self.process = None
65 if not self.stopped:
66 self.done_queue.put(self)
67
68 def terminate_process(self):
Brian Silvermane6bada62014-05-04 16:16:54 -070069 """Asks any currently running process to stop.
70
71 Also changes this object to the stopped state.
72 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070073 with self.process_lock:
74 self.stopped = True
75 if not self.process: return
76 self.process.terminate()
77 def kill_process(self):
Brian Silvermane6bada62014-05-04 16:16:54 -070078 """Forcibly terminates any running process.
79
80 Also changes this object to the stopped state.
81 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070082 with self.process_lock:
83 self.stopped = True
84 if not self.process: return
85 self.process.kill()
Brian Silvermana29ebf92014-04-23 13:08:49 -050086
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050087def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -070088 """Returns:
89 A relative path to the aos directory.
90 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050091 return os.path.join(os.path.dirname(__file__), '..')
92
93def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -070094 """Retrieves the IP address for a given device."""
95 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
96 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050097 if not os.access(FILENAME, os.R_OK):
98 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
99 with open(FILENAME, 'w') as f:
100 f.write('10.9.71')
101 with open(FILENAME, 'r') as f:
102 base = f.readline()
103 if device == 'prime':
104 return base + '.179'
105 elif device == 'robot':
106 return base + '.2'
107 else:
108 raise Exception('Unknown device %s to get an IP address for.' % device)
109
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700110def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700111 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700112 print('build.py: ' + message, file=sys.stderr)
113
Brian Silvermane6bada62014-05-04 16:16:54 -0700114"""A lock to avoid making a mess intermingling test-related messages."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700115test_output_lock = threading.RLock()
116def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700117 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700118 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700119 print('tests: ' + message, file=sys.stdout)
120
121def call_download_externals(argument):
122 """Calls download_externals.sh for a given set of externals.
123
124 Args:
125 argument: The argument to pass to the shell script to tell it what to
126 download.
127 """
128 subprocess.check_call(
129 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
130 argument),
131 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700132
Brian Silvermana29ebf92014-04-23 13:08:49 -0500133class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700134 """Represents a processor architecture we can build for."""
135
Brian Silvermana29ebf92014-04-23 13:08:49 -0500136 class UnknownPlatform(Exception):
137 def __init__(self, message):
138 self.message = message
139
Brian Silvermanb3d50542014-04-23 14:28:55 -0500140 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700141 """Represents a single way to build the code."""
142
Brian Silvermanb3d50542014-04-23 14:28:55 -0500143 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700144 """Returns:
145 The path of the directory build outputs get put in to.
146 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500147 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500148 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500149 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700150 """Returns:
151 The path of the build.ninja file.
152 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500153 return os.path.join(self.outdir(), 'build.ninja')
154
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500155 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700156 """Helper for subclasses to implement deploy.
157
158 Args:
159 dry_run: If True, prints the command instead of actually running it.
160 command: A tuple of command-line arguments.
161 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500162 real_command = (('echo',) + command) if dry_run else command
163 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500164
Brian Silvermane6bada62014-05-04 16:16:54 -0700165 def deploy(self, dry_run):
166 """Downloads the compiled code to the target computer."""
167 raise NotImplementedError('deploy should be overriden')
168 def outname(self):
169 """Returns:
170 The name of the directory the code will be compiled to.
171 """
172 raise NotImplementedError('outname should be overriden')
173 def os(self):
174 """Returns:
175 The name of the operating system this platform is for.
176
177 This will be used as the value of the OS gyp variable.
178 """
179 raise NotImplementedError('os should be overriden')
180 def gyp_platform(self):
181 """Returns:
182 The platform name the .gyp files know.
183
184 This will be used as the value of the PLATFORM gyp variable.
185 """
186 raise NotImplementedError('gyp_platform should be overriden')
187 def architecture(self):
188 """Returns:
189 The processor architecture for this platform.
190
191 This will be used as the value of the ARCHITECTURE gyp variable.
192 """
193 raise NotImplementedError('architecture should be overriden')
194 def compiler(self):
195 """Returns:
196 The compiler used for this platform.
197
198 Everything before the first _ will be used as the value of the
199 COMPILER gyp variable and the whole thing will be used as the value
200 of the FULL_COMPILER gyp variable.
201 """
202 raise NotImplementedError('compiler should be overriden')
203 def debug(self):
204 """Returns:
205 Whether or not this platform compiles with debugging information.
206
207 The DEBUG gyp variable will be set to "yes" or "no" based on this.
208 """
209 raise NotImplementedError('debug should be overriden')
210 def build_env(self):
211 """Returns:
212 A map of environment variables to set while building this platform.
213 """
214 raise NotImplementedError('build_env should be overriden')
215
216 def check_installed(self):
217 """Makes sure that all packages necessary to build are installed."""
218 raise NotImplementedError('check_installed should be overriden')
219 def parse_platforms(self, string):
220 """Args:
221 string: A user-supplied string saying which platforms to select.
222
223 Returns:
224 A tuple of Platform objects.
225
226 Raises:
227 Processor.UnknownPlatform: Parsing string didn't work out.
228 """
229 raise NotImplementedError('parse_platforms should be overriden')
230 def extra_gyp_flags(self):
231 """Returns:
232 A tuple of extra flags to pass to gyp (if any).
233 """
234 return ()
235 def modify_ninja_file(self, ninja_file):
236 """Modifies a freshly generated ninja file as necessary.
237
238 Args:
239 ninja_file: Path to the file to modify.
240 """
241 pass
242 def download_externals(self, platforms):
243 """Calls download_externals as appropriate to build platforms.
244
245 Args:
246 platforms: A list of platforms to download external libraries for.
247 """
248 raise NotImplementedError('download_externals should be overriden')
249
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700250 # TODO(brians): Verify that this (and its callers) catch everything from a
251 # fresh install.
252 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700253 """Helper for subclasses to implement check_installed.
254
255 Args:
256 other_packages: A tuple of platform-specific packages to check for."""
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700257 all_packages = () + other_packages
258 try:
259 result = subprocess.check_output(
260 ('dpkg-query', '--show') + all_packages,
261 stdin=open(os.devnull, 'r'),
262 stderr=subprocess.STDOUT)
263 except subprocess.CalledProcessError as e:
264 user_output('Some packages not installed:\n'
265 + e.output.decode('utf-8').rstrip())
266 exit(1)
267
Brian Silvermana29ebf92014-04-23 13:08:49 -0500268class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700269 """A Processor subclass for building cRIO code."""
270
Brian Silvermanb3d50542014-04-23 14:28:55 -0500271 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700272 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500273 super(CRIOProcessor.Platform, self).__init__()
274
Brian Silvermane6bada62014-05-04 16:16:54 -0700275 self.__debug = debug
276 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500277
278 def __repr__(self):
279 return 'CRIOProcessor.Platform(debug=%s)' % self.debug
280 def __str__(self):
281 return 'crio%s' % ('-debug' if self.debug else '')
282
283 def outname(self):
284 return 'crio-debug' if self.debug else 'crio'
285 def os(self):
286 return 'vxworks'
287 def gyp_platform(self):
288 return 'crio'
289 def architecture(self):
290 return 'ppc'
291 def compiler(self):
292 return 'gcc'
Brian Silvermane6bada62014-05-04 16:16:54 -0700293 def debug(self):
294 return self.__debug
295 def wind_base(self):
296 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500297
Brian Silvermane48c09a2014-04-30 18:04:58 -0700298 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500299 def deploy(self, dry_run):
300 self.do_deploy(dry_run,
301 ('ncftpput', get_ip('robot'), '/',
302 os.path.join(self.outdir(), 'lib', 'FRC_UserProgram.out')))
303
Brian Silvermana4aff562014-05-02 17:43:50 -0700304 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700305 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700306
Brian Silvermana29ebf92014-04-23 13:08:49 -0500307 def __init__(self):
308 super(CRIOProcessor, self).__init__()
309
310 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700311 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500312 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700313 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500314
315 def parse_platforms(self, string):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500316 if string is None or string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700317 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700318 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700319 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500320 else:
321 raise Processor.UnknownPlatform('Unknown cRIO platform "%s".' % string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500322
Brian Silvermane6bada62014-05-04 16:16:54 -0700323 def wind_base(self):
324 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500325
Brian Silvermane6bada62014-05-04 16:16:54 -0700326 def extra_gyp_flags(self):
327 return ('-DWIND_BASE=%s' % self.wind_base(),)
328
329 def modify_ninja_file(self, ninja_file):
330 subprocess.check_call(
331 ('sed', '-i',
332 's/nm -gD/nm/g', ninja_file),
333 stdin=open(os.devnull, 'r'))
334
335 def download_externals(self, _):
336 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500337
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700338 def check_installed(self):
339 # TODO(brians): Add powerpc-wrs-vxworks (a new enough version too).
340 self.do_check_installed(
341 ('ncftp',))
342
Brian Silvermana29ebf92014-04-23 13:08:49 -0500343class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700344 """A Processor subclass for building prime code."""
345
Brian Silvermanb3d50542014-04-23 14:28:55 -0500346 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700347 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500348 super(PrimeProcessor.Platform, self).__init__()
349
Brian Silvermane6bada62014-05-04 16:16:54 -0700350 self.__architecture = architecture
351 self.__compiler = compiler
352 self.__debug = debug
353 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500354
355 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700356 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
357 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700358 % (self.architecture(), self.compiler(), self.debug(),
359 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500360 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700361 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
362 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500363
364 def os(self):
365 return 'linux'
366 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700367 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
368 def architecture(self):
369 return self.__architecture
370 def compiler(self):
371 return self.__compiler
372 def sanitizer(self):
373 return self.__sanitizer
374 def debug(self):
375 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500376
Brian Silvermana29ebf92014-04-23 13:08:49 -0500377 def outname(self):
378 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500379
Brian Silvermane48c09a2014-04-30 18:04:58 -0700380 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500381 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700382 # Downloads code to the prime in a way that avoids clashing too badly with
383 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500384 SUM = 'md5sum'
385 TARGET_DIR = '/home/driver/robot_code/bin'
386 TEMP_DIR = '/tmp/aos_downloader'
387 TARGET = 'driver@' + get_ip('prime')
388
389 from_dir = os.path.join(self.outdir(), 'outputs')
390 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
391 stdin=open(os.devnull, 'r'),
392 cwd=from_dir)
393 to_download = subprocess.check_output(
394 ('ssh', TARGET,
395 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
396 && echo '{SUMS}' | {SUM} --check --quiet
397 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".format(
398 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
399 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700400 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500401 return
402 self.do_deploy(
403 dry_run,
404 ('scp', '-o', 'Compression yes') + to_download
405 + (('%s:%s' % (TARGET, TEMP_DIR)),))
406 if not dry_run:
407 subprocess.check_call(
408 ('ssh', TARGET,
409 """mv {TMPDIR}/* {TO_DIR}
410 && echo 'Done moving new executables into place'
411 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
412 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
413
Brian Silvermana4aff562014-05-02 17:43:50 -0700414 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700415 OTHER_SYSROOT = '/opt/clang-3.5/'
416 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700417 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700418 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
419 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
420 if self.sanitizer() == 'address':
421 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silvermana4aff562014-05-02 17:43:50 -0700422 r['ASAN_OPTIONS'] = 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700423 elif self.sanitizer() == 'memory':
424 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
425 elif self.sanitizer() == 'thread':
426 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700427
428 r['CCACHE_COMPRESS'] = 'yes'
429 r['CCACHE_DIR'] = \
430 os.path.abspath(os.path.join(aos_path(), '..', 'output', 'ccache_dir'))
431 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700432 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700433 # clang doesn't like being run directly on the preprocessed files.
434 r['CCACHE_CPP2'] = 'yes'
435 # Without this, ccache slows down because of the generated header files.
436 # The race condition that this opens up isn't a problem because the build
437 # system finishes modifying header files before compiling anything that
438 # uses them.
439 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700440
Brian Silvermane6bada62014-05-04 16:16:54 -0700441 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700442 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
443 ':' + os.environ['PATH']
444
Brian Silvermana4aff562014-05-02 17:43:50 -0700445 return r
446
Brian Silverman47cd6f62014-05-03 10:35:52 -0700447 ARCHITECTURES = ('arm', 'amd64')
448 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
449 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
450 SANITIZER_TEST_WARNINGS = {
451 'memory': (True,
452"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700453 errors with msan (especially stdlibc++).
454 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700455 'undefined': (False,
456"""There are several warnings in other people's code that ubsan catches.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700457 The following have been verified non-interesting:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700458 include/c++/4.8.2/array:*: runtime error: reference binding to null pointer of type 'int'
459 This happens with ::std::array<T, 0> and it doesn't seem to cause any issues.
460 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
461 That's in the CPUID detection code which only runs on x86."""),
462 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700463 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500464
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500465 def __init__(self, is_test, is_deploy):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500466 super(Processor, self).__init__()
467
468 platforms = []
469 for architecture in PrimeProcessor.ARCHITECTURES:
470 for compiler in PrimeProcessor.COMPILERS:
471 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700472 if architecture == 'arm' and compiler == 'gcc_4.8':
473 # We don't have a compiler to use here.
474 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500475 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700476 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
477 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700478 for compiler in ('gcc_4.8', 'clang'):
479 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
480 sanitizer == 'integer' or
481 sanitizer == 'memory'):
482 # GCC 4.8 doesn't support these sanitizers.
483 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700484 if sanitizer == 'none':
485 # We already added sanitizer == 'none' above.
486 continue
487 platforms.append(
488 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
489 self.__platforms = frozenset(platforms)
490
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500491 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700492 default_platforms = self.select_platforms(architecture='amd64',
493 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700494 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
495 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700496 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500497 elif is_deploy:
498 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700499 default_platforms = self.select_platforms(architecture='arm',
500 compiler='gcc',
501 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500502 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700503 default_platforms = self.select_platforms(debug=False)
504 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500505
Brian Silvermane6bada62014-05-04 16:16:54 -0700506 def platforms(self):
507 return self.__platforms
508 def default_platforms(self):
509 return self.__default_platforms
510
511 def download_externals(self, platforms):
512 to_download = set()
513 for architecture in PrimeProcessor.ARCHITECTURES:
514 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
515 if platforms & self.select_platforms(architecture=architecture,
516 sanitizer=sanitizer):
517 to_download.add(architecture + '-fPIE')
518 if platforms & self.select_platforms(architecture=architecture,
519 sanitizer='none'):
520 to_download.add(architecture)
521 for download_target in to_download:
522 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500523
524 def parse_platforms(self, string):
525 if string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700526 return self.default_platforms()
527 r = self.default_platforms()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500528 for part in string.split(','):
529 if part[0] == '+':
530 r = r | self.select_platforms_string(part[1:])
531 elif part[0] == '-':
532 r = r - self.select_platforms_string(part[1:])
533 elif part[0] == '=':
534 r = self.select_platforms_string(part[1:])
535 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500536 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700537 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500538 if not r:
539 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500540 return r
541
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700542 def select_platforms(self, architecture=None, compiler=None, debug=None, sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500543 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700544 for platform in self.platforms():
545 if architecture is None or platform.architecture() == architecture:
546 if compiler is None or platform.compiler() == compiler:
547 if debug is None or platform.debug() == debug:
548 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700549 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500550 return set(r)
551
552 def select_platforms_string(self, string):
553 r = []
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700554 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500555 for part in string.split('-'):
556 if part in PrimeProcessor.ARCHITECTURES:
557 architecture = part
558 elif part in PrimeProcessor.COMPILERS:
559 compiler = part
560 elif part in ['debug', 'dbg']:
561 debug = True
562 elif part in ['release', 'nodebug', 'ndb']:
563 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700564 elif part in PrimeProcessor.SANITIZERS:
565 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500566 else:
567 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
568 return self.select_platforms(
569 architecture=architecture,
570 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700571 debug=debug,
572 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500573
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700574 def check_installed(self):
575 self.do_check_installed(
576 ('clang-3.5', 'gcc-4.7-arm-linux-gnueabihf',
577 'g++-4.7-arm-linux-gnueabihf', 'openssh-client'))
578
Brian Silvermana29ebf92014-04-23 13:08:49 -0500579def main():
580 class TryParsingAgain(Exception):
581 pass
582
583 class TryAgainArgumentParser(argparse.ArgumentParser):
584 def __init__(self, **kwargs):
585 super(TryAgainArgumentParser, self).__init__(**kwargs)
586
587 def error(self, message):
588 raise TryParsingAgain
589
Brian Silvermane6bada62014-05-04 16:16:54 -0700590 def set_up_parser(parser, args):
591 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500592 parser.add_argument(
593 'target',
594 help='target to build',
595 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700596 parser.add_argument(
597 '--jobs', '-j',
598 help='number of things to do at once',
599 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700600 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500601 parser.add_argument(
602 'platforms',
603 help='platform(s) to act on',
604 nargs='?')
605
606 parser.add_argument('--processor', required=True, help='prime or crio')
607 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
608 subparsers = parser.add_subparsers(dest='action_name')
609
610 build_parser = subparsers.add_parser(
611 'build',
612 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700613 add_common_args(build_parser)
614 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500615
616 clean_parser = subparsers.add_parser(
617 'clean',
618 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700619 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500620
621 deploy_parser = subparsers.add_parser(
622 'deploy',
623 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700624 add_common_args(deploy_parser)
625 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500626 deploy_parser.add_argument(
627 '-n', '--dry-run',
628 help="don't actually download anything",
629 action='store_true')
630
Brian Silvermane48c09a2014-04-30 18:04:58 -0700631 tests_parser = subparsers.add_parser(
632 'tests',
633 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700634 add_common_args(tests_parser)
635 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700636
Brian Silvermana29ebf92014-04-23 13:08:49 -0500637 return parser.parse_args(args)
638
639 try:
640 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700641 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500642 except TryParsingAgain:
643 parser = argparse.ArgumentParser()
644 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700645 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
646 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500647
648 if args.processor == 'crio':
649 processor = CRIOProcessor()
650 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500651 processor = PrimeProcessor(args.action_name == 'tests',
652 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500653 else:
654 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700655 processor.check_installed()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500656
657 if 'target' in args:
658 targets = args.target[:]
659 else:
660 targets = []
661 unknown_platform_error = None
662 try:
663 platforms = processor.parse_platforms(args.platforms)
664 except Processor.UnknownPlatform as e:
665 unknown_platform_error = e.message
666 targets.append(args.platforms)
667 platforms = processor.parse_platforms(None)
668 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700669 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500670 exit(1)
671
Brian Silvermane6bada62014-05-04 16:16:54 -0700672 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500673
674 class ToolsConfig(object):
675 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500676 self.variables = {'AOS': aos_path()}
677 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500678 for line in f:
679 if line[0] == '#':
680 pass
681 elif line.isspace():
682 pass
683 else:
684 new_name, new_value = line.rstrip().split('=')
685 for name, value in self.variables.items():
686 new_value = new_value.replace('${%s}' % name, value)
687 self.variables[new_name] = new_value
688 def __getitem__(self, key):
689 return self.variables[key]
690
691 tools_config = ToolsConfig()
692
693 def handle_clean_error(function, path, excinfo):
694 if issubclass(OSError, excinfo[0]):
695 if excinfo[1].errno == errno.ENOENT:
696 # Who cares if the file we're deleting isn't there?
697 return
698 raise excinfo[1]
699
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700700 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700701 """Determines if we need to run gyp again or not.
702
703 The generated build files are supposed to re-run gyp again themselves, but
704 that doesn't work (or at least it used to not) and we sometimes want to
705 modify the results anyways.
706
707 Args:
708 platform: The platform to check for.
709 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700710 if not os.path.exists(platform.build_ninja()):
711 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700712 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
713 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700714 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700715 # Looking through these folders takes a long time and isn't useful.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700716 dirs.remove('output')
Brian Silvermana4aff562014-05-02 17:43:50 -0700717 dirs.remove('.git')
718 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700719 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
720 + ('-newer', platform.build_ninja(),
721 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700722 stdin=open(os.devnull, 'r'))
723
724 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700725 """Makes sure we pass through important environmental variables.
726
727 Returns:
728 An environment suitable for passing to subprocess.Popen and friends.
729 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700730 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700731 if not 'TERM' in build_env:
732 build_env['TERM'] = os.environ['TERM']
733 if not 'PATH' in build_env:
734 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700735 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700736
Brian Silverman47cd6f62014-05-03 10:35:52 -0700737 to_build = []
738 for platform in platforms:
739 to_build.append(str(platform))
740 if len(to_build) > 1:
741 to_build[-1] = 'and ' + to_build[-1]
742 user_output('Building %s...' % ', '.join(to_build))
743
744 if args.action_name == 'tests':
745 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
746 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
747 if warned_about:
748 user_output(warning[1])
749 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700750 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700751 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
752 exit(1)
753
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700754 num = 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500755 for platform in platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700756 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500757 if args.action_name == 'clean':
758 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
759 else:
760 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700761 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500762 gyp = subprocess.Popen(
763 (tools_config['GYP'],
764 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500765 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500766 '--no-circular-check',
767 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500768 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500769 '-I/dev/stdin', '-Goutput_dir=output',
770 '-DOS=%s' % platform.os(),
771 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700772 '-DARCHITECTURE=%s' % platform.architecture(),
773 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
774 '-DFULL_COMPILER=%s' % platform.compiler(),
775 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
776 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silvermana5301e32014-05-03 10:51:49 -0700777 '-DSANITIZER_FPIE=%s' % ('-fPIE'
Brian Silvermane6bada62014-05-04 16:16:54 -0700778 if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700779 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500780 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500781 stdin=subprocess.PIPE)
782 gyp.communicate(("""
783{
784 'target_defaults': {
785 'configurations': {
786 '%s': {}
787 }
788 }
789}""" % platform.outname()).encode())
790 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700791 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500792 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700793 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700794 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500795 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700796 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500797
798 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700799 call = (tools_config['NINJA'],
800 '-C', platform.outdir()) + tuple(targets)
801 if args.jobs:
802 call += ('-j', str(args.jobs))
803 subprocess.check_call(call,
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500804 stdin=open(os.devnull, 'r'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700805 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500806 except subprocess.CalledProcessError as e:
807 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700808 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500809 raise e
810
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500811 if args.action_name == 'deploy':
812 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700813 elif args.action_name == 'tests':
814 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700815 done_queue = queue.Queue()
816 running = []
817 if args.jobs:
818 number_jobs = args.jobs
819 else:
820 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
821 test_start_semaphore = threading.Semaphore(number_jobs)
822 if targets:
823 to_run = []
824 for target in targets:
825 if target.endswith('_test'):
826 to_run.append(target)
827 else:
828 to_run = os.listdir(dirname)
829 for f in to_run:
830 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
831 test_start_semaphore)
832 running.append(thread)
833 thread.start()
834 try:
835 while running:
836 done = done_queue.get()
837 running.remove(done)
838 with test_output_lock:
839 test_output('Output from test %s:' % done.name)
840 for line in os.fdopen(done.output):
841 if not sys.stdout.isatty():
842 # Remove color escape codes.
843 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
844 sys.stdout.write(line)
Brian Silvermane6bada62014-05-04 16:16:54 -0700845 if not done.returncode:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700846 test_output('Test %s succeeded' % done.name)
847 else:
848 test_output('Test %s failed' % done.name)
849 user_output('Aborting because of test failure.')
850 exit(1)
851 finally:
852 if running:
853 test_output('Killing other tests...')
854 for thread in running:
855 thread.terminate_process()
856 to_remove = []
857 for thread in running:
858 thread.join(5)
859 if not thread.is_alive():
860 to_remove.append(thread);
861 for thread in to_remove: running.remove(thread)
862 for thread in running:
863 test_output(
864 'Test %s did not terminate. Killing it.' % thread.name)
865 thread.kill_process()
866 thread.join()
867 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500868
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700869 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
870 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500871
872if __name__ == '__main__':
873 main()