blob: 8a878063b949d675c38679d1dc36935481ecf9a4 [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.
Brian Silvermanb9e89602014-06-27 14:21:08 -050028 args: A tuple of arguments to give the executable.
Brian Silvermane6bada62014-05-04 16:16:54 -070029 env: The environment variables to set.
30 done_queue: A queue.Queue to place self on once done running the test.
31 start_semaphore: A threading.Semaphore to wait on before starting.
32 process_lock: A lock around process.
33 process: The currently executing test process or None. Synchronized by
34 process_lock.
35 stopped: True if we're stopped.
Brian Silverman730bb012014-06-08 13:05:20 -070036 output: A queue of lines of output from the test.
Brian Silvermane6bada62014-05-04 16:16:54 -070037 """
Brian Silverman730bb012014-06-08 13:05:20 -070038
39 class OutputCopier(threading.Thread):
40 """Copies the output of a test from its output pty into a queue.
41
42 This is necessary because otherwise everything locks up if the test writes
43 too much output and fills up the pty's buffer.
44 """
45
46 def __init__(self, name, fd, queue):
47 super(TestThread.OutputCopier, self).__init__(
48 name=(name + '.OutputCopier'))
49
50 self.fd = fd
51 self.queue = queue
52
53 def run(self):
54 with os.fdopen(self.fd) as to_read:
55 try:
56 for line in to_read:
57 self.queue.put(line)
58 except IOError as e:
59# An EIO from the master side of the pty means we hit the end.
60 if e.errno == errno.EIO:
61 return
62 else:
63 raise e
64
Brian Silvermanb9e89602014-06-27 14:21:08 -050065 def __init__(self, executable, args, env, done_queue, start_semaphore):
Brian Silvermanc3740c32014-05-04 12:42:47 -070066 super(TestThread, self).__init__(
Brian Silvermanb9e89602014-06-27 14:21:08 -050067 name=os.path.split(executable)[-1])
Brian Silvermanc3740c32014-05-04 12:42:47 -070068
69 self.executable = executable
Brian Silvermanb9e89602014-06-27 14:21:08 -050070 self.args = args
Brian Silvermanc3740c32014-05-04 12:42:47 -070071 self.env = env
72 self.done_queue = done_queue
73 self.start_semaphore = start_semaphore
74
Brian Silverman730bb012014-06-08 13:05:20 -070075 self.output = queue.Queue()
76
Brian Silvermanc3740c32014-05-04 12:42:47 -070077 self.process_lock = threading.Lock()
78 self.process = None
79 self.stopped = False
Brian Silverman452aaec2014-05-05 16:52:18 -070080 self.returncode = None
Brian Silverman730bb012014-06-08 13:05:20 -070081 self.output_copier = None
Brian Silvermanc3740c32014-05-04 12:42:47 -070082
83 def run(self):
84 with self.start_semaphore:
Brian Silverman452aaec2014-05-05 16:52:18 -070085 if self.stopped:
86 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070087 test_output('Starting test %s...' % self.name)
Brian Silverman730bb012014-06-08 13:05:20 -070088 output_to_read, subprocess_output = pty.openpty()
89 self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
90 self.output)
91 self.output_copier.start()
Brian Silvermanf2bbe092014-05-13 16:55:03 -070092 try:
93 with self.process_lock:
Brian Silvermanb9e89602014-06-27 14:21:08 -050094 self.process = subprocess.Popen((self.name,) + self.args,
95 executable=self.executable,
Brian Silvermanf2bbe092014-05-13 16:55:03 -070096 env=self.env,
97 stderr=subprocess.STDOUT,
98 stdout=subprocess_output,
99 stdin=open(os.devnull, 'r'))
100 finally:
101 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700102 self.process.wait()
103 with self.process_lock:
104 self.returncode = self.process.returncode
105 self.process = None
106 if not self.stopped:
Brian Silverman730bb012014-06-08 13:05:20 -0700107 self.output_copier.join()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700108 self.done_queue.put(self)
109
110 def terminate_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700111 """Asks any currently running process to stop."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700112 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700113 if not self.process:
114 return
Brian Silvermana5826582014-06-03 19:46:35 -0700115 try:
116 self.process.terminate()
117 except OSError as e:
118 if e.errno == errno.ESRCH:
119 # We don't really care if it's already gone.
120 pass
121 else:
122 raise e
Brian Silvermanc3740c32014-05-04 12:42:47 -0700123 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700124 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700125 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700126 if not self.process:
127 return
Brian Silverman6bca4722014-05-20 17:02:49 -0700128 try:
129 self.process.kill()
130 except OSError as e:
131 if e.errno == errno.ESRCH:
132 # We don't really care if it's already gone.
133 pass
134 else:
135 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700136 def stop(self):
137 """Changes self to the stopped state."""
138 with self.process_lock:
139 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500140
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500141def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700142 """Returns:
143 A relative path to the aos directory.
144 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500145 return os.path.join(os.path.dirname(__file__), '..')
146
Austin Schuhd3680052014-10-25 17:52:18 -0700147def get_ip_base():
148 """Retrieves the IP address base."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700149 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman452aaec2014-05-05 16:52:18 -0700150 'output', 'ip_base.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500151 if not os.access(FILENAME, os.R_OK):
152 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
153 with open(FILENAME, 'w') as f:
154 f.write('10.9.71')
155 with open(FILENAME, 'r') as f:
Brian Silvermand043d472014-06-21 15:32:39 -0700156 base = f.readline().strip()
Austin Schuhd3680052014-10-25 17:52:18 -0700157 return base
158
159def get_ip(device):
160 """Retrieves the IP address for a given device."""
161 base = get_ip_base()
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500162 if device == 'prime':
163 return base + '.179'
164 elif device == 'robot':
165 return base + '.2'
Austin Schuh93426072014-10-21 22:22:06 -0700166 elif device == 'roboRIO':
167 return base + '.2'
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500168 else:
169 raise Exception('Unknown device %s to get an IP address for.' % device)
170
Austin Schuhd3680052014-10-25 17:52:18 -0700171def get_user(device):
172 """Retrieves the user for a given device."""
173 if device == 'prime':
174 return 'driver'
175 elif device == 'roboRIO':
176 return 'admin'
177 else:
178 raise Exception('Unknown device %s to get a user for.' % device)
179
180def get_temp_dir(device):
181 """Retrieves the temporary download directory for a given device."""
182 if device == 'prime':
183 return '/tmp/aos_downloader'
184 elif device == 'roboRIO':
185 return '/home/admin/tmp/aos_downloader'
186 else:
187 raise Exception('Unknown device %s to get a temp_dir for.' % device)
188
189def get_target_dir(device):
190 """Retrieves the tempory deploy directory for a given device."""
191 if device == 'prime':
192 return '/home/driver/robot_code/bin'
193 elif device == 'roboRIO':
194 return '/home/admin/robot_code'
195 else:
196 raise Exception('Unknown device %s to get a temp_dir for.' % device)
197
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700198def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700199 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700200 print('build.py: ' + message, file=sys.stderr)
201
Brian Silverman452aaec2014-05-05 16:52:18 -0700202# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700203test_output_lock = threading.RLock()
204def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700205 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700206 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700207 print('tests: ' + message, file=sys.stdout)
208
209def call_download_externals(argument):
210 """Calls download_externals.sh for a given set of externals.
211
212 Args:
213 argument: The argument to pass to the shell script to tell it what to
214 download.
215 """
216 subprocess.check_call(
217 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
218 argument),
219 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700220
Brian Silvermana29ebf92014-04-23 13:08:49 -0500221class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700222 """Represents a processor architecture we can build for."""
223
Brian Silvermana29ebf92014-04-23 13:08:49 -0500224 class UnknownPlatform(Exception):
225 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700226 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500227 self.message = message
228
Brian Silvermanb3d50542014-04-23 14:28:55 -0500229 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700230 """Represents a single way to build the code."""
231
Brian Silvermanb3d50542014-04-23 14:28:55 -0500232 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700233 """Returns:
234 The path of the directory build outputs get put in to.
235 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500236 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500237 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500238 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700239 """Returns:
240 The path of the build.ninja file.
241 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500242 return os.path.join(self.outdir(), 'build.ninja')
243
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500244 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700245 """Helper for subclasses to implement deploy.
246
247 Args:
248 dry_run: If True, prints the command instead of actually running it.
249 command: A tuple of command-line arguments.
250 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500251 real_command = (('echo',) + command) if dry_run else command
252 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500253
Brian Silvermane6bada62014-05-04 16:16:54 -0700254 def deploy(self, dry_run):
255 """Downloads the compiled code to the target computer."""
256 raise NotImplementedError('deploy should be overriden')
257 def outname(self):
258 """Returns:
259 The name of the directory the code will be compiled to.
260 """
261 raise NotImplementedError('outname should be overriden')
262 def os(self):
263 """Returns:
264 The name of the operating system this platform is for.
265
266 This will be used as the value of the OS gyp variable.
267 """
268 raise NotImplementedError('os should be overriden')
269 def gyp_platform(self):
270 """Returns:
271 The platform name the .gyp files know.
272
273 This will be used as the value of the PLATFORM gyp variable.
274 """
275 raise NotImplementedError('gyp_platform should be overriden')
276 def architecture(self):
277 """Returns:
278 The processor architecture for this platform.
279
280 This will be used as the value of the ARCHITECTURE gyp variable.
281 """
282 raise NotImplementedError('architecture should be overriden')
283 def compiler(self):
284 """Returns:
285 The compiler used for this platform.
286
287 Everything before the first _ will be used as the value of the
288 COMPILER gyp variable and the whole thing will be used as the value
289 of the FULL_COMPILER gyp variable.
290 """
291 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700292 def sanitizer(self):
293 """Returns:
294 The sanitizer used on this platform.
295
296 This will be used as the value of the SANITIZER gyp variable.
297
298 "none" if there isn't one.
299 """
300 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700301 def debug(self):
302 """Returns:
303 Whether or not this platform compiles with debugging information.
304
305 The DEBUG gyp variable will be set to "yes" or "no" based on this.
306 """
307 raise NotImplementedError('debug should be overriden')
308 def build_env(self):
309 """Returns:
310 A map of environment variables to set while building this platform.
311 """
312 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700313 def priority(self):
314 """Returns:
315 A relative priority for this platform relative to other ones.
316
317 Higher priority platforms will get built, tested, etc first. Generally,
318 platforms which give higher-quality compiler errors etc should come first.
319 """
320 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700321
Brian Silverman9b7a6842014-05-05 16:19:11 -0700322 def check_installed(self, platforms, is_deploy):
323 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700324 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700325 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700326 """Args:
327 string: A user-supplied string saying which platforms to select.
328
329 Returns:
330 A tuple of Platform objects.
331
332 Raises:
333 Processor.UnknownPlatform: Parsing string didn't work out.
334 """
335 raise NotImplementedError('parse_platforms should be overriden')
336 def extra_gyp_flags(self):
337 """Returns:
338 A tuple of extra flags to pass to gyp (if any).
339 """
340 return ()
341 def modify_ninja_file(self, ninja_file):
342 """Modifies a freshly generated ninja file as necessary.
343
344 Args:
345 ninja_file: Path to the file to modify.
346 """
347 pass
348 def download_externals(self, platforms):
349 """Calls download_externals as appropriate to build platforms.
350
351 Args:
352 platforms: A list of platforms to download external libraries for.
353 """
354 raise NotImplementedError('download_externals should be overriden')
355
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700356 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700357 """Helper for subclasses to implement check_installed.
358
359 Args:
360 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700361 all_packages = other_packages
362 # Necessary to build stuff.
363 all_packages += ('ccache', 'make')
364 # Necessary to download stuff to build.
365 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
366 # Necessary to build externals stuff.
367 all_packages += ('python', 'gcc', 'g++')
Brian Silverman5e94a442014-12-15 15:21:20 -0500368 not_found = []
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700369 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700370 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700371 result = subprocess.check_output(
Brian Silverman5e94a442014-12-15 15:21:20 -0500372 ('dpkg-query',
373 r"--showformat='${binary:Package}\t${db:Status-Abbrev}\n'",
374 '--show') + all_packages,
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700375 stdin=open(os.devnull, 'r'),
376 stderr=subprocess.STDOUT)
Brian Silverman5e94a442014-12-15 15:21:20 -0500377 for line in result.decode('utf-8').rstrip().splitlines(True):
378 match = re.match('^([^\t]+)\t[^i][^i]$', line)
379 if match:
380 not_found.append(match.group(1))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700381 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700382 output = e.output.decode('utf-8').rstrip()
Brian Silverman9b7a6842014-05-05 16:19:11 -0700383 for line in output.splitlines(True):
384 match = re.match(r'dpkg-query: no packages found matching (.*)',
385 line)
386 if match:
387 not_found.append(match.group(1))
Brian Silverman5e94a442014-12-15 15:21:20 -0500388 if not_found:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700389 user_output('Some packages not installed: %s.' % ', '.join(not_found))
390 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700391 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700392 exit(1)
393
Brian Silvermana29ebf92014-04-23 13:08:49 -0500394class CRIOProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700395 """A Processor subclass for building cRIO code."""
396
Brian Silvermanb3d50542014-04-23 14:28:55 -0500397 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -0700398 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500399 super(CRIOProcessor.Platform, self).__init__()
400
Brian Silvermane6bada62014-05-04 16:16:54 -0700401 self.__debug = debug
402 self.__wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500403
404 def __repr__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700405 return 'CRIOProcessor.Platform(debug=%s)' % self.debug()
Brian Silvermanb3d50542014-04-23 14:28:55 -0500406 def __str__(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700407 return 'crio%s' % ('-debug' if self.debug() else '')
Brian Silvermanb3d50542014-04-23 14:28:55 -0500408
409 def outname(self):
Brian Silverman9b7a6842014-05-05 16:19:11 -0700410 return 'crio-debug' if self.debug() else 'crio'
Brian Silvermanb3d50542014-04-23 14:28:55 -0500411 def os(self):
412 return 'vxworks'
413 def gyp_platform(self):
414 return 'crio'
415 def architecture(self):
416 return 'ppc'
417 def compiler(self):
418 return 'gcc'
Brian Silverman9b7a6842014-05-05 16:19:11 -0700419 def sanitizer(self):
420 return 'none'
Brian Silvermane6bada62014-05-04 16:16:54 -0700421 def debug(self):
422 return self.__debug
423 def wind_base(self):
424 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500425
Brian Silvermane48c09a2014-04-30 18:04:58 -0700426 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500427 def deploy(self, dry_run):
428 self.do_deploy(dry_run,
429 ('ncftpput', get_ip('robot'), '/',
Brian Silverman452aaec2014-05-05 16:52:18 -0700430 os.path.join(self.outdir(), 'lib',
431 'FRC_UserProgram.out')))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500432
Brian Silvermana4aff562014-05-02 17:43:50 -0700433 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700434 return {'WIND_BASE': self.wind_base()}
Brian Silvermana4aff562014-05-02 17:43:50 -0700435
Brian Silvermana29ebf92014-04-23 13:08:49 -0500436 def __init__(self):
437 super(CRIOProcessor, self).__init__()
438
439 if 'WIND_BASE' in os.environ:
Brian Silvermane6bada62014-05-04 16:16:54 -0700440 self.__wind_base = os.environ['WIND_BASE']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500441 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700442 self.__wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
Brian Silvermana29ebf92014-04-23 13:08:49 -0500443
Brian Silverman452aaec2014-05-05 16:52:18 -0700444 def parse_platforms(self, platforms_string):
445 if platforms_string is None or platforms_string == 'crio':
Brian Silvermane6bada62014-05-04 16:16:54 -0700446 return (CRIOProcessor.Platform(False, self.wind_base()),)
Brian Silvermana4aff562014-05-02 17:43:50 -0700447 elif string == 'crio-debug' or string == 'debug':
Brian Silvermane6bada62014-05-04 16:16:54 -0700448 return (CRIOProcessor.Platform(True, self.wind_base()),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500449 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700450 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700451 '"%s" not recognized as a cRIO platform.' % platforms_string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500452
Brian Silvermane6bada62014-05-04 16:16:54 -0700453 def wind_base(self):
454 return self.__wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -0500455
Brian Silvermane6bada62014-05-04 16:16:54 -0700456 def extra_gyp_flags(self):
457 return ('-DWIND_BASE=%s' % self.wind_base(),)
458
459 def modify_ninja_file(self, ninja_file):
460 subprocess.check_call(
461 ('sed', '-i',
462 's/nm -gD/nm/g', ninja_file),
463 stdin=open(os.devnull, 'r'))
464
465 def download_externals(self, _):
466 call_download_externals('crio')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500467
Brian Silverman9b7a6842014-05-05 16:19:11 -0700468 def check_installed(self, platforms, is_deploy):
469 packages = ('powerpc-wrs-vxworks', 'tcl')
470 if is_deploy:
471 packages += ('ncftp',)
472 self.do_check_installed(packages)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700473
Brian Silvermana29ebf92014-04-23 13:08:49 -0500474class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700475 """A Processor subclass for building prime code."""
476
Brian Silvermanb3d50542014-04-23 14:28:55 -0500477 class Platform(Processor.Platform):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700478 def __init__(self, architecture, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500479 super(PrimeProcessor.Platform, self).__init__()
480
Brian Silvermane6bada62014-05-04 16:16:54 -0700481 self.__architecture = architecture
482 self.__compiler = compiler
483 self.__debug = debug
484 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500485
486 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700487 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
488 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700489 % (self.architecture(), self.compiler(), self.debug(),
490 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500491 def __str__(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700492 return '%s-%s%s-%s' % (self.architecture(), self.compiler(),
Brian Silverman452aaec2014-05-05 16:52:18 -0700493 '-debug' if self.debug() else '', self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500494
495 def os(self):
496 return 'linux'
497 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700498 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
499 def architecture(self):
500 return self.__architecture
501 def compiler(self):
502 return self.__compiler
503 def sanitizer(self):
504 return self.__sanitizer
505 def debug(self):
506 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500507
Brian Silvermana29ebf92014-04-23 13:08:49 -0500508 def outname(self):
509 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500510
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700511 def priority(self):
512 r = 0
513 if self.compiler() == 'gcc':
514 r -= 100
515 elif self.compiler() == 'clang':
516 r += 100
517 if self.sanitizer() != 'none':
518 r -= 50
519 elif self.debug():
520 r -= 10
521 if self.architecture() == 'amd64':
522 r += 5
523 return r
524
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500525 def deploy(self, dry_run):
Brian Silvermane6bada62014-05-04 16:16:54 -0700526 # Downloads code to the prime in a way that avoids clashing too badly with
527 # starter (like the naive download everything one at a time).
Austin Schuhd3680052014-10-25 17:52:18 -0700528 if self.compiler() == 'gcc_frc':
529 device = 'roboRIO'
530 else:
531 device = 'prime'
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500532 SUM = 'md5sum'
Austin Schuhd3680052014-10-25 17:52:18 -0700533 TARGET_DIR = get_target_dir(device)
534 TEMP_DIR = get_temp_dir(device)
535 TARGET = get_user(device) + '@' + get_ip(device)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500536
537 from_dir = os.path.join(self.outdir(), 'outputs')
538 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
539 stdin=open(os.devnull, 'r'),
540 cwd=from_dir)
541 to_download = subprocess.check_output(
542 ('ssh', TARGET,
Austin Schuh93426072014-10-21 22:22:06 -0700543 """rm -rf {TMPDIR} && mkdir -p {TMPDIR} && cd {TO_DIR} \\
544 && echo '{SUMS}' | {SUM} -c \\
Brian Silvermanff485782014-06-18 19:59:09 -0700545 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*$/\\1/g'""".
546 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums.decode('utf-8'),
547 SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500548 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700549 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500550 return
551 self.do_deploy(
552 dry_run,
Brian Silvermanff485782014-06-18 19:59:09 -0700553 ('scp', '-o', 'Compression yes')
554 + 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 -0500555 + (('%s:%s' % (TARGET, TEMP_DIR)),))
556 if not dry_run:
Austin Schuhd3680052014-10-25 17:52:18 -0700557 mv_cmd = ['mv {TMPDIR}/* {TO_DIR} ']
558 if device == 'roboRIO':
559 mv_cmd.append('&& chmod u+s {TO_DIR}/starter_exe ')
560 mv_cmd.append('&& echo \'Done moving new executables into place\' ')
561 mv_cmd.append('&& bash -c \'sync && sync && sync\'')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500562 subprocess.check_call(
563 ('ssh', TARGET,
Austin Schuhd3680052014-10-25 17:52:18 -0700564 ''.join(mv_cmd).format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500565
Brian Silvermana4aff562014-05-02 17:43:50 -0700566 def build_env(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700567 OTHER_SYSROOT = '/opt/clang-3.5/'
568 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700569 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700570 if self.compiler() == 'clang' or self.compiler() == 'gcc_4.8':
571 r['LD_LIBRARY_PATH'] = OTHER_SYSROOT + 'lib64'
572 if self.sanitizer() == 'address':
573 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700574 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700575 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
Brian Silverman415e65d2014-06-21 22:39:28 -0700576 ':detect_stack_use_after_return=1:detect_odr_violation=2' \
577 ':allow_user_segv_handler=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700578 elif self.sanitizer() == 'memory':
579 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
580 elif self.sanitizer() == 'thread':
581 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermand3fac732014-05-03 16:03:46 -0700582
583 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700584 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
585 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700586 r['CCACHE_HASHDIR'] = 'yes'
Brian Silvermane6bada62014-05-04 16:16:54 -0700587 if self.compiler() == 'clang':
Brian Silvermand3fac732014-05-03 16:03:46 -0700588 # clang doesn't like being run directly on the preprocessed files.
589 r['CCACHE_CPP2'] = 'yes'
590 # Without this, ccache slows down because of the generated header files.
591 # The race condition that this opens up isn't a problem because the build
592 # system finishes modifying header files before compiling anything that
593 # uses them.
594 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700595
Brian Silvermane6bada62014-05-04 16:16:54 -0700596 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700597 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
598 ':' + os.environ['PATH']
599
Brian Silvermana4aff562014-05-02 17:43:50 -0700600 return r
601
Brian Silverman47cd6f62014-05-03 10:35:52 -0700602 ARCHITECTURES = ('arm', 'amd64')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400603 COMPILERS = ('clang', 'gcc', 'gcc_4.8', 'gcc_frc')
Brian Silverman47cd6f62014-05-03 10:35:52 -0700604 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
605 SANITIZER_TEST_WARNINGS = {
606 'memory': (True,
607"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700608 errors with msan (especially stdlibc++).
609 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700610 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700611 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500612
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500613 def __init__(self, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700614 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500615
616 platforms = []
617 for architecture in PrimeProcessor.ARCHITECTURES:
618 for compiler in PrimeProcessor.COMPILERS:
619 for debug in [True, False]:
Brian Silvermanbae86d62014-09-14 01:05:31 -0400620 if ((architecture == 'arm' and compiler == 'gcc_4.8') or
621 (architecture == 'amd64' and compiler == 'gcc_frc')):
Brian Silverman47cd6f62014-05-03 10:35:52 -0700622 # We don't have a compiler to use here.
623 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500624 platforms.append(
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700625 PrimeProcessor.Platform(architecture, compiler, debug, 'none'))
626 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700627 for compiler in ('gcc_4.8', 'clang'):
628 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
629 sanitizer == 'integer' or
630 sanitizer == 'memory'):
631 # GCC 4.8 doesn't support these sanitizers.
632 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700633 if sanitizer == 'none':
634 # We already added sanitizer == 'none' above.
635 continue
636 platforms.append(
637 PrimeProcessor.Platform('amd64', compiler, True, sanitizer))
638 self.__platforms = frozenset(platforms)
639
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500640 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700641 default_platforms = self.select_platforms(architecture='amd64',
642 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700643 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
644 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700645 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500646 elif is_deploy:
Austin Schuhd3680052014-10-25 17:52:18 -0700647 if get_ip_base() == '10.99.71':
648 compiler = 'gcc_frc'
649 else:
650 compiler = 'clang'
Brian Silvermane6bada62014-05-04 16:16:54 -0700651 default_platforms = self.select_platforms(architecture='arm',
Austin Schuhd3680052014-10-25 17:52:18 -0700652 compiler=compiler,
Brian Silvermane6bada62014-05-04 16:16:54 -0700653 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500654 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700655 default_platforms = self.select_platforms(debug=False)
656 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500657
Brian Silvermane6bada62014-05-04 16:16:54 -0700658 def platforms(self):
659 return self.__platforms
660 def default_platforms(self):
661 return self.__default_platforms
662
663 def download_externals(self, platforms):
664 to_download = set()
665 for architecture in PrimeProcessor.ARCHITECTURES:
Brian Silverman99895b92014-09-14 01:01:15 -0400666 pie_sanitizers = set()
Brian Silvermane6bada62014-05-04 16:16:54 -0700667 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
Brian Silverman99895b92014-09-14 01:01:15 -0400668 pie_sanitizers.update(self.select_platforms(architecture=architecture,
669 sanitizer=sanitizer))
670 if platforms & pie_sanitizers:
671 to_download.add(architecture + '-fPIE')
672
Brian Silvermanbae86d62014-09-14 01:05:31 -0400673 frc_platforms = self.select_platforms(architecture=architecture,
674 compiler='gcc_frc')
675 if platforms & frc_platforms:
676 to_download.add(architecture + '_frc')
677
678 if platforms & (self.platforms() - pie_sanitizers - frc_platforms):
Brian Silverman99895b92014-09-14 01:01:15 -0400679 to_download.add(architecture)
680
Brian Silvermane6bada62014-05-04 16:16:54 -0700681 for download_target in to_download:
682 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500683
Brian Silverman452aaec2014-05-05 16:52:18 -0700684 def parse_platforms(self, platform_string):
685 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700686 return self.default_platforms()
687 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700688 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700689 if part == 'all':
690 r = self.platforms()
691 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500692 r = r | self.select_platforms_string(part[1:])
693 elif part[0] == '-':
694 r = r - self.select_platforms_string(part[1:])
695 elif part[0] == '=':
696 r = self.select_platforms_string(part[1:])
697 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500698 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700699 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500700 if not r:
701 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500702 return r
703
Brian Silverman452aaec2014-05-05 16:52:18 -0700704 def select_platforms(self, architecture=None, compiler=None, debug=None,
705 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500706 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700707 for platform in self.platforms():
708 if architecture is None or platform.architecture() == architecture:
709 if compiler is None or platform.compiler() == compiler:
710 if debug is None or platform.debug() == debug:
711 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700712 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500713 return set(r)
714
Brian Silverman452aaec2014-05-05 16:52:18 -0700715 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700716 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700717 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500718 if part in PrimeProcessor.ARCHITECTURES:
719 architecture = part
720 elif part in PrimeProcessor.COMPILERS:
721 compiler = part
722 elif part in ['debug', 'dbg']:
723 debug = True
724 elif part in ['release', 'nodebug', 'ndb']:
725 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700726 elif part in PrimeProcessor.SANITIZERS:
727 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700728 elif part == 'all':
729 architecture = compiler = debug = sanitizer = None
Brian Silvermana29ebf92014-04-23 13:08:49 -0500730 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700731 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700732 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500733 return self.select_platforms(
734 architecture=architecture,
735 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700736 debug=debug,
737 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500738
Brian Silverman9b7a6842014-05-05 16:19:11 -0700739 def check_installed(self, platforms, is_deploy):
740 packages = set(('lzip', 'm4', 'realpath'))
741 packages.add('ruby')
742 # clang-format from here gets used for all versions.
743 packages.add('clang-3.5')
744 packages.add('arm-eabi-gcc')
745 for platform in platforms:
746 if platform.architecture() == 'arm':
747 packages.add('gcc-4.7-arm-linux-gnueabihf')
748 packages.add('g++-4.7-arm-linux-gnueabihf')
749 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
750 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700751 if platform.compiler() == 'gcc_4.8':
752 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700753 if is_deploy:
754 packages.add('openssh-client')
755 if platform.compiler() == 'gcc' and platform.architecture() == 'amd64':
756 packages.add('gcc-4.7')
757 packages.add('g++-4.7')
Brian Silvermanbae86d62014-09-14 01:05:31 -0400758 elif platform.compiler() == 'gcc_frc':
759 packages.add('gcc-4.9-arm-frc-linux-gnueabi')
760 packages.add('g++-4.9-arm-frc-linux-gnueabi')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700761
762 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700763
Brian Silverman6bca4722014-05-20 17:02:49 -0700764def strsignal(num):
765 # It ends up with SIGIOT instead otherwise, which is weird.
766 if num == signal.SIGABRT:
767 return 'SIGABRT'
768 # SIGCLD is a weird way to spell it.
769 if num == signal.SIGCHLD:
770 return 'SIGCHLD'
771
772 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
773 for n in dir(signal) if n.startswith('SIG')
774 and '_' not in n)
775 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
776
Brian Silvermana29ebf92014-04-23 13:08:49 -0500777def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700778 sys.argv.pop(0)
779 exec_name = sys.argv.pop(0)
780 def print_help(exit_status=None, message=None):
781 if message:
782 print(message)
783 sys.stdout.write(
Brian Silvermanb9e89602014-06-27 14:21:08 -0500784"""Usage: {name} [-j n] [action] [-n] [platform] [target|extra_flag]...
Brian Silvermandf5348a2014-06-12 23:25:08 -0700785Arguments:
786 -j, --jobs Explicitly specify how many jobs to run at a time.
787 Defaults to the number of processors + 2.
788 -n, --dry-run Don't actually do whatever.
789 Currently only meaningful for deploy.
790 action What to do. Defaults to build.
791 build: Build the code.
792 clean: Remove all the built output.
793 tests: Build and then run tests.
794 deploy: Build and then download.
795 platform What variants of the code to build.
796 Defaults to something reasonable.
797 See below for details.
798 target... Which targets to build/test/etc.
799 Defaults to everything.
Brian Silvermanb9e89602014-06-27 14:21:08 -0500800 extra_flag... Extra flags associated with the targets.
801 --gtest_*: Arguments to pass on to tests.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500802
Brian Silvermandf5348a2014-06-12 23:25:08 -0700803Specifying targets:
804 Targets are combinations of architecture, compiler, and debug flags. Which
805 ones actually get run is built up as a set. It defaults to something
806 reasonable for the action (specified below).
807 The platform specification (the argument given to this script) is a comma-
808 separated sequence of hyphen-separated platforms, each with an optional
809 prefix.
810 Each selector (the things separated by commas) selects all of the platforms
811 which match all of its components. Its effect on the set of current platforms
812 depends on the prefix character.
813 Here are the prefix characters:
814 + Adds the selected platforms.
815 - Removes the selected platforms.
816 = Sets the current set to the selected platforms.
817 [none] Removes all non-selected platforms.
818 If this makes the current set empty, acts like =.
819 There is also the special psuedo-platform "all" which selects all platforms.
820 All of the available platforms:
821 {all_platforms}
822 Default platforms for deploying:
823 {deploy_platforms}
824 Default platforms for testing:
825 {test_platforms}
826 Default platforms for everything else:
827 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500828
Brian Silvermandf5348a2014-06-12 23:25:08 -0700829Examples of specifying targets:
830 build everything: "all"
831 only build things with clang: "clang"
832 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
833 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
834""".format(
835 name=exec_name,
836 all_platforms=str_platforms(PrimeProcessor(False, False).platforms()),
837 deploy_platforms=str_platforms(PrimeProcessor(False, True).default_platforms()),
838 test_platforms=str_platforms(PrimeProcessor(True, False).default_platforms()),
839 default_platforms=str_platforms(PrimeProcessor(False, False).default_platforms()),
840 ))
841 if exit_status is not None:
842 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500843
Brian Silvermandf5348a2014-06-12 23:25:08 -0700844 def sort_platforms(platforms):
845 return sorted(
846 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500847
Brian Silvermandf5348a2014-06-12 23:25:08 -0700848 def str_platforms(platforms):
849 r = []
850 for platform in sort_platforms(platforms):
851 r.append(str(platform))
852 if len(r) > 1:
853 r[-1] = 'and ' + r[-1]
854 return ', '.join(r)
855
856 class Arguments(object):
857 def __init__(self):
858 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
859 self.action_name = 'build'
860 self.dry_run = False
861 self.targets = []
862 self.platform = None
Brian Silvermanb9e89602014-06-27 14:21:08 -0500863 self.extra_flags = []
Brian Silvermana29ebf92014-04-23 13:08:49 -0500864
Brian Silvermandf5348a2014-06-12 23:25:08 -0700865 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500866
Brian Silvermandf5348a2014-06-12 23:25:08 -0700867 if len(sys.argv) < 2:
868 print_help(1, 'Not enough arguments')
869 args.processor = sys.argv.pop(0)
870 args.main_gyp = sys.argv.pop(0)
871 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests']
872 while sys.argv:
873 arg = sys.argv.pop(0)
874 if arg == '-j' or arg == '--jobs':
875 args.jobs = int(sys.argv.pop(0))
876 continue
877 if arg in VALID_ACTIONS:
878 args.action_name = arg
879 continue
880 if arg == '-n' or arg == '--dry-run':
881 if args.action_name != 'deploy':
882 print_help(1, '--dry-run is only valid for deploy')
883 args.dry_run = True
884 continue
885 if arg == '-h' or arg == '--help':
886 print_help(0)
Brian Silvermanb9e89602014-06-27 14:21:08 -0500887 if re.match('^--gtest_.*$', arg):
888 if args.action_name == 'tests':
889 args.extra_flags.append(arg)
890 continue
891 else:
892 print_help(1, '--gtest_* is only valid for tests')
Brian Silvermandf5348a2014-06-12 23:25:08 -0700893 if args.platform:
894 args.targets.append(arg)
895 else:
896 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500897
898 if args.processor == 'crio':
899 processor = CRIOProcessor()
900 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500901 processor = PrimeProcessor(args.action_name == 'tests',
902 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500903 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700904 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500905
Brian Silvermana29ebf92014-04-23 13:08:49 -0500906 unknown_platform_error = None
907 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700908 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500909 except Processor.UnknownPlatform as e:
910 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700911 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500912 platforms = processor.parse_platforms(None)
913 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700914 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500915
Brian Silverman9b7a6842014-05-05 16:19:11 -0700916 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700917 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500918
919 class ToolsConfig(object):
920 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500921 self.variables = {'AOS': aos_path()}
922 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500923 for line in f:
924 if line[0] == '#':
925 pass
926 elif line.isspace():
927 pass
928 else:
929 new_name, new_value = line.rstrip().split('=')
930 for name, value in self.variables.items():
931 new_value = new_value.replace('${%s}' % name, value)
932 self.variables[new_name] = new_value
933 def __getitem__(self, key):
934 return self.variables[key]
935
936 tools_config = ToolsConfig()
937
938 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700939 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500940 if issubclass(OSError, excinfo[0]):
941 if excinfo[1].errno == errno.ENOENT:
942 # Who cares if the file we're deleting isn't there?
943 return
944 raise excinfo[1]
945
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700946 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700947 """Determines if we need to run gyp again or not.
948
949 The generated build files are supposed to re-run gyp again themselves, but
950 that doesn't work (or at least it used to not) and we sometimes want to
951 modify the results anyways.
952
953 Args:
954 platform: The platform to check for.
955 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700956 if not os.path.exists(platform.build_ninja()):
957 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700958 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
959 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700960 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700961 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700962 if dirs.count('output'):
963 dirs.remove('output')
964 if dirs.count('.git'):
965 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700966 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700967 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700968 + ('-newer', platform.build_ninja(),
969 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700970 stdin=open(os.devnull, 'r'))
971
972 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700973 """Makes sure we pass through important environmental variables.
974
975 Returns:
976 An environment suitable for passing to subprocess.Popen and friends.
977 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700978 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700979 if not 'TERM' in build_env:
980 build_env['TERM'] = os.environ['TERM']
981 if not 'PATH' in build_env:
982 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700983 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700984
Brian Silvermandf5348a2014-06-12 23:25:08 -0700985 sorted_platforms = sort_platforms(platforms)
986 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700987
988 if args.action_name == 'tests':
989 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
990 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
991 if warned_about:
992 user_output(warning[1])
993 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700994 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700995 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
996 exit(1)
997
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700998 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700999 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001000 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -05001001 if args.action_name == 'clean':
1002 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
1003 else:
1004 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001005 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -05001006 gyp = subprocess.Popen(
1007 (tools_config['GYP'],
1008 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001009 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001010 '--no-circular-check',
1011 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001012 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001013 '-I/dev/stdin', '-Goutput_dir=output',
1014 '-DOS=%s' % platform.os(),
1015 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -07001016 '-DARCHITECTURE=%s' % platform.architecture(),
1017 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
1018 '-DFULL_COMPILER=%s' % platform.compiler(),
1019 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
1020 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman99895b92014-09-14 01:01:15 -04001021 '-DEXTERNALS_EXTRA=%s' %
Brian Silverman452aaec2014-05-05 16:52:18 -07001022 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silvermanbae86d62014-09-14 01:05:31 -04001023 else ('_frc' if platform.compiler() == 'gcc_frc' else ''))) +
Brian Silvermanb3d50542014-04-23 14:28:55 -05001024 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -05001025 stdin=subprocess.PIPE)
1026 gyp.communicate(("""
1027{
1028 'target_defaults': {
1029 'configurations': {
1030 '%s': {}
1031 }
1032 }
1033}""" % platform.outname()).encode())
1034 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001035 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -05001036 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -07001037 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -07001038 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -05001039 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -07001040 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -05001041
1042 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001043 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -07001044 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001045 if args.jobs:
1046 call += ('-j', str(args.jobs))
1047 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -07001048 stdin=open(os.devnull, 'r'),
1049 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -05001050 except subprocess.CalledProcessError as e:
1051 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -07001052 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -05001053 raise e
1054
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001055 if args.action_name == 'deploy':
1056 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -07001057 elif args.action_name == 'tests':
1058 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001059 done_queue = queue.Queue()
1060 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001061 test_start_semaphore = threading.Semaphore(args.jobs)
1062 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001063 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -07001064 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001065 if target.endswith('_test'):
1066 to_run.append(target)
1067 else:
1068 to_run = os.listdir(dirname)
1069 for f in to_run:
Brian Silvermanb9e89602014-06-27 14:21:08 -05001070 thread = TestThread(os.path.join(dirname, f), tuple(args.extra_flags),
1071 env(platform), done_queue,
Brian Silvermanc3740c32014-05-04 12:42:47 -07001072 test_start_semaphore)
1073 running.append(thread)
1074 thread.start()
1075 try:
1076 while running:
1077 done = done_queue.get()
1078 running.remove(done)
1079 with test_output_lock:
1080 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -07001081 try:
1082 while True:
1083 line = done.output.get(False)
1084 if not sys.stdout.isatty():
1085 # Remove color escape codes.
1086 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
1087 sys.stdout.write(line)
1088 except queue.Empty:
1089 pass
1090 if not done.returncode:
1091 test_output('Test %s succeeded' % done.name)
1092 else:
1093 if done.returncode < 0:
1094 sig = -done.returncode
1095 test_output('Test %s was killed by signal %d (%s)' % \
1096 (done.name, sig, strsignal(sig)))
1097 elif done.returncode != 1:
1098 test_output('Test %s exited with %d' % \
1099 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -07001100 else:
Brian Silverman730bb012014-06-08 13:05:20 -07001101 test_output('Test %s failed' % done.name)
1102 user_output('Aborting because of test failure for %s.' % \
1103 platform)
1104 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001105 finally:
1106 if running:
1107 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001108# Stop all of them before killing processes because otherwise stopping some of
1109# them tends to let other ones that are waiting to start go.
1110 for thread in running:
Brian Silvermana5826582014-06-03 19:46:35 -07001111 test_output('\tKilling %s' % thread.name)
Brian Silvermanbf0e1db2014-05-10 22:13:15 -07001112 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -07001113 for thread in running:
1114 thread.terminate_process()
1115 to_remove = []
1116 for thread in running:
1117 thread.join(5)
1118 if not thread.is_alive():
Brian Silverman452aaec2014-05-05 16:52:18 -07001119 to_remove.append(thread)
1120 for thread in to_remove:
1121 running.remove(thread)
Brian Silvermanc3740c32014-05-04 12:42:47 -07001122 for thread in running:
1123 test_output(
1124 'Test %s did not terminate. Killing it.' % thread.name)
1125 thread.kill_process()
1126 thread.join()
1127 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001128
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001129 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1130 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001131
1132if __name__ == '__main__':
1133 main()