blob: 77edd8930b161bb306ce2a9255c94067948f0e60 [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):
Brian Silverman48766e42014-12-29 21:37:04 -080084 def setup_test_process():
85# Shove it into its own process group so we can kill any subprocesses easily.
86 os.setpgid(0, 0)
87
Brian Silvermanc3740c32014-05-04 12:42:47 -070088 with self.start_semaphore:
Brian Silverman48766e42014-12-29 21:37:04 -080089 with self.process_lock:
90 if self.stopped:
91 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070092 test_output('Starting test %s...' % self.name)
Brian Silverman730bb012014-06-08 13:05:20 -070093 output_to_read, subprocess_output = pty.openpty()
94 self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
95 self.output)
96 self.output_copier.start()
Brian Silvermanf2bbe092014-05-13 16:55:03 -070097 try:
98 with self.process_lock:
Brian Silvermanb9e89602014-06-27 14:21:08 -050099 self.process = subprocess.Popen((self.name,) + self.args,
100 executable=self.executable,
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700101 env=self.env,
102 stderr=subprocess.STDOUT,
103 stdout=subprocess_output,
Brian Silverman48766e42014-12-29 21:37:04 -0800104 stdin=open(os.devnull, 'r'),
105 preexec_fn=setup_test_process)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700106 finally:
107 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700108 self.process.wait()
109 with self.process_lock:
110 self.returncode = self.process.returncode
111 self.process = None
112 if not self.stopped:
Brian Silverman730bb012014-06-08 13:05:20 -0700113 self.output_copier.join()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700114 self.done_queue.put(self)
115
Brian Silvermanc3740c32014-05-04 12:42:47 -0700116 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700117 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700118 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700119 if not self.process:
120 return
Brian Silverman6bca4722014-05-20 17:02:49 -0700121 try:
Brian Silverman48766e42014-12-29 21:37:04 -0800122 os.killpg(self.process.pid, signal.SIGKILL)
Brian Silverman6bca4722014-05-20 17:02:49 -0700123 except OSError as e:
124 if e.errno == errno.ESRCH:
125 # We don't really care if it's already gone.
126 pass
127 else:
128 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700129 def stop(self):
130 """Changes self to the stopped state."""
131 with self.process_lock:
132 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500133
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500134def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700135 """Returns:
136 A relative path to the aos directory.
137 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500138 return os.path.join(os.path.dirname(__file__), '..')
139
Austin Schuhd3680052014-10-25 17:52:18 -0700140def get_ip_base():
141 """Retrieves the IP address base."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700142 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700143 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500144 if not os.access(FILENAME, os.R_OK):
145 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
146 with open(FILENAME, 'w') as f:
147 f.write('10.9.71')
148 with open(FILENAME, 'r') as f:
Brian Silvermand043d472014-06-21 15:32:39 -0700149 base = f.readline().strip()
Austin Schuhd3680052014-10-25 17:52:18 -0700150 return base
151
152def get_ip(device):
153 """Retrieves the IP address for a given device."""
154 base = get_ip_base()
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500155 if device == 'prime':
156 return base + '.179'
157 elif device == 'robot':
158 return base + '.2'
Austin Schuh93426072014-10-21 22:22:06 -0700159 elif device == 'roboRIO':
160 return base + '.2'
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500161 else:
162 raise Exception('Unknown device %s to get an IP address for.' % device)
163
Austin Schuhd3680052014-10-25 17:52:18 -0700164def get_user(device):
165 """Retrieves the user for a given device."""
166 if device == 'prime':
167 return 'driver'
168 elif device == 'roboRIO':
169 return 'admin'
170 else:
171 raise Exception('Unknown device %s to get a user for.' % device)
172
173def get_temp_dir(device):
174 """Retrieves the temporary download directory for a given device."""
175 if device == 'prime':
176 return '/tmp/aos_downloader'
177 elif device == 'roboRIO':
178 return '/home/admin/tmp/aos_downloader'
179 else:
180 raise Exception('Unknown device %s to get a temp_dir for.' % device)
181
182def get_target_dir(device):
183 """Retrieves the tempory deploy directory for a given device."""
184 if device == 'prime':
185 return '/home/driver/robot_code/bin'
186 elif device == 'roboRIO':
187 return '/home/admin/robot_code'
188 else:
189 raise Exception('Unknown device %s to get a temp_dir for.' % device)
190
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700191def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700192 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700193 print('build.py: ' + message, file=sys.stderr)
194
Brian Silverman452aaec2014-05-05 16:52:18 -0700195# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700196test_output_lock = threading.RLock()
197def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700198 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700199 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700200 print('tests: ' + message, file=sys.stdout)
201
202def call_download_externals(argument):
203 """Calls download_externals.sh for a given set of externals.
204
205 Args:
206 argument: The argument to pass to the shell script to tell it what to
207 download.
208 """
209 subprocess.check_call(
210 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
211 argument),
212 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700213
Brian Silvermana29ebf92014-04-23 13:08:49 -0500214class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700215 """Represents a processor architecture we can build for."""
216
Brian Silvermana29ebf92014-04-23 13:08:49 -0500217 class UnknownPlatform(Exception):
218 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700219 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500220 self.message = message
221
Brian Silvermanb3d50542014-04-23 14:28:55 -0500222 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700223 """Represents a single way to build the code."""
224
Brian Silvermanb3d50542014-04-23 14:28:55 -0500225 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700226 """Returns:
227 The path of the directory build outputs get put in to.
228 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500229 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500230 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500231 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700232 """Returns:
233 The path of the build.ninja file.
234 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500235 return os.path.join(self.outdir(), 'build.ninja')
236
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500237 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700238 """Helper for subclasses to implement deploy.
239
240 Args:
241 dry_run: If True, prints the command instead of actually running it.
242 command: A tuple of command-line arguments.
243 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500244 real_command = (('echo',) + command) if dry_run else command
245 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500246
Brian Silvermane6bada62014-05-04 16:16:54 -0700247 def deploy(self, dry_run):
248 """Downloads the compiled code to the target computer."""
249 raise NotImplementedError('deploy should be overriden')
250 def outname(self):
251 """Returns:
252 The name of the directory the code will be compiled to.
253 """
254 raise NotImplementedError('outname should be overriden')
255 def os(self):
256 """Returns:
257 The name of the operating system this platform is for.
258
259 This will be used as the value of the OS gyp variable.
260 """
261 raise NotImplementedError('os should be overriden')
262 def gyp_platform(self):
263 """Returns:
264 The platform name the .gyp files know.
265
266 This will be used as the value of the PLATFORM gyp variable.
267 """
268 raise NotImplementedError('gyp_platform should be overriden')
269 def architecture(self):
270 """Returns:
271 The processor architecture for this platform.
272
273 This will be used as the value of the ARCHITECTURE gyp variable.
274 """
275 raise NotImplementedError('architecture should be overriden')
276 def compiler(self):
277 """Returns:
278 The compiler used for this platform.
279
280 Everything before the first _ will be used as the value of the
281 COMPILER gyp variable and the whole thing will be used as the value
282 of the FULL_COMPILER gyp variable.
283 """
284 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700285 def sanitizer(self):
286 """Returns:
287 The sanitizer used on this platform.
288
289 This will be used as the value of the SANITIZER gyp variable.
290
291 "none" if there isn't one.
292 """
293 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700294 def debug(self):
295 """Returns:
296 Whether or not this platform compiles with debugging information.
297
298 The DEBUG gyp variable will be set to "yes" or "no" based on this.
299 """
300 raise NotImplementedError('debug should be overriden')
301 def build_env(self):
302 """Returns:
303 A map of environment variables to set while building this platform.
304 """
305 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700306 def priority(self):
307 """Returns:
308 A relative priority for this platform relative to other ones.
309
310 Higher priority platforms will get built, tested, etc first. Generally,
311 platforms which give higher-quality compiler errors etc should come first.
312 """
313 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700314
Brian Silverman9b7a6842014-05-05 16:19:11 -0700315 def check_installed(self, platforms, is_deploy):
316 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700317 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700318 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700319 """Args:
320 string: A user-supplied string saying which platforms to select.
321
322 Returns:
323 A tuple of Platform objects.
324
325 Raises:
326 Processor.UnknownPlatform: Parsing string didn't work out.
327 """
328 raise NotImplementedError('parse_platforms should be overriden')
329 def extra_gyp_flags(self):
330 """Returns:
331 A tuple of extra flags to pass to gyp (if any).
332 """
333 return ()
334 def modify_ninja_file(self, ninja_file):
335 """Modifies a freshly generated ninja file as necessary.
336
337 Args:
338 ninja_file: Path to the file to modify.
339 """
340 pass
341 def download_externals(self, platforms):
342 """Calls download_externals as appropriate to build platforms.
343
344 Args:
345 platforms: A list of platforms to download external libraries for.
346 """
347 raise NotImplementedError('download_externals should be overriden')
348
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700349 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700350 """Helper for subclasses to implement check_installed.
351
352 Args:
353 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700354 all_packages = other_packages
355 # Necessary to build stuff.
356 all_packages += ('ccache', 'make')
357 # Necessary to download stuff to build.
358 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
359 # Necessary to build externals stuff.
360 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700361 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700362 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700363 result = subprocess.check_output(
364 ('dpkg-query', '--show') + all_packages,
365 stdin=open(os.devnull, 'r'),
366 stderr=subprocess.STDOUT)
367 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700368 output = e.output.decode('utf-8').rstrip()
369 not_found = []
370 for line in output.splitlines(True):
371 match = re.match(r'dpkg-query: no packages found matching (.*)',
372 line)
373 if match:
374 not_found.append(match.group(1))
375 user_output('Some packages not installed: %s.' % ', '.join(not_found))
376 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700377 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700378 exit(1)
379
Brian Silvermana29ebf92014-04-23 13:08:49 -0500380class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700381 """A Processor subclass for building cRIO code."""
382
Brian Silvermanb3d50542014-04-23 14:28:55 -0500383 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700384 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500385 super(CRIOProcessor.Platform, self).__init__()
386
Brian Silvermane6bada62014-05-04 16:16:54 -0700387 self.__debug = debug
388 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500389
390 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700391 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500392 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700393 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500394
395 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700396 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500397 def os(self):
398 return 'vxworks'
399 def gyp_platform(self):
400 return 'crio'
401 def architecture(self):
402 return 'ppc'
403 def compiler(self):
404 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700405 def sanitizer(self):
406 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700407 def debug(self):
408 return self.__debug
409 def wind_base(self):
410 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500411
Brian Silvermane48c09a2014-04-30 18:04:58 -0700412 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500413 def deploy(self, dry_run):
414 self.do_deploy(dry_run,
415 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700416 os.path.join(self.outdir(), 'lib',
417 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500418
Brian Silvermana4aff562014-05-02 17:43:50 -0700419 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700420 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700421
Brian Silvermana29ebf92014-04-23 13:08:49 -0500422 def __init__(self):
423 super(CRIOProcessor, self).__init__()
424
425 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700426 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500427 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700428 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500429
Brian Silverman452aaec2014-05-05 16:52:18 -0700430 def parse_platforms(self, platforms_string):
431 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700432 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700433 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700434 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500435 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700436 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700437 '"%s" not recognized as a cRIO platform.' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500438
Brian Silvermane6bada62014-05-04 16:16:54 -0700439 def wind_base(self):
440 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500441
Brian Silvermane6bada62014-05-04 16:16:54 -0700442 def extra_gyp_flags(self):
443 return ('-DWIND_BASE=%s' % self.wind_base(),)
444
445 def modify_ninja_file(self, ninja_file):
446 subprocess.check_call(
447 ('sed', '-i',
448 's/nm -gD/nm/g', ninja_file),
449 stdin=open(os.devnull, 'r'))
450
451 def download_externals(self, _):
452 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500453
Brian Silverman9b7a6842014-05-05 16:19:11 -0700454 def check_installed(self, platforms, is_deploy):
455 packages = ('powerpc-wrs-vxworks', 'tcl')
456 if is_deploy:
457 packages += ('ncftp',)
458 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700459
Brian Silvermana29ebf92014-04-23 13:08:49 -0500460class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700461 """A Processor subclass for building prime code."""
462
Brian Silvermanb3d50542014-04-23 14:28:55 -0500463 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700464 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500465 super(PrimeProcessor.Platform, self).__init__()
466
Brian Silvermane6bada62014-05-04 16:16:54 -0700467 self.__architecture = architecture
468 self.__compiler = compiler
469 self.__debug = debug
470 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500471
472 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700473 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
474 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700475 % (self.architecture(), self.compiler(), self.debug(),
476 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500477 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700478 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700479 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500480
481 def os(self):
482 return 'linux'
483 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700484 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
485 def architecture(self):
486 return self.__architecture
487 def compiler(self):
488 return self.__compiler
489 def sanitizer(self):
490 return self.__sanitizer
491 def debug(self):
492 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500493
Brian Silvermana29ebf92014-04-23 13:08:49 -0500494 def outname(self):
495 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500496
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700497 def priority(self):
498 r = 0
499 if self.compiler() == 'gcc':
500 r -= 100
501 elif self.compiler() == 'clang':
502 r += 100
503 if self.sanitizer() != 'none':
504 r -= 50
505 elif self.debug():
506 r -= 10
507 if self.architecture() == 'amd64':
508 r += 5
509 return r
510
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500511 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700512 # Downloads code to the prime in a way that avoids clashing too badly with
513 # starter (like the naive download everything one at a time).
Austin Schuhd3680052014-10-25 17:52:18 -0700514 if self.compiler() == 'gcc_frc':
515 device = 'roboRIO'
516 else:
517 device = 'prime'
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500518 SUM = 'md5sum'
Austin Schuhd3680052014-10-25 17:52:18 -0700519 TARGET_DIR = get_target_dir(device)
520 TEMP_DIR = get_temp_dir(device)
521 TARGET = get_user(device) + '@' + get_ip(device)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500522
523 from_dir = os.path.join(self.outdir(), 'outputs')
524 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
525 stdin=open(os.devnull, 'r'),
526 cwd=from_dir)
527 to_download = subprocess.check_output(
528 ('ssh', TARGET,
Austin Schuh93426072014-10-21 22:22:06 -0700529 """rm -rf {TMPDIR} && mkdir -p {TMPDIR} && cd {TO_DIR} \\
530 && echo '{SUMS}' | {SUM} -c \\
Brian Silvermanff485782014-06-18 19:59:09 -0700531 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*$/\\1/g'""".
532 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums.decode('utf-8'),
533 SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500534 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700535 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500536 return
537 self.do_deploy(
538 dry_run,
Brian Silvermanff485782014-06-18 19:59:09 -0700539 ('scp', '-o', 'Compression yes')
540 + 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 -0500541 + (('%s:%s' % (TARGET, TEMP_DIR)),))
542 if not dry_run:
Austin Schuhd3680052014-10-25 17:52:18 -0700543 mv_cmd = ['mv {TMPDIR}/* {TO_DIR} ']
544 if device == 'roboRIO':
545 mv_cmd.append('&& chmod u+s {TO_DIR}/starter_exe ')
546 mv_cmd.append('&& echo \'Done moving new executables into place\' ')
547 mv_cmd.append('&& bash -c \'sync && sync && sync\'')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500548 subprocess.check_call(
549 ('ssh', TARGET,
Austin Schuhd3680052014-10-25 17:52:18 -0700550 ''.join(mv_cmd).format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500551
Brian Silvermana4aff562014-05-02 17:43:50 -0700552 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700553 OTHER_SYSROOT = '/opt/clang-3.5/'
554 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700555 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700556 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
557 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
558 if self.sanitizer() == 'address':
559 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700560 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700561 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
Brian Silverman415e65d2014-06-21 22:39:28 -0700562 ':detect_stack_use_after_return=1:detect_odr_violation=2' \
563 ':allow_user_segv_handler=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700564 elif self.sanitizer() == 'memory':
565 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
566 elif self.sanitizer() == 'thread':
567 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700568
569 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700570 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
571 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700572 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700573 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700574 # clang doesn't like being run directly on the preprocessed files.
575 r['CCACHE_CPP2'] = 'yes'
576 # Without this, ccache slows down because of the generated header files.
577 # The race condition that this opens up isn't a problem because the build
578 # system finishes modifying header files before compiling anything that
579 # uses them.
580 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700581
Brian Silvermane6bada62014-05-04 16:16:54 -0700582 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700583 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
584 ':' + os.environ['PATH']
585
Brian Silvermana4aff562014-05-02 17:43:50 -0700586 return r
587
Brian Silverman47cd6f62014-05-03 10:35:52 -0700588 ARCHITECTURES = ('arm', 'amd64')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400589 COMPILERS = ('clang', 'gcc', 'gcc_4.8', 'gcc_frc')
Brian Silverman47cd6f62014-05-03 10:35:52 -0700590 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
591 SANITIZER_TEST_WARNINGS = {
592 'memory': (True,
593"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700594 errors with msan (especially stdlibc++).
595 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700596 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700597 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500598
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500599 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700600 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500601
602 platforms = []
603 for architecture in PrimeProcessor.ARCHITECTURES:
604 for compiler in PrimeProcessor.COMPILERS:
605 for debug in [True, False]:
Brian Silvermanbae86d62014-09-14 01:05:31 -0400606 if ((architecture == 'arm' and compiler == 'gcc_4.8') or
607 (architecture == 'amd64' and compiler == 'gcc_frc')):
Brian Silverman47cd6f62014-05-03 10:35:52 -0700608 # We don't have a compiler to use here.
609 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500610 platforms.append(
Daniel Pettiaece37f2014-10-25 17:13:44 -0700611 self.Platform(architecture, compiler, debug, 'none'))
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700612 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700613 for compiler in ('gcc_4.8', 'clang'):
614 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
615 sanitizer == 'integer' or
616 sanitizer == 'memory'):
617 # GCC 4.8 doesn't support these sanitizers.
618 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700619 if sanitizer == 'none':
620 # We already added sanitizer == 'none' above.
621 continue
622 platforms.append(
Daniel Pettiaece37f2014-10-25 17:13:44 -0700623 self.Platform('amd64', compiler, True, sanitizer))
Brian Silvermane6bada62014-05-04 16:16:54 -0700624 self.__platforms = frozenset(platforms)
625
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500626 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700627 default_platforms = self.select_platforms(architecture='amd64',
628 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700629 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
630 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700631 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500632 elif is_deploy:
Austin Schuhd3680052014-10-25 17:52:18 -0700633 if get_ip_base() == '10.99.71':
634 compiler = 'gcc_frc'
635 else:
636 compiler = 'clang'
Brian Silvermane6bada62014-05-04 16:16:54 -0700637 default_platforms = self.select_platforms(architecture='arm',
Austin Schuhd3680052014-10-25 17:52:18 -0700638 compiler=compiler,
Brian Silvermane6bada62014-05-04 16:16:54 -0700639 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500640 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700641 default_platforms = self.select_platforms(debug=False)
642 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500643
Brian Silvermane6bada62014-05-04 16:16:54 -0700644 def platforms(self):
645 return self.__platforms
646 def default_platforms(self):
647 return self.__default_platforms
648
649 def download_externals(self, platforms):
650 to_download = set()
651 for architecture in PrimeProcessor.ARCHITECTURES:
Brian Silverman99895b92014-09-14 01:01:15 -0400652 pie_sanitizers = set()
Brian Silvermane6bada62014-05-04 16:16:54 -0700653 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
Brian Silverman99895b92014-09-14 01:01:15 -0400654 pie_sanitizers.update(self.select_platforms(architecture=architecture,
655 sanitizer=sanitizer))
656 if platforms & pie_sanitizers:
657 to_download.add(architecture + '-fPIE')
658
Brian Silvermanbae86d62014-09-14 01:05:31 -0400659 frc_platforms = self.select_platforms(architecture=architecture,
660 compiler='gcc_frc')
661 if platforms & frc_platforms:
662 to_download.add(architecture + '_frc')
663
Brian Silvermanc5f56952015-01-07 21:20:47 -0800664 if platforms & (self.select_platforms(architecture=architecture) -
665 pie_sanitizers - frc_platforms):
Brian Silverman99895b92014-09-14 01:01:15 -0400666 to_download.add(architecture)
667
Brian Silvermane6bada62014-05-04 16:16:54 -0700668 for download_target in to_download:
669 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500670
Brian Silverman452aaec2014-05-05 16:52:18 -0700671 def parse_platforms(self, platform_string):
672 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700673 return self.default_platforms()
674 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700675 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700676 if part == 'all':
677 r = self.platforms()
678 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500679 r = r | self.select_platforms_string(part[1:])
680 elif part[0] == '-':
681 r = r - self.select_platforms_string(part[1:])
682 elif part[0] == '=':
683 r = self.select_platforms_string(part[1:])
684 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500685 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700686 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500687 if not r:
688 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500689 return r
690
Brian Silverman452aaec2014-05-05 16:52:18 -0700691 def select_platforms(self, architecture=None, compiler=None, debug=None,
692 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500693 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700694 for platform in self.platforms():
695 if architecture is None or platform.architecture() == architecture:
696 if compiler is None or platform.compiler() == compiler:
697 if debug is None or platform.debug() == debug:
698 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700699 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500700 return set(r)
701
Brian Silverman452aaec2014-05-05 16:52:18 -0700702 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700703 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700704 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500705 if part in PrimeProcessor.ARCHITECTURES:
706 architecture = part
707 elif part in PrimeProcessor.COMPILERS:
708 compiler = part
709 elif part in ['debug', 'dbg']:
710 debug = True
711 elif part in ['release', 'nodebug', 'ndb']:
712 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700713 elif part in PrimeProcessor.SANITIZERS:
714 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700715 elif part == 'all':
716 architecture = compiler = debug = sanitizer = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500717 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700718 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700719 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500720 return self.select_platforms(
721 architecture=architecture,
722 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700723 debug=debug,
724 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500725
Brian Silverman9b7a6842014-05-05 16:19:11 -0700726 def check_installed(self, platforms, is_deploy):
727 packages = set(('lzip', 'm4', 'realpath'))
728 packages.add('ruby')
729 # clang-format from here gets used for all versions.
730 packages.add('clang-3.5')
731 packages.add('arm-eabi-gcc')
732 for platform in platforms:
733 if platform.architecture() == 'arm':
734 packages.add('gcc-4.7-arm-linux-gnueabihf')
735 packages.add('g++-4.7-arm-linux-gnueabihf')
736 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
737 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700738 if platform.compiler() == 'gcc_4.8':
739 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700740 if is_deploy:
741 packages.add('openssh-client')
742 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
743 packages.add('gcc-4.7')
744 packages.add('g++-4.7')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400745 elif platform.compiler() == 'gcc_frc':
746 packages.add('gcc-4.9-arm-frc-linux-gnueabi')
747 packages.add('g++-4.9-arm-frc-linux-gnueabi')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700748
749 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700750
Daniel Pettiaece37f2014-10-25 17:13:44 -0700751class Bot3PrimeProcessor(PrimeProcessor):
752 """A very simple subclass of PrimeProcessor whose main function is to allow
753 the building of third robot targets in separate directories from those of
754 the main robot."""
755 class Platform(PrimeProcessor.Platform):
756 def __str__(self):
757 return 'bot3-%s' % (super(Bot3PrimeProcessor.Platform, self).__str__())
758
759
Brian Silverman6bca4722014-05-20 17:02:49 -0700760def strsignal(num):
761 # It ends up with SIGIOT instead otherwise, which is weird.
762 if num == signal.SIGABRT:
763 return 'SIGABRT'
764 # SIGCLD is a weird way to spell it.
765 if num == signal.SIGCHLD:
766 return 'SIGCHLD'
767
768 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
769 for n in dir(signal) if n.startswith('SIG')
770 and '_' not in n)
771 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
772
Brian Silvermana29ebf92014-04-23 13:08:49 -0500773def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700774 sys.argv.pop(0)
775 exec_name = sys.argv.pop(0)
776 def print_help(exit_status=None, message=None):
777 if message:
778 print(message)
779 sys.stdout.write(
Brian Silvermanb9e89602014-06-27 14:21:08 -0500780"""Usage: {name} [-j n] [action] [-n] [platform] [target|extra_flag]...
Brian Silvermandf5348a2014-06-12 23:25:08 -0700781Arguments:
782 -j, --jobs Explicitly specify how many jobs to run at a time.
783 Defaults to the number of processors + 2.
784 -n, --dry-run Don't actually do whatever.
785 Currently only meaningful for deploy.
786 action What to do. Defaults to build.
787 build: Build the code.
788 clean: Remove all the built output.
789 tests: Build and then run tests.
790 deploy: Build and then download.
791 platform What variants of the code to build.
792 Defaults to something reasonable.
793 See below for details.
794 target... Which targets to build/test/etc.
795 Defaults to everything.
Brian Silvermanb9e89602014-06-27 14:21:08 -0500796 extra_flag... Extra flags associated with the targets.
797 --gtest_*: Arguments to pass on to tests.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500798
Brian Silvermandf5348a2014-06-12 23:25:08 -0700799Specifying targets:
800 Targets are combinations of architecture, compiler, and debug flags. Which
801 ones actually get run is built up as a set. It defaults to something
802 reasonable for the action (specified below).
803 The platform specification (the argument given to this script) is a comma-
804 separated sequence of hyphen-separated platforms, each with an optional
805 prefix.
806 Each selector (the things separated by commas) selects all of the platforms
807 which match all of its components. Its effect on the set of current platforms
808 depends on the prefix character.
809 Here are the prefix characters:
810 + Adds the selected platforms.
811 - Removes the selected platforms.
812 = Sets the current set to the selected platforms.
813 [none] Removes all non-selected platforms.
814 If this makes the current set empty, acts like =.
815 There is also the special psuedo-platform "all" which selects all platforms.
816 All of the available platforms:
817 {all_platforms}
818 Default platforms for deploying:
819 {deploy_platforms}
820 Default platforms for testing:
821 {test_platforms}
822 Default platforms for everything else:
823 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500824
Brian Silvermandf5348a2014-06-12 23:25:08 -0700825Examples of specifying targets:
826 build everything: "all"
827 only build things with clang: "clang"
828 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
829 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
830""".format(
831 name=exec_name,
832 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
833 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
834 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
835 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
836 ))
837 if exit_status is not None:
838 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500839
Brian Silvermandf5348a2014-06-12 23:25:08 -0700840 def sort_platforms(platforms):
841 return sorted(
842 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500843
Brian Silvermandf5348a2014-06-12 23:25:08 -0700844 def str_platforms(platforms):
845 r = []
846 for platform in sort_platforms(platforms):
847 r.append(str(platform))
848 if len(r) > 1:
849 r[-1] = 'and ' + r[-1]
850 return ', '.join(r)
851
852 class Arguments(object):
853 def __init__(self):
854 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
855 self.action_name = 'build'
856 self.dry_run = False
857 self.targets = []
858 self.platform = None
Brian Silvermanb9e89602014-06-27 14:21:08 -0500859 self.extra_flags = []
Brian Silvermana29ebf92014-04-23 13:08:49 -0500860
Brian Silvermandf5348a2014-06-12 23:25:08 -0700861 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500862
Brian Silvermandf5348a2014-06-12 23:25:08 -0700863 if len(sys.argv) < 2:
864 print_help(1, 'Not enough arguments')
865 args.processor = sys.argv.pop(0)
866 args.main_gyp = sys.argv.pop(0)
867 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
868 while sys.argv:
869 arg = sys.argv.pop(0)
870 if arg == '-j' or arg == '--jobs':
871 args.jobs = int(sys.argv.pop(0))
872 continue
873 if arg in VALID_ACTIONS:
874 args.action_name = arg
875 continue
876 if arg == '-n' or arg == '--dry-run':
877 if args.action_name != 'deploy':
878 print_help(1, '--dry-run is only valid for deploy')
879 args.dry_run = True
880 continue
881 if arg == '-h' or arg == '--help':
882 print_help(0)
Brian Silvermanb9e89602014-06-27 14:21:08 -0500883 if re.match('^--gtest_.*$', arg):
884 if args.action_name == 'tests':
885 args.extra_flags.append(arg)
886 continue
887 else:
888 print_help(1, '--gtest_* is only valid for tests')
Brian Silvermandf5348a2014-06-12 23:25:08 -0700889 if args.platform:
890 args.targets.append(arg)
891 else:
892 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500893
894 if args.processor == 'crio':
895 processor = CRIOProcessor()
896 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500897 processor = PrimeProcessor(args.action_name == 'tests',
898 args.action_name == 'deploy')
Daniel Pettiaece37f2014-10-25 17:13:44 -0700899 elif args.processor == 'bot3_prime':
900 processor = Bot3PrimeProcessor(args.action_name == 'tests',
901 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500902 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700903 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500904
Brian Silvermana29ebf92014-04-23 13:08:49 -0500905 unknown_platform_error = None
906 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700907 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500908 except Processor.UnknownPlatform as e:
909 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700910 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500911 platforms = processor.parse_platforms(None)
912 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700913 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500914
Brian Silverman9b7a6842014-05-05 16:19:11 -0700915 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700916 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500917
918 class ToolsConfig(object):
919 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500920 self.variables = {'AOS': aos_path()}
921 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500922 for line in f:
923 if line[0] == '#':
924 pass
925 elif line.isspace():
926 pass
927 else:
928 new_name, new_value = line.rstrip().split('=')
929 for name, value in self.variables.items():
930 new_value = new_value.replace('${%s}' % name, value)
931 self.variables[new_name] = new_value
932 def __getitem__(self, key):
933 return self.variables[key]
934
935 tools_config = ToolsConfig()
936
937 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700938 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500939 if issubclass(OSError, excinfo[0]):
940 if excinfo[1].errno == errno.ENOENT:
941 # Who cares if the file we're deleting isn't there?
942 return
943 raise excinfo[1]
944
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700945 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700946 """Determines if we need to run gyp again or not.
947
948 The generated build files are supposed to re-run gyp again themselves, but
949 that doesn't work (or at least it used to not) and we sometimes want to
950 modify the results anyways.
951
952 Args:
953 platform: The platform to check for.
954 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700955 if not os.path.exists(platform.build_ninja()):
956 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700957 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
958 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700959 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700960 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700961 if dirs.count('output'):
962 dirs.remove('output')
963 if dirs.count('.git'):
964 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700965 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700966 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700967 + ('-newer', platform.build_ninja(),
968 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700969 stdin=open(os.devnull, 'r'))
970
971 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700972 """Makes sure we pass through important environmental variables.
973
974 Returns:
975 An environment suitable for passing to subprocess.Popen and friends.
976 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700977 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700978 if not 'TERM' in build_env:
979 build_env['TERM'] = os.environ['TERM']
980 if not 'PATH' in build_env:
981 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700982 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700983
Brian Silvermandf5348a2014-06-12 23:25:08 -0700984 sorted_platforms = sort_platforms(platforms)
985 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700986
987 if args.action_name == 'tests':
988 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
989 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
990 if warned_about:
991 user_output(warning[1])
992 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700993 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700994 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
995 exit(1)
996
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700997 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700998 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700999 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -05001000 if args.action_name == 'clean':
1001 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
1002 else:
1003 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001004 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -05001005 gyp = subprocess.Popen(
1006 (tools_config['GYP'],
1007 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001008 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001009 '--no-circular-check',
1010 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001011 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001012 '-I/dev/stdin', '-Goutput_dir=output',
1013 '-DOS=%s' % platform.os(),
1014 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -07001015 '-DARCHITECTURE=%s' % platform.architecture(),
1016 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
1017 '-DFULL_COMPILER=%s' % platform.compiler(),
1018 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
1019 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman99895b92014-09-14 01:01:15 -04001020 '-DEXTERNALS_EXTRA=%s' %
Brian Silverman452aaec2014-05-05 16:52:18 -07001021 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanbae86d62014-09-14 01:05:31 -04001022 else ('_frc' if platform.compiler() == 'gcc_frc' else ''))) +
Brian Silvermanb3d50542014-04-23 14:28:55 -05001023 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001024 stdin=subprocess.PIPE)
1025 gyp.communicate(("""
1026{
1027 'target_defaults': {
1028 'configurations': {
1029 '%s': {}
1030 }
1031 }
1032}""" % platform.outname()).encode())
1033 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001034 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -05001035 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -07001036 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -07001037 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -05001038 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -07001039 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -05001040
1041 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001042 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -07001043 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001044 if args.jobs:
1045 call += ('-j', str(args.jobs))
1046 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -07001047 stdin=open(os.devnull, 'r'),
1048 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -05001049 except subprocess.CalledProcessError as e:
1050 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001051 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -05001052 raise e
1053
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001054 if args.action_name == 'deploy':
1055 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -07001056 elif args.action_name == 'tests':
1057 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001058 done_queue = queue.Queue()
1059 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001060 test_start_semaphore = threading.Semaphore(args.jobs)
1061 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001062 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001063 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001064 if target.endswith('_test'):
1065 to_run.append(target)
1066 else:
1067 to_run = os.listdir(dirname)
1068 for f in to_run:
Brian Silvermanb9e89602014-06-27 14:21:08 -05001069 thread = TestThread(os.path.join(dirname, f), tuple(args.extra_flags),
1070 env(platform), done_queue,
Brian Silvermanc3740c32014-05-04 12:42:47 -07001071 test_start_semaphore)
1072 running.append(thread)
1073 thread.start()
1074 try:
1075 while running:
1076 done = done_queue.get()
1077 running.remove(done)
1078 with test_output_lock:
1079 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -07001080 try:
1081 while True:
1082 line = done.output.get(False)
1083 if not sys.stdout.isatty():
1084 # Remove color escape codes.
1085 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1086 sys.stdout.write(line)
1087 except queue.Empty:
1088 pass
1089 if not done.returncode:
1090 test_output('Test %s succeeded' % done.name)
1091 else:
1092 if done.returncode < 0:
1093 sig = -done.returncode
1094 test_output('Test %s was killed by signal %d (%s)' % \
1095 (done.name, sig, strsignal(sig)))
1096 elif done.returncode != 1:
1097 test_output('Test %s exited with %d' % \
1098 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001099 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001100 test_output('Test %s failed' % done.name)
1101 user_output('Aborting because of test failure for %s.' % \
1102 platform)
1103 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001104 finally:
1105 if running:
1106 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001107# Stop all of them before killing processes because otherwise stopping some of
1108# them tends to let other ones that are waiting to start go.
1109 for thread in running:
1110 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001111 for thread in running:
Brian Silverman48766e42014-12-29 21:37:04 -08001112 test_output('\tKilling %s' % thread.name)
1113 thread.kill_process()
1114 thread.kill_process()
1115 test_output('Waiting for other tests to die')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001116 for thread in running:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001117 thread.kill_process()
1118 thread.join()
1119 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001120
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001121 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1122 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001123
1124if __name__ == '__main__':
1125 main()