blob: d74ab10a51723f26556d375b66d4b4c70d85ea77 [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' \
Brian Silverman415e65d2014-06-21 22:39:28 -0700526 ':detect_stack_use_after_return=1:detect_odr_violation=2' \
527 ':allow_user_segv_handler=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700528 elif self.sanitizer() == 'memory':
529 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
530 elif self.sanitizer() == 'thread':
531 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700532
533 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700534 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
535 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700536 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700537 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700538 # clang doesn't like being run directly on the preprocessed files.
539 r['CCACHE_CPP2'] = 'yes'
540 # Without this, ccache slows down because of the generated header files.
541 # The race condition that this opens up isn't a problem because the build
542 # system finishes modifying header files before compiling anything that
543 # uses them.
544 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700545
Brian Silvermane6bada62014-05-04 16:16:54 -0700546 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700547 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
548 ':' + os.environ['PATH']
549
Brian Silvermana4aff562014-05-02 17:43:50 -0700550 return r
551
Brian Silverman47cd6f62014-05-03 10:35:52 -0700552 ARCHITECTURES = ('arm', 'amd64')
553 COMPILERS = ('clang', 'gcc', 'gcc_4.8')
554 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
555 SANITIZER_TEST_WARNINGS = {
556 'memory': (True,
557"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700558 errors with msan (especially stdlibc++).
559 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700560 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700561 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500562
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500563 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700564 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500565
566 platforms = []
567 for architecture in PrimeProcessor.ARCHITECTURES:
568 for compiler in PrimeProcessor.COMPILERS:
569 for debug in [True, False]:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700570 if architecture == 'arm' and compiler == 'gcc_4.8':
571 # We don't have a compiler to use here.
572 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500573 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700574 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
575 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700576 for compiler in ('gcc_4.8', 'clang'):
577 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
578 sanitizer == 'integer' or
579 sanitizer == 'memory'):
580 # GCC 4.8 doesn't support these sanitizers.
581 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700582 if sanitizer == 'none':
583 # We already added sanitizer == 'none' above.
584 continue
585 platforms.append(
586 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
587 self.__platforms = frozenset(platforms)
588
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500589 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700590 default_platforms = self.select_platforms(architecture='amd64',
591 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700592 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
593 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700594 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500595 elif is_deploy:
Brian Silvermane6bada62014-05-04 16:16:54 -0700596 default_platforms = self.select_platforms(architecture='arm',
Brian Silvermanff485782014-06-18 19:59:09 -0700597 compiler='clang',
Brian Silvermane6bada62014-05-04 16:16:54 -0700598 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500599 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700600 default_platforms = self.select_platforms(debug=False)
601 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500602
Brian Silvermane6bada62014-05-04 16:16:54 -0700603 def platforms(self):
604 return self.__platforms
605 def default_platforms(self):
606 return self.__default_platforms
607
608 def download_externals(self, platforms):
609 to_download = set()
610 for architecture in PrimeProcessor.ARCHITECTURES:
611 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
612 if platforms & self.select_platforms(architecture=architecture,
613 sanitizer=sanitizer):
614 to_download.add(architecture + '-fPIE')
615 if platforms & self.select_platforms(architecture=architecture,
616 sanitizer='none'):
617 to_download.add(architecture)
618 for download_target in to_download:
619 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500620
Brian Silverman452aaec2014-05-05 16:52:18 -0700621 def parse_platforms(self, platform_string):
622 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700623 return self.default_platforms()
624 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700625 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700626 if part == 'all':
627 r = self.platforms()
628 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500629 r = r | self.select_platforms_string(part[1:])
630 elif part[0] == '-':
631 r = r - self.select_platforms_string(part[1:])
632 elif part[0] == '=':
633 r = self.select_platforms_string(part[1:])
634 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500635 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700636 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500637 if not r:
638 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500639 return r
640
Brian Silverman452aaec2014-05-05 16:52:18 -0700641 def select_platforms(self, architecture=None, compiler=None, debug=None,
642 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500643 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700644 for platform in self.platforms():
645 if architecture is None or platform.architecture() == architecture:
646 if compiler is None or platform.compiler() == compiler:
647 if debug is None or platform.debug() == debug:
648 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700649 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500650 return set(r)
651
Brian Silverman452aaec2014-05-05 16:52:18 -0700652 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700653 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700654 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500655 if part in PrimeProcessor.ARCHITECTURES:
656 architecture = part
657 elif part in PrimeProcessor.COMPILERS:
658 compiler = part
659 elif part in ['debug', 'dbg']:
660 debug = True
661 elif part in ['release', 'nodebug', 'ndb']:
662 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700663 elif part in PrimeProcessor.SANITIZERS:
664 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700665 elif part == 'all':
666 architecture = compiler = debug = sanitizer = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500667 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700668 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700669 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500670 return self.select_platforms(
671 architecture=architecture,
672 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700673 debug=debug,
674 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500675
Brian Silverman9b7a6842014-05-05 16:19:11 -0700676 def check_installed(self, platforms, is_deploy):
677 packages = set(('lzip', 'm4', 'realpath'))
678 packages.add('ruby')
679 # clang-format from here gets used for all versions.
680 packages.add('clang-3.5')
681 packages.add('arm-eabi-gcc')
682 for platform in platforms:
683 if platform.architecture() == 'arm':
684 packages.add('gcc-4.7-arm-linux-gnueabihf')
685 packages.add('g++-4.7-arm-linux-gnueabihf')
686 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
687 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700688 if platform.compiler() == 'gcc_4.8':
689 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700690 if is_deploy:
691 packages.add('openssh-client')
692 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
693 packages.add('gcc-4.7')
694 packages.add('g++-4.7')
695
696 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700697
Brian Silverman6bca4722014-05-20 17:02:49 -0700698def strsignal(num):
699 # It ends up with SIGIOT instead otherwise, which is weird.
700 if num == signal.SIGABRT:
701 return 'SIGABRT'
702 # SIGCLD is a weird way to spell it.
703 if num == signal.SIGCHLD:
704 return 'SIGCHLD'
705
706 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
707 for n in dir(signal) if n.startswith('SIG')
708 and '_' not in n)
709 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
710
Brian Silvermana29ebf92014-04-23 13:08:49 -0500711def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700712 sys.argv.pop(0)
713 exec_name = sys.argv.pop(0)
714 def print_help(exit_status=None, message=None):
715 if message:
716 print(message)
717 sys.stdout.write(
718"""Usage: {name} [-j n] [action] [-n] [platform] [target]...
719Arguments:
720 -j, --jobs Explicitly specify how many jobs to run at a time.
721 Defaults to the number of processors + 2.
722 -n, --dry-run Don't actually do whatever.
723 Currently only meaningful for deploy.
724 action What to do. Defaults to build.
725 build: Build the code.
726 clean: Remove all the built output.
727 tests: Build and then run tests.
728 deploy: Build and then download.
729 platform What variants of the code to build.
730 Defaults to something reasonable.
731 See below for details.
732 target... Which targets to build/test/etc.
733 Defaults to everything.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500734
Brian Silvermandf5348a2014-06-12 23:25:08 -0700735Specifying targets:
736 Targets are combinations of architecture, compiler, and debug flags. Which
737 ones actually get run is built up as a set. It defaults to something
738 reasonable for the action (specified below).
739 The platform specification (the argument given to this script) is a comma-
740 separated sequence of hyphen-separated platforms, each with an optional
741 prefix.
742 Each selector (the things separated by commas) selects all of the platforms
743 which match all of its components. Its effect on the set of current platforms
744 depends on the prefix character.
745 Here are the prefix characters:
746 + Adds the selected platforms.
747 - Removes the selected platforms.
748 = Sets the current set to the selected platforms.
749 [none] Removes all non-selected platforms.
750 If this makes the current set empty, acts like =.
751 There is also the special psuedo-platform "all" which selects all platforms.
752 All of the available platforms:
753 {all_platforms}
754 Default platforms for deploying:
755 {deploy_platforms}
756 Default platforms for testing:
757 {test_platforms}
758 Default platforms for everything else:
759 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500760
Brian Silvermandf5348a2014-06-12 23:25:08 -0700761Examples of specifying targets:
762 build everything: "all"
763 only build things with clang: "clang"
764 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
765 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
766""".format(
767 name=exec_name,
768 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
769 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
770 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
771 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
772 ))
773 if exit_status is not None:
774 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500775
Brian Silvermandf5348a2014-06-12 23:25:08 -0700776 def sort_platforms(platforms):
777 return sorted(
778 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500779
Brian Silvermandf5348a2014-06-12 23:25:08 -0700780 def str_platforms(platforms):
781 r = []
782 for platform in sort_platforms(platforms):
783 r.append(str(platform))
784 if len(r) > 1:
785 r[-1] = 'and ' + r[-1]
786 return ', '.join(r)
787
788 class Arguments(object):
789 def __init__(self):
790 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
791 self.action_name = 'build'
792 self.dry_run = False
793 self.targets = []
794 self.platform = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500795
Brian Silvermandf5348a2014-06-12 23:25:08 -0700796 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500797
Brian Silvermandf5348a2014-06-12 23:25:08 -0700798 if len(sys.argv) < 2:
799 print_help(1, 'Not enough arguments')
800 args.processor = sys.argv.pop(0)
801 args.main_gyp = sys.argv.pop(0)
802 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
803 while sys.argv:
804 arg = sys.argv.pop(0)
805 if arg == '-j' or arg == '--jobs':
806 args.jobs = int(sys.argv.pop(0))
807 continue
808 if arg in VALID_ACTIONS:
809 args.action_name = arg
810 continue
811 if arg == '-n' or arg == '--dry-run':
812 if args.action_name != 'deploy':
813 print_help(1, '--dry-run is only valid for deploy')
814 args.dry_run = True
815 continue
816 if arg == '-h' or arg == '--help':
817 print_help(0)
818 if args.platform:
819 args.targets.append(arg)
820 else:
821 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500822
823 if args.processor == 'crio':
824 processor = CRIOProcessor()
825 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500826 processor = PrimeProcessor(args.action_name == 'tests',
827 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500828 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700829 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500830
Brian Silvermana29ebf92014-04-23 13:08:49 -0500831 unknown_platform_error = None
832 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700833 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500834 except Processor.UnknownPlatform as e:
835 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700836 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500837 platforms = processor.parse_platforms(None)
838 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700839 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500840
Brian Silverman9b7a6842014-05-05 16:19:11 -0700841 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700842 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500843
844 class ToolsConfig(object):
845 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500846 self.variables = {'AOS': aos_path()}
847 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500848 for line in f:
849 if line[0] == '#':
850 pass
851 elif line.isspace():
852 pass
853 else:
854 new_name, new_value = line.rstrip().split('=')
855 for name, value in self.variables.items():
856 new_value = new_value.replace('${%s}' % name, value)
857 self.variables[new_name] = new_value
858 def __getitem__(self, key):
859 return self.variables[key]
860
861 tools_config = ToolsConfig()
862
863 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700864 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500865 if issubclass(OSError, excinfo[0]):
866 if excinfo[1].errno == errno.ENOENT:
867 # Who cares if the file we're deleting isn't there?
868 return
869 raise excinfo[1]
870
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700871 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700872 """Determines if we need to run gyp again or not.
873
874 The generated build files are supposed to re-run gyp again themselves, but
875 that doesn't work (or at least it used to not) and we sometimes want to
876 modify the results anyways.
877
878 Args:
879 platform: The platform to check for.
880 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700881 if not os.path.exists(platform.build_ninja()):
882 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700883 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
884 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700885 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700886 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700887 if dirs.count('output'):
888 dirs.remove('output')
889 if dirs.count('.git'):
890 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700891 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700892 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700893 + ('-newer', platform.build_ninja(),
894 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700895 stdin=open(os.devnull, 'r'))
896
897 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700898 """Makes sure we pass through important environmental variables.
899
900 Returns:
901 An environment suitable for passing to subprocess.Popen and friends.
902 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700903 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700904 if not 'TERM' in build_env:
905 build_env['TERM'] = os.environ['TERM']
906 if not 'PATH' in build_env:
907 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700908 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700909
Brian Silvermandf5348a2014-06-12 23:25:08 -0700910 sorted_platforms = sort_platforms(platforms)
911 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700912
913 if args.action_name == 'tests':
914 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
915 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
916 if warned_about:
917 user_output(warning[1])
918 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700919 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700920 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
921 exit(1)
922
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700923 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700924 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700925 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500926 if args.action_name == 'clean':
927 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
928 else:
929 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700930 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500931 gyp = subprocess.Popen(
932 (tools_config['GYP'],
933 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500934 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500935 '--no-circular-check',
936 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500937 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500938 '-I/dev/stdin', '-Goutput_dir=output',
939 '-DOS=%s' % platform.os(),
940 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700941 '-DARCHITECTURE=%s' % platform.architecture(),
942 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
943 '-DFULL_COMPILER=%s' % platform.compiler(),
944 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
945 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700946 '-DSANITIZER_FPIE=%s' %
947 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
948 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500949 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500950 stdin=subprocess.PIPE)
951 gyp.communicate(("""
952{
953 'target_defaults': {
954 'configurations': {
955 '%s': {}
956 }
957 }
958}""" % platform.outname()).encode())
959 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700960 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500961 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700962 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700963 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500964 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700965 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500966
967 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700968 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -0700969 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700970 if args.jobs:
971 call += ('-j', str(args.jobs))
972 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700973 stdin=open(os.devnull, 'r'),
974 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500975 except subprocess.CalledProcessError as e:
976 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700977 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500978 raise e
979
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500980 if args.action_name == 'deploy':
981 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700982 elif args.action_name == 'tests':
983 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700984 done_queue = queue.Queue()
985 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700986 test_start_semaphore = threading.Semaphore(args.jobs)
987 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700988 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700989 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700990 if target.endswith('_test'):
991 to_run.append(target)
992 else:
993 to_run = os.listdir(dirname)
994 for f in to_run:
995 thread = TestThread(os.path.join(dirname, f), env(platform), done_queue,
996 test_start_semaphore)
997 running.append(thread)
998 thread.start()
999 try:
1000 while running:
1001 done = done_queue.get()
1002 running.remove(done)
1003 with test_output_lock:
1004 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -07001005 try:
1006 while True:
1007 line = done.output.get(False)
1008 if not sys.stdout.isatty():
1009 # Remove color escape codes.
1010 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1011 sys.stdout.write(line)
1012 except queue.Empty:
1013 pass
1014 if not done.returncode:
1015 test_output('Test %s succeeded' % done.name)
1016 else:
1017 if done.returncode < 0:
1018 sig = -done.returncode
1019 test_output('Test %s was killed by signal %d (%s)' % \
1020 (done.name, sig, strsignal(sig)))
1021 elif done.returncode != 1:
1022 test_output('Test %s exited with %d' % \
1023 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001024 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001025 test_output('Test %s failed' % done.name)
1026 user_output('Aborting because of test failure for %s.' % \
1027 platform)
1028 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001029 finally:
1030 if running:
1031 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001032# Stop all of them before killing processes because otherwise stopping some of
1033# them tends to let other ones that are waiting to start go.
1034 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -07001035 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001036 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001037 for thread in running:
1038 thread.terminate_process()
1039 to_remove = []
1040 for thread in running:
1041 thread.join(5)
1042 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -07001043 to_remove.append(thread)
1044 for thread in to_remove:
1045 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001046 for thread in running:
1047 test_output(
1048 'Test %s did not terminate. Killing it.' % thread.name)
1049 thread.kill_process()
1050 thread.join()
1051 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001052
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001053 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1054 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001055
1056if __name__ == '__main__':
1057 main()