blob: f6b4949023f86fbe922e20473ecefbbc2c691623 [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 Silvermanc3740c32014-05-04 12:42:47 -070015
16class TestThread(threading.Thread):
Brian Silvermane6bada62014-05-04 16:16:54 -070017 """Runs 1 test and keeps track of its current state.
18
19 A TestThread is either waiting to start the test, actually running it, done,
20 running it, or stopped. The first 3 always happen in that order and can
21 change to stopped at any time.
22
23 It will finish (ie join() will return) once the process has exited, at which
24 point accessing process to see the status is OK.
25
26 Attributes:
27 executable: The file path of the executable to run.
28 env: The environment variables to set.
29 done_queue: A queue.Queue to place self on once done running the test.
30 start_semaphore: A threading.Semaphore to wait on before starting.
31 process_lock: A lock around process.
32 process: The currently executing test process or None. Synchronized by
33 process_lock.
34 stopped: True if we're stopped.
35 """
Brian Silvermanc3740c32014-05-04 12:42:47 -070036 def __init__(self, executable, env, done_queue, start_semaphore):
37 super(TestThread, self).__init__(
38 name=os.path.split(executable)[1])
39
40 self.executable = executable
41 self.env = env
42 self.done_queue = done_queue
43 self.start_semaphore = start_semaphore
44
45 self.process_lock = threading.Lock()
46 self.process = None
47 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070048 self.returncode = None
49 self.output = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070050
51 def run(self):
52 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070053 if self.stopped:
54 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070055 test_output('Starting test %s...' % self.name)
Brian Silvermanf2bbe092014-05-13 16:55:03 -070056 self.output, subprocess_output = pty.openpty()
57 try:
58 with self.process_lock:
59 self.process = subprocess.Popen(self.executable,
60 env=self.env,
61 stderr=subprocess.STDOUT,
62 stdout=subprocess_output,
63 stdin=open(os.devnull, 'r'))
64 finally:
65 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -070066 self.process.wait()
67 with self.process_lock:
68 self.returncode = self.process.returncode
69 self.process = None
70 if not self.stopped:
71 self.done_queue.put(self)
72
73 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070074 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070075 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070076 if not self.process:
77 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070078 self.process.terminate()
79 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070080 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -070081 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -070082 if not self.process:
83 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070084 self.process.kill()
Brian Silvermanbf0e1db2014-05-10 22:13:15 -070085 def stop(self):
86 """Changes self to the stopped state."""
87 with self.process_lock:
88 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -050089
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050090def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -070091 """Returns:
92 A relative path to the aos directory.
93 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050094 return os.path.join(os.path.dirname(__file__), '..')
95
96def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -070097 """Retrieves the IP address for a given device."""
98 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -070099 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500100 if not os.access(FILENAME, os.R_OK):
101 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
102 with open(FILENAME, 'w') as f:
103 f.write('10.9.71')
104 with open(FILENAME, 'r') as f:
105 base = f.readline()
106 if device == 'prime':
107 return base + '.179'
108 elif device == 'robot':
109 return base + '.2'
110 else:
111 raise Exception('Unknown device %s to get an IP address for.' % device)
112
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700113def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700114 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700115 print('build.py: ' + message, file=sys.stderr)
116
Brian Silverman452aaec2014-05-05 16:52:18 -0700117# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700118test_output_lock = threading.RLock()
119def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700120 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700121 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700122 print('tests: ' + message, file=sys.stdout)
123
124def call_download_externals(argument):
125 """Calls download_externals.sh for a given set of externals.
126
127 Args:
128 argument: The argument to pass to the shell script to tell it what to
129 download.
130 """
131 subprocess.check_call(
132 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
133 argument),
134 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700135
Brian Silvermana29ebf92014-04-23 13:08:49 -0500136class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700137 """Represents a processor architecture we can build for."""
138
Brian Silvermana29ebf92014-04-23 13:08:49 -0500139 class UnknownPlatform(Exception):
140 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700141 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500142 self.message = message
143
Brian Silvermanb3d50542014-04-23 14:28:55 -0500144 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700145 """Represents a single way to build the code."""
146
Brian Silvermanb3d50542014-04-23 14:28:55 -0500147 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700148 """Returns:
149 The path of the directory build outputs get put in to.
150 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500151 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500152 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500153 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700154 """Returns:
155 The path of the build.ninja file.
156 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500157 return os.path.join(self.outdir(), 'build.ninja')
158
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500159 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700160 """Helper for subclasses to implement deploy.
161
162 Args:
163 dry_run: If True, prints the command instead of actually running it.
164 command: A tuple of command-line arguments.
165 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500166 real_command = (('echo',) + command) if dry_run else command
167 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500168
Brian Silvermane6bada62014-05-04 16:16:54 -0700169 def deploy(self, dry_run):
170 """Downloads the compiled code to the target computer."""
171 raise NotImplementedError('deploy should be overriden')
172 def outname(self):
173 """Returns:
174 The name of the directory the code will be compiled to.
175 """
176 raise NotImplementedError('outname should be overriden')
177 def os(self):
178 """Returns:
179 The name of the operating system this platform is for.
180
181 This will be used as the value of the OS gyp variable.
182 """
183 raise NotImplementedError('os should be overriden')
184 def gyp_platform(self):
185 """Returns:
186 The platform name the .gyp files know.
187
188 This will be used as the value of the PLATFORM gyp variable.
189 """
190 raise NotImplementedError('gyp_platform should be overriden')
191 def architecture(self):
192 """Returns:
193 The processor architecture for this platform.
194
195 This will be used as the value of the ARCHITECTURE gyp variable.
196 """
197 raise NotImplementedError('architecture should be overriden')
198 def compiler(self):
199 """Returns:
200 The compiler used for this platform.
201
202 Everything before the first _ will be used as the value of the
203 COMPILER gyp variable and the whole thing will be used as the value
204 of the FULL_COMPILER gyp variable.
205 """
206 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700207 def sanitizer(self):
208 """Returns:
209 The sanitizer used on this platform.
210
211 This will be used as the value of the SANITIZER gyp variable.
212
213 "none" if there isn't one.
214 """
215 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700216 def debug(self):
217 """Returns:
218 Whether or not this platform compiles with debugging information.
219
220 The DEBUG gyp variable will be set to "yes" or "no" based on this.
221 """
222 raise NotImplementedError('debug should be overriden')
223 def build_env(self):
224 """Returns:
225 A map of environment variables to set while building this platform.
226 """
227 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700228 def priority(self):
229 """Returns:
230 A relative priority for this platform relative to other ones.
231
232 Higher priority platforms will get built, tested, etc first. Generally,
233 platforms which give higher-quality compiler errors etc should come first.
234 """
235 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700236
Brian Silverman9b7a6842014-05-05 16:19:11 -0700237 def check_installed(self, platforms, is_deploy):
238 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700239 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700240 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700241 """Args:
242 string: A user-supplied string saying which platforms to select.
243
244 Returns:
245 A tuple of Platform objects.
246
247 Raises:
248 Processor.UnknownPlatform: Parsing string didn't work out.
249 """
250 raise NotImplementedError('parse_platforms should be overriden')
251 def extra_gyp_flags(self):
252 """Returns:
253 A tuple of extra flags to pass to gyp (if any).
254 """
255 return ()
256 def modify_ninja_file(self, ninja_file):
257 """Modifies a freshly generated ninja file as necessary.
258
259 Args:
260 ninja_file: Path to the file to modify.
261 """
262 pass
263 def download_externals(self, platforms):
264 """Calls download_externals as appropriate to build platforms.
265
266 Args:
267 platforms: A list of platforms to download external libraries for.
268 """
269 raise NotImplementedError('download_externals should be overriden')
270
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700271 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700272 """Helper for subclasses to implement check_installed.
273
274 Args:
275 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700276 all_packages = other_packages
277 # Necessary to build stuff.
278 all_packages += ('ccache', 'make')
279 # Necessary to download stuff to build.
280 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
281 # Necessary to build externals stuff.
282 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700283 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700284 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700285 result = subprocess.check_output(
286 ('dpkg-query', '--show') + all_packages,
287 stdin=open(os.devnull, 'r'),
288 stderr=subprocess.STDOUT)
289 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700290 output = e.output.decode('utf-8').rstrip()
291 not_found = []
292 for line in output.splitlines(True):
293 match = re.match(r'dpkg-query: no packages found matching (.*)',
294 line)
295 if match:
296 not_found.append(match.group(1))
297 user_output('Some packages not installed: %s.' % ', '.join(not_found))
298 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700299 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700300 exit(1)
301
Brian Silvermana29ebf92014-04-23 13:08:49 -0500302class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700303 """A Processor subclass for building cRIO code."""
304
Brian Silvermanb3d50542014-04-23 14:28:55 -0500305 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700306 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500307 super(CRIOProcessor.Platform, self).__init__()
308
Brian Silvermane6bada62014-05-04 16:16:54 -0700309 self.__debug = debug
310 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500311
312 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700313 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500314 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700315 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500316
317 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700318 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500319 def os(self):
320 return 'vxworks'
321 def gyp_platform(self):
322 return 'crio'
323 def architecture(self):
324 return 'ppc'
325 def compiler(self):
326 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700327 def sanitizer(self):
328 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700329 def debug(self):
330 return self.__debug
331 def wind_base(self):
332 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500333
Brian Silvermane48c09a2014-04-30 18:04:58 -0700334 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500335 def deploy(self, dry_run):
336 self.do_deploy(dry_run,
337 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700338 os.path.join(self.outdir(), 'lib',
339 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500340
Brian Silvermana4aff562014-05-02 17:43:50 -0700341 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700342 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700343
Brian Silvermana29ebf92014-04-23 13:08:49 -0500344 def __init__(self):
345 super(CRIOProcessor, self).__init__()
346
347 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700348 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500349 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700350 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500351
Brian Silverman452aaec2014-05-05 16:52:18 -0700352 def parse_platforms(self, platforms_string):
353 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700354 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700355 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700356 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500357 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700358 raise Processor.UnknownPlatform(
359 'Unknown cRIO platform "%s".' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500360
Brian Silvermane6bada62014-05-04 16:16:54 -0700361 def wind_base(self):
362 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500363
Brian Silvermane6bada62014-05-04 16:16:54 -0700364 def extra_gyp_flags(self):
365 return ('-DWIND_BASE=%s' % self.wind_base(),)
366
367 def modify_ninja_file(self, ninja_file):
368 subprocess.check_call(
369 ('sed', '-i',
370 's/nm -gD/nm/g', ninja_file),
371 stdin=open(os.devnull, 'r'))
372
373 def download_externals(self, _):
374 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500375
Brian Silverman9b7a6842014-05-05 16:19:11 -0700376 def check_installed(self, platforms, is_deploy):
377 packages = ('powerpc-wrs-vxworks', 'tcl')
378 if is_deploy:
379 packages += ('ncftp',)
380 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700381
Brian Silvermana29ebf92014-04-23 13:08:49 -0500382class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700383 """A Processor subclass for building prime code."""
384
Brian Silvermanb3d50542014-04-23 14:28:55 -0500385 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700386 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500387 super(PrimeProcessor.Platform, self).__init__()
388
Brian Silvermane6bada62014-05-04 16:16:54 -0700389 self.__architecture = architecture
390 self.__compiler = compiler
391 self.__debug = debug
392 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500393
394 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700395 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
396 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700397 % (self.architecture(), self.compiler(), self.debug(),
398 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500399 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700400 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700401 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500402
403 def os(self):
404 return 'linux'
405 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700406 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
407 def architecture(self):
408 return self.__architecture
409 def compiler(self):
410 return self.__compiler
411 def sanitizer(self):
412 return self.__sanitizer
413 def debug(self):
414 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500415
Brian Silvermana29ebf92014-04-23 13:08:49 -0500416 def outname(self):
417 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500418
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700419 def priority(self):
420 r = 0
421 if self.compiler() == 'gcc':
422 r -= 100
423 elif self.compiler() == 'clang':
424 r += 100
425 if self.sanitizer() != 'none':
426 r -= 50
427 elif self.debug():
428 r -= 10
429 if self.architecture() == 'amd64':
430 r += 5
431 return r
432
Brian Silvermane48c09a2014-04-30 18:04:58 -0700433 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500434 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700435 # Downloads code to the prime in a way that avoids clashing too badly with
436 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500437 SUM = 'md5sum'
438 TARGET_DIR = '/home/driver/robot_code/bin'
439 TEMP_DIR = '/tmp/aos_downloader'
440 TARGET = 'driver@' + get_ip('prime')
441
442 from_dir = os.path.join(self.outdir(), 'outputs')
443 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
444 stdin=open(os.devnull, 'r'),
445 cwd=from_dir)
446 to_download = subprocess.check_output(
447 ('ssh', TARGET,
448 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
449 && echo '{SUMS}' | {SUM} --check --quiet
Brian Silverman452aaec2014-05-05 16:52:18 -0700450 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".
451 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500452 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700453 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500454 return
455 self.do_deploy(
456 dry_run,
457 ('scp', '-o', 'Compression yes') + to_download
458 + (('%s:%s' % (TARGET, TEMP_DIR)),))
459 if not dry_run:
460 subprocess.check_call(
461 ('ssh', TARGET,
462 """mv {TMPDIR}/* {TO_DIR}
463 && echo 'Done moving new executables into place'
464 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
465 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
466
Brian Silvermana4aff562014-05-02 17:43:50 -0700467 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700468 OTHER_SYSROOT = '/opt/clang-3.5/'
469 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700470 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700471 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
472 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
473 if self.sanitizer() == 'address':
474 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700475 r['ASAN_OPTIONS'] = \
476 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700477 elif self.sanitizer() == 'memory':
478 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
479 elif self.sanitizer() == 'thread':
480 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700481
482 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700483 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
484 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700485 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700486 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700487 # clang doesn't like being run directly on the preprocessed files.
488 r['CCACHE_CPP2'] = 'yes'
489 # Without this, ccache slows down because of the generated header files.
490 # The race condition that this opens up isn't a problem because the build
491 # system finishes modifying header files before compiling anything that
492 # uses them.
493 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700494
Brian Silvermane6bada62014-05-04 16:16:54 -0700495 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700496 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
497 ':' + os.environ['PATH']
498
Brian Silvermana4aff562014-05-02 17:43:50 -0700499 return r
500
Brian Silverman47cd6f62014-05-03 10:35:52 -0700501 ARCHITECTURES = ('arm', 'amd64')
502 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
503 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
504 SANITIZER_TEST_WARNINGS = {
505 'memory': (True,
506"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700507 errors with msan (especially stdlibc++).
508 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700509 'undefined': (False,
510"""There are several warnings in other people's code that ubsan catches.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700511 The following have been verified non-interesting:
Brian Silverman452aaec2014-05-05 16:52:18 -0700512 include/c++/4.8.2/array:*: runtime error: reference binding to null pointer
513 of type 'int'
514 This happens with ::std::array<T, 0> and it doesn't seem to cause any
515 issues.
516 output/downloaded/eigen-3.2.1/Eigen/src/Core/util/Memory.h:782:*: runtime
517 error: load of misaligned address 0x* for type 'const int', which
518 requires 4 byte alignment
Brian Silverman47cd6f62014-05-03 10:35:52 -0700519 That's in the CPUID detection code which only runs on x86."""),
520 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700521 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500522
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500523 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700524 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500525
526 platforms = []
527 for architecture in PrimeProcessor.ARCHITECTURES:
528 for compiler in PrimeProcessor.COMPILERS:
529 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700530 if architecture == 'arm' and compiler == 'gcc_4.8':
531 # We don't have a compiler to use here.
532 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500533 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700534 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
535 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700536 for compiler in ('gcc_4.8', 'clang'):
537 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
538 sanitizer == 'integer' or
539 sanitizer == 'memory'):
540 # GCC 4.8 doesn't support these sanitizers.
541 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700542 if sanitizer == 'none':
543 # We already added sanitizer == 'none' above.
544 continue
545 platforms.append(
546 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
547 self.__platforms = frozenset(platforms)
548
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500549 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700550 default_platforms = self.select_platforms(architecture='amd64',
551 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700552 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
553 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700554 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500555 elif is_deploy:
556 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermane6bada62014-05-04 16:16:54 -0700557 default_platforms = self.select_platforms(architecture='arm',
558 compiler='gcc',
559 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500560 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700561 default_platforms = self.select_platforms(debug=False)
562 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500563
Brian Silvermane6bada62014-05-04 16:16:54 -0700564 def platforms(self):
565 return self.__platforms
566 def default_platforms(self):
567 return self.__default_platforms
568
569 def download_externals(self, platforms):
570 to_download = set()
571 for architecture in PrimeProcessor.ARCHITECTURES:
572 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
573 if platforms & self.select_platforms(architecture=architecture,
574 sanitizer=sanitizer):
575 to_download.add(architecture + '-fPIE')
576 if platforms & self.select_platforms(architecture=architecture,
577 sanitizer='none'):
578 to_download.add(architecture)
579 for download_target in to_download:
580 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500581
Brian Silverman452aaec2014-05-05 16:52:18 -0700582 def parse_platforms(self, platform_string):
583 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700584 return self.default_platforms()
585 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700586 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700587 if part == 'all':
588 r = self.platforms()
589 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500590 r = r | self.select_platforms_string(part[1:])
591 elif part[0] == '-':
592 r = r - self.select_platforms_string(part[1:])
593 elif part[0] == '=':
594 r = self.select_platforms_string(part[1:])
595 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500596 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700597 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500598 if not r:
599 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500600 return r
601
Brian Silverman452aaec2014-05-05 16:52:18 -0700602 def select_platforms(self, architecture=None, compiler=None, debug=None,
603 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500604 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700605 for platform in self.platforms():
606 if architecture is None or platform.architecture() == architecture:
607 if compiler is None or platform.compiler() == compiler:
608 if debug is None or platform.debug() == debug:
609 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700610 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500611 return set(r)
612
Brian Silverman452aaec2014-05-05 16:52:18 -0700613 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700614 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700615 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500616 if part in PrimeProcessor.ARCHITECTURES:
617 architecture = part
618 elif part in PrimeProcessor.COMPILERS:
619 compiler = part
620 elif part in ['debug', 'dbg']:
621 debug = True
622 elif part in ['release', 'nodebug', 'ndb']:
623 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700624 elif part in PrimeProcessor.SANITIZERS:
625 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500626 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700627 raise Processor.UnknownPlatform(
628 'Unknown platform string component "%s".' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500629 return self.select_platforms(
630 architecture=architecture,
631 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700632 debug=debug,
633 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500634
Brian Silverman9b7a6842014-05-05 16:19:11 -0700635 def check_installed(self, platforms, is_deploy):
636 packages = set(('lzip', 'm4', 'realpath'))
637 packages.add('ruby')
638 # clang-format from here gets used for all versions.
639 packages.add('clang-3.5')
640 packages.add('arm-eabi-gcc')
641 for platform in platforms:
642 if platform.architecture() == 'arm':
643 packages.add('gcc-4.7-arm-linux-gnueabihf')
644 packages.add('g++-4.7-arm-linux-gnueabihf')
645 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
646 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700647 if platform.compiler() == 'gcc_4.8':
648 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700649 if is_deploy:
650 packages.add('openssh-client')
651 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
652 packages.add('gcc-4.7')
653 packages.add('g++-4.7')
654
655 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700656
Brian Silvermana29ebf92014-04-23 13:08:49 -0500657def main():
658 class TryParsingAgain(Exception):
659 pass
660
661 class TryAgainArgumentParser(argparse.ArgumentParser):
662 def __init__(self, **kwargs):
663 super(TryAgainArgumentParser, self).__init__(**kwargs)
664
665 def error(self, message):
666 raise TryParsingAgain
667
Brian Silvermane6bada62014-05-04 16:16:54 -0700668 def set_up_parser(parser, args):
669 def add_build_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500670 parser.add_argument(
671 'target',
672 help='target to build',
673 nargs='*')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700674 parser.add_argument(
675 '--jobs', '-j',
676 help='number of things to do at once',
677 type=int)
Brian Silvermane6bada62014-05-04 16:16:54 -0700678 def add_common_args(parser):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500679 parser.add_argument(
680 'platforms',
681 help='platform(s) to act on',
682 nargs='?')
683
684 parser.add_argument('--processor', required=True, help='prime or crio')
685 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
686 subparsers = parser.add_subparsers(dest='action_name')
687
688 build_parser = subparsers.add_parser(
689 'build',
690 help='build the code (default)')
Brian Silvermane6bada62014-05-04 16:16:54 -0700691 add_common_args(build_parser)
692 add_build_args(build_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500693
694 clean_parser = subparsers.add_parser(
695 'clean',
696 help='remove all output directories')
Brian Silvermane6bada62014-05-04 16:16:54 -0700697 add_common_args(clean_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500698
699 deploy_parser = subparsers.add_parser(
700 'deploy',
701 help='build and download the code')
Brian Silvermane6bada62014-05-04 16:16:54 -0700702 add_common_args(deploy_parser)
703 add_build_args(deploy_parser)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500704 deploy_parser.add_argument(
Brian Silverman452aaec2014-05-05 16:52:18 -0700705 '-n', '--dry-run',
706 help="don't actually download anything",
707 action='store_true')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500708
Brian Silvermane48c09a2014-04-30 18:04:58 -0700709 tests_parser = subparsers.add_parser(
710 'tests',
711 help='run tests')
Brian Silvermane6bada62014-05-04 16:16:54 -0700712 add_common_args(tests_parser)
713 add_build_args(tests_parser)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700714
Brian Silvermana29ebf92014-04-23 13:08:49 -0500715 return parser.parse_args(args)
716
717 try:
718 parser = TryAgainArgumentParser()
Brian Silvermane6bada62014-05-04 16:16:54 -0700719 args = set_up_parser(parser, sys.argv[1:])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500720 except TryParsingAgain:
721 parser = argparse.ArgumentParser()
722 REQUIRED_ARGS_END = 5
Brian Silvermane6bada62014-05-04 16:16:54 -0700723 args = set_up_parser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
724 sys.argv[(REQUIRED_ARGS_END):])
Brian Silvermana29ebf92014-04-23 13:08:49 -0500725
726 if args.processor == 'crio':
727 processor = CRIOProcessor()
728 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500729 processor = PrimeProcessor(args.action_name == 'tests',
730 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500731 else:
732 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
733
734 if 'target' in args:
735 targets = args.target[:]
736 else:
737 targets = []
738 unknown_platform_error = None
739 try:
740 platforms = processor.parse_platforms(args.platforms)
741 except Processor.UnknownPlatform as e:
742 unknown_platform_error = e.message
743 targets.append(args.platforms)
744 platforms = processor.parse_platforms(None)
745 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700746 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500747 exit(1)
748
Brian Silverman9b7a6842014-05-05 16:19:11 -0700749 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700750 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500751
752 class ToolsConfig(object):
753 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500754 self.variables = {'AOS': aos_path()}
755 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500756 for line in f:
757 if line[0] == '#':
758 pass
759 elif line.isspace():
760 pass
761 else:
762 new_name, new_value = line.rstrip().split('=')
763 for name, value in self.variables.items():
764 new_value = new_value.replace('${%s}' % name, value)
765 self.variables[new_name] = new_value
766 def __getitem__(self, key):
767 return self.variables[key]
768
769 tools_config = ToolsConfig()
770
771 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700772 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500773 if issubclass(OSError, excinfo[0]):
774 if excinfo[1].errno == errno.ENOENT:
775 # Who cares if the file we're deleting isn't there?
776 return
777 raise excinfo[1]
778
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700779 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700780 """Determines if we need to run gyp again or not.
781
782 The generated build files are supposed to re-run gyp again themselves, but
783 that doesn't work (or at least it used to not) and we sometimes want to
784 modify the results anyways.
785
786 Args:
787 platform: The platform to check for.
788 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700789 if not os.path.exists(platform.build_ninja()):
790 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700791 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
792 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700793 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700794 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700795 if dirs.count('output'):
796 dirs.remove('output')
797 if dirs.count('.git'):
798 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700799 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700800 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700801 + ('-newer', platform.build_ninja(),
802 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700803 stdin=open(os.devnull, 'r'))
804
805 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700806 """Makes sure we pass through important environmental variables.
807
808 Returns:
809 An environment suitable for passing to subprocess.Popen and friends.
810 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700811 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700812 if not 'TERM' in build_env:
813 build_env['TERM'] = os.environ['TERM']
814 if not 'PATH' in build_env:
815 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700816 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700817
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700818 sorted_platforms = sorted(platforms,
819 key=lambda platform: -platform.priority())
820
Brian Silverman47cd6f62014-05-03 10:35:52 -0700821 to_build = []
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700822 for platform in sorted_platforms:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700823 to_build.append(str(platform))
824 if len(to_build) > 1:
825 to_build[-1] = 'and ' + to_build[-1]
826 user_output('Building %s...' % ', '.join(to_build))
827
828 if args.action_name == 'tests':
829 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
830 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
831 if warned_about:
832 user_output(warning[1])
833 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700834 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700835 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
836 exit(1)
837
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700838 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700839 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700840 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500841 if args.action_name == 'clean':
842 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
843 else:
844 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700845 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500846 gyp = subprocess.Popen(
847 (tools_config['GYP'],
848 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500849 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500850 '--no-circular-check',
851 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500852 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500853 '-I/dev/stdin', '-Goutput_dir=output',
854 '-DOS=%s' % platform.os(),
855 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700856 '-DARCHITECTURE=%s' % platform.architecture(),
857 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
858 '-DFULL_COMPILER=%s' % platform.compiler(),
859 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
860 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700861 '-DSANITIZER_FPIE=%s' %
862 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
863 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500864 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500865 stdin=subprocess.PIPE)
866 gyp.communicate(("""
867{
868 'target_defaults': {
869 'configurations': {
870 '%s': {}
871 }
872 }
873}""" % platform.outname()).encode())
874 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700875 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500876 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700877 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700878 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500879 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700880 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500881
882 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700883 call = (tools_config['NINJA'],
884 '-C', platform.outdir()) + tuple(targets)
885 if args.jobs:
886 call += ('-j', str(args.jobs))
887 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700888 stdin=open(os.devnull, 'r'),
889 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500890 except subprocess.CalledProcessError as e:
891 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700892 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500893 raise e
894
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500895 if args.action_name == 'deploy':
896 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700897 elif args.action_name == 'tests':
898 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700899 done_queue = queue.Queue()
900 running = []
901 if args.jobs:
902 number_jobs = args.jobs
903 else:
904 number_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
905 test_start_semaphore = threading.Semaphore(number_jobs)
906 if targets:
907 to_run = []
908 for target in targets:
909 if target.endswith('_test'):
910 to_run.append(target)
911 else:
912 to_run = os.listdir(dirname)
913 for f in to_run:
914 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
915 test_start_semaphore)
916 running.append(thread)
917 thread.start()
918 try:
919 while running:
920 done = done_queue.get()
921 running.remove(done)
922 with test_output_lock:
923 test_output('Output from test %s:' % done.name)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700924 with os.fdopen(done.output) as output_file:
925 try:
926 for line in output_file:
927 if not sys.stdout.isatty():
928 # Remove color escape codes.
929 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
930 sys.stdout.write(line)
931 except IOError as e:
932# We want to just ignore EIOs from reading the master pty because that just
933# means we hit the end.
934 if e.errno != errno.EIO:
935 raise e
936 if not done.returncode:
937 test_output('Test %s succeeded' % done.name)
938 else:
939 test_output('Test %s failed' % done.name)
940 user_output('Aborting because of test failure.')
941 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700942 finally:
943 if running:
944 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700945# Stop all of them before killing processes because otherwise stopping some of
946# them tends to let other ones that are waiting to start go.
947 for thread in running:
948 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700949 for thread in running:
950 thread.terminate_process()
951 to_remove = []
952 for thread in running:
953 thread.join(5)
954 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -0700955 to_remove.append(thread)
956 for thread in to_remove:
957 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700958 for thread in running:
959 test_output(
960 'Test %s did not terminate. Killing it.' % thread.name)
961 thread.kill_process()
962 thread.join()
963 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500964
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700965 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
966 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500967
968if __name__ == '__main__':
969 main()