blob: 14f890a8ae9b227da7f954089b7378b673d33eb2 [file] [log] [blame]
Brian Silvermana29ebf92014-04-23 13:08:49 -05001#!/usr/bin/python3
2
Brian Silvermana29ebf92014-04-23 13:08:49 -05003import sys
4import subprocess
5import re
6import os
7import os.path
8import string
9import shutil
10import errno
Brian Silvermanc3740c32014-05-04 12:42:47 -070011import queue
12import threading
Brian Silvermanf2bbe092014-05-13 16:55:03 -070013import pty
Brian Silverman6bca4722014-05-20 17:02:49 -070014import signal
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.
Brian Silverman730bb012014-06-08 13:05:20 -070035 output: A queue of lines of output from the test.
Brian Silvermane6bada62014-05-04 16:16:54 -070036 """
Brian Silverman730bb012014-06-08 13:05:20 -070037
38 class OutputCopier(threading.Thread):
39 """Copies the output of a test from its output pty into a queue.
40
41 This is necessary because otherwise everything locks up if the test writes
42 too much output and fills up the pty's buffer.
43 """
44
45 def __init__(self, name, fd, queue):
46 super(TestThread.OutputCopier, self).__init__(
47 name=(name + '.OutputCopier'))
48
49 self.fd = fd
50 self.queue = queue
51
52 def run(self):
53 with os.fdopen(self.fd) as to_read:
54 try:
55 for line in to_read:
56 self.queue.put(line)
57 except IOError as e:
58# An EIO from the master side of the pty means we hit the end.
59 if e.errno == errno.EIO:
60 return
61 else:
62 raise e
63
Brian Silvermanc3740c32014-05-04 12:42:47 -070064 def __init__(self, executable, env, done_queue, start_semaphore):
65 super(TestThread, self).__init__(
66 name=os.path.split(executable)[1])
67
68 self.executable = executable
69 self.env = env
70 self.done_queue = done_queue
71 self.start_semaphore = start_semaphore
72
Brian Silverman730bb012014-06-08 13:05:20 -070073 self.output = queue.Queue()
74
Brian Silvermanc3740c32014-05-04 12:42:47 -070075 self.process_lock = threading.Lock()
76 self.process = None
77 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070078 self.returncode = None
Brian Silverman730bb012014-06-08 13:05:20 -070079 self.output_copier = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070080
81 def run(self):
82 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070083 if self.stopped:
84 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070085 test_output('Starting test %s...' % self.name)
Brian Silverman730bb012014-06-08 13:05:20 -070086 output_to_read, subprocess_output = pty.openpty()
87 self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
88 self.output)
89 self.output_copier.start()
Brian Silvermanf2bbe092014-05-13 16:55:03 -070090 try:
91 with self.process_lock:
92 self.process = subprocess.Popen(self.executable,
93 env=self.env,
94 stderr=subprocess.STDOUT,
95 stdout=subprocess_output,
96 stdin=open(os.devnull, 'r'))
97 finally:
98 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -070099 self.process.wait()
100 with self.process_lock:
101 self.returncode = self.process.returncode
102 self.process = None
103 if not self.stopped:
Brian Silverman730bb012014-06-08 13:05:20 -0700104 self.output_copier.join()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700105 self.done_queue.put(self)
106
107 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700108 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700109 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700110 if not self.process:
111 return
Brian Silvermana5826582014-06-03 19:46:35 -0700112 try:
113 self.process.terminate()
114 except OSError as e:
115 if e.errno == errno.ESRCH:
116 # We don't really care if it's already gone.
117 pass
118 else:
119 raise e
Brian Silvermanc3740c32014-05-04 12:42:47 -0700120 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700121 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700122 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700123 if not self.process:
124 return
Brian Silverman6bca4722014-05-20 17:02:49 -0700125 try:
126 self.process.kill()
127 except OSError as e:
128 if e.errno == errno.ESRCH:
129 # We don't really care if it's already gone.
130 pass
131 else:
132 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700133 def stop(self):
134 """Changes self to the stopped state."""
135 with self.process_lock:
136 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500137
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500138def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700139 """Returns:
140 A relative path to the aos directory.
141 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500142 return os.path.join(os.path.dirname(__file__), '..')
143
144def get_ip(device):
Brian Silvermane6bada62014-05-04 16:16:54 -0700145 """Retrieves the IP address for a given device."""
146 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700147 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500148 if not os.access(FILENAME, os.R_OK):
149 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
150 with open(FILENAME, 'w') as f:
151 f.write('10.9.71')
152 with open(FILENAME, 'r') as f:
153 base = f.readline()
154 if device == 'prime':
155 return base + '.179'
156 elif device == 'robot':
157 return base + '.2'
158 else:
159 raise Exception('Unknown device %s to get an IP address for.' % device)
160
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700161def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700162 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700163 print('build.py: ' + message, file=sys.stderr)
164
Brian Silverman452aaec2014-05-05 16:52:18 -0700165# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700166test_output_lock = threading.RLock()
167def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700168 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700169 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700170 print('tests: ' + message, file=sys.stdout)
171
172def call_download_externals(argument):
173 """Calls download_externals.sh for a given set of externals.
174
175 Args:
176 argument: The argument to pass to the shell script to tell it what to
177 download.
178 """
179 subprocess.check_call(
180 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
181 argument),
182 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700183
Brian Silvermana29ebf92014-04-23 13:08:49 -0500184class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700185 """Represents a processor architecture we can build for."""
186
Brian Silvermana29ebf92014-04-23 13:08:49 -0500187 class UnknownPlatform(Exception):
188 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700189 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500190 self.message = message
191
Brian Silvermanb3d50542014-04-23 14:28:55 -0500192 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700193 """Represents a single way to build the code."""
194
Brian Silvermanb3d50542014-04-23 14:28:55 -0500195 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700196 """Returns:
197 The path of the directory build outputs get put in to.
198 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500199 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500200 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500201 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700202 """Returns:
203 The path of the build.ninja file.
204 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500205 return os.path.join(self.outdir(), 'build.ninja')
206
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500207 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700208 """Helper for subclasses to implement deploy.
209
210 Args:
211 dry_run: If True, prints the command instead of actually running it.
212 command: A tuple of command-line arguments.
213 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500214 real_command = (('echo',) + command) if dry_run else command
215 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500216
Brian Silvermane6bada62014-05-04 16:16:54 -0700217 def deploy(self, dry_run):
218 """Downloads the compiled code to the target computer."""
219 raise NotImplementedError('deploy should be overriden')
220 def outname(self):
221 """Returns:
222 The name of the directory the code will be compiled to.
223 """
224 raise NotImplementedError('outname should be overriden')
225 def os(self):
226 """Returns:
227 The name of the operating system this platform is for.
228
229 This will be used as the value of the OS gyp variable.
230 """
231 raise NotImplementedError('os should be overriden')
232 def gyp_platform(self):
233 """Returns:
234 The platform name the .gyp files know.
235
236 This will be used as the value of the PLATFORM gyp variable.
237 """
238 raise NotImplementedError('gyp_platform should be overriden')
239 def architecture(self):
240 """Returns:
241 The processor architecture for this platform.
242
243 This will be used as the value of the ARCHITECTURE gyp variable.
244 """
245 raise NotImplementedError('architecture should be overriden')
246 def compiler(self):
247 """Returns:
248 The compiler used for this platform.
249
250 Everything before the first _ will be used as the value of the
251 COMPILER gyp variable and the whole thing will be used as the value
252 of the FULL_COMPILER gyp variable.
253 """
254 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700255 def sanitizer(self):
256 """Returns:
257 The sanitizer used on this platform.
258
259 This will be used as the value of the SANITIZER gyp variable.
260
261 "none" if there isn't one.
262 """
263 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700264 def debug(self):
265 """Returns:
266 Whether or not this platform compiles with debugging information.
267
268 The DEBUG gyp variable will be set to "yes" or "no" based on this.
269 """
270 raise NotImplementedError('debug should be overriden')
271 def build_env(self):
272 """Returns:
273 A map of environment variables to set while building this platform.
274 """
275 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700276 def priority(self):
277 """Returns:
278 A relative priority for this platform relative to other ones.
279
280 Higher priority platforms will get built, tested, etc first. Generally,
281 platforms which give higher-quality compiler errors etc should come first.
282 """
283 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700284
Brian Silverman9b7a6842014-05-05 16:19:11 -0700285 def check_installed(self, platforms, is_deploy):
286 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700287 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700288 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700289 """Args:
290 string: A user-supplied string saying which platforms to select.
291
292 Returns:
293 A tuple of Platform objects.
294
295 Raises:
296 Processor.UnknownPlatform: Parsing string didn't work out.
297 """
298 raise NotImplementedError('parse_platforms should be overriden')
299 def extra_gyp_flags(self):
300 """Returns:
301 A tuple of extra flags to pass to gyp (if any).
302 """
303 return ()
304 def modify_ninja_file(self, ninja_file):
305 """Modifies a freshly generated ninja file as necessary.
306
307 Args:
308 ninja_file: Path to the file to modify.
309 """
310 pass
311 def download_externals(self, platforms):
312 """Calls download_externals as appropriate to build platforms.
313
314 Args:
315 platforms: A list of platforms to download external libraries for.
316 """
317 raise NotImplementedError('download_externals should be overriden')
318
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700319 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700320 """Helper for subclasses to implement check_installed.
321
322 Args:
323 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700324 all_packages = other_packages
325 # Necessary to build stuff.
326 all_packages += ('ccache', 'make')
327 # Necessary to download stuff to build.
328 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
329 # Necessary to build externals stuff.
330 all_packages += ('python', 'gcc', 'g++')
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700331 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700332 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700333 result = subprocess.check_output(
334 ('dpkg-query', '--show') + all_packages,
335 stdin=open(os.devnull, 'r'),
336 stderr=subprocess.STDOUT)
337 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700338 output = e.output.decode('utf-8').rstrip()
339 not_found = []
340 for line in output.splitlines(True):
341 match = re.match(r'dpkg-query: no packages found matching (.*)',
342 line)
343 if match:
344 not_found.append(match.group(1))
345 user_output('Some packages not installed: %s.' % ', '.join(not_found))
346 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700347 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700348 exit(1)
349
Brian Silvermana29ebf92014-04-23 13:08:49 -0500350class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700351 """A Processor subclass for building cRIO code."""
352
Brian Silvermanb3d50542014-04-23 14:28:55 -0500353 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700354 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500355 super(CRIOProcessor.Platform, self).__init__()
356
Brian Silvermane6bada62014-05-04 16:16:54 -0700357 self.__debug = debug
358 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500359
360 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700361 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500362 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700363 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500364
365 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700366 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500367 def os(self):
368 return 'vxworks'
369 def gyp_platform(self):
370 return 'crio'
371 def architecture(self):
372 return 'ppc'
373 def compiler(self):
374 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700375 def sanitizer(self):
376 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700377 def debug(self):
378 return self.__debug
379 def wind_base(self):
380 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500381
Brian Silvermane48c09a2014-04-30 18:04:58 -0700382 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500383 def deploy(self, dry_run):
384 self.do_deploy(dry_run,
385 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700386 os.path.join(self.outdir(), 'lib',
387 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500388
Brian Silvermana4aff562014-05-02 17:43:50 -0700389 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700390 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700391
Brian Silvermana29ebf92014-04-23 13:08:49 -0500392 def __init__(self):
393 super(CRIOProcessor, self).__init__()
394
395 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700396 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500397 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700398 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500399
Brian Silverman452aaec2014-05-05 16:52:18 -0700400 def parse_platforms(self, platforms_string):
401 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700402 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700403 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700404 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500405 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700406 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700407 '"%s" not recognized as a cRIO platform.' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500408
Brian Silvermane6bada62014-05-04 16:16:54 -0700409 def wind_base(self):
410 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500411
Brian Silvermane6bada62014-05-04 16:16:54 -0700412 def extra_gyp_flags(self):
413 return ('-DWIND_BASE=%s' % self.wind_base(),)
414
415 def modify_ninja_file(self, ninja_file):
416 subprocess.check_call(
417 ('sed', '-i',
418 's/nm -gD/nm/g', ninja_file),
419 stdin=open(os.devnull, 'r'))
420
421 def download_externals(self, _):
422 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500423
Brian Silverman9b7a6842014-05-05 16:19:11 -0700424 def check_installed(self, platforms, is_deploy):
425 packages = ('powerpc-wrs-vxworks', 'tcl')
426 if is_deploy:
427 packages += ('ncftp',)
428 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700429
Brian Silvermana29ebf92014-04-23 13:08:49 -0500430class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700431 """A Processor subclass for building prime code."""
432
Brian Silvermanb3d50542014-04-23 14:28:55 -0500433 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700434 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500435 super(PrimeProcessor.Platform, self).__init__()
436
Brian Silvermane6bada62014-05-04 16:16:54 -0700437 self.__architecture = architecture
438 self.__compiler = compiler
439 self.__debug = debug
440 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500441
442 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700443 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
444 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700445 % (self.architecture(), self.compiler(), self.debug(),
446 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500447 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700448 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700449 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500450
451 def os(self):
452 return 'linux'
453 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700454 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
455 def architecture(self):
456 return self.__architecture
457 def compiler(self):
458 return self.__compiler
459 def sanitizer(self):
460 return self.__sanitizer
461 def debug(self):
462 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500463
Brian Silvermana29ebf92014-04-23 13:08:49 -0500464 def outname(self):
465 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500466
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700467 def priority(self):
468 r = 0
469 if self.compiler() == 'gcc':
470 r -= 100
471 elif self.compiler() == 'clang':
472 r += 100
473 if self.sanitizer() != 'none':
474 r -= 50
475 elif self.debug():
476 r -= 10
477 if self.architecture() == 'amd64':
478 r += 5
479 return r
480
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500481 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700482 # Downloads code to the prime in a way that avoids clashing too badly with
483 # starter (like the naive download everything one at a time).
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500484 SUM = 'md5sum'
485 TARGET_DIR = '/home/driver/robot_code/bin'
486 TEMP_DIR = '/tmp/aos_downloader'
487 TARGET = 'driver@' + get_ip('prime')
488
489 from_dir = os.path.join(self.outdir(), 'outputs')
490 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
491 stdin=open(os.devnull, 'r'),
492 cwd=from_dir)
493 to_download = subprocess.check_output(
494 ('ssh', TARGET,
Brian Silvermanff485782014-06-18 19:59:09 -0700495 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR} \\
496 && echo '{SUMS}' | {SUM} --check --quiet \\
497 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*$/\\1/g'""".
498 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums.decode('utf-8'),
499 SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500500 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700501 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500502 return
503 self.do_deploy(
504 dry_run,
Brian Silvermanff485782014-06-18 19:59:09 -0700505 ('scp', '-o', 'Compression yes')
506 + tuple([os.path.join(from_dir, f) for f in to_download.decode('utf-8').split('\n')[:-1]])
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500507 + (('%s:%s' % (TARGET, TEMP_DIR)),))
508 if not dry_run:
509 subprocess.check_call(
510 ('ssh', TARGET,
Brian Silvermanff485782014-06-18 19:59:09 -0700511 """mv {TMPDIR}/* {TO_DIR} \\
512 && echo 'Done moving new executables into place' \\
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500513 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
514 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
515
Brian Silvermana4aff562014-05-02 17:43:50 -0700516 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700517 OTHER_SYSROOT = '/opt/clang-3.5/'
518 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700519 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700520 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
521 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
522 if self.sanitizer() == 'address':
523 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700524 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700525 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
526 ':detect_stack_use_after_return=1:detect_odr_violation=2'
Brian Silvermane6bada62014-05-04 16:16:54 -0700527 elif self.sanitizer() == 'memory':
528 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
529 elif self.sanitizer() == 'thread':
530 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700531
532 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700533 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
534 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700535 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700536 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700537 # clang doesn't like being run directly on the preprocessed files.
538 r['CCACHE_CPP2'] = 'yes'
539 # Without this, ccache slows down because of the generated header files.
540 # The race condition that this opens up isn't a problem because the build
541 # system finishes modifying header files before compiling anything that
542 # uses them.
543 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700544
Brian Silvermane6bada62014-05-04 16:16:54 -0700545 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700546 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
547 ':' + os.environ['PATH']
548
Brian Silvermana4aff562014-05-02 17:43:50 -0700549 return r
550
Brian Silverman47cd6f62014-05-03 10:35:52 -0700551 ARCHITECTURES = ('arm', 'amd64')
552 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
553 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
554 SANITIZER_TEST_WARNINGS = {
555 'memory': (True,
556"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700557 errors with msan (especially stdlibc++).
558 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700559 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700560 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500561
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500562 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700563 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500564
565 platforms = []
566 for architecture in PrimeProcessor.ARCHITECTURES:
567 for compiler in PrimeProcessor.COMPILERS:
568 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700569 if architecture == 'arm' and compiler == 'gcc_4.8':
570 # We don't have a compiler to use here.
571 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500572 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700573 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
574 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700575 for compiler in ('gcc_4.8', 'clang'):
576 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
577 sanitizer == 'integer' or
578 sanitizer == 'memory'):
579 # GCC 4.8 doesn't support these sanitizers.
580 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700581 if sanitizer == 'none':
582 # We already added sanitizer == 'none' above.
583 continue
584 platforms.append(
585 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
586 self.__platforms = frozenset(platforms)
587
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500588 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700589 default_platforms = self.select_platforms(architecture='amd64',
590 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700591 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
592 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700593 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500594 elif is_deploy:
Brian Silvermane6bada62014-05-04 16:16:54 -0700595 default_platforms = self.select_platforms(architecture='arm',
Brian Silvermanff485782014-06-18 19:59:09 -0700596 compiler='clang',
Brian Silvermane6bada62014-05-04 16:16:54 -0700597 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500598 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700599 default_platforms = self.select_platforms(debug=False)
600 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500601
Brian Silvermane6bada62014-05-04 16:16:54 -0700602 def platforms(self):
603 return self.__platforms
604 def default_platforms(self):
605 return self.__default_platforms
606
607 def download_externals(self, platforms):
608 to_download = set()
609 for architecture in PrimeProcessor.ARCHITECTURES:
610 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
611 if platforms & self.select_platforms(architecture=architecture,
612 sanitizer=sanitizer):
613 to_download.add(architecture + '-fPIE')
614 if platforms & self.select_platforms(architecture=architecture,
615 sanitizer='none'):
616 to_download.add(architecture)
617 for download_target in to_download:
618 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500619
Brian Silverman452aaec2014-05-05 16:52:18 -0700620 def parse_platforms(self, platform_string):
621 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700622 return self.default_platforms()
623 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700624 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700625 if part == 'all':
626 r = self.platforms()
627 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500628 r = r | self.select_platforms_string(part[1:])
629 elif part[0] == '-':
630 r = r - self.select_platforms_string(part[1:])
631 elif part[0] == '=':
632 r = self.select_platforms_string(part[1:])
633 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500634 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700635 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500636 if not r:
637 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500638 return r
639
Brian Silverman452aaec2014-05-05 16:52:18 -0700640 def select_platforms(self, architecture=None, compiler=None, debug=None,
641 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500642 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700643 for platform in self.platforms():
644 if architecture is None or platform.architecture() == architecture:
645 if compiler is None or platform.compiler() == compiler:
646 if debug is None or platform.debug() == debug:
647 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700648 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500649 return set(r)
650
Brian Silverman452aaec2014-05-05 16:52:18 -0700651 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700652 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700653 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500654 if part in PrimeProcessor.ARCHITECTURES:
655 architecture = part
656 elif part in PrimeProcessor.COMPILERS:
657 compiler = part
658 elif part in ['debug', 'dbg']:
659 debug = True
660 elif part in ['release', 'nodebug', 'ndb']:
661 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700662 elif part in PrimeProcessor.SANITIZERS:
663 sanitizer = part
Brian Silvermana29ebf92014-04-23 13:08:49 -0500664 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700665 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700666 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500667 return self.select_platforms(
668 architecture=architecture,
669 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700670 debug=debug,
671 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500672
Brian Silverman9b7a6842014-05-05 16:19:11 -0700673 def check_installed(self, platforms, is_deploy):
674 packages = set(('lzip', 'm4', 'realpath'))
675 packages.add('ruby')
676 # clang-format from here gets used for all versions.
677 packages.add('clang-3.5')
678 packages.add('arm-eabi-gcc')
679 for platform in platforms:
680 if platform.architecture() == 'arm':
681 packages.add('gcc-4.7-arm-linux-gnueabihf')
682 packages.add('g++-4.7-arm-linux-gnueabihf')
683 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
684 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700685 if platform.compiler() == 'gcc_4.8':
686 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700687 if is_deploy:
688 packages.add('openssh-client')
689 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
690 packages.add('gcc-4.7')
691 packages.add('g++-4.7')
692
693 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700694
Brian Silverman6bca4722014-05-20 17:02:49 -0700695def strsignal(num):
696 # It ends up with SIGIOT instead otherwise, which is weird.
697 if num == signal.SIGABRT:
698 return 'SIGABRT'
699 # SIGCLD is a weird way to spell it.
700 if num == signal.SIGCHLD:
701 return 'SIGCHLD'
702
703 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
704 for n in dir(signal) if n.startswith('SIG')
705 and '_' not in n)
706 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
707
Brian Silvermana29ebf92014-04-23 13:08:49 -0500708def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700709 sys.argv.pop(0)
710 exec_name = sys.argv.pop(0)
711 def print_help(exit_status=None, message=None):
712 if message:
713 print(message)
714 sys.stdout.write(
715"""Usage: {name} [-j n] [action] [-n] [platform] [target]...
716Arguments:
717 -j, --jobs Explicitly specify how many jobs to run at a time.
718 Defaults to the number of processors + 2.
719 -n, --dry-run Don't actually do whatever.
720 Currently only meaningful for deploy.
721 action What to do. Defaults to build.
722 build: Build the code.
723 clean: Remove all the built output.
724 tests: Build and then run tests.
725 deploy: Build and then download.
726 platform What variants of the code to build.
727 Defaults to something reasonable.
728 See below for details.
729 target... Which targets to build/test/etc.
730 Defaults to everything.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500731
Brian Silvermandf5348a2014-06-12 23:25:08 -0700732Specifying targets:
733 Targets are combinations of architecture, compiler, and debug flags. Which
734 ones actually get run is built up as a set. It defaults to something
735 reasonable for the action (specified below).
736 The platform specification (the argument given to this script) is a comma-
737 separated sequence of hyphen-separated platforms, each with an optional
738 prefix.
739 Each selector (the things separated by commas) selects all of the platforms
740 which match all of its components. Its effect on the set of current platforms
741 depends on the prefix character.
742 Here are the prefix characters:
743 + Adds the selected platforms.
744 - Removes the selected platforms.
745 = Sets the current set to the selected platforms.
746 [none] Removes all non-selected platforms.
747 If this makes the current set empty, acts like =.
748 There is also the special psuedo-platform "all" which selects all platforms.
749 All of the available platforms:
750 {all_platforms}
751 Default platforms for deploying:
752 {deploy_platforms}
753 Default platforms for testing:
754 {test_platforms}
755 Default platforms for everything else:
756 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500757
Brian Silvermandf5348a2014-06-12 23:25:08 -0700758Examples of specifying targets:
759 build everything: "all"
760 only build things with clang: "clang"
761 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
762 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
763""".format(
764 name=exec_name,
765 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
766 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
767 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
768 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
769 ))
770 if exit_status is not None:
771 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500772
Brian Silvermandf5348a2014-06-12 23:25:08 -0700773 def sort_platforms(platforms):
774 return sorted(
775 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500776
Brian Silvermandf5348a2014-06-12 23:25:08 -0700777 def str_platforms(platforms):
778 r = []
779 for platform in sort_platforms(platforms):
780 r.append(str(platform))
781 if len(r) > 1:
782 r[-1] = 'and ' + r[-1]
783 return ', '.join(r)
784
785 class Arguments(object):
786 def __init__(self):
787 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
788 self.action_name = 'build'
789 self.dry_run = False
790 self.targets = []
791 self.platform = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500792
Brian Silvermandf5348a2014-06-12 23:25:08 -0700793 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500794
Brian Silvermandf5348a2014-06-12 23:25:08 -0700795 if len(sys.argv) < 2:
796 print_help(1, 'Not enough arguments')
797 args.processor = sys.argv.pop(0)
798 args.main_gyp = sys.argv.pop(0)
799 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
800 while sys.argv:
801 arg = sys.argv.pop(0)
802 if arg == '-j' or arg == '--jobs':
803 args.jobs = int(sys.argv.pop(0))
804 continue
805 if arg in VALID_ACTIONS:
806 args.action_name = arg
807 continue
808 if arg == '-n' or arg == '--dry-run':
809 if args.action_name != 'deploy':
810 print_help(1, '--dry-run is only valid for deploy')
811 args.dry_run = True
812 continue
813 if arg == '-h' or arg == '--help':
814 print_help(0)
815 if args.platform:
816 args.targets.append(arg)
817 else:
818 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500819
820 if args.processor == 'crio':
821 processor = CRIOProcessor()
822 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500823 processor = PrimeProcessor(args.action_name == 'tests',
824 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500825 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700826 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500827
Brian Silvermana29ebf92014-04-23 13:08:49 -0500828 unknown_platform_error = None
829 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700830 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500831 except Processor.UnknownPlatform as e:
832 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700833 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500834 platforms = processor.parse_platforms(None)
835 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700836 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500837
Brian Silverman9b7a6842014-05-05 16:19:11 -0700838 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700839 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500840
841 class ToolsConfig(object):
842 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500843 self.variables = {'AOS': aos_path()}
844 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500845 for line in f:
846 if line[0] == '#':
847 pass
848 elif line.isspace():
849 pass
850 else:
851 new_name, new_value = line.rstrip().split('=')
852 for name, value in self.variables.items():
853 new_value = new_value.replace('${%s}' % name, value)
854 self.variables[new_name] = new_value
855 def __getitem__(self, key):
856 return self.variables[key]
857
858 tools_config = ToolsConfig()
859
860 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700861 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500862 if issubclass(OSError, excinfo[0]):
863 if excinfo[1].errno == errno.ENOENT:
864 # Who cares if the file we're deleting isn't there?
865 return
866 raise excinfo[1]
867
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700868 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700869 """Determines if we need to run gyp again or not.
870
871 The generated build files are supposed to re-run gyp again themselves, but
872 that doesn't work (or at least it used to not) and we sometimes want to
873 modify the results anyways.
874
875 Args:
876 platform: The platform to check for.
877 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700878 if not os.path.exists(platform.build_ninja()):
879 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700880 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
881 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700882 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700883 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700884 if dirs.count('output'):
885 dirs.remove('output')
886 if dirs.count('.git'):
887 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700888 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700889 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700890 + ('-newer', platform.build_ninja(),
891 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700892 stdin=open(os.devnull, 'r'))
893
894 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700895 """Makes sure we pass through important environmental variables.
896
897 Returns:
898 An environment suitable for passing to subprocess.Popen and friends.
899 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700900 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700901 if not 'TERM' in build_env:
902 build_env['TERM'] = os.environ['TERM']
903 if not 'PATH' in build_env:
904 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700905 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700906
Brian Silvermandf5348a2014-06-12 23:25:08 -0700907 sorted_platforms = sort_platforms(platforms)
908 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700909
910 if args.action_name == 'tests':
911 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
912 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
913 if warned_about:
914 user_output(warning[1])
915 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700916 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700917 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
918 exit(1)
919
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700920 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700921 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700922 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500923 if args.action_name == 'clean':
924 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
925 else:
926 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700927 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500928 gyp = subprocess.Popen(
929 (tools_config['GYP'],
930 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500931 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500932 '--no-circular-check',
933 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500934 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500935 '-I/dev/stdin', '-Goutput_dir=output',
936 '-DOS=%s' % platform.os(),
937 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700938 '-DARCHITECTURE=%s' % platform.architecture(),
939 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
940 '-DFULL_COMPILER=%s' % platform.compiler(),
941 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
942 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700943 '-DSANITIZER_FPIE=%s' %
944 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
945 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500946 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500947 stdin=subprocess.PIPE)
948 gyp.communicate(("""
949{
950 'target_defaults': {
951 'configurations': {
952 '%s': {}
953 }
954 }
955}""" % platform.outname()).encode())
956 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700957 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500958 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700959 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700960 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500961 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700962 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500963
964 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700965 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -0700966 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700967 if args.jobs:
968 call += ('-j', str(args.jobs))
969 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700970 stdin=open(os.devnull, 'r'),
971 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500972 except subprocess.CalledProcessError as e:
973 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700974 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500975 raise e
976
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500977 if args.action_name == 'deploy':
978 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700979 elif args.action_name == 'tests':
980 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700981 done_queue = queue.Queue()
982 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700983 test_start_semaphore = threading.Semaphore(args.jobs)
984 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700985 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700986 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700987 if target.endswith('_test'):
988 to_run.append(target)
989 else:
990 to_run = os.listdir(dirname)
991 for f in to_run:
992 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
993 test_start_semaphore)
994 running.append(thread)
995 thread.start()
996 try:
997 while running:
998 done = done_queue.get()
999 running.remove(done)
1000 with test_output_lock:
1001 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -07001002 try:
1003 while True:
1004 line = done.output.get(False)
1005 if not sys.stdout.isatty():
1006 # Remove color escape codes.
1007 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1008 sys.stdout.write(line)
1009 except queue.Empty:
1010 pass
1011 if not done.returncode:
1012 test_output('Test %s succeeded' % done.name)
1013 else:
1014 if done.returncode < 0:
1015 sig = -done.returncode
1016 test_output('Test %s was killed by signal %d (%s)' % \
1017 (done.name, sig, strsignal(sig)))
1018 elif done.returncode != 1:
1019 test_output('Test %s exited with %d' % \
1020 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001021 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001022 test_output('Test %s failed' % done.name)
1023 user_output('Aborting because of test failure for %s.' % \
1024 platform)
1025 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001026 finally:
1027 if running:
1028 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001029# Stop all of them before killing processes because otherwise stopping some of
1030# them tends to let other ones that are waiting to start go.
1031 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -07001032 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001033 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001034 for thread in running:
1035 thread.terminate_process()
1036 to_remove = []
1037 for thread in running:
1038 thread.join(5)
1039 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -07001040 to_remove.append(thread)
1041 for thread in to_remove:
1042 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001043 for thread in running:
1044 test_output(
1045 'Test %s did not terminate. Killing it.' % thread.name)
1046 thread.kill_process()
1047 thread.join()
1048 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001049
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001050 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1051 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001052
1053if __name__ == '__main__':
1054 main()