blob: 2d2f9324469dff01aa58e46095d9e66c969ea3d3 [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 Silvermana5826582014-06-03 19:46:35 -070079 try:
80 self.process.terminate()
81 except OSError as e:
82 if e.errno == errno.ESRCH:
83 # We don't really care if it's already gone.
84 pass
85 else:
86 raise e
Brian Silvermanc3740c32014-05-04 12:42:47 -070087 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070088 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070089 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070090 if not self.process:
91 return
Brian Silverman6bca4722014-05-20 17:02:49 -070092 try:
93 self.process.kill()
94 except OSError as e:
95 if e.errno == errno.ESRCH:
96 # We don't really care if it's already gone.
97 pass
98 else:
99 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700100 def stop(self):
101 """Changes self to the stopped state."""
102 with self.process_lock:
103 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500104
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500105def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700106 """Returns:
107 A relative path to the aos directory.
108 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500109 return os.path.join(os.path.dirname(__file__), '..')
110
111def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -0700112 """Retrieves the IP address for a given device."""
113 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700114 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500115 if not os.access(FILENAME, os.R_OK):
116 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
117 with open(FILENAME, 'w') as f:
118 f.write('10.9.71')
119 with open(FILENAME, 'r') as f:
120 base = f.readline()
121 if device == 'prime':
122 return base + '.179'
123 elif device == 'robot':
124 return base + '.2'
125 else:
126 raise Exception('Unknown device %s to get an IP address for.' % device)
127
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700128def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700129 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700130 print('build.py: ' + message, file=sys.stderr)
131
Brian Silverman452aaec2014-05-05 16:52:18 -0700132# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700133test_output_lock = threading.RLock()
134def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700135 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700136 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700137 print('tests: ' + message, file=sys.stdout)
138
139def call_download_externals(argument):
140 """Calls download_externals.sh for a given set of externals.
141
142 Args:
143 argument: The argument to pass to the shell script to tell it what to
144 download.
145 """
146 subprocess.check_call(
147 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
148 argument),
149 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700150
Brian Silvermana29ebf92014-04-23 13:08:49 -0500151class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700152 """Represents a processor architecture we can build for."""
153
Brian Silvermana29ebf92014-04-23 13:08:49 -0500154 class UnknownPlatform(Exception):
155 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700156 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500157 self.message = message
158
Brian Silvermanb3d50542014-04-23 14:28:55 -0500159 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700160 """Represents a single way to build the code."""
161
Brian Silvermanb3d50542014-04-23 14:28:55 -0500162 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700163 """Returns:
164 The path of the directory build outputs get put in to.
165 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500166 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500167 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500168 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700169 """Returns:
170 The path of the build.ninja file.
171 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500172 return os.path.join(self.outdir(), 'build.ninja')
173
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500174 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700175 """Helper for subclasses to implement deploy.
176
177 Args:
178 dry_run: If True, prints the command instead of actually running it.
179 command: A tuple of command-line arguments.
180 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500181 real_command = (('echo',) + command) if dry_run else command
182 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500183
Brian Silvermane6bada62014-05-04 16:16:54 -0700184 def deploy(self, dry_run):
185 """Downloads the compiled code to the target computer."""
186 raise NotImplementedError('deploy should be overriden')
187 def outname(self):
188 """Returns:
189 The name of the directory the code will be compiled to.
190 """
191 raise NotImplementedError('outname should be overriden')
192 def os(self):
193 """Returns:
194 The name of the operating system this platform is for.
195
196 This will be used as the value of the OS gyp variable.
197 """
198 raise NotImplementedError('os should be overriden')
199 def gyp_platform(self):
200 """Returns:
201 The platform name the .gyp files know.
202
203 This will be used as the value of the PLATFORM gyp variable.
204 """
205 raise NotImplementedError('gyp_platform should be overriden')
206 def architecture(self):
207 """Returns:
208 The processor architecture for this platform.
209
210 This will be used as the value of the ARCHITECTURE gyp variable.
211 """
212 raise NotImplementedError('architecture should be overriden')
213 def compiler(self):
214 """Returns:
215 The compiler used for this platform.
216
217 Everything before the first _ will be used as the value of the
218 COMPILER gyp variable and the whole thing will be used as the value
219 of the FULL_COMPILER gyp variable.
220 """
221 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700222 def sanitizer(self):
223 """Returns:
224 The sanitizer used on this platform.
225
226 This will be used as the value of the SANITIZER gyp variable.
227
228 "none" if there isn't one.
229 """
230 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700231 def debug(self):
232 """Returns:
233 Whether or not this platform compiles with debugging information.
234
235 The DEBUG gyp variable will be set to "yes" or "no" based on this.
236 """
237 raise NotImplementedError('debug should be overriden')
238 def build_env(self):
239 """Returns:
240 A map of environment variables to set while building this platform.
241 """
242 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700243 def priority(self):
244 """Returns:
245 A relative priority for this platform relative to other ones.
246
247 Higher priority platforms will get built, tested, etc first. Generally,
248 platforms which give higher-quality compiler errors etc should come first.
249 """
250 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700251
Brian Silverman9b7a6842014-05-05 16:19:11 -0700252 def check_installed(self, platforms, is_deploy):
253 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700254 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700255 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700256 """Args:
257 string: A user-supplied string saying which platforms to select.
258
259 Returns:
260 A tuple of Platform objects.
261
262 Raises:
263 Processor.UnknownPlatform: Parsing string didn't work out.
264 """
265 raise NotImplementedError('parse_platforms should be overriden')
266 def extra_gyp_flags(self):
267 """Returns:
268 A tuple of extra flags to pass to gyp (if any).
269 """
270 return ()
271 def modify_ninja_file(self, ninja_file):
272 """Modifies a freshly generated ninja file as necessary.
273
274 Args:
275 ninja_file: Path to the file to modify.
276 """
277 pass
278 def download_externals(self, platforms):
279 """Calls download_externals as appropriate to build platforms.
280
281 Args:
282 platforms: A list of platforms to download external libraries for.
283 """
284 raise NotImplementedError('download_externals should be overriden')
285
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700286 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700287 """Helper for subclasses to implement check_installed.
288
289 Args:
290 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700291 all_packages = other_packages
292 # Necessary to build stuff.
293 all_packages += ('ccache', 'make')
294 # Necessary to download stuff to build.
295 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
296 # Necessary to build externals stuff.
297 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700298 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700299 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700300 result = subprocess.check_output(
301 ('dpkg-query', '--show') + all_packages,
302 stdin=open(os.devnull, 'r'),
303 stderr=subprocess.STDOUT)
304 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700305 output = e.output.decode('utf-8').rstrip()
306 not_found = []
307 for line in output.splitlines(True):
308 match = re.match(r'dpkg-query: no packages found matching (.*)',
309 line)
310 if match:
311 not_found.append(match.group(1))
312 user_output('Some packages not installed: %s.' % ', '.join(not_found))
313 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700314 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700315 exit(1)
316
Brian Silvermana29ebf92014-04-23 13:08:49 -0500317class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700318 """A Processor subclass for building cRIO code."""
319
Brian Silvermanb3d50542014-04-23 14:28:55 -0500320 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700321 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500322 super(CRIOProcessor.Platform, self).__init__()
323
Brian Silvermane6bada62014-05-04 16:16:54 -0700324 self.__debug = debug
325 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500326
327 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700328 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500329 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700330 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500331
332 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700333 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500334 def os(self):
335 return 'vxworks'
336 def gyp_platform(self):
337 return 'crio'
338 def architecture(self):
339 return 'ppc'
340 def compiler(self):
341 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700342 def sanitizer(self):
343 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700344 def debug(self):
345 return self.__debug
346 def wind_base(self):
347 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500348
Brian Silvermane48c09a2014-04-30 18:04:58 -0700349 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500350 def deploy(self, dry_run):
351 self.do_deploy(dry_run,
352 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700353 os.path.join(self.outdir(), 'lib',
354 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500355
Brian Silvermana4aff562014-05-02 17:43:50 -0700356 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700357 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700358
Brian Silvermana29ebf92014-04-23 13:08:49 -0500359 def __init__(self):
360 super(CRIOProcessor, self).__init__()
361
362 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700363 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500364 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700365 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500366
Brian Silverman452aaec2014-05-05 16:52:18 -0700367 def parse_platforms(self, platforms_string):
368 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700369 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700370 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700371 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500372 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700373 raise Processor.UnknownPlatform(
374 'Unknown cRIO platform "%s".' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500375
Brian Silvermane6bada62014-05-04 16:16:54 -0700376 def wind_base(self):
377 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500378
Brian Silvermane6bada62014-05-04 16:16:54 -0700379 def extra_gyp_flags(self):
380 return ('-DWIND_BASE=%s' % self.wind_base(),)
381
382 def modify_ninja_file(self, ninja_file):
383 subprocess.check_call(
384 ('sed', '-i',
385 's/nm -gD/nm/g', ninja_file),
386 stdin=open(os.devnull, 'r'))
387
388 def download_externals(self, _):
389 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500390
Brian Silverman9b7a6842014-05-05 16:19:11 -0700391 def check_installed(self, platforms, is_deploy):
392 packages = ('powerpc-wrs-vxworks', 'tcl')
393 if is_deploy:
394 packages += ('ncftp',)
395 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700396
Brian Silvermana29ebf92014-04-23 13:08:49 -0500397class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700398 """A Processor subclass for building prime code."""
399
Brian Silvermanb3d50542014-04-23 14:28:55 -0500400 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700401 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500402 super(PrimeProcessor.Platform, self).__init__()
403
Brian Silvermane6bada62014-05-04 16:16:54 -0700404 self.__architecture = architecture
405 self.__compiler = compiler
406 self.__debug = debug
407 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500408
409 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700410 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
411 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700412 % (self.architecture(), self.compiler(), self.debug(),
413 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500414 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700415 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700416 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500417
418 def os(self):
419 return 'linux'
420 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700421 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
422 def architecture(self):
423 return self.__architecture
424 def compiler(self):
425 return self.__compiler
426 def sanitizer(self):
427 return self.__sanitizer
428 def debug(self):
429 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500430
Brian Silvermana29ebf92014-04-23 13:08:49 -0500431 def outname(self):
432 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500433
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700434 def priority(self):
435 r = 0
436 if self.compiler() == 'gcc':
437 r -= 100
438 elif self.compiler() == 'clang':
439 r += 100
440 if self.sanitizer() != 'none':
441 r -= 50
442 elif self.debug():
443 r -= 10
444 if self.architecture() == 'amd64':
445 r += 5
446 return r
447
Brian Silvermane48c09a2014-04-30 18:04:58 -0700448 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500449 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700450 # Downloads code to the prime in a way that avoids clashing too badly with
451 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500452 SUM = 'md5sum'
453 TARGET_DIR = '/home/driver/robot_code/bin'
454 TEMP_DIR = '/tmp/aos_downloader'
455 TARGET = 'driver@' + get_ip('prime')
456
457 from_dir = os.path.join(self.outdir(), 'outputs')
458 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
459 stdin=open(os.devnull, 'r'),
460 cwd=from_dir)
461 to_download = subprocess.check_output(
462 ('ssh', TARGET,
463 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
464 && echo '{SUMS}' | {SUM} --check --quiet
Brian Silverman452aaec2014-05-05 16:52:18 -0700465 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".
466 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500467 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700468 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500469 return
470 self.do_deploy(
471 dry_run,
472 ('scp', '-o', 'Compression yes') + to_download
473 + (('%s:%s' % (TARGET, TEMP_DIR)),))
474 if not dry_run:
475 subprocess.check_call(
476 ('ssh', TARGET,
477 """mv {TMPDIR}/* {TO_DIR}
478 && echo 'Done moving new executables into place'
479 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
480 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
481
Brian Silvermana4aff562014-05-02 17:43:50 -0700482 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700483 OTHER_SYSROOT = '/opt/clang-3.5/'
484 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700485 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700486 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
487 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
488 if self.sanitizer() == 'address':
489 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700490 r['ASAN_OPTIONS'] = \
491 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700492 elif self.sanitizer() == 'memory':
493 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
494 elif self.sanitizer() == 'thread':
495 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700496
497 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700498 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
499 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700500 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700501 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700502 # clang doesn't like being run directly on the preprocessed files.
503 r['CCACHE_CPP2'] = 'yes'
504 # Without this, ccache slows down because of the generated header files.
505 # The race condition that this opens up isn't a problem because the build
506 # system finishes modifying header files before compiling anything that
507 # uses them.
508 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700509
Brian Silvermane6bada62014-05-04 16:16:54 -0700510 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700511 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
512 ':' + os.environ['PATH']
513
Brian Silvermana4aff562014-05-02 17:43:50 -0700514 return r
515
Brian Silverman47cd6f62014-05-03 10:35:52 -0700516 ARCHITECTURES = ('arm', 'amd64')
517 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
518 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
519 SANITIZER_TEST_WARNINGS = {
520 'memory': (True,
521"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700522 errors with msan (especially stdlibc++).
523 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700524 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700525 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500526
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500527 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700528 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500529
530 platforms = []
531 for architecture in PrimeProcessor.ARCHITECTURES:
532 for compiler in PrimeProcessor.COMPILERS:
533 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700534 if architecture == 'arm' and compiler == 'gcc_4.8':
535 # We don't have a compiler to use here.
536 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500537 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700538 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
539 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700540 for compiler in ('gcc_4.8', 'clang'):
541 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
542 sanitizer == 'integer' or
543 sanitizer == 'memory'):
544 # GCC 4.8 doesn't support these sanitizers.
545 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700546 if sanitizer == 'none':
547 # We already added sanitizer == 'none' above.
548 continue
549 platforms.append(
550 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
551 self.__platforms = frozenset(platforms)
552
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500553 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700554 default_platforms = self.select_platforms(architecture='amd64',
555 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700556 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
557 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700558 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500559 elif is_deploy:
560 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700561 default_platforms = self.select_platforms(architecture='arm',
562 compiler='gcc',
563 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500564 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700565 default_platforms = self.select_platforms(debug=False)
566 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500567
Brian Silvermane6bada62014-05-04 16:16:54 -0700568 def platforms(self):
569 return self.__platforms
570 def default_platforms(self):
571 return self.__default_platforms
572
573 def download_externals(self, platforms):
574 to_download = set()
575 for architecture in PrimeProcessor.ARCHITECTURES:
576 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
577 if platforms & self.select_platforms(architecture=architecture,
578 sanitizer=sanitizer):
579 to_download.add(architecture + '-fPIE')
580 if platforms & self.select_platforms(architecture=architecture,
581 sanitizer='none'):
582 to_download.add(architecture)
583 for download_target in to_download:
584 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500585
Brian Silverman452aaec2014-05-05 16:52:18 -0700586 def parse_platforms(self, platform_string):
587 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700588 return self.default_platforms()
589 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700590 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700591 if part == 'all':
592 r = self.platforms()
593 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500594 r = r | self.select_platforms_string(part[1:])
595 elif part[0] == '-':
596 r = r - self.select_platforms_string(part[1:])
597 elif part[0] == '=':
598 r = self.select_platforms_string(part[1:])
599 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500600 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700601 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500602 if not r:
603 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500604 return r
605
Brian Silverman452aaec2014-05-05 16:52:18 -0700606 def select_platforms(self, architecture=None, compiler=None, debug=None,
607 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500608 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700609 for platform in self.platforms():
610 if architecture is None or platform.architecture() == architecture:
611 if compiler is None or platform.compiler() == compiler:
612 if debug is None or platform.debug() == debug:
613 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700614 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500615 return set(r)
616
Brian Silverman452aaec2014-05-05 16:52:18 -0700617 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700618 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700619 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500620 if part in PrimeProcessor.ARCHITECTURES:
621 architecture = part
622 elif part in PrimeProcessor.COMPILERS:
623 compiler = part
624 elif part in ['debug', 'dbg']:
625 debug = True
626 elif part in ['release', 'nodebug', 'ndb']:
627 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700628 elif part in PrimeProcessor.SANITIZERS:
629 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500630 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700631 raise Processor.UnknownPlatform(
632 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500633 return self.select_platforms(
634 architecture=architecture,
635 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700636 debug=debug,
637 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500638
Brian Silverman9b7a6842014-05-05 16:19:11 -0700639 def check_installed(self, platforms, is_deploy):
640 packages = set(('lzip', 'm4', 'realpath'))
641 packages.add('ruby')
642 # clang-format from here gets used for all versions.
643 packages.add('clang-3.5')
644 packages.add('arm-eabi-gcc')
645 for platform in platforms:
646 if platform.architecture() == 'arm':
647 packages.add('gcc-4.7-arm-linux-gnueabihf')
648 packages.add('g++-4.7-arm-linux-gnueabihf')
649 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
650 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700651 if platform.compiler() == 'gcc_4.8':
652 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700653 if is_deploy:
654 packages.add('openssh-client')
655 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
656 packages.add('gcc-4.7')
657 packages.add('g++-4.7')
658
659 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700660
Brian Silverman6bca4722014-05-20 17:02:49 -0700661def strsignal(num):
662 # It ends up with SIGIOT instead otherwise, which is weird.
663 if num == signal.SIGABRT:
664 return 'SIGABRT'
665 # SIGCLD is a weird way to spell it.
666 if num == signal.SIGCHLD:
667 return 'SIGCHLD'
668
669 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
670 for n in dir(signal) if n.startswith('SIG')
671 and '_' not in n)
672 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
673
Brian Silvermana29ebf92014-04-23 13:08:49 -0500674def main():
675 class TryParsingAgain(Exception):
676 pass
677
678 class TryAgainArgumentParser(argparse.ArgumentParser):
679 def __init__(self, **kwargs):
680 super(TryAgainArgumentParser, self).__init__(**kwargs)
681
682 def error(self, message):
683 raise TryParsingAgain
684
Brian Silvermane6bada62014-05-04 16:16:54 -0700685 def set_up_parser(parser, args):
686 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500687 parser.add_argument(
688 'target',
689 help='target to build',
690 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700691 parser.add_argument(
692 '--jobs', '-j',
693 help='number of things to do at once',
694 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700695 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500696 parser.add_argument(
697 'platforms',
698 help='platform(s) to act on',
699 nargs='?')
700
701 parser.add_argument('--processor', required=True, help='prime or crio')
702 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
703 subparsers = parser.add_subparsers(dest='action_name')
704
705 build_parser = subparsers.add_parser(
706 'build',
707 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700708 add_common_args(build_parser)
709 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500710
711 clean_parser = subparsers.add_parser(
712 'clean',
713 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700714 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500715
716 deploy_parser = subparsers.add_parser(
717 'deploy',
718 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700719 add_common_args(deploy_parser)
720 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500721 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700722 '-n', '--dry-run',
723 help="don't actually download anything",
724 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500725
Brian Silvermane48c09a2014-04-30 18:04:58 -0700726 tests_parser = subparsers.add_parser(
727 'tests',
728 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700729 add_common_args(tests_parser)
730 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700731
Brian Silvermana29ebf92014-04-23 13:08:49 -0500732 return parser.parse_args(args)
733
734 try:
735 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700736 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500737 except TryParsingAgain:
738 parser = argparse.ArgumentParser()
739 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700740 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
741 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500742
743 if args.processor == 'crio':
744 processor = CRIOProcessor()
745 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500746 processor = PrimeProcessor(args.action_name == 'tests',
747 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500748 else:
749 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
750
751 if 'target' in args:
752 targets = args.target[:]
753 else:
754 targets = []
755 unknown_platform_error = None
756 try:
757 platforms = processor.parse_platforms(args.platforms)
758 except Processor.UnknownPlatform as e:
759 unknown_platform_error = e.message
760 targets.append(args.platforms)
761 platforms = processor.parse_platforms(None)
762 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700763 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500764 exit(1)
765
Brian Silverman9b7a6842014-05-05 16:19:11 -0700766 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700767 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500768
769 class ToolsConfig(object):
770 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500771 self.variables = {'AOS': aos_path()}
772 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500773 for line in f:
774 if line[0] == '#':
775 pass
776 elif line.isspace():
777 pass
778 else:
779 new_name, new_value = line.rstrip().split('=')
780 for name, value in self.variables.items():
781 new_value = new_value.replace('${%s}' % name, value)
782 self.variables[new_name] = new_value
783 def __getitem__(self, key):
784 return self.variables[key]
785
786 tools_config = ToolsConfig()
787
788 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700789 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500790 if issubclass(OSError, excinfo[0]):
791 if excinfo[1].errno == errno.ENOENT:
792 # Who cares if the file we're deleting isn't there?
793 return
794 raise excinfo[1]
795
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700796 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700797 """Determines if we need to run gyp again or not.
798
799 The generated build files are supposed to re-run gyp again themselves, but
800 that doesn't work (or at least it used to not) and we sometimes want to
801 modify the results anyways.
802
803 Args:
804 platform: The platform to check for.
805 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700806 if not os.path.exists(platform.build_ninja()):
807 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700808 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
809 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700810 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700811 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700812 if dirs.count('output'):
813 dirs.remove('output')
814 if dirs.count('.git'):
815 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700816 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700817 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700818 + ('-newer', platform.build_ninja(),
819 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700820 stdin=open(os.devnull, 'r'))
821
822 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700823 """Makes sure we pass through important environmental variables.
824
825 Returns:
826 An environment suitable for passing to subprocess.Popen and friends.
827 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700828 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700829 if not 'TERM' in build_env:
830 build_env['TERM'] = os.environ['TERM']
831 if not 'PATH' in build_env:
832 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700833 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700834
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700835 sorted_platforms = sorted(platforms,
836 key=lambda platform: -platform.priority())
837
Brian Silverman47cd6f62014-05-03 10:35:52 -0700838 to_build = []
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700839 for platform in sorted_platforms:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700840 to_build.append(str(platform))
841 if len(to_build) > 1:
842 to_build[-1] = 'and ' + to_build[-1]
843 user_output('Building %s...' % ', '.join(to_build))
844
845 if args.action_name == 'tests':
846 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
847 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
848 if warned_about:
849 user_output(warning[1])
850 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700851 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700852 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
853 exit(1)
854
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700855 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700856 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700857 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500858 if args.action_name == 'clean':
859 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
860 else:
861 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700862 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500863 gyp = subprocess.Popen(
864 (tools_config['GYP'],
865 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500866 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500867 '--no-circular-check',
868 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500869 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500870 '-I/dev/stdin', '-Goutput_dir=output',
871 '-DOS=%s' % platform.os(),
872 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700873 '-DARCHITECTURE=%s' % platform.architecture(),
874 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
875 '-DFULL_COMPILER=%s' % platform.compiler(),
876 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
877 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700878 '-DSANITIZER_FPIE=%s' %
879 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
880 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500881 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500882 stdin=subprocess.PIPE)
883 gyp.communicate(("""
884{
885 'target_defaults': {
886 'configurations': {
887 '%s': {}
888 }
889 }
890}""" % platform.outname()).encode())
891 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700892 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500893 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700894 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700895 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500896 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700897 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500898
899 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700900 call = (tools_config['NINJA'],
901 '-C', platform.outdir()) + tuple(targets)
902 if args.jobs:
903 call += ('-j', str(args.jobs))
904 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700905 stdin=open(os.devnull, 'r'),
906 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500907 except subprocess.CalledProcessError as e:
908 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700909 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500910 raise e
911
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500912 if args.action_name == 'deploy':
913 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700914 elif args.action_name == 'tests':
915 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700916 done_queue = queue.Queue()
917 running = []
918 if args.jobs:
919 number_jobs = args.jobs
920 else:
921 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
922 test_start_semaphore = threading.Semaphore(number_jobs)
923 if targets:
924 to_run = []
925 for target in targets:
926 if target.endswith('_test'):
927 to_run.append(target)
928 else:
929 to_run = os.listdir(dirname)
930 for f in to_run:
931 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
932 test_start_semaphore)
933 running.append(thread)
934 thread.start()
935 try:
936 while running:
937 done = done_queue.get()
938 running.remove(done)
939 with test_output_lock:
940 test_output('Output from test %s:' % done.name)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700941 with os.fdopen(done.output) as output_file:
942 try:
943 for line in output_file:
944 if not sys.stdout.isatty():
945 # Remove color escape codes.
946 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
947 sys.stdout.write(line)
948 except IOError as e:
949# We want to just ignore EIOs from reading the master pty because that just
950# means we hit the end.
951 if e.errno != errno.EIO:
952 raise e
953 if not done.returncode:
954 test_output('Test %s succeeded' % done.name)
955 else:
Brian Silverman6bca4722014-05-20 17:02:49 -0700956 if done.returncode < 0:
957 sig = -done.returncode
958 test_output('Test %s was killed by signal %d (%s)' % \
959 (done.name, sig, strsignal(sig)))
960 elif done.returncode != 1:
961 test_output('Test %s exited with %d' % \
962 (done.name, done.returncode))
963 else:
964 test_output('Test %s failed' % done.name)
965 user_output('Aborting because of test failure for %s.' % \
966 platform)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700967 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700968 finally:
969 if running:
970 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700971# Stop all of them before killing processes because otherwise stopping some of
972# them tends to let other ones that are waiting to start go.
973 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -0700974 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700975 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700976 for thread in running:
977 thread.terminate_process()
978 to_remove = []
979 for thread in running:
980 thread.join(5)
981 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -0700982 to_remove.append(thread)
983 for thread in to_remove:
984 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700985 for thread in running:
986 test_output(
987 'Test %s did not terminate. Killing it.' % thread.name)
988 thread.kill_process()
989 thread.join()
990 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500991
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700992 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
993 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500994
995if __name__ == '__main__':
996 main()