blob: faa6fe19191fa3f4f3cccc13d0ca825a7fdea519 [file] [log] [blame]
Brian Silvermana29ebf92014-04-23 13:08:49 -05001#!/usr/bin/python3
2
Brian Silvermana29ebf92014-04-23 13:08:49 -05003import sys
4import subprocess
5import re
6import os
7import os.path
8import string
9import shutil
10import errno
Brian Silvermanc3740c32014-05-04 12:42:47 -070011import queue
12import threading
Brian Silvermanf2bbe092014-05-13 16:55:03 -070013import pty
Brian Silverman6bca4722014-05-20 17:02:49 -070014import signal
Brian Silvermanc3740c32014-05-04 12:42:47 -070015
16class TestThread(threading.Thread):
Brian Silvermane6bada62014-05-04 16:16:54 -070017 """Runs 1 test and keeps track of its current state.
18
19 A TestThread is either waiting to start the test, actually running it, done,
20 running it, or stopped. The first 3 always happen in that order and can
21 change to stopped at any time.
22
23 It will finish (ie join() will return) once the process has exited, at which
24 point accessing process to see the status is OK.
25
26 Attributes:
27 executable: The file path of the executable to run.
Brian Silvermanb9e89602014-06-27 14:21:08 -050028 args: A tuple of arguments to give the executable.
Brian Silvermane6bada62014-05-04 16:16:54 -070029 env: The environment variables to set.
30 done_queue: A queue.Queue to place self on once done running the test.
31 start_semaphore: A threading.Semaphore to wait on before starting.
32 process_lock: A lock around process.
33 process: The currently executing test process or None. Synchronized by
34 process_lock.
35 stopped: True if we're stopped.
Brian Silverman730bb012014-06-08 13:05:20 -070036 output: A queue of lines of output from the test.
Brian Silvermane6bada62014-05-04 16:16:54 -070037 """
Brian Silverman730bb012014-06-08 13:05:20 -070038
39 class OutputCopier(threading.Thread):
40 """Copies the output of a test from its output pty into a queue.
41
42 This is necessary because otherwise everything locks up if the test writes
43 too much output and fills up the pty's buffer.
44 """
45
46 def __init__(self, name, fd, queue):
47 super(TestThread.OutputCopier, self).__init__(
48 name=(name + '.OutputCopier'))
49
50 self.fd = fd
51 self.queue = queue
52
53 def run(self):
54 with os.fdopen(self.fd) as to_read:
55 try:
56 for line in to_read:
57 self.queue.put(line)
58 except IOError as e:
59# An EIO from the master side of the pty means we hit the end.
60 if e.errno == errno.EIO:
61 return
62 else:
63 raise e
64
Brian Silvermanb9e89602014-06-27 14:21:08 -050065 def __init__(self, executable, args, env, done_queue, start_semaphore):
Brian Silvermanc3740c32014-05-04 12:42:47 -070066 super(TestThread, self).__init__(
Brian Silvermanb9e89602014-06-27 14:21:08 -050067 name=os.path.split(executable)[-1])
Brian Silvermanc3740c32014-05-04 12:42:47 -070068
69 self.executable = executable
Brian Silvermanb9e89602014-06-27 14:21:08 -050070 self.args = args
Brian Silvermanc3740c32014-05-04 12:42:47 -070071 self.env = env
72 self.done_queue = done_queue
73 self.start_semaphore = start_semaphore
74
Brian Silverman730bb012014-06-08 13:05:20 -070075 self.output = queue.Queue()
76
Brian Silvermanc3740c32014-05-04 12:42:47 -070077 self.process_lock = threading.Lock()
78 self.process = None
79 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070080 self.returncode = None
Brian Silverman730bb012014-06-08 13:05:20 -070081 self.output_copier = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070082
83 def run(self):
84 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070085 if self.stopped:
86 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070087 test_output('Starting test %s...' % self.name)
Brian Silverman730bb012014-06-08 13:05:20 -070088 output_to_read, subprocess_output = pty.openpty()
89 self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
90 self.output)
91 self.output_copier.start()
Brian Silvermanf2bbe092014-05-13 16:55:03 -070092 try:
93 with self.process_lock:
Brian Silvermanb9e89602014-06-27 14:21:08 -050094 self.process = subprocess.Popen((self.name,) + self.args,
95 executable=self.executable,
Brian Silvermanf2bbe092014-05-13 16:55:03 -070096 env=self.env,
97 stderr=subprocess.STDOUT,
98 stdout=subprocess_output,
99 stdin=open(os.devnull, 'r'))
100 finally:
101 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700102 self.process.wait()
103 with self.process_lock:
104 self.returncode = self.process.returncode
105 self.process = None
106 if not self.stopped:
Brian Silverman730bb012014-06-08 13:05:20 -0700107 self.output_copier.join()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700108 self.done_queue.put(self)
109
110 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700111 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700112 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700113 if not self.process:
114 return
Brian Silvermana5826582014-06-03 19:46:35 -0700115 try:
116 self.process.terminate()
117 except OSError as e:
118 if e.errno == errno.ESRCH:
119 # We don't really care if it's already gone.
120 pass
121 else:
122 raise e
Brian Silvermanc3740c32014-05-04 12:42:47 -0700123 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700124 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700125 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700126 if not self.process:
127 return
Brian Silverman6bca4722014-05-20 17:02:49 -0700128 try:
129 self.process.kill()
130 except OSError as e:
131 if e.errno == errno.ESRCH:
132 # We don't really care if it's already gone.
133 pass
134 else:
135 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700136 def stop(self):
137 """Changes self to the stopped state."""
138 with self.process_lock:
139 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500140
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500141def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700142 """Returns:
143 A relative path to the aos directory.
144 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500145 return os.path.join(os.path.dirname(__file__), '..')
146
147def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -0700148 """Retrieves the IP address for a given device."""
149 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700150 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500151 if not os.access(FILENAME, os.R_OK):
152 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
153 with open(FILENAME, 'w') as f:
154 f.write('10.9.71')
155 with open(FILENAME, 'r') as f:
Brian Silvermand043d472014-06-21 15:32:39 -0700156 base = f.readline().strip()
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500157 if device == 'prime':
158 return base + '.179'
159 elif device == 'robot':
160 return base + '.2'
161 else:
162 raise Exception('Unknown device %s to get an IP address for.' % device)
163
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700164def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700165 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700166 print('build.py: ' + message, file=sys.stderr)
167
Brian Silverman452aaec2014-05-05 16:52:18 -0700168# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700169test_output_lock = threading.RLock()
170def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700171 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700172 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700173 print('tests: ' + message, file=sys.stdout)
174
175def call_download_externals(argument):
176 """Calls download_externals.sh for a given set of externals.
177
178 Args:
179 argument: The argument to pass to the shell script to tell it what to
180 download.
181 """
182 subprocess.check_call(
183 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
184 argument),
185 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700186
Brian Silvermana29ebf92014-04-23 13:08:49 -0500187class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700188 """Represents a processor architecture we can build for."""
189
Brian Silvermana29ebf92014-04-23 13:08:49 -0500190 class UnknownPlatform(Exception):
191 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700192 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500193 self.message = message
194
Brian Silvermanb3d50542014-04-23 14:28:55 -0500195 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700196 """Represents a single way to build the code."""
197
Brian Silvermanb3d50542014-04-23 14:28:55 -0500198 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700199 """Returns:
200 The path of the directory build outputs get put in to.
201 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500202 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500203 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500204 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700205 """Returns:
206 The path of the build.ninja file.
207 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500208 return os.path.join(self.outdir(), 'build.ninja')
209
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500210 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700211 """Helper for subclasses to implement deploy.
212
213 Args:
214 dry_run: If True, prints the command instead of actually running it.
215 command: A tuple of command-line arguments.
216 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500217 real_command = (('echo',) + command) if dry_run else command
218 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500219
Brian Silvermane6bada62014-05-04 16:16:54 -0700220 def deploy(self, dry_run):
221 """Downloads the compiled code to the target computer."""
222 raise NotImplementedError('deploy should be overriden')
223 def outname(self):
224 """Returns:
225 The name of the directory the code will be compiled to.
226 """
227 raise NotImplementedError('outname should be overriden')
228 def os(self):
229 """Returns:
230 The name of the operating system this platform is for.
231
232 This will be used as the value of the OS gyp variable.
233 """
234 raise NotImplementedError('os should be overriden')
235 def gyp_platform(self):
236 """Returns:
237 The platform name the .gyp files know.
238
239 This will be used as the value of the PLATFORM gyp variable.
240 """
241 raise NotImplementedError('gyp_platform should be overriden')
242 def architecture(self):
243 """Returns:
244 The processor architecture for this platform.
245
246 This will be used as the value of the ARCHITECTURE gyp variable.
247 """
248 raise NotImplementedError('architecture should be overriden')
249 def compiler(self):
250 """Returns:
251 The compiler used for this platform.
252
253 Everything before the first _ will be used as the value of the
254 COMPILER gyp variable and the whole thing will be used as the value
255 of the FULL_COMPILER gyp variable.
256 """
257 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700258 def sanitizer(self):
259 """Returns:
260 The sanitizer used on this platform.
261
262 This will be used as the value of the SANITIZER gyp variable.
263
264 "none" if there isn't one.
265 """
266 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700267 def debug(self):
268 """Returns:
269 Whether or not this platform compiles with debugging information.
270
271 The DEBUG gyp variable will be set to "yes" or "no" based on this.
272 """
273 raise NotImplementedError('debug should be overriden')
274 def build_env(self):
275 """Returns:
276 A map of environment variables to set while building this platform.
277 """
278 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700279 def priority(self):
280 """Returns:
281 A relative priority for this platform relative to other ones.
282
283 Higher priority platforms will get built, tested, etc first. Generally,
284 platforms which give higher-quality compiler errors etc should come first.
285 """
286 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700287
Brian Silverman9b7a6842014-05-05 16:19:11 -0700288 def check_installed(self, platforms, is_deploy):
289 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700290 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700291 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700292 """Args:
293 string: A user-supplied string saying which platforms to select.
294
295 Returns:
296 A tuple of Platform objects.
297
298 Raises:
299 Processor.UnknownPlatform: Parsing string didn't work out.
300 """
301 raise NotImplementedError('parse_platforms should be overriden')
302 def extra_gyp_flags(self):
303 """Returns:
304 A tuple of extra flags to pass to gyp (if any).
305 """
306 return ()
307 def modify_ninja_file(self, ninja_file):
308 """Modifies a freshly generated ninja file as necessary.
309
310 Args:
311 ninja_file: Path to the file to modify.
312 """
313 pass
314 def download_externals(self, platforms):
315 """Calls download_externals as appropriate to build platforms.
316
317 Args:
318 platforms: A list of platforms to download external libraries for.
319 """
320 raise NotImplementedError('download_externals should be overriden')
321
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700322 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700323 """Helper for subclasses to implement check_installed.
324
325 Args:
326 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700327 all_packages = other_packages
328 # Necessary to build stuff.
329 all_packages += ('ccache', 'make')
330 # Necessary to download stuff to build.
331 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
332 # Necessary to build externals stuff.
333 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700334 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700335 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700336 result = subprocess.check_output(
337 ('dpkg-query', '--show') + all_packages,
338 stdin=open(os.devnull, 'r'),
339 stderr=subprocess.STDOUT)
340 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700341 output = e.output.decode('utf-8').rstrip()
342 not_found = []
343 for line in output.splitlines(True):
344 match = re.match(r'dpkg-query: no packages found matching (.*)',
345 line)
346 if match:
347 not_found.append(match.group(1))
348 user_output('Some packages not installed: %s.' % ', '.join(not_found))
349 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700350 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700351 exit(1)
352
Brian Silvermana29ebf92014-04-23 13:08:49 -0500353class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700354 """A Processor subclass for building cRIO code."""
355
Brian Silvermanb3d50542014-04-23 14:28:55 -0500356 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700357 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500358 super(CRIOProcessor.Platform, self).__init__()
359
Brian Silvermane6bada62014-05-04 16:16:54 -0700360 self.__debug = debug
361 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500362
363 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700364 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500365 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700366 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500367
368 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700369 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500370 def os(self):
371 return 'vxworks'
372 def gyp_platform(self):
373 return 'crio'
374 def architecture(self):
375 return 'ppc'
376 def compiler(self):
377 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700378 def sanitizer(self):
379 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700380 def debug(self):
381 return self.__debug
382 def wind_base(self):
383 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500384
Brian Silvermane48c09a2014-04-30 18:04:58 -0700385 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500386 def deploy(self, dry_run):
387 self.do_deploy(dry_run,
388 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700389 os.path.join(self.outdir(), 'lib',
390 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500391
Brian Silvermana4aff562014-05-02 17:43:50 -0700392 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700393 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700394
Brian Silvermana29ebf92014-04-23 13:08:49 -0500395 def __init__(self):
396 super(CRIOProcessor, self).__init__()
397
398 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700399 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500400 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700401 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500402
Brian Silverman452aaec2014-05-05 16:52:18 -0700403 def parse_platforms(self, platforms_string):
404 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700405 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700406 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700407 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500408 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700409 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700410 '"%s" not recognized as a cRIO platform.' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500411
Brian Silvermane6bada62014-05-04 16:16:54 -0700412 def wind_base(self):
413 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500414
Brian Silvermane6bada62014-05-04 16:16:54 -0700415 def extra_gyp_flags(self):
416 return ('-DWIND_BASE=%s' % self.wind_base(),)
417
418 def modify_ninja_file(self, ninja_file):
419 subprocess.check_call(
420 ('sed', '-i',
421 's/nm -gD/nm/g', ninja_file),
422 stdin=open(os.devnull, 'r'))
423
424 def download_externals(self, _):
425 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500426
Brian Silverman9b7a6842014-05-05 16:19:11 -0700427 def check_installed(self, platforms, is_deploy):
428 packages = ('powerpc-wrs-vxworks', 'tcl')
429 if is_deploy:
430 packages += ('ncftp',)
431 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700432
Brian Silvermana29ebf92014-04-23 13:08:49 -0500433class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700434 """A Processor subclass for building prime code."""
435
Brian Silvermanb3d50542014-04-23 14:28:55 -0500436 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700437 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500438 super(PrimeProcessor.Platform, self).__init__()
439
Brian Silvermane6bada62014-05-04 16:16:54 -0700440 self.__architecture = architecture
441 self.__compiler = compiler
442 self.__debug = debug
443 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500444
445 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700446 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
447 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700448 % (self.architecture(), self.compiler(), self.debug(),
449 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500450 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700451 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700452 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500453
454 def os(self):
455 return 'linux'
456 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700457 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
458 def architecture(self):
459 return self.__architecture
460 def compiler(self):
461 return self.__compiler
462 def sanitizer(self):
463 return self.__sanitizer
464 def debug(self):
465 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500466
Brian Silvermana29ebf92014-04-23 13:08:49 -0500467 def outname(self):
468 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500469
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700470 def priority(self):
471 r = 0
472 if self.compiler() == 'gcc':
473 r -= 100
474 elif self.compiler() == 'clang':
475 r += 100
476 if self.sanitizer() != 'none':
477 r -= 50
478 elif self.debug():
479 r -= 10
480 if self.architecture() == 'amd64':
481 r += 5
482 return r
483
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500484 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700485 # Downloads code to the prime in a way that avoids clashing too badly with
486 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500487 SUM = 'md5sum'
488 TARGET_DIR = '/home/driver/robot_code/bin'
489 TEMP_DIR = '/tmp/aos_downloader'
490 TARGET = 'driver@' + get_ip('prime')
491
492 from_dir = os.path.join(self.outdir(), 'outputs')
493 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
494 stdin=open(os.devnull, 'r'),
495 cwd=from_dir)
496 to_download = subprocess.check_output(
497 ('ssh', TARGET,
Brian Silvermanff485782014-06-18 19:59:09 -0700498 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR} \\
499 && echo '{SUMS}' | {SUM} --check --quiet \\
500 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*$/\\1/g'""".
501 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums.decode('utf-8'),
502 SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500503 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700504 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500505 return
506 self.do_deploy(
507 dry_run,
Brian Silvermanff485782014-06-18 19:59:09 -0700508 ('scp', '-o', 'Compression yes')
509 + tuple([os.path.join(from_dir, f) for f in to_download.decode('utf-8').split('\n')[:-1]])
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500510 + (('%s:%s' % (TARGET, TEMP_DIR)),))
511 if not dry_run:
512 subprocess.check_call(
513 ('ssh', TARGET,
Brian Silvermanff485782014-06-18 19:59:09 -0700514 """mv {TMPDIR}/* {TO_DIR} \\
515 && echo 'Done moving new executables into place' \\
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500516 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
517 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
518
Brian Silvermana4aff562014-05-02 17:43:50 -0700519 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700520 OTHER_SYSROOT = '/opt/clang-3.5/'
521 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700522 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700523 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
524 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
525 if self.sanitizer() == 'address':
526 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700527 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700528 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
Brian Silverman415e65d2014-06-21 22:39:28 -0700529 ':detect_stack_use_after_return=1:detect_odr_violation=2' \
530 ':allow_user_segv_handler=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700531 elif self.sanitizer() == 'memory':
532 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
533 elif self.sanitizer() == 'thread':
534 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700535
536 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700537 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
538 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700539 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700540 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700541 # clang doesn't like being run directly on the preprocessed files.
542 r['CCACHE_CPP2'] = 'yes'
543 # Without this, ccache slows down because of the generated header files.
544 # The race condition that this opens up isn't a problem because the build
545 # system finishes modifying header files before compiling anything that
546 # uses them.
547 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700548
Brian Silvermane6bada62014-05-04 16:16:54 -0700549 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700550 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
551 ':' + os.environ['PATH']
552
Brian Silvermana4aff562014-05-02 17:43:50 -0700553 return r
554
Brian Silverman47cd6f62014-05-03 10:35:52 -0700555 ARCHITECTURES = ('arm', 'amd64')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400556 COMPILERS = ('clang', 'gcc', 'gcc_4.8', 'gcc_frc')
Brian Silverman47cd6f62014-05-03 10:35:52 -0700557 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
558 SANITIZER_TEST_WARNINGS = {
559 'memory': (True,
560"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700561 errors with msan (especially stdlibc++).
562 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700563 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700564 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500565
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500566 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700567 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500568
569 platforms = []
570 for architecture in PrimeProcessor.ARCHITECTURES:
571 for compiler in PrimeProcessor.COMPILERS:
572 for debug in [True, False]:
Brian Silvermanbae86d62014-09-14 01:05:31 -0400573 if ((architecture == 'arm' and compiler == 'gcc_4.8') or
574 (architecture == 'amd64' and compiler == 'gcc_frc')):
Brian Silverman47cd6f62014-05-03 10:35:52 -0700575 # We don't have a compiler to use here.
576 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500577 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700578 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
579 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700580 for compiler in ('gcc_4.8', 'clang'):
581 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
582 sanitizer == 'integer' or
583 sanitizer == 'memory'):
584 # GCC 4.8 doesn't support these sanitizers.
585 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700586 if sanitizer == 'none':
587 # We already added sanitizer == 'none' above.
588 continue
589 platforms.append(
590 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
591 self.__platforms = frozenset(platforms)
592
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500593 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700594 default_platforms = self.select_platforms(architecture='amd64',
595 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700596 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
597 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700598 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500599 elif is_deploy:
Brian Silvermane6bada62014-05-04 16:16:54 -0700600 default_platforms = self.select_platforms(architecture='arm',
Brian Silvermanff485782014-06-18 19:59:09 -0700601 compiler='clang',
Brian Silvermane6bada62014-05-04 16:16:54 -0700602 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500603 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700604 default_platforms = self.select_platforms(debug=False)
605 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500606
Brian Silvermane6bada62014-05-04 16:16:54 -0700607 def platforms(self):
608 return self.__platforms
609 def default_platforms(self):
610 return self.__default_platforms
611
612 def download_externals(self, platforms):
613 to_download = set()
614 for architecture in PrimeProcessor.ARCHITECTURES:
Brian Silverman99895b92014-09-14 01:01:15 -0400615 pie_sanitizers = set()
Brian Silvermane6bada62014-05-04 16:16:54 -0700616 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
Brian Silverman99895b92014-09-14 01:01:15 -0400617 pie_sanitizers.update(self.select_platforms(architecture=architecture,
618 sanitizer=sanitizer))
619 if platforms & pie_sanitizers:
620 to_download.add(architecture + '-fPIE')
621
Brian Silvermanbae86d62014-09-14 01:05:31 -0400622 frc_platforms = self.select_platforms(architecture=architecture,
623 compiler='gcc_frc')
624 if platforms & frc_platforms:
625 to_download.add(architecture + '_frc')
626
627 if platforms & (self.platforms() - pie_sanitizers - frc_platforms):
Brian Silverman99895b92014-09-14 01:01:15 -0400628 to_download.add(architecture)
629
Brian Silvermane6bada62014-05-04 16:16:54 -0700630 for download_target in to_download:
631 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500632
Brian Silverman452aaec2014-05-05 16:52:18 -0700633 def parse_platforms(self, platform_string):
634 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700635 return self.default_platforms()
636 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700637 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700638 if part == 'all':
639 r = self.platforms()
640 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500641 r = r | self.select_platforms_string(part[1:])
642 elif part[0] == '-':
643 r = r - self.select_platforms_string(part[1:])
644 elif part[0] == '=':
645 r = self.select_platforms_string(part[1:])
646 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500647 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700648 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500649 if not r:
650 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500651 return r
652
Brian Silverman452aaec2014-05-05 16:52:18 -0700653 def select_platforms(self, architecture=None, compiler=None, debug=None,
654 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500655 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700656 for platform in self.platforms():
657 if architecture is None or platform.architecture() == architecture:
658 if compiler is None or platform.compiler() == compiler:
659 if debug is None or platform.debug() == debug:
660 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700661 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500662 return set(r)
663
Brian Silverman452aaec2014-05-05 16:52:18 -0700664 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700665 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700666 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500667 if part in PrimeProcessor.ARCHITECTURES:
668 architecture = part
669 elif part in PrimeProcessor.COMPILERS:
670 compiler = part
671 elif part in ['debug', 'dbg']:
672 debug = True
673 elif part in ['release', 'nodebug', 'ndb']:
674 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700675 elif part in PrimeProcessor.SANITIZERS:
676 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700677 elif part == 'all':
678 architecture = compiler = debug = sanitizer = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500679 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700680 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700681 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500682 return self.select_platforms(
683 architecture=architecture,
684 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700685 debug=debug,
686 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500687
Brian Silverman9b7a6842014-05-05 16:19:11 -0700688 def check_installed(self, platforms, is_deploy):
689 packages = set(('lzip', 'm4', 'realpath'))
690 packages.add('ruby')
691 # clang-format from here gets used for all versions.
692 packages.add('clang-3.5')
693 packages.add('arm-eabi-gcc')
694 for platform in platforms:
695 if platform.architecture() == 'arm':
696 packages.add('gcc-4.7-arm-linux-gnueabihf')
697 packages.add('g++-4.7-arm-linux-gnueabihf')
698 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
699 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700700 if platform.compiler() == 'gcc_4.8':
701 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700702 if is_deploy:
703 packages.add('openssh-client')
704 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
705 packages.add('gcc-4.7')
706 packages.add('g++-4.7')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400707 elif platform.compiler() == 'gcc_frc':
708 packages.add('gcc-4.9-arm-frc-linux-gnueabi')
709 packages.add('g++-4.9-arm-frc-linux-gnueabi')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700710
711 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700712
Brian Silverman6bca4722014-05-20 17:02:49 -0700713def strsignal(num):
714 # It ends up with SIGIOT instead otherwise, which is weird.
715 if num == signal.SIGABRT:
716 return 'SIGABRT'
717 # SIGCLD is a weird way to spell it.
718 if num == signal.SIGCHLD:
719 return 'SIGCHLD'
720
721 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
722 for n in dir(signal) if n.startswith('SIG')
723 and '_' not in n)
724 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
725
Brian Silvermana29ebf92014-04-23 13:08:49 -0500726def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700727 sys.argv.pop(0)
728 exec_name = sys.argv.pop(0)
729 def print_help(exit_status=None, message=None):
730 if message:
731 print(message)
732 sys.stdout.write(
Brian Silvermanb9e89602014-06-27 14:21:08 -0500733"""Usage: {name} [-j n] [action] [-n] [platform] [target|extra_flag]...
Brian Silvermandf5348a2014-06-12 23:25:08 -0700734Arguments:
735 -j, --jobs Explicitly specify how many jobs to run at a time.
736 Defaults to the number of processors + 2.
737 -n, --dry-run Don't actually do whatever.
738 Currently only meaningful for deploy.
739 action What to do. Defaults to build.
740 build: Build the code.
741 clean: Remove all the built output.
742 tests: Build and then run tests.
743 deploy: Build and then download.
744 platform What variants of the code to build.
745 Defaults to something reasonable.
746 See below for details.
747 target... Which targets to build/test/etc.
748 Defaults to everything.
Brian Silvermanb9e89602014-06-27 14:21:08 -0500749 extra_flag... Extra flags associated with the targets.
750 --gtest_*: Arguments to pass on to tests.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500751
Brian Silvermandf5348a2014-06-12 23:25:08 -0700752Specifying targets:
753 Targets are combinations of architecture, compiler, and debug flags. Which
754 ones actually get run is built up as a set. It defaults to something
755 reasonable for the action (specified below).
756 The platform specification (the argument given to this script) is a comma-
757 separated sequence of hyphen-separated platforms, each with an optional
758 prefix.
759 Each selector (the things separated by commas) selects all of the platforms
760 which match all of its components. Its effect on the set of current platforms
761 depends on the prefix character.
762 Here are the prefix characters:
763 + Adds the selected platforms.
764 - Removes the selected platforms.
765 = Sets the current set to the selected platforms.
766 [none] Removes all non-selected platforms.
767 If this makes the current set empty, acts like =.
768 There is also the special psuedo-platform "all" which selects all platforms.
769 All of the available platforms:
770 {all_platforms}
771 Default platforms for deploying:
772 {deploy_platforms}
773 Default platforms for testing:
774 {test_platforms}
775 Default platforms for everything else:
776 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500777
Brian Silvermandf5348a2014-06-12 23:25:08 -0700778Examples of specifying targets:
779 build everything: "all"
780 only build things with clang: "clang"
781 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
782 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
783""".format(
784 name=exec_name,
785 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
786 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
787 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
788 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
789 ))
790 if exit_status is not None:
791 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500792
Brian Silvermandf5348a2014-06-12 23:25:08 -0700793 def sort_platforms(platforms):
794 return sorted(
795 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500796
Brian Silvermandf5348a2014-06-12 23:25:08 -0700797 def str_platforms(platforms):
798 r = []
799 for platform in sort_platforms(platforms):
800 r.append(str(platform))
801 if len(r) > 1:
802 r[-1] = 'and ' + r[-1]
803 return ', '.join(r)
804
805 class Arguments(object):
806 def __init__(self):
807 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
808 self.action_name = 'build'
809 self.dry_run = False
810 self.targets = []
811 self.platform = None
Brian Silvermanb9e89602014-06-27 14:21:08 -0500812 self.extra_flags = []
Brian Silvermana29ebf92014-04-23 13:08:49 -0500813
Brian Silvermandf5348a2014-06-12 23:25:08 -0700814 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500815
Brian Silvermandf5348a2014-06-12 23:25:08 -0700816 if len(sys.argv) < 2:
817 print_help(1, 'Not enough arguments')
818 args.processor = sys.argv.pop(0)
819 args.main_gyp = sys.argv.pop(0)
820 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
821 while sys.argv:
822 arg = sys.argv.pop(0)
823 if arg == '-j' or arg == '--jobs':
824 args.jobs = int(sys.argv.pop(0))
825 continue
826 if arg in VALID_ACTIONS:
827 args.action_name = arg
828 continue
829 if arg == '-n' or arg == '--dry-run':
830 if args.action_name != 'deploy':
831 print_help(1, '--dry-run is only valid for deploy')
832 args.dry_run = True
833 continue
834 if arg == '-h' or arg == '--help':
835 print_help(0)
Brian Silvermanb9e89602014-06-27 14:21:08 -0500836 if re.match('^--gtest_.*$', arg):
837 if args.action_name == 'tests':
838 args.extra_flags.append(arg)
839 continue
840 else:
841 print_help(1, '--gtest_* is only valid for tests')
Brian Silvermandf5348a2014-06-12 23:25:08 -0700842 if args.platform:
843 args.targets.append(arg)
844 else:
845 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500846
847 if args.processor == 'crio':
848 processor = CRIOProcessor()
849 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500850 processor = PrimeProcessor(args.action_name == 'tests',
851 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500852 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700853 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500854
Brian Silvermana29ebf92014-04-23 13:08:49 -0500855 unknown_platform_error = None
856 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700857 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500858 except Processor.UnknownPlatform as e:
859 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700860 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500861 platforms = processor.parse_platforms(None)
862 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700863 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500864
Brian Silverman9b7a6842014-05-05 16:19:11 -0700865 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700866 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500867
868 class ToolsConfig(object):
869 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500870 self.variables = {'AOS': aos_path()}
871 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500872 for line in f:
873 if line[0] == '#':
874 pass
875 elif line.isspace():
876 pass
877 else:
878 new_name, new_value = line.rstrip().split('=')
879 for name, value in self.variables.items():
880 new_value = new_value.replace('${%s}' % name, value)
881 self.variables[new_name] = new_value
882 def __getitem__(self, key):
883 return self.variables[key]
884
885 tools_config = ToolsConfig()
886
887 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700888 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500889 if issubclass(OSError, excinfo[0]):
890 if excinfo[1].errno == errno.ENOENT:
891 # Who cares if the file we're deleting isn't there?
892 return
893 raise excinfo[1]
894
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700895 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700896 """Determines if we need to run gyp again or not.
897
898 The generated build files are supposed to re-run gyp again themselves, but
899 that doesn't work (or at least it used to not) and we sometimes want to
900 modify the results anyways.
901
902 Args:
903 platform: The platform to check for.
904 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700905 if not os.path.exists(platform.build_ninja()):
906 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700907 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
908 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700909 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700910 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700911 if dirs.count('output'):
912 dirs.remove('output')
913 if dirs.count('.git'):
914 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700915 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700916 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700917 + ('-newer', platform.build_ninja(),
918 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700919 stdin=open(os.devnull, 'r'))
920
921 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700922 """Makes sure we pass through important environmental variables.
923
924 Returns:
925 An environment suitable for passing to subprocess.Popen and friends.
926 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700927 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700928 if not 'TERM' in build_env:
929 build_env['TERM'] = os.environ['TERM']
930 if not 'PATH' in build_env:
931 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700932 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700933
Brian Silvermandf5348a2014-06-12 23:25:08 -0700934 sorted_platforms = sort_platforms(platforms)
935 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700936
937 if args.action_name == 'tests':
938 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
939 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
940 if warned_about:
941 user_output(warning[1])
942 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700943 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700944 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
945 exit(1)
946
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700947 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700948 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700949 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500950 if args.action_name == 'clean':
951 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
952 else:
953 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700954 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500955 gyp = subprocess.Popen(
956 (tools_config['GYP'],
957 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500958 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500959 '--no-circular-check',
960 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500961 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500962 '-I/dev/stdin', '-Goutput_dir=output',
963 '-DOS=%s' % platform.os(),
964 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700965 '-DARCHITECTURE=%s' % platform.architecture(),
966 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
967 '-DFULL_COMPILER=%s' % platform.compiler(),
968 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
969 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman99895b92014-09-14 01:01:15 -0400970 '-DEXTERNALS_EXTRA=%s' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700971 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanbae86d62014-09-14 01:05:31 -0400972 else ('_frc' if platform.compiler() == 'gcc_frc' else ''))) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500973 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500974 stdin=subprocess.PIPE)
975 gyp.communicate(("""
976{
977 'target_defaults': {
978 'configurations': {
979 '%s': {}
980 }
981 }
982}""" % platform.outname()).encode())
983 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700984 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500985 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700986 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700987 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500988 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700989 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500990
991 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700992 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -0700993 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700994 if args.jobs:
995 call += ('-j', str(args.jobs))
996 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700997 stdin=open(os.devnull, 'r'),
998 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500999 except subprocess.CalledProcessError as e:
1000 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001001 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -05001002 raise e
1003
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001004 if args.action_name == 'deploy':
1005 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -07001006 elif args.action_name == 'tests':
1007 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001008 done_queue = queue.Queue()
1009 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001010 test_start_semaphore = threading.Semaphore(args.jobs)
1011 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001012 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001013 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001014 if target.endswith('_test'):
1015 to_run.append(target)
1016 else:
1017 to_run = os.listdir(dirname)
1018 for f in to_run:
Brian Silvermanb9e89602014-06-27 14:21:08 -05001019 thread = TestThread(os.path.join(dirname, f), tuple(args.extra_flags),
1020 env(platform), done_queue,
Brian Silvermanc3740c32014-05-04 12:42:47 -07001021 test_start_semaphore)
1022 running.append(thread)
1023 thread.start()
1024 try:
1025 while running:
1026 done = done_queue.get()
1027 running.remove(done)
1028 with test_output_lock:
1029 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -07001030 try:
1031 while True:
1032 line = done.output.get(False)
1033 if not sys.stdout.isatty():
1034 # Remove color escape codes.
1035 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1036 sys.stdout.write(line)
1037 except queue.Empty:
1038 pass
1039 if not done.returncode:
1040 test_output('Test %s succeeded' % done.name)
1041 else:
1042 if done.returncode < 0:
1043 sig = -done.returncode
1044 test_output('Test %s was killed by signal %d (%s)' % \
1045 (done.name, sig, strsignal(sig)))
1046 elif done.returncode != 1:
1047 test_output('Test %s exited with %d' % \
1048 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001049 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001050 test_output('Test %s failed' % done.name)
1051 user_output('Aborting because of test failure for %s.' % \
1052 platform)
1053 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001054 finally:
1055 if running:
1056 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001057# Stop all of them before killing processes because otherwise stopping some of
1058# them tends to let other ones that are waiting to start go.
1059 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -07001060 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001061 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001062 for thread in running:
1063 thread.terminate_process()
1064 to_remove = []
1065 for thread in running:
1066 thread.join(5)
1067 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -07001068 to_remove.append(thread)
1069 for thread in to_remove:
1070 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001071 for thread in running:
1072 test_output(
1073 'Test %s did not terminate. Killing it.' % thread.name)
1074 thread.kill_process()
1075 thread.join()
1076 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001077
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001078 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1079 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001080
1081if __name__ == '__main__':
1082 main()