blob: 9d2325d7e3591e76645eaedc7cee265ba1747bdb [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'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700491 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
492 ':detect_stack_use_after_return=1:detect_odr_violation=2'
Brian Silvermane6bada62014-05-04 16:16:54 -0700493 elif self.sanitizer() == 'memory':
494 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
495 elif self.sanitizer() == 'thread':
496 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700497
498 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700499 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
500 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700501 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700502 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700503 # clang doesn't like being run directly on the preprocessed files.
504 r['CCACHE_CPP2'] = 'yes'
505 # Without this, ccache slows down because of the generated header files.
506 # The race condition that this opens up isn't a problem because the build
507 # system finishes modifying header files before compiling anything that
508 # uses them.
509 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700510
Brian Silvermane6bada62014-05-04 16:16:54 -0700511 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700512 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
513 ':' + os.environ['PATH']
514
Brian Silvermana4aff562014-05-02 17:43:50 -0700515 return r
516
Brian Silverman47cd6f62014-05-03 10:35:52 -0700517 ARCHITECTURES = ('arm', 'amd64')
518 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
519 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
520 SANITIZER_TEST_WARNINGS = {
521 'memory': (True,
522"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700523 errors with msan (especially stdlibc++).
524 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700525 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700526 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500527
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500528 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700529 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500530
531 platforms = []
532 for architecture in PrimeProcessor.ARCHITECTURES:
533 for compiler in PrimeProcessor.COMPILERS:
534 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700535 if architecture == 'arm' and compiler == 'gcc_4.8':
536 # We don't have a compiler to use here.
537 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500538 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700539 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
540 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700541 for compiler in ('gcc_4.8', 'clang'):
542 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
543 sanitizer == 'integer' or
544 sanitizer == 'memory'):
545 # GCC 4.8 doesn't support these sanitizers.
546 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700547 if sanitizer == 'none':
548 # We already added sanitizer == 'none' above.
549 continue
550 platforms.append(
551 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
552 self.__platforms = frozenset(platforms)
553
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500554 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700555 default_platforms = self.select_platforms(architecture='amd64',
556 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700557 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
558 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700559 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500560 elif is_deploy:
561 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700562 default_platforms = self.select_platforms(architecture='arm',
563 compiler='gcc',
564 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500565 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700566 default_platforms = self.select_platforms(debug=False)
567 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500568
Brian Silvermane6bada62014-05-04 16:16:54 -0700569 def platforms(self):
570 return self.__platforms
571 def default_platforms(self):
572 return self.__default_platforms
573
574 def download_externals(self, platforms):
575 to_download = set()
576 for architecture in PrimeProcessor.ARCHITECTURES:
577 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
578 if platforms & self.select_platforms(architecture=architecture,
579 sanitizer=sanitizer):
580 to_download.add(architecture + '-fPIE')
581 if platforms & self.select_platforms(architecture=architecture,
582 sanitizer='none'):
583 to_download.add(architecture)
584 for download_target in to_download:
585 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500586
Brian Silverman452aaec2014-05-05 16:52:18 -0700587 def parse_platforms(self, platform_string):
588 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700589 return self.default_platforms()
590 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700591 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700592 if part == 'all':
593 r = self.platforms()
594 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500595 r = r | self.select_platforms_string(part[1:])
596 elif part[0] == '-':
597 r = r - self.select_platforms_string(part[1:])
598 elif part[0] == '=':
599 r = self.select_platforms_string(part[1:])
600 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500601 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700602 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500603 if not r:
604 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500605 return r
606
Brian Silverman452aaec2014-05-05 16:52:18 -0700607 def select_platforms(self, architecture=None, compiler=None, debug=None,
608 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500609 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700610 for platform in self.platforms():
611 if architecture is None or platform.architecture() == architecture:
612 if compiler is None or platform.compiler() == compiler:
613 if debug is None or platform.debug() == debug:
614 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700615 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500616 return set(r)
617
Brian Silverman452aaec2014-05-05 16:52:18 -0700618 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700619 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700620 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500621 if part in PrimeProcessor.ARCHITECTURES:
622 architecture = part
623 elif part in PrimeProcessor.COMPILERS:
624 compiler = part
625 elif part in ['debug', 'dbg']:
626 debug = True
627 elif part in ['release', 'nodebug', 'ndb']:
628 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700629 elif part in PrimeProcessor.SANITIZERS:
630 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500631 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700632 raise Processor.UnknownPlatform(
633 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500634 return self.select_platforms(
635 architecture=architecture,
636 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700637 debug=debug,
638 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500639
Brian Silverman9b7a6842014-05-05 16:19:11 -0700640 def check_installed(self, platforms, is_deploy):
641 packages = set(('lzip', 'm4', 'realpath'))
642 packages.add('ruby')
643 # clang-format from here gets used for all versions.
644 packages.add('clang-3.5')
645 packages.add('arm-eabi-gcc')
646 for platform in platforms:
647 if platform.architecture() == 'arm':
648 packages.add('gcc-4.7-arm-linux-gnueabihf')
649 packages.add('g++-4.7-arm-linux-gnueabihf')
650 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
651 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700652 if platform.compiler() == 'gcc_4.8':
653 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700654 if is_deploy:
655 packages.add('openssh-client')
656 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
657 packages.add('gcc-4.7')
658 packages.add('g++-4.7')
659
660 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700661
Brian Silverman6bca4722014-05-20 17:02:49 -0700662def strsignal(num):
663 # It ends up with SIGIOT instead otherwise, which is weird.
664 if num == signal.SIGABRT:
665 return 'SIGABRT'
666 # SIGCLD is a weird way to spell it.
667 if num == signal.SIGCHLD:
668 return 'SIGCHLD'
669
670 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
671 for n in dir(signal) if n.startswith('SIG')
672 and '_' not in n)
673 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
674
Brian Silvermana29ebf92014-04-23 13:08:49 -0500675def main():
676 class TryParsingAgain(Exception):
677 pass
678
679 class TryAgainArgumentParser(argparse.ArgumentParser):
680 def __init__(self, **kwargs):
681 super(TryAgainArgumentParser, self).__init__(**kwargs)
682
683 def error(self, message):
684 raise TryParsingAgain
685
Brian Silvermane6bada62014-05-04 16:16:54 -0700686 def set_up_parser(parser, args):
687 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500688 parser.add_argument(
689 'target',
690 help='target to build',
691 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700692 parser.add_argument(
693 '--jobs', '-j',
694 help='number of things to do at once',
695 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700696 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500697 parser.add_argument(
698 'platforms',
699 help='platform(s) to act on',
700 nargs='?')
701
702 parser.add_argument('--processor', required=True, help='prime or crio')
703 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
704 subparsers = parser.add_subparsers(dest='action_name')
705
706 build_parser = subparsers.add_parser(
707 'build',
708 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700709 add_common_args(build_parser)
710 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500711
712 clean_parser = subparsers.add_parser(
713 'clean',
714 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700715 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500716
717 deploy_parser = subparsers.add_parser(
718 'deploy',
719 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700720 add_common_args(deploy_parser)
721 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500722 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700723 '-n', '--dry-run',
724 help="don't actually download anything",
725 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500726
Brian Silvermane48c09a2014-04-30 18:04:58 -0700727 tests_parser = subparsers.add_parser(
728 'tests',
729 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700730 add_common_args(tests_parser)
731 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700732
Brian Silvermana29ebf92014-04-23 13:08:49 -0500733 return parser.parse_args(args)
734
735 try:
736 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700737 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500738 except TryParsingAgain:
739 parser = argparse.ArgumentParser()
740 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700741 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
742 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500743
744 if args.processor == 'crio':
745 processor = CRIOProcessor()
746 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500747 processor = PrimeProcessor(args.action_name == 'tests',
748 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500749 else:
750 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
751
752 if 'target' in args:
753 targets = args.target[:]
754 else:
755 targets = []
756 unknown_platform_error = None
757 try:
758 platforms = processor.parse_platforms(args.platforms)
759 except Processor.UnknownPlatform as e:
760 unknown_platform_error = e.message
761 targets.append(args.platforms)
762 platforms = processor.parse_platforms(None)
763 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700764 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500765 exit(1)
766
Brian Silverman9b7a6842014-05-05 16:19:11 -0700767 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700768 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500769
770 class ToolsConfig(object):
771 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500772 self.variables = {'AOS': aos_path()}
773 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500774 for line in f:
775 if line[0] == '#':
776 pass
777 elif line.isspace():
778 pass
779 else:
780 new_name, new_value = line.rstrip().split('=')
781 for name, value in self.variables.items():
782 new_value = new_value.replace('${%s}' % name, value)
783 self.variables[new_name] = new_value
784 def __getitem__(self, key):
785 return self.variables[key]
786
787 tools_config = ToolsConfig()
788
789 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700790 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500791 if issubclass(OSError, excinfo[0]):
792 if excinfo[1].errno == errno.ENOENT:
793 # Who cares if the file we're deleting isn't there?
794 return
795 raise excinfo[1]
796
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700797 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700798 """Determines if we need to run gyp again or not.
799
800 The generated build files are supposed to re-run gyp again themselves, but
801 that doesn't work (or at least it used to not) and we sometimes want to
802 modify the results anyways.
803
804 Args:
805 platform: The platform to check for.
806 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700807 if not os.path.exists(platform.build_ninja()):
808 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700809 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
810 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700811 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700812 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700813 if dirs.count('output'):
814 dirs.remove('output')
815 if dirs.count('.git'):
816 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700817 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700818 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700819 + ('-newer', platform.build_ninja(),
820 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700821 stdin=open(os.devnull, 'r'))
822
823 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700824 """Makes sure we pass through important environmental variables.
825
826 Returns:
827 An environment suitable for passing to subprocess.Popen and friends.
828 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700829 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700830 if not 'TERM' in build_env:
831 build_env['TERM'] = os.environ['TERM']
832 if not 'PATH' in build_env:
833 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700834 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700835
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700836 sorted_platforms = sorted(platforms,
837 key=lambda platform: -platform.priority())
838
Brian Silverman47cd6f62014-05-03 10:35:52 -0700839 to_build = []
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700840 for platform in sorted_platforms:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700841 to_build.append(str(platform))
842 if len(to_build) > 1:
843 to_build[-1] = 'and ' + to_build[-1]
844 user_output('Building %s...' % ', '.join(to_build))
845
846 if args.action_name == 'tests':
847 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
848 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
849 if warned_about:
850 user_output(warning[1])
851 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700852 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700853 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
854 exit(1)
855
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700856 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700857 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700858 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500859 if args.action_name == 'clean':
860 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
861 else:
862 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700863 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500864 gyp = subprocess.Popen(
865 (tools_config['GYP'],
866 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500867 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500868 '--no-circular-check',
869 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500870 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500871 '-I/dev/stdin', '-Goutput_dir=output',
872 '-DOS=%s' % platform.os(),
873 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700874 '-DARCHITECTURE=%s' % platform.architecture(),
875 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
876 '-DFULL_COMPILER=%s' % platform.compiler(),
877 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
878 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700879 '-DSANITIZER_FPIE=%s' %
880 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
881 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500882 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500883 stdin=subprocess.PIPE)
884 gyp.communicate(("""
885{
886 'target_defaults': {
887 'configurations': {
888 '%s': {}
889 }
890 }
891}""" % platform.outname()).encode())
892 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700893 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500894 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700895 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700896 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500897 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700898 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500899
900 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700901 call = (tools_config['NINJA'],
902 '-C', platform.outdir()) + tuple(targets)
903 if args.jobs:
904 call += ('-j', str(args.jobs))
905 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700906 stdin=open(os.devnull, 'r'),
907 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500908 except subprocess.CalledProcessError as e:
909 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700910 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500911 raise e
912
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500913 if args.action_name == 'deploy':
914 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700915 elif args.action_name == 'tests':
916 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700917 done_queue = queue.Queue()
918 running = []
919 if args.jobs:
920 number_jobs = args.jobs
921 else:
922 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
923 test_start_semaphore = threading.Semaphore(number_jobs)
924 if targets:
925 to_run = []
926 for target in targets:
927 if target.endswith('_test'):
928 to_run.append(target)
929 else:
930 to_run = os.listdir(dirname)
931 for f in to_run:
932 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
933 test_start_semaphore)
934 running.append(thread)
935 thread.start()
936 try:
937 while running:
938 done = done_queue.get()
939 running.remove(done)
940 with test_output_lock:
941 test_output('Output from test %s:' % done.name)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700942 with os.fdopen(done.output) as output_file:
943 try:
944 for line in output_file:
945 if not sys.stdout.isatty():
946 # Remove color escape codes.
947 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
948 sys.stdout.write(line)
949 except IOError as e:
950# We want to just ignore EIOs from reading the master pty because that just
951# means we hit the end.
952 if e.errno != errno.EIO:
953 raise e
954 if not done.returncode:
955 test_output('Test %s succeeded' % done.name)
956 else:
Brian Silverman6bca4722014-05-20 17:02:49 -0700957 if done.returncode < 0:
958 sig = -done.returncode
959 test_output('Test %s was killed by signal %d (%s)' % \
960 (done.name, sig, strsignal(sig)))
961 elif done.returncode != 1:
962 test_output('Test %s exited with %d' % \
963 (done.name, done.returncode))
964 else:
965 test_output('Test %s failed' % done.name)
966 user_output('Aborting because of test failure for %s.' % \
967 platform)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700968 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700969 finally:
970 if running:
971 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700972# Stop all of them before killing processes because otherwise stopping some of
973# them tends to let other ones that are waiting to start go.
974 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -0700975 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700976 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700977 for thread in running:
978 thread.terminate_process()
979 to_remove = []
980 for thread in running:
981 thread.join(5)
982 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -0700983 to_remove.append(thread)
984 for thread in to_remove:
985 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700986 for thread in running:
987 test_output(
988 'Test %s did not terminate. Killing it.' % thread.name)
989 thread.kill_process()
990 thread.join()
991 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500992
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700993 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
994 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500995
996if __name__ == '__main__':
997 main()