blob: c89e175acf2ed9dc8c0deb78de12846e2efec5cc [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:
Austin Schuhe77e9812015-02-16 02:58:17 -0800147 f.write('roboRIO-971.local')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500148 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':
Austin Schuhe77e9812015-02-16 02:58:17 -0800160 return base
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 Silverman5e94a442014-12-15 15:21:20 -0500361 not_found = []
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700362 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700363 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700364 result = subprocess.check_output(
Brian Silverman5e94a442014-12-15 15:21:20 -0500365 ('dpkg-query',
366 r"--showformat='${binary:Package}\t${db:Status-Abbrev}\n'",
367 '--show') + all_packages,
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700368 stdin=open(os.devnull, 'r'),
369 stderr=subprocess.STDOUT)
Brian Silverman5e94a442014-12-15 15:21:20 -0500370 for line in result.decode('utf-8').rstrip().splitlines(True):
371 match = re.match('^([^\t]+)\t[^i][^i]$', line)
372 if match:
373 not_found.append(match.group(1))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700374 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700375 output = e.output.decode('utf-8').rstrip()
Brian Silverman9b7a6842014-05-05 16:19:11 -0700376 for line in output.splitlines(True):
377 match = re.match(r'dpkg-query: no packages found matching (.*)',
378 line)
379 if match:
380 not_found.append(match.group(1))
Brian Silverman5e94a442014-12-15 15:21:20 -0500381 if not_found:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700382 user_output('Some packages not installed: %s.' % ', '.join(not_found))
383 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700384 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700385 exit(1)
386
Brian Silvermana29ebf92014-04-23 13:08:49 -0500387class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700388 """A Processor subclass for building prime code."""
389
Brian Silvermanb3d50542014-04-23 14:28:55 -0500390 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700391 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500392 super(PrimeProcessor.Platform, self).__init__()
393
Brian Silvermane6bada62014-05-04 16:16:54 -0700394 self.__architecture = architecture
395 self.__compiler = compiler
396 self.__debug = debug
397 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500398
399 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700400 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
401 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700402 % (self.architecture(), self.compiler(), self.debug(),
403 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500404 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700405 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700406 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500407
408 def os(self):
409 return 'linux'
410 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700411 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
412 def architecture(self):
413 return self.__architecture
414 def compiler(self):
415 return self.__compiler
416 def sanitizer(self):
417 return self.__sanitizer
418 def debug(self):
419 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500420
Brian Silvermana29ebf92014-04-23 13:08:49 -0500421 def outname(self):
422 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500423
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700424 def priority(self):
425 r = 0
426 if self.compiler() == 'gcc':
427 r -= 100
428 elif self.compiler() == 'clang':
429 r += 100
430 if self.sanitizer() != 'none':
431 r -= 50
432 elif self.debug():
433 r -= 10
434 if self.architecture() == 'amd64':
435 r += 5
436 return r
437
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500438 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700439 # Downloads code to the prime in a way that avoids clashing too badly with
440 # starter (like the naive download everything one at a time).
Brian Silverman20141f92015-01-05 17:39:01 -0800441 if self.compiler().endswith('_frc'):
Austin Schuhd3680052014-10-25 17:52:18 -0700442 device = 'roboRIO'
443 else:
444 device = 'prime'
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500445 SUM = 'md5sum'
Austin Schuhd3680052014-10-25 17:52:18 -0700446 TARGET_DIR = get_target_dir(device)
447 TEMP_DIR = get_temp_dir(device)
448 TARGET = get_user(device) + '@' + get_ip(device)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500449
450 from_dir = os.path.join(self.outdir(), 'outputs')
451 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
452 stdin=open(os.devnull, 'r'),
453 cwd=from_dir)
454 to_download = subprocess.check_output(
455 ('ssh', TARGET,
Austin Schuhe77e9812015-02-16 02:58:17 -0800456 """rm -rf {TMPDIR} && mkdir -p {TMPDIR} && \\
457 mkdir -p {TO_DIR} && cd {TO_DIR} \\
Austin Schuh93426072014-10-21 22:22:06 -0700458 && echo '{SUMS}' | {SUM} -c \\
Brian Silvermanff485782014-06-18 19:59:09 -0700459 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*$/\\1/g'""".
460 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums.decode('utf-8'),
461 SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500462 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700463 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500464 return
465 self.do_deploy(
466 dry_run,
Brian Silvermanff485782014-06-18 19:59:09 -0700467 ('scp', '-o', 'Compression yes')
468 + 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 -0500469 + (('%s:%s' % (TARGET, TEMP_DIR)),))
470 if not dry_run:
Austin Schuhd3680052014-10-25 17:52:18 -0700471 mv_cmd = ['mv {TMPDIR}/* {TO_DIR} ']
472 if device == 'roboRIO':
473 mv_cmd.append('&& chmod u+s {TO_DIR}/starter_exe ')
474 mv_cmd.append('&& echo \'Done moving new executables into place\' ')
475 mv_cmd.append('&& bash -c \'sync && sync && sync\'')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500476 subprocess.check_call(
477 ('ssh', TARGET,
Austin Schuhd3680052014-10-25 17:52:18 -0700478 ''.join(mv_cmd).format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500479
Brian Silvermana4aff562014-05-02 17:43:50 -0700480 def build_env(self):
Brian Silverman20141f92015-01-05 17:39:01 -0800481 OTHER_SYSROOT = '/usr/lib/llvm-3.5'
Brian Silvermane6bada62014-05-04 16:16:54 -0700482 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700483 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700484 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
Brian Silverman20141f92015-01-05 17:39:01 -0800485 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib'
Brian Silvermane6bada62014-05-04 16:16:54 -0700486 if self.sanitizer() == 'address':
487 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700488 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700489 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
Brian Silverman415e65d2014-06-21 22:39:28 -0700490 ':detect_stack_use_after_return=1:detect_odr_violation=2' \
491 ':allow_user_segv_handler=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700492 elif self.sanitizer() == 'memory':
493 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
494 elif self.sanitizer() == 'thread':
495 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700496
497 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700498 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
499 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700500 r['CCACHE_HASHDIR'] = 'yes'
Brian Silverman20141f92015-01-05 17:39:01 -0800501 if self.compiler().startswith('clang'):
Brian Silvermand3fac732014-05-03 16:03:46 -0700502 # clang doesn't like being run directly on the preprocessed files.
503 r['CCACHE_CPP2'] = 'yes'
504 # Without this, ccache slows down because of the generated header files.
505 # The race condition that this opens up isn't a problem because the build
506 # system finishes modifying header files before compiling anything that
507 # uses them.
508 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700509
Brian Silvermane6bada62014-05-04 16:16:54 -0700510 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700511 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
512 ':' + os.environ['PATH']
513
Brian Silvermana4aff562014-05-02 17:43:50 -0700514 return r
515
Brian Silverman47cd6f62014-05-03 10:35:52 -0700516 ARCHITECTURES = ('arm', 'amd64')
Brian Silverman20141f92015-01-05 17:39:01 -0800517 COMPILERS = ('clang', 'gcc', 'gcc_frc')
Brian Silverman47cd6f62014-05-03 10:35:52 -0700518 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
519 SANITIZER_TEST_WARNINGS = {
520 'memory': (True,
521"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700522 errors with msan (especially stdlibc++).
523 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700524 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700525 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500526
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500527 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700528 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500529
530 platforms = []
531 for architecture in PrimeProcessor.ARCHITECTURES:
532 for compiler in PrimeProcessor.COMPILERS:
533 for debug in [True, False]:
Brian Silverman20141f92015-01-05 17:39:01 -0800534 if ((architecture == 'arm' and not compiler.endswith('_frc')) or
535 (architecture == 'amd64' and compiler.endswith('_frc'))):
Brian Silverman47cd6f62014-05-03 10:35:52 -0700536 # We don't have a compiler to use here.
537 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500538 platforms.append(
Daniel Pettiaece37f2014-10-25 17:13:44 -0700539 self.Platform(architecture, compiler, debug, 'none'))
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700540 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman20141f92015-01-05 17:39:01 -0800541 for compiler in ('clang',):
Brian Silverman47cd6f62014-05-03 10:35:52 -0700542 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
543 sanitizer == 'integer' or
544 sanitizer == 'memory'):
545 # GCC 4.8 doesn't support these sanitizers.
546 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700547 if sanitizer == 'none':
548 # We already added sanitizer == 'none' above.
549 continue
550 platforms.append(
Daniel Pettiaece37f2014-10-25 17:13:44 -0700551 self.Platform('amd64', compiler, True, sanitizer))
Brian Silvermane6bada62014-05-04 16:16:54 -0700552 self.__platforms = frozenset(platforms)
553
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500554 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700555 default_platforms = self.select_platforms(architecture='amd64',
556 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700557 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
558 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700559 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500560 elif is_deploy:
Brian Silvermane6bada62014-05-04 16:16:54 -0700561 default_platforms = self.select_platforms(architecture='arm',
Brian Silverman20141f92015-01-05 17:39:01 -0800562 compiler='gcc_frc',
Brian Silvermane6bada62014-05-04 16:16:54 -0700563 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500564 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700565 default_platforms = self.select_platforms(debug=False)
566 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500567
Brian Silvermane6bada62014-05-04 16:16:54 -0700568 def platforms(self):
569 return self.__platforms
570 def default_platforms(self):
571 return self.__default_platforms
572
573 def download_externals(self, platforms):
574 to_download = set()
575 for architecture in PrimeProcessor.ARCHITECTURES:
Brian Silverman99895b92014-09-14 01:01:15 -0400576 pie_sanitizers = set()
Brian Silvermane6bada62014-05-04 16:16:54 -0700577 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
Brian Silverman99895b92014-09-14 01:01:15 -0400578 pie_sanitizers.update(self.select_platforms(architecture=architecture,
579 sanitizer=sanitizer))
580 if platforms & pie_sanitizers:
581 to_download.add(architecture + '-fPIE')
582
Brian Silvermanbae86d62014-09-14 01:05:31 -0400583 frc_platforms = self.select_platforms(architecture=architecture,
584 compiler='gcc_frc')
585 if platforms & frc_platforms:
586 to_download.add(architecture + '_frc')
587
Brian Silvermanc5f56952015-01-07 21:20:47 -0800588 if platforms & (self.select_platforms(architecture=architecture) -
589 pie_sanitizers - frc_platforms):
Brian Silverman99895b92014-09-14 01:01:15 -0400590 to_download.add(architecture)
591
Brian Silvermane6bada62014-05-04 16:16:54 -0700592 for download_target in to_download:
593 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500594
Brian Silverman452aaec2014-05-05 16:52:18 -0700595 def parse_platforms(self, platform_string):
596 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700597 return self.default_platforms()
598 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700599 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700600 if part == 'all':
601 r = self.platforms()
602 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500603 r = r | self.select_platforms_string(part[1:])
604 elif part[0] == '-':
605 r = r - self.select_platforms_string(part[1:])
606 elif part[0] == '=':
607 r = self.select_platforms_string(part[1:])
608 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500609 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700610 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500611 if not r:
612 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500613 return r
614
Brian Silverman452aaec2014-05-05 16:52:18 -0700615 def select_platforms(self, architecture=None, compiler=None, debug=None,
616 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500617 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700618 for platform in self.platforms():
619 if architecture is None or platform.architecture() == architecture:
620 if compiler is None or platform.compiler() == compiler:
621 if debug is None or platform.debug() == debug:
622 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700623 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500624 return set(r)
625
Brian Silverman452aaec2014-05-05 16:52:18 -0700626 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700627 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700628 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500629 if part in PrimeProcessor.ARCHITECTURES:
630 architecture = part
631 elif part in PrimeProcessor.COMPILERS:
632 compiler = part
633 elif part in ['debug', 'dbg']:
634 debug = True
635 elif part in ['release', 'nodebug', 'ndb']:
636 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700637 elif part in PrimeProcessor.SANITIZERS:
638 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700639 elif part == 'all':
640 architecture = compiler = debug = sanitizer = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500641 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700642 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700643 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500644 return self.select_platforms(
645 architecture=architecture,
646 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700647 debug=debug,
648 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500649
Brian Silverman9b7a6842014-05-05 16:19:11 -0700650 def check_installed(self, platforms, is_deploy):
651 packages = set(('lzip', 'm4', 'realpath'))
652 packages.add('ruby')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700653 packages.add('clang-3.5')
Brian Silverman718bca52015-01-07 21:23:36 -0800654 packages.add('clang-format-3.5')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700655 for platform in platforms:
Brian Silverman20141f92015-01-05 17:39:01 -0800656 if (platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8' or
657 platform.compiler() == 'clang_frc'):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700658 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700659 if platform.compiler() == 'gcc_4.8':
660 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700661 if is_deploy:
662 packages.add('openssh-client')
663 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
664 packages.add('gcc-4.7')
665 packages.add('g++-4.7')
Daniel Petti0081c182015-01-24 20:44:23 -0800666 elif platform.compiler() == 'gcc_frc' or platform.compiler() == 'clang_frc':
Brian Silvermanbae86d62014-09-14 01:05:31 -0400667 packages.add('gcc-4.9-arm-frc-linux-gnueabi')
668 packages.add('g++-4.9-arm-frc-linux-gnueabi')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700669
670 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700671
Daniel Pettiaece37f2014-10-25 17:13:44 -0700672class Bot3PrimeProcessor(PrimeProcessor):
673 """A very simple subclass of PrimeProcessor whose main function is to allow
674 the building of third robot targets in separate directories from those of
675 the main robot."""
676 class Platform(PrimeProcessor.Platform):
677 def __str__(self):
678 return 'bot3-%s' % (super(Bot3PrimeProcessor.Platform, self).__str__())
679
680
Brian Silverman6bca4722014-05-20 17:02:49 -0700681def strsignal(num):
682 # It ends up with SIGIOT instead otherwise, which is weird.
683 if num == signal.SIGABRT:
684 return 'SIGABRT'
685 # SIGCLD is a weird way to spell it.
686 if num == signal.SIGCHLD:
687 return 'SIGCHLD'
688
689 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
690 for n in dir(signal) if n.startswith('SIG')
691 and '_' not in n)
692 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
693
Brian Silvermana29ebf92014-04-23 13:08:49 -0500694def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700695 sys.argv.pop(0)
696 exec_name = sys.argv.pop(0)
697 def print_help(exit_status=None, message=None):
698 if message:
699 print(message)
700 sys.stdout.write(
Brian Silvermanb9e89602014-06-27 14:21:08 -0500701"""Usage: {name} [-j n] [action] [-n] [platform] [target|extra_flag]...
Brian Silvermandf5348a2014-06-12 23:25:08 -0700702Arguments:
703 -j, --jobs Explicitly specify how many jobs to run at a time.
704 Defaults to the number of processors + 2.
705 -n, --dry-run Don't actually do whatever.
706 Currently only meaningful for deploy.
707 action What to do. Defaults to build.
708 build: Build the code.
709 clean: Remove all the built output.
710 tests: Build and then run tests.
711 deploy: Build and then download.
712 platform What variants of the code to build.
713 Defaults to something reasonable.
714 See below for details.
715 target... Which targets to build/test/etc.
716 Defaults to everything.
Brian Silvermanb9e89602014-06-27 14:21:08 -0500717 extra_flag... Extra flags associated with the targets.
718 --gtest_*: Arguments to pass on to tests.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500719
Brian Silvermandf5348a2014-06-12 23:25:08 -0700720Specifying targets:
721 Targets are combinations of architecture, compiler, and debug flags. Which
722 ones actually get run is built up as a set. It defaults to something
723 reasonable for the action (specified below).
724 The platform specification (the argument given to this script) is a comma-
725 separated sequence of hyphen-separated platforms, each with an optional
726 prefix.
727 Each selector (the things separated by commas) selects all of the platforms
728 which match all of its components. Its effect on the set of current platforms
729 depends on the prefix character.
730 Here are the prefix characters:
731 + Adds the selected platforms.
732 - Removes the selected platforms.
733 = Sets the current set to the selected platforms.
734 [none] Removes all non-selected platforms.
735 If this makes the current set empty, acts like =.
736 There is also the special psuedo-platform "all" which selects all platforms.
737 All of the available platforms:
738 {all_platforms}
739 Default platforms for deploying:
740 {deploy_platforms}
741 Default platforms for testing:
742 {test_platforms}
743 Default platforms for everything else:
744 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500745
Brian Silvermandf5348a2014-06-12 23:25:08 -0700746Examples of specifying targets:
747 build everything: "all"
748 only build things with clang: "clang"
749 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
750 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
751""".format(
752 name=exec_name,
753 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
754 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
755 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
756 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
757 ))
758 if exit_status is not None:
759 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500760
Brian Silvermandf5348a2014-06-12 23:25:08 -0700761 def sort_platforms(platforms):
762 return sorted(
763 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500764
Brian Silvermandf5348a2014-06-12 23:25:08 -0700765 def str_platforms(platforms):
766 r = []
767 for platform in sort_platforms(platforms):
768 r.append(str(platform))
769 if len(r) > 1:
770 r[-1] = 'and ' + r[-1]
771 return ', '.join(r)
Brian Silverman20141f92015-01-05 17:39:01 -0800772
Brian Silvermandf5348a2014-06-12 23:25:08 -0700773 class Arguments(object):
774 def __init__(self):
775 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
776 self.action_name = 'build'
777 self.dry_run = False
778 self.targets = []
779 self.platform = None
Brian Silvermanb9e89602014-06-27 14:21:08 -0500780 self.extra_flags = []
Brian Silvermana29ebf92014-04-23 13:08:49 -0500781
Brian Silvermandf5348a2014-06-12 23:25:08 -0700782 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500783
Brian Silvermandf5348a2014-06-12 23:25:08 -0700784 if len(sys.argv) < 2:
785 print_help(1, 'Not enough arguments')
786 args.processor = sys.argv.pop(0)
787 args.main_gyp = sys.argv.pop(0)
788 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
789 while sys.argv:
790 arg = sys.argv.pop(0)
791 if arg == '-j' or arg == '--jobs':
792 args.jobs = int(sys.argv.pop(0))
793 continue
794 if arg in VALID_ACTIONS:
795 args.action_name = arg
796 continue
797 if arg == '-n' or arg == '--dry-run':
798 if args.action_name != 'deploy':
799 print_help(1, '--dry-run is only valid for deploy')
800 args.dry_run = True
801 continue
802 if arg == '-h' or arg == '--help':
803 print_help(0)
Brian Silvermanb9e89602014-06-27 14:21:08 -0500804 if re.match('^--gtest_.*$', arg):
805 if args.action_name == 'tests':
806 args.extra_flags.append(arg)
807 continue
808 else:
809 print_help(1, '--gtest_* is only valid for tests')
Brian Silvermandf5348a2014-06-12 23:25:08 -0700810 if args.platform:
811 args.targets.append(arg)
812 else:
813 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500814
Brian Silverman20141f92015-01-05 17:39:01 -0800815 if args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500816 processor = PrimeProcessor(args.action_name == 'tests',
817 args.action_name == 'deploy')
Daniel Pettiaece37f2014-10-25 17:13:44 -0700818 elif args.processor == 'bot3_prime':
819 processor = Bot3PrimeProcessor(args.action_name == 'tests',
820 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500821 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700822 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500823
Brian Silvermana29ebf92014-04-23 13:08:49 -0500824 unknown_platform_error = None
825 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700826 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500827 except Processor.UnknownPlatform as e:
828 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700829 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500830 platforms = processor.parse_platforms(None)
831 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700832 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500833
Brian Silverman9b7a6842014-05-05 16:19:11 -0700834 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700835 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500836
837 class ToolsConfig(object):
838 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500839 self.variables = {'AOS': aos_path()}
840 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500841 for line in f:
842 if line[0] == '#':
843 pass
844 elif line.isspace():
845 pass
846 else:
847 new_name, new_value = line.rstrip().split('=')
848 for name, value in self.variables.items():
849 new_value = new_value.replace('${%s}' % name, value)
850 self.variables[new_name] = new_value
851 def __getitem__(self, key):
852 return self.variables[key]
853
854 tools_config = ToolsConfig()
855
856 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700857 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500858 if issubclass(OSError, excinfo[0]):
859 if excinfo[1].errno == errno.ENOENT:
860 # Who cares if the file we're deleting isn't there?
861 return
862 raise excinfo[1]
863
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700864 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700865 """Determines if we need to run gyp again or not.
866
867 The generated build files are supposed to re-run gyp again themselves, but
868 that doesn't work (or at least it used to not) and we sometimes want to
869 modify the results anyways.
870
871 Args:
872 platform: The platform to check for.
873 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700874 if not os.path.exists(platform.build_ninja()):
875 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700876 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
877 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700878 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700879 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700880 if dirs.count('output'):
881 dirs.remove('output')
882 if dirs.count('.git'):
883 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700884 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700885 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700886 + ('-newer', platform.build_ninja(),
887 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700888 stdin=open(os.devnull, 'r'))
889
890 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700891 """Makes sure we pass through important environmental variables.
892
893 Returns:
894 An environment suitable for passing to subprocess.Popen and friends.
895 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700896 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700897 if not 'TERM' in build_env:
898 build_env['TERM'] = os.environ['TERM']
899 if not 'PATH' in build_env:
900 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700901 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700902
Brian Silvermandf5348a2014-06-12 23:25:08 -0700903 sorted_platforms = sort_platforms(platforms)
904 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700905
906 if args.action_name == 'tests':
907 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
908 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
909 if warned_about:
910 user_output(warning[1])
911 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700912 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700913 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
914 exit(1)
915
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700916 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700917 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700918 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500919 if args.action_name == 'clean':
920 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
921 else:
922 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700923 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500924 gyp = subprocess.Popen(
925 (tools_config['GYP'],
926 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500927 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500928 '--no-circular-check',
929 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500930 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500931 '-I/dev/stdin', '-Goutput_dir=output',
932 '-DOS=%s' % platform.os(),
933 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700934 '-DARCHITECTURE=%s' % platform.architecture(),
935 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
936 '-DFULL_COMPILER=%s' % platform.compiler(),
937 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
938 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman99895b92014-09-14 01:01:15 -0400939 '-DEXTERNALS_EXTRA=%s' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700940 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silverman20141f92015-01-05 17:39:01 -0800941 else ('_frc' if platform.compiler().endswith('_frc') else ''))) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500942 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500943 stdin=subprocess.PIPE)
944 gyp.communicate(("""
945{
946 'target_defaults': {
947 'configurations': {
948 '%s': {}
949 }
950 }
951}""" % platform.outname()).encode())
952 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700953 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500954 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700955 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700956 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500957 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700958 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500959
960 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700961 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -0700962 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700963 if args.jobs:
964 call += ('-j', str(args.jobs))
965 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700966 stdin=open(os.devnull, 'r'),
967 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500968 except subprocess.CalledProcessError as e:
969 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700970 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500971 raise e
972
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500973 if args.action_name == 'deploy':
974 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700975 elif args.action_name == 'tests':
976 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700977 done_queue = queue.Queue()
978 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700979 test_start_semaphore = threading.Semaphore(args.jobs)
980 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700981 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700982 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700983 if target.endswith('_test'):
984 to_run.append(target)
985 else:
986 to_run = os.listdir(dirname)
987 for f in to_run:
Brian Silvermanb9e89602014-06-27 14:21:08 -0500988 thread = TestThread(os.path.join(dirname, f), tuple(args.extra_flags),
989 env(platform), done_queue,
Brian Silvermanc3740c32014-05-04 12:42:47 -0700990 test_start_semaphore)
991 running.append(thread)
992 thread.start()
993 try:
994 while running:
995 done = done_queue.get()
996 running.remove(done)
997 with test_output_lock:
998 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -0700999 try:
1000 while True:
1001 line = done.output.get(False)
1002 if not sys.stdout.isatty():
1003 # Remove color escape codes.
1004 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1005 sys.stdout.write(line)
1006 except queue.Empty:
1007 pass
1008 if not done.returncode:
1009 test_output('Test %s succeeded' % done.name)
1010 else:
1011 if done.returncode < 0:
1012 sig = -done.returncode
1013 test_output('Test %s was killed by signal %d (%s)' % \
1014 (done.name, sig, strsignal(sig)))
1015 elif done.returncode != 1:
1016 test_output('Test %s exited with %d' % \
1017 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001018 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001019 test_output('Test %s failed' % done.name)
1020 user_output('Aborting because of test failure for %s.' % \
1021 platform)
1022 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001023 finally:
1024 if running:
1025 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001026# Stop all of them before killing processes because otherwise stopping some of
1027# them tends to let other ones that are waiting to start go.
1028 for thread in running:
1029 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001030 for thread in running:
Brian Silverman48766e42014-12-29 21:37:04 -08001031 test_output('\tKilling %s' % thread.name)
1032 thread.kill_process()
1033 thread.kill_process()
1034 test_output('Waiting for other tests to die')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001035 for thread in running:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001036 thread.kill_process()
1037 thread.join()
1038 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001039
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001040 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1041 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001042
1043if __name__ == '__main__':
1044 main()