blob: 61e8ba9df1f018224a666e6ce6ffa022a0f55f92 [file] [log] [blame]
Brian Silvermana29ebf92014-04-23 13:08:49 -05001#!/usr/bin/python3
2
Brian Silvermana29ebf92014-04-23 13:08:49 -05003import sys
4import subprocess
5import re
6import os
7import os.path
8import string
9import shutil
10import errno
Brian Silvermanc3740c32014-05-04 12:42:47 -070011import queue
12import threading
Brian Silvermanf2bbe092014-05-13 16:55:03 -070013import pty
Brian Silverman6bca4722014-05-20 17:02:49 -070014import signal
Brian Silvermanc3740c32014-05-04 12:42:47 -070015
16class TestThread(threading.Thread):
Brian Silvermane6bada62014-05-04 16:16:54 -070017 """Runs 1 test and keeps track of its current state.
18
19 A TestThread is either waiting to start the test, actually running it, done,
20 running it, or stopped. The first 3 always happen in that order and can
21 change to stopped at any time.
22
23 It will finish (ie join() will return) once the process has exited, at which
24 point accessing process to see the status is OK.
25
26 Attributes:
27 executable: The file path of the executable to run.
Brian Silvermanb9e89602014-06-27 14:21:08 -050028 args: A tuple of arguments to give the executable.
Brian Silvermane6bada62014-05-04 16:16:54 -070029 env: The environment variables to set.
30 done_queue: A queue.Queue to place self on once done running the test.
31 start_semaphore: A threading.Semaphore to wait on before starting.
32 process_lock: A lock around process.
33 process: The currently executing test process or None. Synchronized by
34 process_lock.
35 stopped: True if we're stopped.
Brian Silverman730bb012014-06-08 13:05:20 -070036 output: A queue of lines of output from the test.
Brian Silvermane6bada62014-05-04 16:16:54 -070037 """
Brian Silverman730bb012014-06-08 13:05:20 -070038
39 class OutputCopier(threading.Thread):
40 """Copies the output of a test from its output pty into a queue.
41
42 This is necessary because otherwise everything locks up if the test writes
43 too much output and fills up the pty's buffer.
44 """
45
46 def __init__(self, name, fd, queue):
47 super(TestThread.OutputCopier, self).__init__(
48 name=(name + '.OutputCopier'))
49
50 self.fd = fd
51 self.queue = queue
52
53 def run(self):
54 with os.fdopen(self.fd) as to_read:
55 try:
56 for line in to_read:
57 self.queue.put(line)
58 except IOError as e:
59# An EIO from the master side of the pty means we hit the end.
60 if e.errno == errno.EIO:
61 return
62 else:
63 raise e
64
Brian Silvermanb9e89602014-06-27 14:21:08 -050065 def __init__(self, executable, args, env, done_queue, start_semaphore):
Brian Silvermanc3740c32014-05-04 12:42:47 -070066 super(TestThread, self).__init__(
Brian Silvermanb9e89602014-06-27 14:21:08 -050067 name=os.path.split(executable)[-1])
Brian Silvermanc3740c32014-05-04 12:42:47 -070068
69 self.executable = executable
Brian Silvermanb9e89602014-06-27 14:21:08 -050070 self.args = args
Brian Silvermanc3740c32014-05-04 12:42:47 -070071 self.env = env
72 self.done_queue = done_queue
73 self.start_semaphore = start_semaphore
74
Brian Silverman730bb012014-06-08 13:05:20 -070075 self.output = queue.Queue()
76
Brian Silvermanc3740c32014-05-04 12:42:47 -070077 self.process_lock = threading.Lock()
78 self.process = None
79 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070080 self.returncode = None
Brian Silverman730bb012014-06-08 13:05:20 -070081 self.output_copier = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070082
83 def run(self):
84 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070085 if self.stopped:
86 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070087 test_output('Starting test %s...' % self.name)
Brian Silverman730bb012014-06-08 13:05:20 -070088 output_to_read, subprocess_output = pty.openpty()
89 self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
90 self.output)
91 self.output_copier.start()
Brian Silvermanf2bbe092014-05-13 16:55:03 -070092 try:
93 with self.process_lock:
Brian Silvermanb9e89602014-06-27 14:21:08 -050094 self.process = subprocess.Popen((self.name,) + self.args,
95 executable=self.executable,
Brian Silvermanf2bbe092014-05-13 16:55:03 -070096 env=self.env,
97 stderr=subprocess.STDOUT,
98 stdout=subprocess_output,
99 stdin=open(os.devnull, 'r'))
100 finally:
101 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700102 self.process.wait()
103 with self.process_lock:
104 self.returncode = self.process.returncode
105 self.process = None
106 if not self.stopped:
Brian Silverman730bb012014-06-08 13:05:20 -0700107 self.output_copier.join()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700108 self.done_queue.put(self)
109
110 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700111 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700112 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700113 if not self.process:
114 return
Brian Silvermana5826582014-06-03 19:46:35 -0700115 try:
116 self.process.terminate()
117 except OSError as e:
118 if e.errno == errno.ESRCH:
119 # We don't really care if it's already gone.
120 pass
121 else:
122 raise e
Brian Silvermanc3740c32014-05-04 12:42:47 -0700123 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700124 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700125 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700126 if not self.process:
127 return
Brian Silverman6bca4722014-05-20 17:02:49 -0700128 try:
129 self.process.kill()
130 except OSError as e:
131 if e.errno == errno.ESRCH:
132 # We don't really care if it's already gone.
133 pass
134 else:
135 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700136 def stop(self):
137 """Changes self to the stopped state."""
138 with self.process_lock:
139 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500140
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500141def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700142 """Returns:
143 A relative path to the aos directory.
144 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500145 return os.path.join(os.path.dirname(__file__), '..')
146
147def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -0700148 """Retrieves the IP address for a given device."""
149 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700150 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500151 if not os.access(FILENAME, os.R_OK):
152 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
153 with open(FILENAME, 'w') as f:
154 f.write('10.9.71')
155 with open(FILENAME, 'r') as f:
Brian Silvermand043d472014-06-21 15:32:39 -0700156 base = f.readline().strip()
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500157 if device == 'prime':
158 return base + '.179'
159 elif device == 'robot':
160 return base + '.2'
Austin Schuh93426072014-10-21 22:22:06 -0700161 elif device == 'roboRIO':
162 return base + '.2'
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500163 else:
164 raise Exception('Unknown device %s to get an IP address for.' % device)
165
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700166def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700167 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700168 print('build.py: ' + message, file=sys.stderr)
169
Brian Silverman452aaec2014-05-05 16:52:18 -0700170# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700171test_output_lock = threading.RLock()
172def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700173 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700174 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700175 print('tests: ' + message, file=sys.stdout)
176
177def call_download_externals(argument):
178 """Calls download_externals.sh for a given set of externals.
179
180 Args:
181 argument: The argument to pass to the shell script to tell it what to
182 download.
183 """
184 subprocess.check_call(
185 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
186 argument),
187 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700188
Brian Silvermana29ebf92014-04-23 13:08:49 -0500189class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700190 """Represents a processor architecture we can build for."""
191
Brian Silvermana29ebf92014-04-23 13:08:49 -0500192 class UnknownPlatform(Exception):
193 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700194 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500195 self.message = message
196
Brian Silvermanb3d50542014-04-23 14:28:55 -0500197 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700198 """Represents a single way to build the code."""
199
Brian Silvermanb3d50542014-04-23 14:28:55 -0500200 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700201 """Returns:
202 The path of the directory build outputs get put in to.
203 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500204 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500205 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500206 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700207 """Returns:
208 The path of the build.ninja file.
209 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500210 return os.path.join(self.outdir(), 'build.ninja')
211
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500212 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700213 """Helper for subclasses to implement deploy.
214
215 Args:
216 dry_run: If True, prints the command instead of actually running it.
217 command: A tuple of command-line arguments.
218 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500219 real_command = (('echo',) + command) if dry_run else command
220 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500221
Brian Silvermane6bada62014-05-04 16:16:54 -0700222 def deploy(self, dry_run):
223 """Downloads the compiled code to the target computer."""
224 raise NotImplementedError('deploy should be overriden')
225 def outname(self):
226 """Returns:
227 The name of the directory the code will be compiled to.
228 """
229 raise NotImplementedError('outname should be overriden')
230 def os(self):
231 """Returns:
232 The name of the operating system this platform is for.
233
234 This will be used as the value of the OS gyp variable.
235 """
236 raise NotImplementedError('os should be overriden')
237 def gyp_platform(self):
238 """Returns:
239 The platform name the .gyp files know.
240
241 This will be used as the value of the PLATFORM gyp variable.
242 """
243 raise NotImplementedError('gyp_platform should be overriden')
244 def architecture(self):
245 """Returns:
246 The processor architecture for this platform.
247
248 This will be used as the value of the ARCHITECTURE gyp variable.
249 """
250 raise NotImplementedError('architecture should be overriden')
251 def compiler(self):
252 """Returns:
253 The compiler used for this platform.
254
255 Everything before the first _ will be used as the value of the
256 COMPILER gyp variable and the whole thing will be used as the value
257 of the FULL_COMPILER gyp variable.
258 """
259 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700260 def sanitizer(self):
261 """Returns:
262 The sanitizer used on this platform.
263
264 This will be used as the value of the SANITIZER gyp variable.
265
266 "none" if there isn't one.
267 """
268 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700269 def debug(self):
270 """Returns:
271 Whether or not this platform compiles with debugging information.
272
273 The DEBUG gyp variable will be set to "yes" or "no" based on this.
274 """
275 raise NotImplementedError('debug should be overriden')
276 def build_env(self):
277 """Returns:
278 A map of environment variables to set while building this platform.
279 """
280 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700281 def priority(self):
282 """Returns:
283 A relative priority for this platform relative to other ones.
284
285 Higher priority platforms will get built, tested, etc first. Generally,
286 platforms which give higher-quality compiler errors etc should come first.
287 """
288 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700289
Brian Silverman9b7a6842014-05-05 16:19:11 -0700290 def check_installed(self, platforms, is_deploy):
291 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700292 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700293 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700294 """Args:
295 string: A user-supplied string saying which platforms to select.
296
297 Returns:
298 A tuple of Platform objects.
299
300 Raises:
301 Processor.UnknownPlatform: Parsing string didn't work out.
302 """
303 raise NotImplementedError('parse_platforms should be overriden')
304 def extra_gyp_flags(self):
305 """Returns:
306 A tuple of extra flags to pass to gyp (if any).
307 """
308 return ()
309 def modify_ninja_file(self, ninja_file):
310 """Modifies a freshly generated ninja file as necessary.
311
312 Args:
313 ninja_file: Path to the file to modify.
314 """
315 pass
316 def download_externals(self, platforms):
317 """Calls download_externals as appropriate to build platforms.
318
319 Args:
320 platforms: A list of platforms to download external libraries for.
321 """
322 raise NotImplementedError('download_externals should be overriden')
323
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700324 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700325 """Helper for subclasses to implement check_installed.
326
327 Args:
328 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700329 all_packages = other_packages
330 # Necessary to build stuff.
331 all_packages += ('ccache', 'make')
332 # Necessary to download stuff to build.
333 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
334 # Necessary to build externals stuff.
335 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700336 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700337 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700338 result = subprocess.check_output(
339 ('dpkg-query', '--show') + all_packages,
340 stdin=open(os.devnull, 'r'),
341 stderr=subprocess.STDOUT)
342 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700343 output = e.output.decode('utf-8').rstrip()
344 not_found = []
345 for line in output.splitlines(True):
346 match = re.match(r'dpkg-query: no packages found matching (.*)',
347 line)
348 if match:
349 not_found.append(match.group(1))
350 user_output('Some packages not installed: %s.' % ', '.join(not_found))
351 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700352 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700353 exit(1)
354
Brian Silvermana29ebf92014-04-23 13:08:49 -0500355class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700356 """A Processor subclass for building cRIO code."""
357
Brian Silvermanb3d50542014-04-23 14:28:55 -0500358 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700359 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500360 super(CRIOProcessor.Platform, self).__init__()
361
Brian Silvermane6bada62014-05-04 16:16:54 -0700362 self.__debug = debug
363 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500364
365 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700366 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500367 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700368 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500369
370 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700371 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500372 def os(self):
373 return 'vxworks'
374 def gyp_platform(self):
375 return 'crio'
376 def architecture(self):
377 return 'ppc'
378 def compiler(self):
379 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700380 def sanitizer(self):
381 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700382 def debug(self):
383 return self.__debug
384 def wind_base(self):
385 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500386
Brian Silvermane48c09a2014-04-30 18:04:58 -0700387 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500388 def deploy(self, dry_run):
389 self.do_deploy(dry_run,
390 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700391 os.path.join(self.outdir(), 'lib',
392 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500393
Brian Silvermana4aff562014-05-02 17:43:50 -0700394 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700395 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700396
Brian Silvermana29ebf92014-04-23 13:08:49 -0500397 def __init__(self):
398 super(CRIOProcessor, self).__init__()
399
400 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700401 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500402 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700403 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500404
Brian Silverman452aaec2014-05-05 16:52:18 -0700405 def parse_platforms(self, platforms_string):
406 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700407 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700408 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700409 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500410 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700411 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700412 '"%s" not recognized as a cRIO platform.' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500413
Brian Silvermane6bada62014-05-04 16:16:54 -0700414 def wind_base(self):
415 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500416
Brian Silvermane6bada62014-05-04 16:16:54 -0700417 def extra_gyp_flags(self):
418 return ('-DWIND_BASE=%s' % self.wind_base(),)
419
420 def modify_ninja_file(self, ninja_file):
421 subprocess.check_call(
422 ('sed', '-i',
423 's/nm -gD/nm/g', ninja_file),
424 stdin=open(os.devnull, 'r'))
425
426 def download_externals(self, _):
427 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500428
Brian Silverman9b7a6842014-05-05 16:19:11 -0700429 def check_installed(self, platforms, is_deploy):
430 packages = ('powerpc-wrs-vxworks', 'tcl')
431 if is_deploy:
432 packages += ('ncftp',)
433 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700434
Brian Silvermana29ebf92014-04-23 13:08:49 -0500435class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700436 """A Processor subclass for building prime code."""
437
Brian Silvermanb3d50542014-04-23 14:28:55 -0500438 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700439 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500440 super(PrimeProcessor.Platform, self).__init__()
441
Brian Silvermane6bada62014-05-04 16:16:54 -0700442 self.__architecture = architecture
443 self.__compiler = compiler
444 self.__debug = debug
445 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500446
447 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700448 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
449 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700450 % (self.architecture(), self.compiler(), self.debug(),
451 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500452 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700453 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700454 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500455
456 def os(self):
457 return 'linux'
458 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700459 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
460 def architecture(self):
461 return self.__architecture
462 def compiler(self):
463 return self.__compiler
464 def sanitizer(self):
465 return self.__sanitizer
466 def debug(self):
467 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500468
Brian Silvermana29ebf92014-04-23 13:08:49 -0500469 def outname(self):
470 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500471
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700472 def priority(self):
473 r = 0
474 if self.compiler() == 'gcc':
475 r -= 100
476 elif self.compiler() == 'clang':
477 r += 100
478 if self.sanitizer() != 'none':
479 r -= 50
480 elif self.debug():
481 r -= 10
482 if self.architecture() == 'amd64':
483 r += 5
484 return r
485
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500486 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700487 # Downloads code to the prime in a way that avoids clashing too badly with
488 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500489 SUM = 'md5sum'
490 TARGET_DIR = '/home/driver/robot_code/bin'
491 TEMP_DIR = '/tmp/aos_downloader'
492 TARGET = 'driver@' + get_ip('prime')
493
494 from_dir = os.path.join(self.outdir(), 'outputs')
495 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
496 stdin=open(os.devnull, 'r'),
497 cwd=from_dir)
498 to_download = subprocess.check_output(
499 ('ssh', TARGET,
Austin Schuh93426072014-10-21 22:22:06 -0700500 """rm -rf {TMPDIR} && mkdir -p {TMPDIR} && cd {TO_DIR} \\
501 && echo '{SUMS}' | {SUM} -c \\
Brian Silvermanff485782014-06-18 19:59:09 -0700502 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*$/\\1/g'""".
503 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums.decode('utf-8'),
504 SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500505 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700506 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500507 return
508 self.do_deploy(
509 dry_run,
Brian Silvermanff485782014-06-18 19:59:09 -0700510 ('scp', '-o', 'Compression yes')
511 + 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 -0500512 + (('%s:%s' % (TARGET, TEMP_DIR)),))
513 if not dry_run:
514 subprocess.check_call(
515 ('ssh', TARGET,
Brian Silvermanff485782014-06-18 19:59:09 -0700516 """mv {TMPDIR}/* {TO_DIR} \\
517 && echo 'Done moving new executables into place' \\
Austin Schuh93426072014-10-21 22:22:06 -0700518 && bash -c 'sync && sync && sync'""".format(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500519 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
520
Brian Silvermana4aff562014-05-02 17:43:50 -0700521 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700522 OTHER_SYSROOT = '/opt/clang-3.5/'
523 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700524 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700525 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
526 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
527 if self.sanitizer() == 'address':
528 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700529 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700530 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
Brian Silverman415e65d2014-06-21 22:39:28 -0700531 ':detect_stack_use_after_return=1:detect_odr_violation=2' \
532 ':allow_user_segv_handler=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700533 elif self.sanitizer() == 'memory':
534 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
535 elif self.sanitizer() == 'thread':
536 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700537
538 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700539 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
540 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700541 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700542 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700543 # clang doesn't like being run directly on the preprocessed files.
544 r['CCACHE_CPP2'] = 'yes'
545 # Without this, ccache slows down because of the generated header files.
546 # The race condition that this opens up isn't a problem because the build
547 # system finishes modifying header files before compiling anything that
548 # uses them.
549 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700550
Brian Silvermane6bada62014-05-04 16:16:54 -0700551 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700552 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
553 ':' + os.environ['PATH']
554
Brian Silvermana4aff562014-05-02 17:43:50 -0700555 return r
556
Brian Silverman47cd6f62014-05-03 10:35:52 -0700557 ARCHITECTURES = ('arm', 'amd64')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400558 COMPILERS = ('clang', 'gcc', 'gcc_4.8', 'gcc_frc')
Brian Silverman47cd6f62014-05-03 10:35:52 -0700559 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
560 SANITIZER_TEST_WARNINGS = {
561 'memory': (True,
562"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700563 errors with msan (especially stdlibc++).
564 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700565 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700566 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500567
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500568 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700569 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500570
571 platforms = []
572 for architecture in PrimeProcessor.ARCHITECTURES:
573 for compiler in PrimeProcessor.COMPILERS:
574 for debug in [True, False]:
Brian Silvermanbae86d62014-09-14 01:05:31 -0400575 if ((architecture == 'arm' and compiler == 'gcc_4.8') or
576 (architecture == 'amd64' and compiler == 'gcc_frc')):
Brian Silverman47cd6f62014-05-03 10:35:52 -0700577 # We don't have a compiler to use here.
578 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500579 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700580 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
581 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700582 for compiler in ('gcc_4.8', 'clang'):
583 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
584 sanitizer == 'integer' or
585 sanitizer == 'memory'):
586 # GCC 4.8 doesn't support these sanitizers.
587 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700588 if sanitizer == 'none':
589 # We already added sanitizer == 'none' above.
590 continue
591 platforms.append(
592 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
593 self.__platforms = frozenset(platforms)
594
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500595 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700596 default_platforms = self.select_platforms(architecture='amd64',
597 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700598 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
599 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700600 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500601 elif is_deploy:
Brian Silvermane6bada62014-05-04 16:16:54 -0700602 default_platforms = self.select_platforms(architecture='arm',
Brian Silvermanff485782014-06-18 19:59:09 -0700603 compiler='clang',
Brian Silvermane6bada62014-05-04 16:16:54 -0700604 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500605 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700606 default_platforms = self.select_platforms(debug=False)
607 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500608
Brian Silvermane6bada62014-05-04 16:16:54 -0700609 def platforms(self):
610 return self.__platforms
611 def default_platforms(self):
612 return self.__default_platforms
613
614 def download_externals(self, platforms):
615 to_download = set()
616 for architecture in PrimeProcessor.ARCHITECTURES:
Brian Silverman99895b92014-09-14 01:01:15 -0400617 pie_sanitizers = set()
Brian Silvermane6bada62014-05-04 16:16:54 -0700618 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
Brian Silverman99895b92014-09-14 01:01:15 -0400619 pie_sanitizers.update(self.select_platforms(architecture=architecture,
620 sanitizer=sanitizer))
621 if platforms & pie_sanitizers:
622 to_download.add(architecture + '-fPIE')
623
Brian Silvermanbae86d62014-09-14 01:05:31 -0400624 frc_platforms = self.select_platforms(architecture=architecture,
625 compiler='gcc_frc')
626 if platforms & frc_platforms:
627 to_download.add(architecture + '_frc')
628
629 if platforms & (self.platforms() - pie_sanitizers - frc_platforms):
Brian Silverman99895b92014-09-14 01:01:15 -0400630 to_download.add(architecture)
631
Brian Silvermane6bada62014-05-04 16:16:54 -0700632 for download_target in to_download:
633 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500634
Brian Silverman452aaec2014-05-05 16:52:18 -0700635 def parse_platforms(self, platform_string):
636 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700637 return self.default_platforms()
638 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700639 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700640 if part == 'all':
641 r = self.platforms()
642 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500643 r = r | self.select_platforms_string(part[1:])
644 elif part[0] == '-':
645 r = r - self.select_platforms_string(part[1:])
646 elif part[0] == '=':
647 r = self.select_platforms_string(part[1:])
648 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500649 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700650 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500651 if not r:
652 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500653 return r
654
Brian Silverman452aaec2014-05-05 16:52:18 -0700655 def select_platforms(self, architecture=None, compiler=None, debug=None,
656 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500657 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700658 for platform in self.platforms():
659 if architecture is None or platform.architecture() == architecture:
660 if compiler is None or platform.compiler() == compiler:
661 if debug is None or platform.debug() == debug:
662 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700663 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500664 return set(r)
665
Brian Silverman452aaec2014-05-05 16:52:18 -0700666 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700667 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700668 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500669 if part in PrimeProcessor.ARCHITECTURES:
670 architecture = part
671 elif part in PrimeProcessor.COMPILERS:
672 compiler = part
673 elif part in ['debug', 'dbg']:
674 debug = True
675 elif part in ['release', 'nodebug', 'ndb']:
676 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700677 elif part in PrimeProcessor.SANITIZERS:
678 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700679 elif part == 'all':
680 architecture = compiler = debug = sanitizer = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500681 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700682 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700683 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500684 return self.select_platforms(
685 architecture=architecture,
686 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700687 debug=debug,
688 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500689
Brian Silverman9b7a6842014-05-05 16:19:11 -0700690 def check_installed(self, platforms, is_deploy):
691 packages = set(('lzip', 'm4', 'realpath'))
692 packages.add('ruby')
693 # clang-format from here gets used for all versions.
694 packages.add('clang-3.5')
695 packages.add('arm-eabi-gcc')
696 for platform in platforms:
697 if platform.architecture() == 'arm':
698 packages.add('gcc-4.7-arm-linux-gnueabihf')
699 packages.add('g++-4.7-arm-linux-gnueabihf')
700 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
701 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700702 if platform.compiler() == 'gcc_4.8':
703 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700704 if is_deploy:
705 packages.add('openssh-client')
706 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
707 packages.add('gcc-4.7')
708 packages.add('g++-4.7')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400709 elif platform.compiler() == 'gcc_frc':
710 packages.add('gcc-4.9-arm-frc-linux-gnueabi')
711 packages.add('g++-4.9-arm-frc-linux-gnueabi')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700712
713 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700714
Brian Silverman6bca4722014-05-20 17:02:49 -0700715def strsignal(num):
716 # It ends up with SIGIOT instead otherwise, which is weird.
717 if num == signal.SIGABRT:
718 return 'SIGABRT'
719 # SIGCLD is a weird way to spell it.
720 if num == signal.SIGCHLD:
721 return 'SIGCHLD'
722
723 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
724 for n in dir(signal) if n.startswith('SIG')
725 and '_' not in n)
726 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
727
Brian Silvermana29ebf92014-04-23 13:08:49 -0500728def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700729 sys.argv.pop(0)
730 exec_name = sys.argv.pop(0)
731 def print_help(exit_status=None, message=None):
732 if message:
733 print(message)
734 sys.stdout.write(
Brian Silvermanb9e89602014-06-27 14:21:08 -0500735"""Usage: {name} [-j n] [action] [-n] [platform] [target|extra_flag]...
Brian Silvermandf5348a2014-06-12 23:25:08 -0700736Arguments:
737 -j, --jobs Explicitly specify how many jobs to run at a time.
738 Defaults to the number of processors + 2.
739 -n, --dry-run Don't actually do whatever.
740 Currently only meaningful for deploy.
741 action What to do. Defaults to build.
742 build: Build the code.
743 clean: Remove all the built output.
744 tests: Build and then run tests.
745 deploy: Build and then download.
746 platform What variants of the code to build.
747 Defaults to something reasonable.
748 See below for details.
749 target... Which targets to build/test/etc.
750 Defaults to everything.
Brian Silvermanb9e89602014-06-27 14:21:08 -0500751 extra_flag... Extra flags associated with the targets.
752 --gtest_*: Arguments to pass on to tests.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500753
Brian Silvermandf5348a2014-06-12 23:25:08 -0700754Specifying targets:
755 Targets are combinations of architecture, compiler, and debug flags. Which
756 ones actually get run is built up as a set. It defaults to something
757 reasonable for the action (specified below).
758 The platform specification (the argument given to this script) is a comma-
759 separated sequence of hyphen-separated platforms, each with an optional
760 prefix.
761 Each selector (the things separated by commas) selects all of the platforms
762 which match all of its components. Its effect on the set of current platforms
763 depends on the prefix character.
764 Here are the prefix characters:
765 + Adds the selected platforms.
766 - Removes the selected platforms.
767 = Sets the current set to the selected platforms.
768 [none] Removes all non-selected platforms.
769 If this makes the current set empty, acts like =.
770 There is also the special psuedo-platform "all" which selects all platforms.
771 All of the available platforms:
772 {all_platforms}
773 Default platforms for deploying:
774 {deploy_platforms}
775 Default platforms for testing:
776 {test_platforms}
777 Default platforms for everything else:
778 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500779
Brian Silvermandf5348a2014-06-12 23:25:08 -0700780Examples of specifying targets:
781 build everything: "all"
782 only build things with clang: "clang"
783 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
784 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
785""".format(
786 name=exec_name,
787 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
788 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
789 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
790 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
791 ))
792 if exit_status is not None:
793 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500794
Brian Silvermandf5348a2014-06-12 23:25:08 -0700795 def sort_platforms(platforms):
796 return sorted(
797 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500798
Brian Silvermandf5348a2014-06-12 23:25:08 -0700799 def str_platforms(platforms):
800 r = []
801 for platform in sort_platforms(platforms):
802 r.append(str(platform))
803 if len(r) > 1:
804 r[-1] = 'and ' + r[-1]
805 return ', '.join(r)
806
807 class Arguments(object):
808 def __init__(self):
809 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
810 self.action_name = 'build'
811 self.dry_run = False
812 self.targets = []
813 self.platform = None
Brian Silvermanb9e89602014-06-27 14:21:08 -0500814 self.extra_flags = []
Brian Silvermana29ebf92014-04-23 13:08:49 -0500815
Brian Silvermandf5348a2014-06-12 23:25:08 -0700816 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500817
Brian Silvermandf5348a2014-06-12 23:25:08 -0700818 if len(sys.argv) < 2:
819 print_help(1, 'Not enough arguments')
820 args.processor = sys.argv.pop(0)
821 args.main_gyp = sys.argv.pop(0)
822 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
823 while sys.argv:
824 arg = sys.argv.pop(0)
825 if arg == '-j' or arg == '--jobs':
826 args.jobs = int(sys.argv.pop(0))
827 continue
828 if arg in VALID_ACTIONS:
829 args.action_name = arg
830 continue
831 if arg == '-n' or arg == '--dry-run':
832 if args.action_name != 'deploy':
833 print_help(1, '--dry-run is only valid for deploy')
834 args.dry_run = True
835 continue
836 if arg == '-h' or arg == '--help':
837 print_help(0)
Brian Silvermanb9e89602014-06-27 14:21:08 -0500838 if re.match('^--gtest_.*$', arg):
839 if args.action_name == 'tests':
840 args.extra_flags.append(arg)
841 continue
842 else:
843 print_help(1, '--gtest_* is only valid for tests')
Brian Silvermandf5348a2014-06-12 23:25:08 -0700844 if args.platform:
845 args.targets.append(arg)
846 else:
847 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500848
849 if args.processor == 'crio':
850 processor = CRIOProcessor()
851 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500852 processor = PrimeProcessor(args.action_name == 'tests',
853 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500854 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700855 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500856
Brian Silvermana29ebf92014-04-23 13:08:49 -0500857 unknown_platform_error = None
858 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700859 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500860 except Processor.UnknownPlatform as e:
861 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700862 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500863 platforms = processor.parse_platforms(None)
864 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700865 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500866
Brian Silverman9b7a6842014-05-05 16:19:11 -0700867 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700868 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500869
870 class ToolsConfig(object):
871 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500872 self.variables = {'AOS': aos_path()}
873 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500874 for line in f:
875 if line[0] == '#':
876 pass
877 elif line.isspace():
878 pass
879 else:
880 new_name, new_value = line.rstrip().split('=')
881 for name, value in self.variables.items():
882 new_value = new_value.replace('${%s}' % name, value)
883 self.variables[new_name] = new_value
884 def __getitem__(self, key):
885 return self.variables[key]
886
887 tools_config = ToolsConfig()
888
889 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700890 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500891 if issubclass(OSError, excinfo[0]):
892 if excinfo[1].errno == errno.ENOENT:
893 # Who cares if the file we're deleting isn't there?
894 return
895 raise excinfo[1]
896
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700897 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700898 """Determines if we need to run gyp again or not.
899
900 The generated build files are supposed to re-run gyp again themselves, but
901 that doesn't work (or at least it used to not) and we sometimes want to
902 modify the results anyways.
903
904 Args:
905 platform: The platform to check for.
906 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700907 if not os.path.exists(platform.build_ninja()):
908 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700909 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
910 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700911 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700912 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700913 if dirs.count('output'):
914 dirs.remove('output')
915 if dirs.count('.git'):
916 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700917 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700918 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700919 + ('-newer', platform.build_ninja(),
920 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700921 stdin=open(os.devnull, 'r'))
922
923 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700924 """Makes sure we pass through important environmental variables.
925
926 Returns:
927 An environment suitable for passing to subprocess.Popen and friends.
928 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700929 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700930 if not 'TERM' in build_env:
931 build_env['TERM'] = os.environ['TERM']
932 if not 'PATH' in build_env:
933 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700934 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700935
Brian Silvermandf5348a2014-06-12 23:25:08 -0700936 sorted_platforms = sort_platforms(platforms)
937 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700938
939 if args.action_name == 'tests':
940 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
941 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
942 if warned_about:
943 user_output(warning[1])
944 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700945 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700946 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
947 exit(1)
948
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700949 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700950 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700951 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500952 if args.action_name == 'clean':
953 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
954 else:
955 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700956 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500957 gyp = subprocess.Popen(
958 (tools_config['GYP'],
959 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500960 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500961 '--no-circular-check',
962 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500963 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500964 '-I/dev/stdin', '-Goutput_dir=output',
965 '-DOS=%s' % platform.os(),
966 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700967 '-DARCHITECTURE=%s' % platform.architecture(),
968 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
969 '-DFULL_COMPILER=%s' % platform.compiler(),
970 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
971 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman99895b92014-09-14 01:01:15 -0400972 '-DEXTERNALS_EXTRA=%s' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700973 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanbae86d62014-09-14 01:05:31 -0400974 else ('_frc' if platform.compiler() == 'gcc_frc' else ''))) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500975 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500976 stdin=subprocess.PIPE)
977 gyp.communicate(("""
978{
979 'target_defaults': {
980 'configurations': {
981 '%s': {}
982 }
983 }
984}""" % platform.outname()).encode())
985 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700986 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500987 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700988 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700989 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500990 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700991 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500992
993 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700994 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -0700995 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700996 if args.jobs:
997 call += ('-j', str(args.jobs))
998 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700999 stdin=open(os.devnull, 'r'),
1000 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -05001001 except subprocess.CalledProcessError as e:
1002 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001003 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -05001004 raise e
1005
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001006 if args.action_name == 'deploy':
1007 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -07001008 elif args.action_name == 'tests':
1009 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001010 done_queue = queue.Queue()
1011 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001012 test_start_semaphore = threading.Semaphore(args.jobs)
1013 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001014 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001015 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001016 if target.endswith('_test'):
1017 to_run.append(target)
1018 else:
1019 to_run = os.listdir(dirname)
1020 for f in to_run:
Brian Silvermanb9e89602014-06-27 14:21:08 -05001021 thread = TestThread(os.path.join(dirname, f), tuple(args.extra_flags),
1022 env(platform), done_queue,
Brian Silvermanc3740c32014-05-04 12:42:47 -07001023 test_start_semaphore)
1024 running.append(thread)
1025 thread.start()
1026 try:
1027 while running:
1028 done = done_queue.get()
1029 running.remove(done)
1030 with test_output_lock:
1031 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -07001032 try:
1033 while True:
1034 line = done.output.get(False)
1035 if not sys.stdout.isatty():
1036 # Remove color escape codes.
1037 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1038 sys.stdout.write(line)
1039 except queue.Empty:
1040 pass
1041 if not done.returncode:
1042 test_output('Test %s succeeded' % done.name)
1043 else:
1044 if done.returncode < 0:
1045 sig = -done.returncode
1046 test_output('Test %s was killed by signal %d (%s)' % \
1047 (done.name, sig, strsignal(sig)))
1048 elif done.returncode != 1:
1049 test_output('Test %s exited with %d' % \
1050 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001051 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001052 test_output('Test %s failed' % done.name)
1053 user_output('Aborting because of test failure for %s.' % \
1054 platform)
1055 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001056 finally:
1057 if running:
1058 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001059# Stop all of them before killing processes because otherwise stopping some of
1060# them tends to let other ones that are waiting to start go.
1061 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -07001062 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001063 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001064 for thread in running:
1065 thread.terminate_process()
1066 to_remove = []
1067 for thread in running:
1068 thread.join(5)
1069 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -07001070 to_remove.append(thread)
1071 for thread in to_remove:
1072 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001073 for thread in running:
1074 test_output(
1075 'Test %s did not terminate. Killing it.' % thread.name)
1076 thread.kill_process()
1077 thread.join()
1078 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001079
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001080 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1081 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001082
1083if __name__ == '__main__':
1084 main()