blob: 1a84b1ba5c91d490d5519224bafbb3846e5bd5c8 [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
Brian Silverman9b7a6842014-05-05 16:19:11 -0700216 def check_installed(self, platforms, is_deploy):
217 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700218 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 Silverman9b7a6842014-05-05 16:19:11 -0700257 all_packages = other_packages
258 # Necessary to build stuff.
259 all_packages += ('ccache', 'make')
260 # Necessary to download stuff to build.
261 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
262 # Necessary to build externals stuff.
263 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700264 try:
265 result = subprocess.check_output(
266 ('dpkg-query', '--show') + all_packages,
267 stdin=open(os.devnull, 'r'),
268 stderr=subprocess.STDOUT)
269 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700270 output = e.output.decode('utf-8').rstrip()
271 not_found = []
272 for line in output.splitlines(True):
273 match = re.match(r'dpkg-query: no packages found matching (.*)',
274 line)
275 if match:
276 not_found.append(match.group(1))
277 user_output('Some packages not installed: %s.' % ', '.join(not_found))
278 user_output('Try something like `sudo apt-get install %s`.' %
279 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700280 exit(1)
281
Brian Silvermana29ebf92014-04-23 13:08:49 -0500282class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700283 """A Processor subclass for building cRIO code."""
284
Brian Silvermanb3d50542014-04-23 14:28:55 -0500285 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700286 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500287 super(CRIOProcessor.Platform, self).__init__()
288
Brian Silvermane6bada62014-05-04 16:16:54 -0700289 self.__debug = debug
290 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500291
292 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700293 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500294 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700295 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500296
297 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700298 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500299 def os(self):
300 return 'vxworks'
301 def gyp_platform(self):
302 return 'crio'
303 def architecture(self):
304 return 'ppc'
305 def compiler(self):
306 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700307 def sanitizer(self):
308 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700309 def debug(self):
310 return self.__debug
311 def wind_base(self):
312 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500313
Brian Silvermane48c09a2014-04-30 18:04:58 -0700314 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500315 def deploy(self, dry_run):
316 self.do_deploy(dry_run,
317 ('ncftpput', get_ip('robot'), '/',
318 os.path.join(self.outdir(), 'lib', 'FRC_UserProgram.out')))
319
Brian Silvermana4aff562014-05-02 17:43:50 -0700320 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700321 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700322
Brian Silvermana29ebf92014-04-23 13:08:49 -0500323 def __init__(self):
324 super(CRIOProcessor, self).__init__()
325
326 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700327 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500328 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700329 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500330
331 def parse_platforms(self, string):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500332 if string is None or string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700333 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700334 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700335 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500336 else:
337 raise Processor.UnknownPlatform('Unknown cRIO platform "%s".' % string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500338
Brian Silvermane6bada62014-05-04 16:16:54 -0700339 def wind_base(self):
340 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500341
Brian Silvermane6bada62014-05-04 16:16:54 -0700342 def extra_gyp_flags(self):
343 return ('-DWIND_BASE=%s' % self.wind_base(),)
344
345 def modify_ninja_file(self, ninja_file):
346 subprocess.check_call(
347 ('sed', '-i',
348 's/nm -gD/nm/g', ninja_file),
349 stdin=open(os.devnull, 'r'))
350
351 def download_externals(self, _):
352 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500353
Brian Silverman9b7a6842014-05-05 16:19:11 -0700354 def check_installed(self, platforms, is_deploy):
355 packages = ('powerpc-wrs-vxworks', 'tcl')
356 if is_deploy:
357 packages += ('ncftp',)
358 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700359
Brian Silvermana29ebf92014-04-23 13:08:49 -0500360class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700361 """A Processor subclass for building prime code."""
362
Brian Silvermanb3d50542014-04-23 14:28:55 -0500363 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700364 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500365 super(PrimeProcessor.Platform, self).__init__()
366
Brian Silvermane6bada62014-05-04 16:16:54 -0700367 self.__architecture = architecture
368 self.__compiler = compiler
369 self.__debug = debug
370 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500371
372 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700373 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
374 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700375 % (self.architecture(), self.compiler(), self.debug(),
376 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500377 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700378 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
379 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500380
381 def os(self):
382 return 'linux'
383 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700384 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
385 def architecture(self):
386 return self.__architecture
387 def compiler(self):
388 return self.__compiler
389 def sanitizer(self):
390 return self.__sanitizer
391 def debug(self):
392 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500393
Brian Silvermana29ebf92014-04-23 13:08:49 -0500394 def outname(self):
395 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500396
Brian Silvermane48c09a2014-04-30 18:04:58 -0700397 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500398 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700399 # Downloads code to the prime in a way that avoids clashing too badly with
400 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500401 SUM = 'md5sum'
402 TARGET_DIR = '/home/driver/robot_code/bin'
403 TEMP_DIR = '/tmp/aos_downloader'
404 TARGET = 'driver@' + get_ip('prime')
405
406 from_dir = os.path.join(self.outdir(), 'outputs')
407 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
408 stdin=open(os.devnull, 'r'),
409 cwd=from_dir)
410 to_download = subprocess.check_output(
411 ('ssh', TARGET,
412 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
413 && echo '{SUMS}' | {SUM} --check --quiet
414 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".format(
415 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
416 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700417 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500418 return
419 self.do_deploy(
420 dry_run,
421 ('scp', '-o', 'Compression yes') + to_download
422 + (('%s:%s' % (TARGET, TEMP_DIR)),))
423 if not dry_run:
424 subprocess.check_call(
425 ('ssh', TARGET,
426 """mv {TMPDIR}/* {TO_DIR}
427 && echo 'Done moving new executables into place'
428 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
429 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
430
Brian Silvermana4aff562014-05-02 17:43:50 -0700431 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700432 OTHER_SYSROOT = '/opt/clang-3.5/'
433 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700434 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700435 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
436 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
437 if self.sanitizer() == 'address':
438 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silvermana4aff562014-05-02 17:43:50 -0700439 r['ASAN_OPTIONS'] = 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700440 elif self.sanitizer() == 'memory':
441 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
442 elif self.sanitizer() == 'thread':
443 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700444
445 r['CCACHE_COMPRESS'] = 'yes'
446 r['CCACHE_DIR'] = \
447 os.path.abspath(os.path.join(aos_path(), '..', 'output', 'ccache_dir'))
448 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700449 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700450 # clang doesn't like being run directly on the preprocessed files.
451 r['CCACHE_CPP2'] = 'yes'
452 # Without this, ccache slows down because of the generated header files.
453 # The race condition that this opens up isn't a problem because the build
454 # system finishes modifying header files before compiling anything that
455 # uses them.
456 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700457
Brian Silvermane6bada62014-05-04 16:16:54 -0700458 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700459 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
460 ':' + os.environ['PATH']
461
Brian Silvermana4aff562014-05-02 17:43:50 -0700462 return r
463
Brian Silverman47cd6f62014-05-03 10:35:52 -0700464 ARCHITECTURES = ('arm', 'amd64')
465 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
466 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
467 SANITIZER_TEST_WARNINGS = {
468 'memory': (True,
469"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700470 errors with msan (especially stdlibc++).
471 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700472 'undefined': (False,
473"""There are several warnings in other people's code that ubsan catches.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700474 The following have been verified non-interesting:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700475 include/c++/4.8.2/array:*: runtime error: reference binding to null pointer of type 'int'
476 This happens with ::std::array<T, 0> and it doesn't seem to cause any issues.
477 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
478 That's in the CPUID detection code which only runs on x86."""),
479 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700480 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500481
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500482 def __init__(self, is_test, is_deploy):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500483 super(Processor, self).__init__()
484
485 platforms = []
486 for architecture in PrimeProcessor.ARCHITECTURES:
487 for compiler in PrimeProcessor.COMPILERS:
488 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700489 if architecture == 'arm' and compiler == 'gcc_4.8':
490 # We don't have a compiler to use here.
491 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500492 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700493 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
494 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700495 for compiler in ('gcc_4.8', 'clang'):
496 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
497 sanitizer == 'integer' or
498 sanitizer == 'memory'):
499 # GCC 4.8 doesn't support these sanitizers.
500 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700501 if sanitizer == 'none':
502 # We already added sanitizer == 'none' above.
503 continue
504 platforms.append(
505 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
506 self.__platforms = frozenset(platforms)
507
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500508 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700509 default_platforms = self.select_platforms(architecture='amd64',
510 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700511 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
512 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700513 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500514 elif is_deploy:
515 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700516 default_platforms = self.select_platforms(architecture='arm',
517 compiler='gcc',
518 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500519 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700520 default_platforms = self.select_platforms(debug=False)
521 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500522
Brian Silvermane6bada62014-05-04 16:16:54 -0700523 def platforms(self):
524 return self.__platforms
525 def default_platforms(self):
526 return self.__default_platforms
527
528 def download_externals(self, platforms):
529 to_download = set()
530 for architecture in PrimeProcessor.ARCHITECTURES:
531 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
532 if platforms & self.select_platforms(architecture=architecture,
533 sanitizer=sanitizer):
534 to_download.add(architecture + '-fPIE')
535 if platforms & self.select_platforms(architecture=architecture,
536 sanitizer='none'):
537 to_download.add(architecture)
538 for download_target in to_download:
539 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500540
541 def parse_platforms(self, string):
542 if string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700543 return self.default_platforms()
Brian Silverman9b7a6842014-05-05 16:19:11 -0700544 elif string == 'all':
545 return self.platforms()
Brian Silvermane6bada62014-05-04 16:16:54 -0700546 r = self.default_platforms()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500547 for part in string.split(','):
548 if part[0] == '+':
549 r = r | self.select_platforms_string(part[1:])
550 elif part[0] == '-':
551 r = r - self.select_platforms_string(part[1:])
552 elif part[0] == '=':
553 r = self.select_platforms_string(part[1:])
554 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500555 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700556 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500557 if not r:
558 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500559 return r
560
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700561 def select_platforms(self, architecture=None, compiler=None, debug=None, sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500562 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700563 for platform in self.platforms():
564 if architecture is None or platform.architecture() == architecture:
565 if compiler is None or platform.compiler() == compiler:
566 if debug is None or platform.debug() == debug:
567 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700568 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500569 return set(r)
570
571 def select_platforms_string(self, string):
572 r = []
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700573 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500574 for part in string.split('-'):
575 if part in PrimeProcessor.ARCHITECTURES:
576 architecture = part
577 elif part in PrimeProcessor.COMPILERS:
578 compiler = part
579 elif part in ['debug', 'dbg']:
580 debug = True
581 elif part in ['release', 'nodebug', 'ndb']:
582 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700583 elif part in PrimeProcessor.SANITIZERS:
584 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500585 else:
586 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
587 return self.select_platforms(
588 architecture=architecture,
589 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700590 debug=debug,
591 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500592
Brian Silverman9b7a6842014-05-05 16:19:11 -0700593 def check_installed(self, platforms, is_deploy):
594 packages = set(('lzip', 'm4', 'realpath'))
595 packages.add('ruby')
596 # clang-format from here gets used for all versions.
597 packages.add('clang-3.5')
598 packages.add('arm-eabi-gcc')
599 for platform in platforms:
600 if platform.architecture() == 'arm':
601 packages.add('gcc-4.7-arm-linux-gnueabihf')
602 packages.add('g++-4.7-arm-linux-gnueabihf')
603 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
604 packages.add('clang-3.5')
605 if is_deploy:
606 packages.add('openssh-client')
607 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
608 packages.add('gcc-4.7')
609 packages.add('g++-4.7')
610
611 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700612
Brian Silvermana29ebf92014-04-23 13:08:49 -0500613def main():
614 class TryParsingAgain(Exception):
615 pass
616
617 class TryAgainArgumentParser(argparse.ArgumentParser):
618 def __init__(self, **kwargs):
619 super(TryAgainArgumentParser, self).__init__(**kwargs)
620
621 def error(self, message):
622 raise TryParsingAgain
623
Brian Silvermane6bada62014-05-04 16:16:54 -0700624 def set_up_parser(parser, args):
625 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500626 parser.add_argument(
627 'target',
628 help='target to build',
629 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700630 parser.add_argument(
631 '--jobs', '-j',
632 help='number of things to do at once',
633 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700634 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500635 parser.add_argument(
636 'platforms',
637 help='platform(s) to act on',
638 nargs='?')
639
640 parser.add_argument('--processor', required=True, help='prime or crio')
641 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
642 subparsers = parser.add_subparsers(dest='action_name')
643
644 build_parser = subparsers.add_parser(
645 'build',
646 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700647 add_common_args(build_parser)
648 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500649
650 clean_parser = subparsers.add_parser(
651 'clean',
652 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700653 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500654
655 deploy_parser = subparsers.add_parser(
656 'deploy',
657 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700658 add_common_args(deploy_parser)
659 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500660 deploy_parser.add_argument(
661 '-n', '--dry-run',
662 help="don't actually download anything",
663 action='store_true')
664
Brian Silvermane48c09a2014-04-30 18:04:58 -0700665 tests_parser = subparsers.add_parser(
666 'tests',
667 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700668 add_common_args(tests_parser)
669 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700670
Brian Silvermana29ebf92014-04-23 13:08:49 -0500671 return parser.parse_args(args)
672
673 try:
674 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700675 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500676 except TryParsingAgain:
677 parser = argparse.ArgumentParser()
678 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700679 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
680 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500681
682 if args.processor == 'crio':
683 processor = CRIOProcessor()
684 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500685 processor = PrimeProcessor(args.action_name == 'tests',
686 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500687 else:
688 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
689
690 if 'target' in args:
691 targets = args.target[:]
692 else:
693 targets = []
694 unknown_platform_error = None
695 try:
696 platforms = processor.parse_platforms(args.platforms)
697 except Processor.UnknownPlatform as e:
698 unknown_platform_error = e.message
699 targets.append(args.platforms)
700 platforms = processor.parse_platforms(None)
701 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700702 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500703 exit(1)
704
Brian Silverman9b7a6842014-05-05 16:19:11 -0700705 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700706 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500707
708 class ToolsConfig(object):
709 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500710 self.variables = {'AOS': aos_path()}
711 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500712 for line in f:
713 if line[0] == '#':
714 pass
715 elif line.isspace():
716 pass
717 else:
718 new_name, new_value = line.rstrip().split('=')
719 for name, value in self.variables.items():
720 new_value = new_value.replace('${%s}' % name, value)
721 self.variables[new_name] = new_value
722 def __getitem__(self, key):
723 return self.variables[key]
724
725 tools_config = ToolsConfig()
726
727 def handle_clean_error(function, path, excinfo):
728 if issubclass(OSError, excinfo[0]):
729 if excinfo[1].errno == errno.ENOENT:
730 # Who cares if the file we're deleting isn't there?
731 return
732 raise excinfo[1]
733
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700734 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700735 """Determines if we need to run gyp again or not.
736
737 The generated build files are supposed to re-run gyp again themselves, but
738 that doesn't work (or at least it used to not) and we sometimes want to
739 modify the results anyways.
740
741 Args:
742 platform: The platform to check for.
743 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700744 if not os.path.exists(platform.build_ninja()):
745 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700746 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
747 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700748 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700749 # Looking through these folders takes a long time and isn't useful.
Brian Silverman9b7a6842014-05-05 16:19:11 -0700750 if dirs.count('output'): dirs.remove('output')
751 if dirs.count('.git'): dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700752 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700753 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
754 + ('-newer', platform.build_ninja(),
755 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700756 stdin=open(os.devnull, 'r'))
757
758 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700759 """Makes sure we pass through important environmental variables.
760
761 Returns:
762 An environment suitable for passing to subprocess.Popen and friends.
763 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700764 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700765 if not 'TERM' in build_env:
766 build_env['TERM'] = os.environ['TERM']
767 if not 'PATH' in build_env:
768 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700769 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700770
Brian Silverman47cd6f62014-05-03 10:35:52 -0700771 to_build = []
772 for platform in platforms:
773 to_build.append(str(platform))
774 if len(to_build) > 1:
775 to_build[-1] = 'and ' + to_build[-1]
776 user_output('Building %s...' % ', '.join(to_build))
777
778 if args.action_name == 'tests':
779 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
780 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
781 if warned_about:
782 user_output(warning[1])
783 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700784 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700785 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
786 exit(1)
787
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700788 num = 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500789 for platform in platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700790 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500791 if args.action_name == 'clean':
792 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
793 else:
794 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700795 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500796 gyp = subprocess.Popen(
797 (tools_config['GYP'],
798 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500799 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500800 '--no-circular-check',
801 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500802 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500803 '-I/dev/stdin', '-Goutput_dir=output',
804 '-DOS=%s' % platform.os(),
805 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700806 '-DARCHITECTURE=%s' % platform.architecture(),
807 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
808 '-DFULL_COMPILER=%s' % platform.compiler(),
809 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
810 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silvermana5301e32014-05-03 10:51:49 -0700811 '-DSANITIZER_FPIE=%s' % ('-fPIE'
Brian Silvermane6bada62014-05-04 16:16:54 -0700812 if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700813 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500814 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500815 stdin=subprocess.PIPE)
816 gyp.communicate(("""
817{
818 'target_defaults': {
819 'configurations': {
820 '%s': {}
821 }
822 }
823}""" % platform.outname()).encode())
824 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700825 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500826 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700827 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700828 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500829 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700830 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500831
832 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700833 call = (tools_config['NINJA'],
834 '-C', platform.outdir()) + tuple(targets)
835 if args.jobs:
836 call += ('-j', str(args.jobs))
837 subprocess.check_call(call,
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500838 stdin=open(os.devnull, 'r'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700839 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500840 except subprocess.CalledProcessError as e:
841 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700842 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500843 raise e
844
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500845 if args.action_name == 'deploy':
846 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700847 elif args.action_name == 'tests':
848 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700849 done_queue = queue.Queue()
850 running = []
851 if args.jobs:
852 number_jobs = args.jobs
853 else:
854 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
855 test_start_semaphore = threading.Semaphore(number_jobs)
856 if targets:
857 to_run = []
858 for target in targets:
859 if target.endswith('_test'):
860 to_run.append(target)
861 else:
862 to_run = os.listdir(dirname)
863 for f in to_run:
864 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
865 test_start_semaphore)
866 running.append(thread)
867 thread.start()
868 try:
869 while running:
870 done = done_queue.get()
871 running.remove(done)
872 with test_output_lock:
873 test_output('Output from test %s:' % done.name)
874 for line in os.fdopen(done.output):
875 if not sys.stdout.isatty():
876 # Remove color escape codes.
877 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
878 sys.stdout.write(line)
Brian Silvermane6bada62014-05-04 16:16:54 -0700879 if not done.returncode:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700880 test_output('Test %s succeeded' % done.name)
881 else:
882 test_output('Test %s failed' % done.name)
883 user_output('Aborting because of test failure.')
884 exit(1)
885 finally:
886 if running:
887 test_output('Killing other tests...')
888 for thread in running:
889 thread.terminate_process()
890 to_remove = []
891 for thread in running:
892 thread.join(5)
893 if not thread.is_alive():
894 to_remove.append(thread);
895 for thread in to_remove: running.remove(thread)
896 for thread in running:
897 test_output(
898 'Test %s did not terminate. Killing it.' % thread.name)
899 thread.kill_process()
900 thread.join()
901 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500902
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700903 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
904 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500905
906if __name__ == '__main__':
907 main()