blob: 97295f6adb6abe59de8cb17314b383e3d1f5633e [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.
36 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070037 def __init__(self, executable, env, done_queue, start_semaphore):
38 super(TestThread, self).__init__(
39 name=os.path.split(executable)[1])
40
41 self.executable = executable
42 self.env = env
43 self.done_queue = done_queue
44 self.start_semaphore = start_semaphore
45
46 self.process_lock = threading.Lock()
47 self.process = None
48 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070049 self.returncode = None
50 self.output = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070051
52 def run(self):
53 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070054 if self.stopped:
55 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070056 test_output('Starting test %s...' % self.name)
Brian Silvermanf2bbe092014-05-13 16:55:03 -070057 self.output, subprocess_output = pty.openpty()
58 try:
59 with self.process_lock:
60 self.process = subprocess.Popen(self.executable,
61 env=self.env,
62 stderr=subprocess.STDOUT,
63 stdout=subprocess_output,
64 stdin=open(os.devnull, 'r'))
65 finally:
66 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -070067 self.process.wait()
68 with self.process_lock:
69 self.returncode = self.process.returncode
70 self.process = None
71 if not self.stopped:
72 self.done_queue.put(self)
73
74 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070075 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070076 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070077 if not self.process:
78 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070079 self.process.terminate()
80 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070081 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070082 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070083 if not self.process:
84 return
Brian Silverman6bca4722014-05-20 17:02:49 -070085 try:
86 self.process.kill()
87 except OSError as e:
88 if e.errno == errno.ESRCH:
89 # We don't really care if it's already gone.
90 pass
91 else:
92 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070093 def stop(self):
94 """Changes self to the stopped state."""
95 with self.process_lock:
96 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -050097
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050098def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -070099 """Returns:
100 A relative path to the aos directory.
101 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500102 return os.path.join(os.path.dirname(__file__), '..')
103
104def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -0700105 """Retrieves the IP address for a given device."""
106 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700107 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500108 if not os.access(FILENAME, os.R_OK):
109 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
110 with open(FILENAME, 'w') as f:
111 f.write('10.9.71')
112 with open(FILENAME, 'r') as f:
113 base = f.readline()
114 if device == 'prime':
115 return base + '.179'
116 elif device == 'robot':
117 return base + '.2'
118 else:
119 raise Exception('Unknown device %s to get an IP address for.' % device)
120
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700121def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700122 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700123 print('build.py: ' + message, file=sys.stderr)
124
Brian Silverman452aaec2014-05-05 16:52:18 -0700125# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700126test_output_lock = threading.RLock()
127def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700128 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700129 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700130 print('tests: ' + message, file=sys.stdout)
131
132def call_download_externals(argument):
133 """Calls download_externals.sh for a given set of externals.
134
135 Args:
136 argument: The argument to pass to the shell script to tell it what to
137 download.
138 """
139 subprocess.check_call(
140 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
141 argument),
142 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700143
Brian Silvermana29ebf92014-04-23 13:08:49 -0500144class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700145 """Represents a processor architecture we can build for."""
146
Brian Silvermana29ebf92014-04-23 13:08:49 -0500147 class UnknownPlatform(Exception):
148 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700149 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500150 self.message = message
151
Brian Silvermanb3d50542014-04-23 14:28:55 -0500152 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700153 """Represents a single way to build the code."""
154
Brian Silvermanb3d50542014-04-23 14:28:55 -0500155 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700156 """Returns:
157 The path of the directory build outputs get put in to.
158 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500159 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500160 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500161 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700162 """Returns:
163 The path of the build.ninja file.
164 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500165 return os.path.join(self.outdir(), 'build.ninja')
166
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500167 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700168 """Helper for subclasses to implement deploy.
169
170 Args:
171 dry_run: If True, prints the command instead of actually running it.
172 command: A tuple of command-line arguments.
173 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500174 real_command = (('echo',) + command) if dry_run else command
175 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500176
Brian Silvermane6bada62014-05-04 16:16:54 -0700177 def deploy(self, dry_run):
178 """Downloads the compiled code to the target computer."""
179 raise NotImplementedError('deploy should be overriden')
180 def outname(self):
181 """Returns:
182 The name of the directory the code will be compiled to.
183 """
184 raise NotImplementedError('outname should be overriden')
185 def os(self):
186 """Returns:
187 The name of the operating system this platform is for.
188
189 This will be used as the value of the OS gyp variable.
190 """
191 raise NotImplementedError('os should be overriden')
192 def gyp_platform(self):
193 """Returns:
194 The platform name the .gyp files know.
195
196 This will be used as the value of the PLATFORM gyp variable.
197 """
198 raise NotImplementedError('gyp_platform should be overriden')
199 def architecture(self):
200 """Returns:
201 The processor architecture for this platform.
202
203 This will be used as the value of the ARCHITECTURE gyp variable.
204 """
205 raise NotImplementedError('architecture should be overriden')
206 def compiler(self):
207 """Returns:
208 The compiler used for this platform.
209
210 Everything before the first _ will be used as the value of the
211 COMPILER gyp variable and the whole thing will be used as the value
212 of the FULL_COMPILER gyp variable.
213 """
214 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700215 def sanitizer(self):
216 """Returns:
217 The sanitizer used on this platform.
218
219 This will be used as the value of the SANITIZER gyp variable.
220
221 "none" if there isn't one.
222 """
223 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700224 def debug(self):
225 """Returns:
226 Whether or not this platform compiles with debugging information.
227
228 The DEBUG gyp variable will be set to "yes" or "no" based on this.
229 """
230 raise NotImplementedError('debug should be overriden')
231 def build_env(self):
232 """Returns:
233 A map of environment variables to set while building this platform.
234 """
235 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700236 def priority(self):
237 """Returns:
238 A relative priority for this platform relative to other ones.
239
240 Higher priority platforms will get built, tested, etc first. Generally,
241 platforms which give higher-quality compiler errors etc should come first.
242 """
243 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700244
Brian Silverman9b7a6842014-05-05 16:19:11 -0700245 def check_installed(self, platforms, is_deploy):
246 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700247 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700248 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700249 """Args:
250 string: A user-supplied string saying which platforms to select.
251
252 Returns:
253 A tuple of Platform objects.
254
255 Raises:
256 Processor.UnknownPlatform: Parsing string didn't work out.
257 """
258 raise NotImplementedError('parse_platforms should be overriden')
259 def extra_gyp_flags(self):
260 """Returns:
261 A tuple of extra flags to pass to gyp (if any).
262 """
263 return ()
264 def modify_ninja_file(self, ninja_file):
265 """Modifies a freshly generated ninja file as necessary.
266
267 Args:
268 ninja_file: Path to the file to modify.
269 """
270 pass
271 def download_externals(self, platforms):
272 """Calls download_externals as appropriate to build platforms.
273
274 Args:
275 platforms: A list of platforms to download external libraries for.
276 """
277 raise NotImplementedError('download_externals should be overriden')
278
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700279 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700280 """Helper for subclasses to implement check_installed.
281
282 Args:
283 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700284 all_packages = other_packages
285 # Necessary to build stuff.
286 all_packages += ('ccache', 'make')
287 # Necessary to download stuff to build.
288 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
289 # Necessary to build externals stuff.
290 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700291 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700292 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700293 result = subprocess.check_output(
294 ('dpkg-query', '--show') + all_packages,
295 stdin=open(os.devnull, 'r'),
296 stderr=subprocess.STDOUT)
297 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700298 output = e.output.decode('utf-8').rstrip()
299 not_found = []
300 for line in output.splitlines(True):
301 match = re.match(r'dpkg-query: no packages found matching (.*)',
302 line)
303 if match:
304 not_found.append(match.group(1))
305 user_output('Some packages not installed: %s.' % ', '.join(not_found))
306 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700307 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700308 exit(1)
309
Brian Silvermana29ebf92014-04-23 13:08:49 -0500310class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700311 """A Processor subclass for building cRIO code."""
312
Brian Silvermanb3d50542014-04-23 14:28:55 -0500313 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700314 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500315 super(CRIOProcessor.Platform, self).__init__()
316
Brian Silvermane6bada62014-05-04 16:16:54 -0700317 self.__debug = debug
318 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500319
320 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700321 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500322 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700323 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500324
325 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700326 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500327 def os(self):
328 return 'vxworks'
329 def gyp_platform(self):
330 return 'crio'
331 def architecture(self):
332 return 'ppc'
333 def compiler(self):
334 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700335 def sanitizer(self):
336 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700337 def debug(self):
338 return self.__debug
339 def wind_base(self):
340 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500341
Brian Silvermane48c09a2014-04-30 18:04:58 -0700342 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500343 def deploy(self, dry_run):
344 self.do_deploy(dry_run,
345 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700346 os.path.join(self.outdir(), 'lib',
347 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500348
Brian Silvermana4aff562014-05-02 17:43:50 -0700349 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700350 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700351
Brian Silvermana29ebf92014-04-23 13:08:49 -0500352 def __init__(self):
353 super(CRIOProcessor, self).__init__()
354
355 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700356 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500357 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700358 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500359
Brian Silverman452aaec2014-05-05 16:52:18 -0700360 def parse_platforms(self, platforms_string):
361 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700362 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700363 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700364 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500365 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700366 raise Processor.UnknownPlatform(
367 'Unknown cRIO platform "%s".' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500368
Brian Silvermane6bada62014-05-04 16:16:54 -0700369 def wind_base(self):
370 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500371
Brian Silvermane6bada62014-05-04 16:16:54 -0700372 def extra_gyp_flags(self):
373 return ('-DWIND_BASE=%s' % self.wind_base(),)
374
375 def modify_ninja_file(self, ninja_file):
376 subprocess.check_call(
377 ('sed', '-i',
378 's/nm -gD/nm/g', ninja_file),
379 stdin=open(os.devnull, 'r'))
380
381 def download_externals(self, _):
382 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500383
Brian Silverman9b7a6842014-05-05 16:19:11 -0700384 def check_installed(self, platforms, is_deploy):
385 packages = ('powerpc-wrs-vxworks', 'tcl')
386 if is_deploy:
387 packages += ('ncftp',)
388 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700389
Brian Silvermana29ebf92014-04-23 13:08:49 -0500390class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700391 """A Processor subclass for building prime code."""
392
Brian Silvermanb3d50542014-04-23 14:28:55 -0500393 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700394 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500395 super(PrimeProcessor.Platform, self).__init__()
396
Brian Silvermane6bada62014-05-04 16:16:54 -0700397 self.__architecture = architecture
398 self.__compiler = compiler
399 self.__debug = debug
400 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500401
402 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700403 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
404 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700405 % (self.architecture(), self.compiler(), self.debug(),
406 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500407 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700408 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700409 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500410
411 def os(self):
412 return 'linux'
413 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700414 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
415 def architecture(self):
416 return self.__architecture
417 def compiler(self):
418 return self.__compiler
419 def sanitizer(self):
420 return self.__sanitizer
421 def debug(self):
422 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500423
Brian Silvermana29ebf92014-04-23 13:08:49 -0500424 def outname(self):
425 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500426
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700427 def priority(self):
428 r = 0
429 if self.compiler() == 'gcc':
430 r -= 100
431 elif self.compiler() == 'clang':
432 r += 100
433 if self.sanitizer() != 'none':
434 r -= 50
435 elif self.debug():
436 r -= 10
437 if self.architecture() == 'amd64':
438 r += 5
439 return r
440
Brian Silvermane48c09a2014-04-30 18:04:58 -0700441 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500442 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700443 # Downloads code to the prime in a way that avoids clashing too badly with
444 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500445 SUM = 'md5sum'
446 TARGET_DIR = '/home/driver/robot_code/bin'
447 TEMP_DIR = '/tmp/aos_downloader'
448 TARGET = 'driver@' + get_ip('prime')
449
450 from_dir = os.path.join(self.outdir(), 'outputs')
451 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
452 stdin=open(os.devnull, 'r'),
453 cwd=from_dir)
454 to_download = subprocess.check_output(
455 ('ssh', TARGET,
456 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
457 && echo '{SUMS}' | {SUM} --check --quiet
Brian Silverman452aaec2014-05-05 16:52:18 -0700458 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".
459 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500460 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700461 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500462 return
463 self.do_deploy(
464 dry_run,
465 ('scp', '-o', 'Compression yes') + to_download
466 + (('%s:%s' % (TARGET, TEMP_DIR)),))
467 if not dry_run:
468 subprocess.check_call(
469 ('ssh', TARGET,
470 """mv {TMPDIR}/* {TO_DIR}
471 && echo 'Done moving new executables into place'
472 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
473 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
474
Brian Silvermana4aff562014-05-02 17:43:50 -0700475 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700476 OTHER_SYSROOT = '/opt/clang-3.5/'
477 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700478 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700479 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
480 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
481 if self.sanitizer() == 'address':
482 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700483 r['ASAN_OPTIONS'] = \
484 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700485 elif self.sanitizer() == 'memory':
486 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
487 elif self.sanitizer() == 'thread':
488 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700489
490 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700491 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
492 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700493 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700494 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700495 # clang doesn't like being run directly on the preprocessed files.
496 r['CCACHE_CPP2'] = 'yes'
497 # Without this, ccache slows down because of the generated header files.
498 # The race condition that this opens up isn't a problem because the build
499 # system finishes modifying header files before compiling anything that
500 # uses them.
501 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700502
Brian Silvermane6bada62014-05-04 16:16:54 -0700503 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700504 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
505 ':' + os.environ['PATH']
506
Brian Silvermana4aff562014-05-02 17:43:50 -0700507 return r
508
Brian Silverman47cd6f62014-05-03 10:35:52 -0700509 ARCHITECTURES = ('arm', 'amd64')
510 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
511 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
512 SANITIZER_TEST_WARNINGS = {
513 'memory': (True,
514"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700515 errors with msan (especially stdlibc++).
516 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700517 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700518 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500519
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500520 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700521 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500522
523 platforms = []
524 for architecture in PrimeProcessor.ARCHITECTURES:
525 for compiler in PrimeProcessor.COMPILERS:
526 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700527 if architecture == 'arm' and compiler == 'gcc_4.8':
528 # We don't have a compiler to use here.
529 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500530 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700531 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
532 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700533 for compiler in ('gcc_4.8', 'clang'):
534 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
535 sanitizer == 'integer' or
536 sanitizer == 'memory'):
537 # GCC 4.8 doesn't support these sanitizers.
538 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700539 if sanitizer == 'none':
540 # We already added sanitizer == 'none' above.
541 continue
542 platforms.append(
543 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
544 self.__platforms = frozenset(platforms)
545
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500546 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700547 default_platforms = self.select_platforms(architecture='amd64',
548 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700549 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
550 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700551 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500552 elif is_deploy:
553 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700554 default_platforms = self.select_platforms(architecture='arm',
555 compiler='gcc',
556 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500557 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700558 default_platforms = self.select_platforms(debug=False)
559 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500560
Brian Silvermane6bada62014-05-04 16:16:54 -0700561 def platforms(self):
562 return self.__platforms
563 def default_platforms(self):
564 return self.__default_platforms
565
566 def download_externals(self, platforms):
567 to_download = set()
568 for architecture in PrimeProcessor.ARCHITECTURES:
569 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
570 if platforms & self.select_platforms(architecture=architecture,
571 sanitizer=sanitizer):
572 to_download.add(architecture + '-fPIE')
573 if platforms & self.select_platforms(architecture=architecture,
574 sanitizer='none'):
575 to_download.add(architecture)
576 for download_target in to_download:
577 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500578
Brian Silverman452aaec2014-05-05 16:52:18 -0700579 def parse_platforms(self, platform_string):
580 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700581 return self.default_platforms()
582 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700583 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700584 if part == 'all':
585 r = self.platforms()
586 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500587 r = r | self.select_platforms_string(part[1:])
588 elif part[0] == '-':
589 r = r - self.select_platforms_string(part[1:])
590 elif part[0] == '=':
591 r = self.select_platforms_string(part[1:])
592 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500593 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700594 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500595 if not r:
596 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500597 return r
598
Brian Silverman452aaec2014-05-05 16:52:18 -0700599 def select_platforms(self, architecture=None, compiler=None, debug=None,
600 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500601 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700602 for platform in self.platforms():
603 if architecture is None or platform.architecture() == architecture:
604 if compiler is None or platform.compiler() == compiler:
605 if debug is None or platform.debug() == debug:
606 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700607 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500608 return set(r)
609
Brian Silverman452aaec2014-05-05 16:52:18 -0700610 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700611 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700612 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500613 if part in PrimeProcessor.ARCHITECTURES:
614 architecture = part
615 elif part in PrimeProcessor.COMPILERS:
616 compiler = part
617 elif part in ['debug', 'dbg']:
618 debug = True
619 elif part in ['release', 'nodebug', 'ndb']:
620 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700621 elif part in PrimeProcessor.SANITIZERS:
622 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500623 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700624 raise Processor.UnknownPlatform(
625 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500626 return self.select_platforms(
627 architecture=architecture,
628 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700629 debug=debug,
630 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500631
Brian Silverman9b7a6842014-05-05 16:19:11 -0700632 def check_installed(self, platforms, is_deploy):
633 packages = set(('lzip', 'm4', 'realpath'))
634 packages.add('ruby')
635 # clang-format from here gets used for all versions.
636 packages.add('clang-3.5')
637 packages.add('arm-eabi-gcc')
638 for platform in platforms:
639 if platform.architecture() == 'arm':
640 packages.add('gcc-4.7-arm-linux-gnueabihf')
641 packages.add('g++-4.7-arm-linux-gnueabihf')
642 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
643 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700644 if platform.compiler() == 'gcc_4.8':
645 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700646 if is_deploy:
647 packages.add('openssh-client')
648 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
649 packages.add('gcc-4.7')
650 packages.add('g++-4.7')
651
652 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700653
Brian Silverman6bca4722014-05-20 17:02:49 -0700654def strsignal(num):
655 # It ends up with SIGIOT instead otherwise, which is weird.
656 if num == signal.SIGABRT:
657 return 'SIGABRT'
658 # SIGCLD is a weird way to spell it.
659 if num == signal.SIGCHLD:
660 return 'SIGCHLD'
661
662 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
663 for n in dir(signal) if n.startswith('SIG')
664 and '_' not in n)
665 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
666
Brian Silvermana29ebf92014-04-23 13:08:49 -0500667def main():
668 class TryParsingAgain(Exception):
669 pass
670
671 class TryAgainArgumentParser(argparse.ArgumentParser):
672 def __init__(self, **kwargs):
673 super(TryAgainArgumentParser, self).__init__(**kwargs)
674
675 def error(self, message):
676 raise TryParsingAgain
677
Brian Silvermane6bada62014-05-04 16:16:54 -0700678 def set_up_parser(parser, args):
679 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500680 parser.add_argument(
681 'target',
682 help='target to build',
683 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700684 parser.add_argument(
685 '--jobs', '-j',
686 help='number of things to do at once',
687 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700688 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500689 parser.add_argument(
690 'platforms',
691 help='platform(s) to act on',
692 nargs='?')
693
694 parser.add_argument('--processor', required=True, help='prime or crio')
695 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
696 subparsers = parser.add_subparsers(dest='action_name')
697
698 build_parser = subparsers.add_parser(
699 'build',
700 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700701 add_common_args(build_parser)
702 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500703
704 clean_parser = subparsers.add_parser(
705 'clean',
706 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700707 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500708
709 deploy_parser = subparsers.add_parser(
710 'deploy',
711 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700712 add_common_args(deploy_parser)
713 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500714 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700715 '-n', '--dry-run',
716 help="don't actually download anything",
717 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500718
Brian Silvermane48c09a2014-04-30 18:04:58 -0700719 tests_parser = subparsers.add_parser(
720 'tests',
721 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700722 add_common_args(tests_parser)
723 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700724
Brian Silvermana29ebf92014-04-23 13:08:49 -0500725 return parser.parse_args(args)
726
727 try:
728 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700729 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500730 except TryParsingAgain:
731 parser = argparse.ArgumentParser()
732 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700733 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
734 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500735
736 if args.processor == 'crio':
737 processor = CRIOProcessor()
738 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500739 processor = PrimeProcessor(args.action_name == 'tests',
740 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500741 else:
742 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
743
744 if 'target' in args:
745 targets = args.target[:]
746 else:
747 targets = []
748 unknown_platform_error = None
749 try:
750 platforms = processor.parse_platforms(args.platforms)
751 except Processor.UnknownPlatform as e:
752 unknown_platform_error = e.message
753 targets.append(args.platforms)
754 platforms = processor.parse_platforms(None)
755 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700756 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500757 exit(1)
758
Brian Silverman9b7a6842014-05-05 16:19:11 -0700759 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700760 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500761
762 class ToolsConfig(object):
763 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500764 self.variables = {'AOS': aos_path()}
765 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500766 for line in f:
767 if line[0] == '#':
768 pass
769 elif line.isspace():
770 pass
771 else:
772 new_name, new_value = line.rstrip().split('=')
773 for name, value in self.variables.items():
774 new_value = new_value.replace('${%s}' % name, value)
775 self.variables[new_name] = new_value
776 def __getitem__(self, key):
777 return self.variables[key]
778
779 tools_config = ToolsConfig()
780
781 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700782 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500783 if issubclass(OSError, excinfo[0]):
784 if excinfo[1].errno == errno.ENOENT:
785 # Who cares if the file we're deleting isn't there?
786 return
787 raise excinfo[1]
788
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700789 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700790 """Determines if we need to run gyp again or not.
791
792 The generated build files are supposed to re-run gyp again themselves, but
793 that doesn't work (or at least it used to not) and we sometimes want to
794 modify the results anyways.
795
796 Args:
797 platform: The platform to check for.
798 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700799 if not os.path.exists(platform.build_ninja()):
800 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700801 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
802 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700803 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700804 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700805 if dirs.count('output'):
806 dirs.remove('output')
807 if dirs.count('.git'):
808 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700809 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700810 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700811 + ('-newer', platform.build_ninja(),
812 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700813 stdin=open(os.devnull, 'r'))
814
815 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700816 """Makes sure we pass through important environmental variables.
817
818 Returns:
819 An environment suitable for passing to subprocess.Popen and friends.
820 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700821 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700822 if not 'TERM' in build_env:
823 build_env['TERM'] = os.environ['TERM']
824 if not 'PATH' in build_env:
825 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700826 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700827
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700828 sorted_platforms = sorted(platforms,
829 key=lambda platform: -platform.priority())
830
Brian Silverman47cd6f62014-05-03 10:35:52 -0700831 to_build = []
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700832 for platform in sorted_platforms:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700833 to_build.append(str(platform))
834 if len(to_build) > 1:
835 to_build[-1] = 'and ' + to_build[-1]
836 user_output('Building %s...' % ', '.join(to_build))
837
838 if args.action_name == 'tests':
839 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
840 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
841 if warned_about:
842 user_output(warning[1])
843 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700844 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700845 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
846 exit(1)
847
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700848 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700849 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700850 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500851 if args.action_name == 'clean':
852 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
853 else:
854 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700855 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500856 gyp = subprocess.Popen(
857 (tools_config['GYP'],
858 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500859 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500860 '--no-circular-check',
861 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500862 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500863 '-I/dev/stdin', '-Goutput_dir=output',
864 '-DOS=%s' % platform.os(),
865 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700866 '-DARCHITECTURE=%s' % platform.architecture(),
867 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
868 '-DFULL_COMPILER=%s' % platform.compiler(),
869 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
870 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700871 '-DSANITIZER_FPIE=%s' %
872 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
873 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500874 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500875 stdin=subprocess.PIPE)
876 gyp.communicate(("""
877{
878 'target_defaults': {
879 'configurations': {
880 '%s': {}
881 }
882 }
883}""" % platform.outname()).encode())
884 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700885 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500886 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700887 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700888 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500889 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700890 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500891
892 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700893 call = (tools_config['NINJA'],
894 '-C', platform.outdir()) + tuple(targets)
895 if args.jobs:
896 call += ('-j', str(args.jobs))
897 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700898 stdin=open(os.devnull, 'r'),
899 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500900 except subprocess.CalledProcessError as e:
901 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700902 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500903 raise e
904
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500905 if args.action_name == 'deploy':
906 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700907 elif args.action_name == 'tests':
908 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700909 done_queue = queue.Queue()
910 running = []
911 if args.jobs:
912 number_jobs = args.jobs
913 else:
914 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
915 test_start_semaphore = threading.Semaphore(number_jobs)
916 if targets:
917 to_run = []
918 for target in targets:
919 if target.endswith('_test'):
920 to_run.append(target)
921 else:
922 to_run = os.listdir(dirname)
923 for f in to_run:
924 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
925 test_start_semaphore)
926 running.append(thread)
927 thread.start()
928 try:
929 while running:
930 done = done_queue.get()
931 running.remove(done)
932 with test_output_lock:
933 test_output('Output from test %s:' % done.name)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700934 with os.fdopen(done.output) as output_file:
935 try:
936 for line in output_file:
937 if not sys.stdout.isatty():
938 # Remove color escape codes.
939 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
940 sys.stdout.write(line)
941 except IOError as e:
942# We want to just ignore EIOs from reading the master pty because that just
943# means we hit the end.
944 if e.errno != errno.EIO:
945 raise e
946 if not done.returncode:
947 test_output('Test %s succeeded' % done.name)
948 else:
Brian Silverman6bca4722014-05-20 17:02:49 -0700949 if done.returncode < 0:
950 sig = -done.returncode
951 test_output('Test %s was killed by signal %d (%s)' % \
952 (done.name, sig, strsignal(sig)))
953 elif done.returncode != 1:
954 test_output('Test %s exited with %d' % \
955 (done.name, done.returncode))
956 else:
957 test_output('Test %s failed' % done.name)
958 user_output('Aborting because of test failure for %s.' % \
959 platform)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700960 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700961 finally:
962 if running:
963 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700964# Stop all of them before killing processes because otherwise stopping some of
965# them tends to let other ones that are waiting to start go.
966 for thread in running:
967 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700968 for thread in running:
969 thread.terminate_process()
970 to_remove = []
971 for thread in running:
972 thread.join(5)
973 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -0700974 to_remove.append(thread)
975 for thread in to_remove:
976 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700977 for thread in running:
978 test_output(
979 'Test %s did not terminate. Killing it.' % thread.name)
980 thread.kill_process()
981 thread.join()
982 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500983
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700984 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
985 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500986
987if __name__ == '__main__':
988 main()