blob: acca25f506874593732d1022ee68af6d6e9ef5bd [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
664 if platforms & (self.platforms() - pie_sanitizers - frc_platforms):
Brian Silverman99895b92014-09-14 01:01:15 -0400665 to_download.add(architecture)
666
Brian Silvermane6bada62014-05-04 16:16:54 -0700667 for download_target in to_download:
668 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500669
Brian Silverman452aaec2014-05-05 16:52:18 -0700670 def parse_platforms(self, platform_string):
671 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700672 return self.default_platforms()
673 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700674 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700675 if part == 'all':
676 r = self.platforms()
677 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500678 r = r | self.select_platforms_string(part[1:])
679 elif part[0] == '-':
680 r = r - self.select_platforms_string(part[1:])
681 elif part[0] == '=':
682 r = self.select_platforms_string(part[1:])
683 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500684 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700685 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500686 if not r:
687 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500688 return r
689
Brian Silverman452aaec2014-05-05 16:52:18 -0700690 def select_platforms(self, architecture=None, compiler=None, debug=None,
691 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500692 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700693 for platform in self.platforms():
694 if architecture is None or platform.architecture() == architecture:
695 if compiler is None or platform.compiler() == compiler:
696 if debug is None or platform.debug() == debug:
697 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700698 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500699 return set(r)
700
Brian Silverman452aaec2014-05-05 16:52:18 -0700701 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700702 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700703 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500704 if part in PrimeProcessor.ARCHITECTURES:
705 architecture = part
706 elif part in PrimeProcessor.COMPILERS:
707 compiler = part
708 elif part in ['debug', 'dbg']:
709 debug = True
710 elif part in ['release', 'nodebug', 'ndb']:
711 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700712 elif part in PrimeProcessor.SANITIZERS:
713 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700714 elif part == 'all':
715 architecture = compiler = debug = sanitizer = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500716 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700717 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700718 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500719 return self.select_platforms(
720 architecture=architecture,
721 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700722 debug=debug,
723 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500724
Brian Silverman9b7a6842014-05-05 16:19:11 -0700725 def check_installed(self, platforms, is_deploy):
726 packages = set(('lzip', 'm4', 'realpath'))
727 packages.add('ruby')
728 # clang-format from here gets used for all versions.
729 packages.add('clang-3.5')
730 packages.add('arm-eabi-gcc')
731 for platform in platforms:
732 if platform.architecture() == 'arm':
733 packages.add('gcc-4.7-arm-linux-gnueabihf')
734 packages.add('g++-4.7-arm-linux-gnueabihf')
735 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
736 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700737 if platform.compiler() == 'gcc_4.8':
738 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700739 if is_deploy:
740 packages.add('openssh-client')
741 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
742 packages.add('gcc-4.7')
743 packages.add('g++-4.7')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400744 elif platform.compiler() == 'gcc_frc':
745 packages.add('gcc-4.9-arm-frc-linux-gnueabi')
746 packages.add('g++-4.9-arm-frc-linux-gnueabi')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700747
748 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700749
Daniel Pettiaece37f2014-10-25 17:13:44 -0700750class Bot3PrimeProcessor(PrimeProcessor):
751 """A very simple subclass of PrimeProcessor whose main function is to allow
752 the building of third robot targets in separate directories from those of
753 the main robot."""
754 class Platform(PrimeProcessor.Platform):
755 def __str__(self):
756 return 'bot3-%s' % (super(Bot3PrimeProcessor.Platform, self).__str__())
757
758
Brian Silverman6bca4722014-05-20 17:02:49 -0700759def strsignal(num):
760 # It ends up with SIGIOT instead otherwise, which is weird.
761 if num == signal.SIGABRT:
762 return 'SIGABRT'
763 # SIGCLD is a weird way to spell it.
764 if num == signal.SIGCHLD:
765 return 'SIGCHLD'
766
767 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
768 for n in dir(signal) if n.startswith('SIG')
769 and '_' not in n)
770 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
771
Brian Silvermana29ebf92014-04-23 13:08:49 -0500772def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700773 sys.argv.pop(0)
774 exec_name = sys.argv.pop(0)
775 def print_help(exit_status=None, message=None):
776 if message:
777 print(message)
778 sys.stdout.write(
Brian Silvermanb9e89602014-06-27 14:21:08 -0500779"""Usage: {name} [-j n] [action] [-n] [platform] [target|extra_flag]...
Brian Silvermandf5348a2014-06-12 23:25:08 -0700780Arguments:
781 -j, --jobs Explicitly specify how many jobs to run at a time.
782 Defaults to the number of processors + 2.
783 -n, --dry-run Don't actually do whatever.
784 Currently only meaningful for deploy.
785 action What to do. Defaults to build.
786 build: Build the code.
787 clean: Remove all the built output.
788 tests: Build and then run tests.
789 deploy: Build and then download.
790 platform What variants of the code to build.
791 Defaults to something reasonable.
792 See below for details.
793 target... Which targets to build/test/etc.
794 Defaults to everything.
Brian Silvermanb9e89602014-06-27 14:21:08 -0500795 extra_flag... Extra flags associated with the targets.
796 --gtest_*: Arguments to pass on to tests.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500797
Brian Silvermandf5348a2014-06-12 23:25:08 -0700798Specifying targets:
799 Targets are combinations of architecture, compiler, and debug flags. Which
800 ones actually get run is built up as a set. It defaults to something
801 reasonable for the action (specified below).
802 The platform specification (the argument given to this script) is a comma-
803 separated sequence of hyphen-separated platforms, each with an optional
804 prefix.
805 Each selector (the things separated by commas) selects all of the platforms
806 which match all of its components. Its effect on the set of current platforms
807 depends on the prefix character.
808 Here are the prefix characters:
809 + Adds the selected platforms.
810 - Removes the selected platforms.
811 = Sets the current set to the selected platforms.
812 [none] Removes all non-selected platforms.
813 If this makes the current set empty, acts like =.
814 There is also the special psuedo-platform "all" which selects all platforms.
815 All of the available platforms:
816 {all_platforms}
817 Default platforms for deploying:
818 {deploy_platforms}
819 Default platforms for testing:
820 {test_platforms}
821 Default platforms for everything else:
822 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500823
Brian Silvermandf5348a2014-06-12 23:25:08 -0700824Examples of specifying targets:
825 build everything: "all"
826 only build things with clang: "clang"
827 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
828 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
829""".format(
830 name=exec_name,
831 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
832 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
833 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
834 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
835 ))
836 if exit_status is not None:
837 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500838
Brian Silvermandf5348a2014-06-12 23:25:08 -0700839 def sort_platforms(platforms):
840 return sorted(
841 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500842
Brian Silvermandf5348a2014-06-12 23:25:08 -0700843 def str_platforms(platforms):
844 r = []
845 for platform in sort_platforms(platforms):
846 r.append(str(platform))
847 if len(r) > 1:
848 r[-1] = 'and ' + r[-1]
849 return ', '.join(r)
850
851 class Arguments(object):
852 def __init__(self):
853 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
854 self.action_name = 'build'
855 self.dry_run = False
856 self.targets = []
857 self.platform = None
Brian Silvermanb9e89602014-06-27 14:21:08 -0500858 self.extra_flags = []
Brian Silvermana29ebf92014-04-23 13:08:49 -0500859
Brian Silvermandf5348a2014-06-12 23:25:08 -0700860 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500861
Brian Silvermandf5348a2014-06-12 23:25:08 -0700862 if len(sys.argv) < 2:
863 print_help(1, 'Not enough arguments')
864 args.processor = sys.argv.pop(0)
865 args.main_gyp = sys.argv.pop(0)
866 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
867 while sys.argv:
868 arg = sys.argv.pop(0)
869 if arg == '-j' or arg == '--jobs':
870 args.jobs = int(sys.argv.pop(0))
871 continue
872 if arg in VALID_ACTIONS:
873 args.action_name = arg
874 continue
875 if arg == '-n' or arg == '--dry-run':
876 if args.action_name != 'deploy':
877 print_help(1, '--dry-run is only valid for deploy')
878 args.dry_run = True
879 continue
880 if arg == '-h' or arg == '--help':
881 print_help(0)
Brian Silvermanb9e89602014-06-27 14:21:08 -0500882 if re.match('^--gtest_.*$', arg):
883 if args.action_name == 'tests':
884 args.extra_flags.append(arg)
885 continue
886 else:
887 print_help(1, '--gtest_* is only valid for tests')
Brian Silvermandf5348a2014-06-12 23:25:08 -0700888 if args.platform:
889 args.targets.append(arg)
890 else:
891 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500892
893 if args.processor == 'crio':
894 processor = CRIOProcessor()
895 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500896 processor = PrimeProcessor(args.action_name == 'tests',
897 args.action_name == 'deploy')
Daniel Pettiaece37f2014-10-25 17:13:44 -0700898 elif args.processor == 'bot3_prime':
899 processor = Bot3PrimeProcessor(args.action_name == 'tests',
900 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500901 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700902 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500903
Brian Silvermana29ebf92014-04-23 13:08:49 -0500904 unknown_platform_error = None
905 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700906 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500907 except Processor.UnknownPlatform as e:
908 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700909 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500910 platforms = processor.parse_platforms(None)
911 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700912 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500913
Brian Silverman9b7a6842014-05-05 16:19:11 -0700914 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700915 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500916
917 class ToolsConfig(object):
918 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500919 self.variables = {'AOS': aos_path()}
920 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500921 for line in f:
922 if line[0] == '#':
923 pass
924 elif line.isspace():
925 pass
926 else:
927 new_name, new_value = line.rstrip().split('=')
928 for name, value in self.variables.items():
929 new_value = new_value.replace('${%s}' % name, value)
930 self.variables[new_name] = new_value
931 def __getitem__(self, key):
932 return self.variables[key]
933
934 tools_config = ToolsConfig()
935
936 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700937 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500938 if issubclass(OSError, excinfo[0]):
939 if excinfo[1].errno == errno.ENOENT:
940 # Who cares if the file we're deleting isn't there?
941 return
942 raise excinfo[1]
943
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700944 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700945 """Determines if we need to run gyp again or not.
946
947 The generated build files are supposed to re-run gyp again themselves, but
948 that doesn't work (or at least it used to not) and we sometimes want to
949 modify the results anyways.
950
951 Args:
952 platform: The platform to check for.
953 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700954 if not os.path.exists(platform.build_ninja()):
955 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700956 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
957 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700958 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700959 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700960 if dirs.count('output'):
961 dirs.remove('output')
962 if dirs.count('.git'):
963 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700964 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700965 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700966 + ('-newer', platform.build_ninja(),
967 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700968 stdin=open(os.devnull, 'r'))
969
970 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700971 """Makes sure we pass through important environmental variables.
972
973 Returns:
974 An environment suitable for passing to subprocess.Popen and friends.
975 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700976 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700977 if not 'TERM' in build_env:
978 build_env['TERM'] = os.environ['TERM']
979 if not 'PATH' in build_env:
980 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700981 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700982
Brian Silvermandf5348a2014-06-12 23:25:08 -0700983 sorted_platforms = sort_platforms(platforms)
984 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700985
986 if args.action_name == 'tests':
987 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
988 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
989 if warned_about:
990 user_output(warning[1])
991 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700992 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700993 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
994 exit(1)
995
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700996 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700997 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700998 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500999 if args.action_name == 'clean':
1000 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
1001 else:
1002 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001003 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -05001004 gyp = subprocess.Popen(
1005 (tools_config['GYP'],
1006 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001007 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001008 '--no-circular-check',
1009 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001010 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001011 '-I/dev/stdin', '-Goutput_dir=output',
1012 '-DOS=%s' % platform.os(),
1013 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -07001014 '-DARCHITECTURE=%s' % platform.architecture(),
1015 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
1016 '-DFULL_COMPILER=%s' % platform.compiler(),
1017 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
1018 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman99895b92014-09-14 01:01:15 -04001019 '-DEXTERNALS_EXTRA=%s' %
Brian Silverman452aaec2014-05-05 16:52:18 -07001020 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanbae86d62014-09-14 01:05:31 -04001021 else ('_frc' if platform.compiler() == 'gcc_frc' else ''))) +
Brian Silvermanb3d50542014-04-23 14:28:55 -05001022 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001023 stdin=subprocess.PIPE)
1024 gyp.communicate(("""
1025{
1026 'target_defaults': {
1027 'configurations': {
1028 '%s': {}
1029 }
1030 }
1031}""" % platform.outname()).encode())
1032 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001033 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -05001034 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -07001035 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -07001036 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -05001037 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -07001038 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -05001039
1040 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001041 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -07001042 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001043 if args.jobs:
1044 call += ('-j', str(args.jobs))
1045 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -07001046 stdin=open(os.devnull, 'r'),
1047 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -05001048 except subprocess.CalledProcessError as e:
1049 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001050 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -05001051 raise e
1052
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001053 if args.action_name == 'deploy':
1054 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -07001055 elif args.action_name == 'tests':
1056 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001057 done_queue = queue.Queue()
1058 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001059 test_start_semaphore = threading.Semaphore(args.jobs)
1060 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001061 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001062 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001063 if target.endswith('_test'):
1064 to_run.append(target)
1065 else:
1066 to_run = os.listdir(dirname)
1067 for f in to_run:
Brian Silvermanb9e89602014-06-27 14:21:08 -05001068 thread = TestThread(os.path.join(dirname, f), tuple(args.extra_flags),
1069 env(platform), done_queue,
Brian Silvermanc3740c32014-05-04 12:42:47 -07001070 test_start_semaphore)
1071 running.append(thread)
1072 thread.start()
1073 try:
1074 while running:
1075 done = done_queue.get()
1076 running.remove(done)
1077 with test_output_lock:
1078 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -07001079 try:
1080 while True:
1081 line = done.output.get(False)
1082 if not sys.stdout.isatty():
1083 # Remove color escape codes.
1084 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1085 sys.stdout.write(line)
1086 except queue.Empty:
1087 pass
1088 if not done.returncode:
1089 test_output('Test %s succeeded' % done.name)
1090 else:
1091 if done.returncode < 0:
1092 sig = -done.returncode
1093 test_output('Test %s was killed by signal %d (%s)' % \
1094 (done.name, sig, strsignal(sig)))
1095 elif done.returncode != 1:
1096 test_output('Test %s exited with %d' % \
1097 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001098 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001099 test_output('Test %s failed' % done.name)
1100 user_output('Aborting because of test failure for %s.' % \
1101 platform)
1102 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001103 finally:
1104 if running:
1105 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001106# Stop all of them before killing processes because otherwise stopping some of
1107# them tends to let other ones that are waiting to start go.
1108 for thread in running:
1109 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001110 for thread in running:
Brian Silverman48766e42014-12-29 21:37:04 -08001111 test_output('\tKilling %s' % thread.name)
1112 thread.kill_process()
1113 thread.kill_process()
1114 test_output('Waiting for other tests to die')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001115 for thread in running:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001116 thread.kill_process()
1117 thread.join()
1118 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001119
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001120 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1121 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001122
1123if __name__ == '__main__':
1124 main()