blob: e49f9582f153ba7b83ca7df0afa7d01d60c17a79 [file] [log] [blame]
Brian Silvermana29ebf92014-04-23 13:08:49 -05001#!/usr/bin/python3
2
3import argparse
4import sys
5import subprocess
6import re
7import os
8import os.path
9import string
10import shutil
11import errno
Brian Silvermanc3740c32014-05-04 12:42:47 -070012import queue
13import threading
Brian Silvermanf2bbe092014-05-13 16:55:03 -070014import pty
Brian Silverman6bca4722014-05-20 17:02:49 -070015import signal
Brian Silvermanc3740c32014-05-04 12:42:47 -070016
17class TestThread(threading.Thread):
Brian Silvermane6bada62014-05-04 16:16:54 -070018 """Runs 1 test and keeps track of its current state.
19
20 A TestThread is either waiting to start the test, actually running it, done,
21 running it, or stopped. The first 3 always happen in that order and can
22 change to stopped at any time.
23
24 It will finish (ie join() will return) once the process has exited, at which
25 point accessing process to see the status is OK.
26
27 Attributes:
28 executable: The file path of the executable to run.
29 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 Silvermanc3740c32014-05-04 12:42:47 -070065 def __init__(self, executable, env, done_queue, start_semaphore):
66 super(TestThread, self).__init__(
67 name=os.path.split(executable)[1])
68
69 self.executable = executable
70 self.env = env
71 self.done_queue = done_queue
72 self.start_semaphore = start_semaphore
73
Brian Silverman730bb012014-06-08 13:05:20 -070074 self.output = queue.Queue()
75
Brian Silvermanc3740c32014-05-04 12:42:47 -070076 self.process_lock = threading.Lock()
77 self.process = None
78 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070079 self.returncode = None
Brian Silverman730bb012014-06-08 13:05:20 -070080 self.output_copier = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070081
82 def run(self):
83 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070084 if self.stopped:
85 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070086 test_output('Starting test %s...' % self.name)
Brian Silverman730bb012014-06-08 13:05:20 -070087 output_to_read, subprocess_output = pty.openpty()
88 self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
89 self.output)
90 self.output_copier.start()
Brian Silvermanf2bbe092014-05-13 16:55:03 -070091 try:
92 with self.process_lock:
93 self.process = subprocess.Popen(self.executable,
94 env=self.env,
95 stderr=subprocess.STDOUT,
96 stdout=subprocess_output,
97 stdin=open(os.devnull, 'r'))
98 finally:
99 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700100 self.process.wait()
101 with self.process_lock:
102 self.returncode = self.process.returncode
103 self.process = None
104 if not self.stopped:
Brian Silverman730bb012014-06-08 13:05:20 -0700105 self.output_copier.join()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700106 self.done_queue.put(self)
107
108 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700109 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700110 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700111 if not self.process:
112 return
Brian Silvermana5826582014-06-03 19:46:35 -0700113 try:
114 self.process.terminate()
115 except OSError as e:
116 if e.errno == errno.ESRCH:
117 # We don't really care if it's already gone.
118 pass
119 else:
120 raise e
Brian Silvermanc3740c32014-05-04 12:42:47 -0700121 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700122 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700123 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700124 if not self.process:
125 return
Brian Silverman6bca4722014-05-20 17:02:49 -0700126 try:
127 self.process.kill()
128 except OSError as e:
129 if e.errno == errno.ESRCH:
130 # We don't really care if it's already gone.
131 pass
132 else:
133 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700134 def stop(self):
135 """Changes self to the stopped state."""
136 with self.process_lock:
137 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500138
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500139def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700140 """Returns:
141 A relative path to the aos directory.
142 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500143 return os.path.join(os.path.dirname(__file__), '..')
144
145def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -0700146 """Retrieves the IP address for a given device."""
147 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700148 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500149 if not os.access(FILENAME, os.R_OK):
150 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
151 with open(FILENAME, 'w') as f:
152 f.write('10.9.71')
153 with open(FILENAME, 'r') as f:
154 base = f.readline()
155 if device == 'prime':
156 return base + '.179'
157 elif device == 'robot':
158 return base + '.2'
159 else:
160 raise Exception('Unknown device %s to get an IP address for.' % device)
161
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700162def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700163 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700164 print('build.py: ' + message, file=sys.stderr)
165
Brian Silverman452aaec2014-05-05 16:52:18 -0700166# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700167test_output_lock = threading.RLock()
168def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700169 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700170 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700171 print('tests: ' + message, file=sys.stdout)
172
173def call_download_externals(argument):
174 """Calls download_externals.sh for a given set of externals.
175
176 Args:
177 argument: The argument to pass to the shell script to tell it what to
178 download.
179 """
180 subprocess.check_call(
181 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
182 argument),
183 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700184
Brian Silvermana29ebf92014-04-23 13:08:49 -0500185class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700186 """Represents a processor architecture we can build for."""
187
Brian Silvermana29ebf92014-04-23 13:08:49 -0500188 class UnknownPlatform(Exception):
189 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700190 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500191 self.message = message
192
Brian Silvermanb3d50542014-04-23 14:28:55 -0500193 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700194 """Represents a single way to build the code."""
195
Brian Silvermanb3d50542014-04-23 14:28:55 -0500196 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700197 """Returns:
198 The path of the directory build outputs get put in to.
199 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500200 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500201 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500202 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700203 """Returns:
204 The path of the build.ninja file.
205 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500206 return os.path.join(self.outdir(), 'build.ninja')
207
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500208 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700209 """Helper for subclasses to implement deploy.
210
211 Args:
212 dry_run: If True, prints the command instead of actually running it.
213 command: A tuple of command-line arguments.
214 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500215 real_command = (('echo',) + command) if dry_run else command
216 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500217
Brian Silvermane6bada62014-05-04 16:16:54 -0700218 def deploy(self, dry_run):
219 """Downloads the compiled code to the target computer."""
220 raise NotImplementedError('deploy should be overriden')
221 def outname(self):
222 """Returns:
223 The name of the directory the code will be compiled to.
224 """
225 raise NotImplementedError('outname should be overriden')
226 def os(self):
227 """Returns:
228 The name of the operating system this platform is for.
229
230 This will be used as the value of the OS gyp variable.
231 """
232 raise NotImplementedError('os should be overriden')
233 def gyp_platform(self):
234 """Returns:
235 The platform name the .gyp files know.
236
237 This will be used as the value of the PLATFORM gyp variable.
238 """
239 raise NotImplementedError('gyp_platform should be overriden')
240 def architecture(self):
241 """Returns:
242 The processor architecture for this platform.
243
244 This will be used as the value of the ARCHITECTURE gyp variable.
245 """
246 raise NotImplementedError('architecture should be overriden')
247 def compiler(self):
248 """Returns:
249 The compiler used for this platform.
250
251 Everything before the first _ will be used as the value of the
252 COMPILER gyp variable and the whole thing will be used as the value
253 of the FULL_COMPILER gyp variable.
254 """
255 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700256 def sanitizer(self):
257 """Returns:
258 The sanitizer used on this platform.
259
260 This will be used as the value of the SANITIZER gyp variable.
261
262 "none" if there isn't one.
263 """
264 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700265 def debug(self):
266 """Returns:
267 Whether or not this platform compiles with debugging information.
268
269 The DEBUG gyp variable will be set to "yes" or "no" based on this.
270 """
271 raise NotImplementedError('debug should be overriden')
272 def build_env(self):
273 """Returns:
274 A map of environment variables to set while building this platform.
275 """
276 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700277 def priority(self):
278 """Returns:
279 A relative priority for this platform relative to other ones.
280
281 Higher priority platforms will get built, tested, etc first. Generally,
282 platforms which give higher-quality compiler errors etc should come first.
283 """
284 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700285
Brian Silverman9b7a6842014-05-05 16:19:11 -0700286 def check_installed(self, platforms, is_deploy):
287 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700288 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700289 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700290 """Args:
291 string: A user-supplied string saying which platforms to select.
292
293 Returns:
294 A tuple of Platform objects.
295
296 Raises:
297 Processor.UnknownPlatform: Parsing string didn't work out.
298 """
299 raise NotImplementedError('parse_platforms should be overriden')
300 def extra_gyp_flags(self):
301 """Returns:
302 A tuple of extra flags to pass to gyp (if any).
303 """
304 return ()
305 def modify_ninja_file(self, ninja_file):
306 """Modifies a freshly generated ninja file as necessary.
307
308 Args:
309 ninja_file: Path to the file to modify.
310 """
311 pass
312 def download_externals(self, platforms):
313 """Calls download_externals as appropriate to build platforms.
314
315 Args:
316 platforms: A list of platforms to download external libraries for.
317 """
318 raise NotImplementedError('download_externals should be overriden')
319
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700320 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700321 """Helper for subclasses to implement check_installed.
322
323 Args:
324 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700325 all_packages = other_packages
326 # Necessary to build stuff.
327 all_packages += ('ccache', 'make')
328 # Necessary to download stuff to build.
329 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
330 # Necessary to build externals stuff.
331 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700332 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700333 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700334 result = subprocess.check_output(
335 ('dpkg-query', '--show') + all_packages,
336 stdin=open(os.devnull, 'r'),
337 stderr=subprocess.STDOUT)
338 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700339 output = e.output.decode('utf-8').rstrip()
340 not_found = []
341 for line in output.splitlines(True):
342 match = re.match(r'dpkg-query: no packages found matching (.*)',
343 line)
344 if match:
345 not_found.append(match.group(1))
346 user_output('Some packages not installed: %s.' % ', '.join(not_found))
347 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700348 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700349 exit(1)
350
Brian Silvermana29ebf92014-04-23 13:08:49 -0500351class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700352 """A Processor subclass for building cRIO code."""
353
Brian Silvermanb3d50542014-04-23 14:28:55 -0500354 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700355 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500356 super(CRIOProcessor.Platform, self).__init__()
357
Brian Silvermane6bada62014-05-04 16:16:54 -0700358 self.__debug = debug
359 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500360
361 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700362 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500363 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700364 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500365
366 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700367 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500368 def os(self):
369 return 'vxworks'
370 def gyp_platform(self):
371 return 'crio'
372 def architecture(self):
373 return 'ppc'
374 def compiler(self):
375 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700376 def sanitizer(self):
377 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700378 def debug(self):
379 return self.__debug
380 def wind_base(self):
381 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500382
Brian Silvermane48c09a2014-04-30 18:04:58 -0700383 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500384 def deploy(self, dry_run):
385 self.do_deploy(dry_run,
386 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700387 os.path.join(self.outdir(), 'lib',
388 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500389
Brian Silvermana4aff562014-05-02 17:43:50 -0700390 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700391 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700392
Brian Silvermana29ebf92014-04-23 13:08:49 -0500393 def __init__(self):
394 super(CRIOProcessor, self).__init__()
395
396 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700397 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500398 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700399 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500400
Brian Silverman452aaec2014-05-05 16:52:18 -0700401 def parse_platforms(self, platforms_string):
402 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700403 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700404 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700405 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500406 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700407 raise Processor.UnknownPlatform(
408 'Unknown cRIO platform "%s".' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500409
Brian Silvermane6bada62014-05-04 16:16:54 -0700410 def wind_base(self):
411 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500412
Brian Silvermane6bada62014-05-04 16:16:54 -0700413 def extra_gyp_flags(self):
414 return ('-DWIND_BASE=%s' % self.wind_base(),)
415
416 def modify_ninja_file(self, ninja_file):
417 subprocess.check_call(
418 ('sed', '-i',
419 's/nm -gD/nm/g', ninja_file),
420 stdin=open(os.devnull, 'r'))
421
422 def download_externals(self, _):
423 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500424
Brian Silverman9b7a6842014-05-05 16:19:11 -0700425 def check_installed(self, platforms, is_deploy):
426 packages = ('powerpc-wrs-vxworks', 'tcl')
427 if is_deploy:
428 packages += ('ncftp',)
429 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700430
Brian Silvermana29ebf92014-04-23 13:08:49 -0500431class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700432 """A Processor subclass for building prime code."""
433
Brian Silvermanb3d50542014-04-23 14:28:55 -0500434 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700435 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500436 super(PrimeProcessor.Platform, self).__init__()
437
Brian Silvermane6bada62014-05-04 16:16:54 -0700438 self.__architecture = architecture
439 self.__compiler = compiler
440 self.__debug = debug
441 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500442
443 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700444 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
445 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700446 % (self.architecture(), self.compiler(), self.debug(),
447 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500448 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700449 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700450 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500451
452 def os(self):
453 return 'linux'
454 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700455 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
456 def architecture(self):
457 return self.__architecture
458 def compiler(self):
459 return self.__compiler
460 def sanitizer(self):
461 return self.__sanitizer
462 def debug(self):
463 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500464
Brian Silvermana29ebf92014-04-23 13:08:49 -0500465 def outname(self):
466 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500467
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700468 def priority(self):
469 r = 0
470 if self.compiler() == 'gcc':
471 r -= 100
472 elif self.compiler() == 'clang':
473 r += 100
474 if self.sanitizer() != 'none':
475 r -= 50
476 elif self.debug():
477 r -= 10
478 if self.architecture() == 'amd64':
479 r += 5
480 return r
481
Brian Silvermane48c09a2014-04-30 18:04:58 -0700482 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500483 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700484 # Downloads code to the prime in a way that avoids clashing too badly with
485 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500486 SUM = 'md5sum'
487 TARGET_DIR = '/home/driver/robot_code/bin'
488 TEMP_DIR = '/tmp/aos_downloader'
489 TARGET = 'driver@' + get_ip('prime')
490
491 from_dir = os.path.join(self.outdir(), 'outputs')
492 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
493 stdin=open(os.devnull, 'r'),
494 cwd=from_dir)
495 to_download = subprocess.check_output(
496 ('ssh', TARGET,
497 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
498 && echo '{SUMS}' | {SUM} --check --quiet
Brian Silverman452aaec2014-05-05 16:52:18 -0700499 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".
500 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500501 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700502 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500503 return
504 self.do_deploy(
505 dry_run,
506 ('scp', '-o', 'Compression yes') + to_download
507 + (('%s:%s' % (TARGET, TEMP_DIR)),))
508 if not dry_run:
509 subprocess.check_call(
510 ('ssh', TARGET,
511 """mv {TMPDIR}/* {TO_DIR}
512 && echo 'Done moving new executables into place'
513 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
514 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
515
Brian Silvermana4aff562014-05-02 17:43:50 -0700516 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700517 OTHER_SYSROOT = '/opt/clang-3.5/'
518 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700519 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700520 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
521 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
522 if self.sanitizer() == 'address':
523 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700524 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700525 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
526 ':detect_stack_use_after_return=1:detect_odr_violation=2'
Brian Silvermane6bada62014-05-04 16:16:54 -0700527 elif self.sanitizer() == 'memory':
528 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
529 elif self.sanitizer() == 'thread':
530 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700531
532 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700533 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
534 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700535 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700536 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700537 # clang doesn't like being run directly on the preprocessed files.
538 r['CCACHE_CPP2'] = 'yes'
539 # Without this, ccache slows down because of the generated header files.
540 # The race condition that this opens up isn't a problem because the build
541 # system finishes modifying header files before compiling anything that
542 # uses them.
543 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700544
Brian Silvermane6bada62014-05-04 16:16:54 -0700545 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700546 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
547 ':' + os.environ['PATH']
548
Brian Silvermana4aff562014-05-02 17:43:50 -0700549 return r
550
Brian Silverman47cd6f62014-05-03 10:35:52 -0700551 ARCHITECTURES = ('arm', 'amd64')
552 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
553 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
554 SANITIZER_TEST_WARNINGS = {
555 'memory': (True,
556"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700557 errors with msan (especially stdlibc++).
558 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700559 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700560 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500561
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500562 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700563 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500564
565 platforms = []
566 for architecture in PrimeProcessor.ARCHITECTURES:
567 for compiler in PrimeProcessor.COMPILERS:
568 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700569 if architecture == 'arm' and compiler == 'gcc_4.8':
570 # We don't have a compiler to use here.
571 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500572 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700573 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
574 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700575 for compiler in ('gcc_4.8', 'clang'):
576 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
577 sanitizer == 'integer' or
578 sanitizer == 'memory'):
579 # GCC 4.8 doesn't support these sanitizers.
580 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700581 if sanitizer == 'none':
582 # We already added sanitizer == 'none' above.
583 continue
584 platforms.append(
585 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
586 self.__platforms = frozenset(platforms)
587
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500588 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700589 default_platforms = self.select_platforms(architecture='amd64',
590 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700591 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
592 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700593 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500594 elif is_deploy:
595 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700596 default_platforms = self.select_platforms(architecture='arm',
597 compiler='gcc',
598 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500599 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700600 default_platforms = self.select_platforms(debug=False)
601 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500602
Brian Silvermane6bada62014-05-04 16:16:54 -0700603 def platforms(self):
604 return self.__platforms
605 def default_platforms(self):
606 return self.__default_platforms
607
608 def download_externals(self, platforms):
609 to_download = set()
610 for architecture in PrimeProcessor.ARCHITECTURES:
611 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
612 if platforms & self.select_platforms(architecture=architecture,
613 sanitizer=sanitizer):
614 to_download.add(architecture + '-fPIE')
615 if platforms & self.select_platforms(architecture=architecture,
616 sanitizer='none'):
617 to_download.add(architecture)
618 for download_target in to_download:
619 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500620
Brian Silverman452aaec2014-05-05 16:52:18 -0700621 def parse_platforms(self, platform_string):
622 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700623 return self.default_platforms()
624 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700625 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700626 if part == 'all':
627 r = self.platforms()
628 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500629 r = r | self.select_platforms_string(part[1:])
630 elif part[0] == '-':
631 r = r - self.select_platforms_string(part[1:])
632 elif part[0] == '=':
633 r = self.select_platforms_string(part[1:])
634 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500635 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700636 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500637 if not r:
638 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500639 return r
640
Brian Silverman452aaec2014-05-05 16:52:18 -0700641 def select_platforms(self, architecture=None, compiler=None, debug=None,
642 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500643 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700644 for platform in self.platforms():
645 if architecture is None or platform.architecture() == architecture:
646 if compiler is None or platform.compiler() == compiler:
647 if debug is None or platform.debug() == debug:
648 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700649 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500650 return set(r)
651
Brian Silverman452aaec2014-05-05 16:52:18 -0700652 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700653 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700654 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500655 if part in PrimeProcessor.ARCHITECTURES:
656 architecture = part
657 elif part in PrimeProcessor.COMPILERS:
658 compiler = part
659 elif part in ['debug', 'dbg']:
660 debug = True
661 elif part in ['release', 'nodebug', 'ndb']:
662 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700663 elif part in PrimeProcessor.SANITIZERS:
664 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500665 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700666 raise Processor.UnknownPlatform(
667 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500668 return self.select_platforms(
669 architecture=architecture,
670 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700671 debug=debug,
672 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500673
Brian Silverman9b7a6842014-05-05 16:19:11 -0700674 def check_installed(self, platforms, is_deploy):
675 packages = set(('lzip', 'm4', 'realpath'))
676 packages.add('ruby')
677 # clang-format from here gets used for all versions.
678 packages.add('clang-3.5')
679 packages.add('arm-eabi-gcc')
680 for platform in platforms:
681 if platform.architecture() == 'arm':
682 packages.add('gcc-4.7-arm-linux-gnueabihf')
683 packages.add('g++-4.7-arm-linux-gnueabihf')
684 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
685 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700686 if platform.compiler() == 'gcc_4.8':
687 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700688 if is_deploy:
689 packages.add('openssh-client')
690 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
691 packages.add('gcc-4.7')
692 packages.add('g++-4.7')
693
694 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700695
Brian Silverman6bca4722014-05-20 17:02:49 -0700696def strsignal(num):
697 # It ends up with SIGIOT instead otherwise, which is weird.
698 if num == signal.SIGABRT:
699 return 'SIGABRT'
700 # SIGCLD is a weird way to spell it.
701 if num == signal.SIGCHLD:
702 return 'SIGCHLD'
703
704 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
705 for n in dir(signal) if n.startswith('SIG')
706 and '_' not in n)
707 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
708
Brian Silvermana29ebf92014-04-23 13:08:49 -0500709def main():
710 class TryParsingAgain(Exception):
711 pass
712
713 class TryAgainArgumentParser(argparse.ArgumentParser):
714 def __init__(self, **kwargs):
715 super(TryAgainArgumentParser, self).__init__(**kwargs)
716
717 def error(self, message):
718 raise TryParsingAgain
719
Brian Silvermane6bada62014-05-04 16:16:54 -0700720 def set_up_parser(parser, args):
721 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500722 parser.add_argument(
723 'target',
724 help='target to build',
725 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700726 parser.add_argument(
727 '--jobs', '-j',
728 help='number of things to do at once',
729 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700730 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500731 parser.add_argument(
732 'platforms',
733 help='platform(s) to act on',
734 nargs='?')
735
736 parser.add_argument('--processor', required=True, help='prime or crio')
737 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
738 subparsers = parser.add_subparsers(dest='action_name')
739
740 build_parser = subparsers.add_parser(
741 'build',
742 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700743 add_common_args(build_parser)
744 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500745
746 clean_parser = subparsers.add_parser(
747 'clean',
748 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700749 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500750
751 deploy_parser = subparsers.add_parser(
752 'deploy',
753 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700754 add_common_args(deploy_parser)
755 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500756 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700757 '-n', '--dry-run',
758 help="don't actually download anything",
759 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500760
Brian Silvermane48c09a2014-04-30 18:04:58 -0700761 tests_parser = subparsers.add_parser(
762 'tests',
763 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700764 add_common_args(tests_parser)
765 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700766
Brian Silvermana29ebf92014-04-23 13:08:49 -0500767 return parser.parse_args(args)
768
769 try:
770 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700771 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500772 except TryParsingAgain:
773 parser = argparse.ArgumentParser()
774 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700775 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
776 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500777
778 if args.processor == 'crio':
779 processor = CRIOProcessor()
780 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500781 processor = PrimeProcessor(args.action_name == 'tests',
782 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500783 else:
784 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
785
786 if 'target' in args:
787 targets = args.target[:]
788 else:
789 targets = []
790 unknown_platform_error = None
791 try:
792 platforms = processor.parse_platforms(args.platforms)
793 except Processor.UnknownPlatform as e:
794 unknown_platform_error = e.message
795 targets.append(args.platforms)
796 platforms = processor.parse_platforms(None)
797 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700798 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500799 exit(1)
800
Brian Silverman9b7a6842014-05-05 16:19:11 -0700801 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700802 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500803
804 class ToolsConfig(object):
805 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500806 self.variables = {'AOS': aos_path()}
807 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500808 for line in f:
809 if line[0] == '#':
810 pass
811 elif line.isspace():
812 pass
813 else:
814 new_name, new_value = line.rstrip().split('=')
815 for name, value in self.variables.items():
816 new_value = new_value.replace('${%s}' % name, value)
817 self.variables[new_name] = new_value
818 def __getitem__(self, key):
819 return self.variables[key]
820
821 tools_config = ToolsConfig()
822
823 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700824 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500825 if issubclass(OSError, excinfo[0]):
826 if excinfo[1].errno == errno.ENOENT:
827 # Who cares if the file we're deleting isn't there?
828 return
829 raise excinfo[1]
830
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700831 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700832 """Determines if we need to run gyp again or not.
833
834 The generated build files are supposed to re-run gyp again themselves, but
835 that doesn't work (or at least it used to not) and we sometimes want to
836 modify the results anyways.
837
838 Args:
839 platform: The platform to check for.
840 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700841 if not os.path.exists(platform.build_ninja()):
842 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700843 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
844 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700845 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700846 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700847 if dirs.count('output'):
848 dirs.remove('output')
849 if dirs.count('.git'):
850 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700851 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700852 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700853 + ('-newer', platform.build_ninja(),
854 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700855 stdin=open(os.devnull, 'r'))
856
857 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700858 """Makes sure we pass through important environmental variables.
859
860 Returns:
861 An environment suitable for passing to subprocess.Popen and friends.
862 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700863 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700864 if not 'TERM' in build_env:
865 build_env['TERM'] = os.environ['TERM']
866 if not 'PATH' in build_env:
867 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700868 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700869
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700870 sorted_platforms = sorted(platforms,
871 key=lambda platform: -platform.priority())
872
Brian Silverman47cd6f62014-05-03 10:35:52 -0700873 to_build = []
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700874 for platform in sorted_platforms:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700875 to_build.append(str(platform))
876 if len(to_build) > 1:
877 to_build[-1] = 'and ' + to_build[-1]
878 user_output('Building %s...' % ', '.join(to_build))
879
880 if args.action_name == 'tests':
881 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
882 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
883 if warned_about:
884 user_output(warning[1])
885 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700886 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700887 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
888 exit(1)
889
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700890 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700891 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700892 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500893 if args.action_name == 'clean':
894 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
895 else:
896 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700897 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500898 gyp = subprocess.Popen(
899 (tools_config['GYP'],
900 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500901 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500902 '--no-circular-check',
903 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500904 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500905 '-I/dev/stdin', '-Goutput_dir=output',
906 '-DOS=%s' % platform.os(),
907 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700908 '-DARCHITECTURE=%s' % platform.architecture(),
909 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
910 '-DFULL_COMPILER=%s' % platform.compiler(),
911 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
912 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700913 '-DSANITIZER_FPIE=%s' %
914 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
915 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500916 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500917 stdin=subprocess.PIPE)
918 gyp.communicate(("""
919{
920 'target_defaults': {
921 'configurations': {
922 '%s': {}
923 }
924 }
925}""" % platform.outname()).encode())
926 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700927 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500928 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700929 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700930 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500931 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700932 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500933
934 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700935 call = (tools_config['NINJA'],
936 '-C', platform.outdir()) + tuple(targets)
937 if args.jobs:
938 call += ('-j', str(args.jobs))
939 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700940 stdin=open(os.devnull, 'r'),
941 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500942 except subprocess.CalledProcessError as e:
943 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700944 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500945 raise e
946
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500947 if args.action_name == 'deploy':
948 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700949 elif args.action_name == 'tests':
950 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700951 done_queue = queue.Queue()
952 running = []
953 if args.jobs:
954 number_jobs = args.jobs
955 else:
956 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
957 test_start_semaphore = threading.Semaphore(number_jobs)
958 if targets:
959 to_run = []
960 for target in targets:
961 if target.endswith('_test'):
962 to_run.append(target)
963 else:
964 to_run = os.listdir(dirname)
965 for f in to_run:
966 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
967 test_start_semaphore)
968 running.append(thread)
969 thread.start()
970 try:
971 while running:
972 done = done_queue.get()
973 running.remove(done)
974 with test_output_lock:
975 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -0700976 try:
977 while True:
978 line = done.output.get(False)
979 if not sys.stdout.isatty():
980 # Remove color escape codes.
981 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
982 sys.stdout.write(line)
983 except queue.Empty:
984 pass
985 if not done.returncode:
986 test_output('Test %s succeeded' % done.name)
987 else:
988 if done.returncode < 0:
989 sig = -done.returncode
990 test_output('Test %s was killed by signal %d (%s)' % \
991 (done.name, sig, strsignal(sig)))
992 elif done.returncode != 1:
993 test_output('Test %s exited with %d' % \
994 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700995 else:
Brian Silverman730bb012014-06-08 13:05:20 -0700996 test_output('Test %s failed' % done.name)
997 user_output('Aborting because of test failure for %s.' % \
998 platform)
999 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001000 finally:
1001 if running:
1002 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001003# Stop all of them before killing processes because otherwise stopping some of
1004# them tends to let other ones that are waiting to start go.
1005 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -07001006 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001007 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001008 for thread in running:
1009 thread.terminate_process()
1010 to_remove = []
1011 for thread in running:
1012 thread.join(5)
1013 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -07001014 to_remove.append(thread)
1015 for thread in to_remove:
1016 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001017 for thread in running:
1018 test_output(
1019 'Test %s did not terminate. Killing it.' % thread.name)
1020 thread.kill_process()
1021 thread.join()
1022 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001023
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001024 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1025 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001026
1027if __name__ == '__main__':
1028 main()