blob: 0c8898ba9a70141b6e2e2845439f21a5b54da05d [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):
Brian Silverman48766e42014-12-29 21:37:04 -080084 def setup_test_process():
85# Shove it into its own process group so we can kill any subprocesses easily.
86 os.setpgid(0, 0)
87
Brian Silvermanc3740c32014-05-04 12:42:47 -070088 with self.start_semaphore:
Brian Silverman48766e42014-12-29 21:37:04 -080089 with self.process_lock:
90 if self.stopped:
91 return
Brian Silvermanc3740c32014-05-04 12:42:47 -070092 test_output('Starting test %s...' % self.name)
Brian Silverman730bb012014-06-08 13:05:20 -070093 output_to_read, subprocess_output = pty.openpty()
94 self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
95 self.output)
96 self.output_copier.start()
Brian Silvermanf2bbe092014-05-13 16:55:03 -070097 try:
98 with self.process_lock:
Brian Silvermanb9e89602014-06-27 14:21:08 -050099 self.process = subprocess.Popen((self.name,) + self.args,
100 executable=self.executable,
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700101 env=self.env,
102 stderr=subprocess.STDOUT,
103 stdout=subprocess_output,
Brian Silverman48766e42014-12-29 21:37:04 -0800104 stdin=open(os.devnull, 'r'),
105 preexec_fn=setup_test_process)
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700106 finally:
107 os.close(subprocess_output)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700108 self.process.wait()
109 with self.process_lock:
110 self.returncode = self.process.returncode
111 self.process = None
112 if not self.stopped:
Brian Silverman730bb012014-06-08 13:05:20 -0700113 self.output_copier.join()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700114 self.done_queue.put(self)
115
Brian Silvermanc3740c32014-05-04 12:42:47 -0700116 def kill_process(self):
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700117 """Forcibly terminates any running process."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700118 with self.process_lock:
Brian Silverman452aaec2014-05-05 16:52:18 -0700119 if not self.process:
120 return
Brian Silverman6bca4722014-05-20 17:02:49 -0700121 try:
Brian Silverman48766e42014-12-29 21:37:04 -0800122 os.killpg(self.process.pid, signal.SIGKILL)
Brian Silverman6bca4722014-05-20 17:02:49 -0700123 except OSError as e:
124 if e.errno == errno.ESRCH:
125 # We don't really care if it's already gone.
126 pass
127 else:
128 raise e
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700129 def stop(self):
130 """Changes self to the stopped state."""
131 with self.process_lock:
132 self.stopped = True
Brian Silvermana29ebf92014-04-23 13:08:49 -0500133
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500134def aos_path():
Brian Silvermane6bada62014-05-04 16:16:54 -0700135 """Returns:
136 A relative path to the aos directory.
137 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500138 return os.path.join(os.path.dirname(__file__), '..')
139
Brian Silverman5e8dd492015-03-01 17:53:59 -0500140def get_ip():
141 """Retrieves the IP address to download code to."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700142 FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
Brian Silverman5e8dd492015-03-01 17:53:59 -0500143 'output', 'ip_address.txt'))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500144 if not os.access(FILENAME, os.R_OK):
145 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
146 with open(FILENAME, 'w') as f:
Austin Schuhe77e9812015-02-16 02:58:17 -0800147 f.write('roboRIO-971.local')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500148 with open(FILENAME, 'r') as f:
Brian Silverman5e8dd492015-03-01 17:53:59 -0500149 return f.readline().strip()
Austin Schuhd3680052014-10-25 17:52:18 -0700150
Brian Silverman5e8dd492015-03-01 17:53:59 -0500151def get_temp_dir():
152 """Retrieves the temporary directory to use when downloading."""
153 return '/home/admin/tmp/aos_downloader'
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500154
Brian Silverman5e8dd492015-03-01 17:53:59 -0500155def get_target_dir():
156 """Retrieves the tempory deploy directory for downloading code."""
157 return '/home/admin/robot_code'
Austin Schuhd3680052014-10-25 17:52:18 -0700158
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700159def user_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700160 """Prints message to the user."""
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700161 print('build.py: ' + message, file=sys.stderr)
162
Brian Silverman452aaec2014-05-05 16:52:18 -0700163# A lock to avoid making a mess intermingling test-related messages.
Brian Silvermanc3740c32014-05-04 12:42:47 -0700164test_output_lock = threading.RLock()
165def test_output(message):
Brian Silvermane6bada62014-05-04 16:16:54 -0700166 """Prints message to the user. Intended for messages related to tests."""
Brian Silvermanc3740c32014-05-04 12:42:47 -0700167 with test_output_lock:
Brian Silvermane6bada62014-05-04 16:16:54 -0700168 print('tests: ' + message, file=sys.stdout)
169
170def call_download_externals(argument):
171 """Calls download_externals.sh for a given set of externals.
172
173 Args:
174 argument: The argument to pass to the shell script to tell it what to
175 download.
176 """
177 subprocess.check_call(
178 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
179 argument),
180 stdin=open(os.devnull, 'r'))
Brian Silvermanc3740c32014-05-04 12:42:47 -0700181
Brian Silvermana29ebf92014-04-23 13:08:49 -0500182class Processor(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700183 """Represents a processor architecture we can build for."""
184
Brian Silvermana29ebf92014-04-23 13:08:49 -0500185 class UnknownPlatform(Exception):
186 def __init__(self, message):
Brian Silverman452aaec2014-05-05 16:52:18 -0700187 super(Processor.UnknownPlatform, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500188 self.message = message
189
Brian Silvermanb3d50542014-04-23 14:28:55 -0500190 class Platform(object):
Brian Silvermane6bada62014-05-04 16:16:54 -0700191 """Represents a single way to build the code."""
192
Brian Silvermanb3d50542014-04-23 14:28:55 -0500193 def outdir(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700194 """Returns:
195 The path of the directory build outputs get put in to.
196 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500197 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500198 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -0500199 def build_ninja(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700200 """Returns:
201 The path of the build.ninja file.
202 """
Brian Silvermanb3d50542014-04-23 14:28:55 -0500203 return os.path.join(self.outdir(), 'build.ninja')
204
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500205 def do_deploy(self, dry_run, command):
Brian Silvermane6bada62014-05-04 16:16:54 -0700206 """Helper for subclasses to implement deploy.
207
208 Args:
209 dry_run: If True, prints the command instead of actually running it.
210 command: A tuple of command-line arguments.
211 """
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500212 real_command = (('echo',) + command) if dry_run else command
213 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500214
Brian Silvermane6bada62014-05-04 16:16:54 -0700215 def deploy(self, dry_run):
216 """Downloads the compiled code to the target computer."""
217 raise NotImplementedError('deploy should be overriden')
218 def outname(self):
219 """Returns:
220 The name of the directory the code will be compiled to.
221 """
222 raise NotImplementedError('outname should be overriden')
223 def os(self):
224 """Returns:
225 The name of the operating system this platform is for.
226
227 This will be used as the value of the OS gyp variable.
228 """
229 raise NotImplementedError('os should be overriden')
230 def gyp_platform(self):
231 """Returns:
232 The platform name the .gyp files know.
233
234 This will be used as the value of the PLATFORM gyp variable.
235 """
236 raise NotImplementedError('gyp_platform should be overriden')
237 def architecture(self):
238 """Returns:
239 The processor architecture for this platform.
240
241 This will be used as the value of the ARCHITECTURE gyp variable.
242 """
243 raise NotImplementedError('architecture should be overriden')
244 def compiler(self):
245 """Returns:
246 The compiler used for this platform.
247
248 Everything before the first _ will be used as the value of the
249 COMPILER gyp variable and the whole thing will be used as the value
250 of the FULL_COMPILER gyp variable.
251 """
252 raise NotImplementedError('compiler should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700253 def sanitizer(self):
254 """Returns:
255 The sanitizer used on this platform.
256
257 This will be used as the value of the SANITIZER gyp variable.
258
259 "none" if there isn't one.
260 """
261 raise NotImplementedError('sanitizer should be overriden')
Brian Silvermane6bada62014-05-04 16:16:54 -0700262 def debug(self):
263 """Returns:
264 Whether or not this platform compiles with debugging information.
265
266 The DEBUG gyp variable will be set to "yes" or "no" based on this.
267 """
268 raise NotImplementedError('debug should be overriden')
269 def build_env(self):
270 """Returns:
271 A map of environment variables to set while building this platform.
272 """
273 raise NotImplementedError('build_env should be overriden')
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700274 def priority(self):
275 """Returns:
276 A relative priority for this platform relative to other ones.
277
278 Higher priority platforms will get built, tested, etc first. Generally,
279 platforms which give higher-quality compiler errors etc should come first.
280 """
281 return 0
Brian Silvermane6bada62014-05-04 16:16:54 -0700282
Brian Silverman9b7a6842014-05-05 16:19:11 -0700283 def check_installed(self, platforms, is_deploy):
284 """Makes sure that everything necessary to build platforms are installed."""
Brian Silvermane6bada62014-05-04 16:16:54 -0700285 raise NotImplementedError('check_installed should be overriden')
Brian Silverman452aaec2014-05-05 16:52:18 -0700286 def parse_platforms(self, platforms_string):
Brian Silvermane6bada62014-05-04 16:16:54 -0700287 """Args:
288 string: A user-supplied string saying which platforms to select.
289
290 Returns:
291 A tuple of Platform objects.
292
293 Raises:
294 Processor.UnknownPlatform: Parsing string didn't work out.
295 """
296 raise NotImplementedError('parse_platforms should be overriden')
297 def extra_gyp_flags(self):
298 """Returns:
299 A tuple of extra flags to pass to gyp (if any).
300 """
301 return ()
302 def modify_ninja_file(self, ninja_file):
303 """Modifies a freshly generated ninja file as necessary.
304
305 Args:
306 ninja_file: Path to the file to modify.
307 """
308 pass
309 def download_externals(self, platforms):
310 """Calls download_externals as appropriate to build platforms.
311
312 Args:
313 platforms: A list of platforms to download external libraries for.
314 """
315 raise NotImplementedError('download_externals should be overriden')
316
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700317 def do_check_installed(self, other_packages):
Brian Silvermane6bada62014-05-04 16:16:54 -0700318 """Helper for subclasses to implement check_installed.
319
320 Args:
321 other_packages: A tuple of platform-specific packages to check for."""
Brian Silverman9b7a6842014-05-05 16:19:11 -0700322 all_packages = other_packages
323 # Necessary to build stuff.
324 all_packages += ('ccache', 'make')
325 # Necessary to download stuff to build.
326 all_packages += ('wget', 'git', 'subversion', 'patch', 'unzip', 'bzip2')
327 # Necessary to build externals stuff.
328 all_packages += ('python', 'gcc', 'g++')
Brian Silverman5e94a442014-12-15 15:21:20 -0500329 not_found = []
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700330 try:
Brian Silverman452aaec2014-05-05 16:52:18 -0700331 # TODO(brians): Check versions too.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700332 result = subprocess.check_output(
Brian Silverman5e94a442014-12-15 15:21:20 -0500333 ('dpkg-query',
334 r"--showformat='${binary:Package}\t${db:Status-Abbrev}\n'",
335 '--show') + all_packages,
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700336 stdin=open(os.devnull, 'r'),
337 stderr=subprocess.STDOUT)
Brian Silverman5e94a442014-12-15 15:21:20 -0500338 for line in result.decode('utf-8').rstrip().splitlines(True):
339 match = re.match('^([^\t]+)\t[^i][^i]$', line)
340 if match:
341 not_found.append(match.group(1))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700342 except subprocess.CalledProcessError as e:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700343 output = e.output.decode('utf-8').rstrip()
Brian Silverman9b7a6842014-05-05 16:19:11 -0700344 for line in output.splitlines(True):
345 match = re.match(r'dpkg-query: no packages found matching (.*)',
346 line)
347 if match:
348 not_found.append(match.group(1))
Brian Silverman5e94a442014-12-15 15:21:20 -0500349 if not_found:
Brian Silverman9b7a6842014-05-05 16:19:11 -0700350 user_output('Some packages not installed: %s.' % ', '.join(not_found))
351 user_output('Try something like `sudo apt-get install %s`.' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700352 ' '.join(not_found))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700353 exit(1)
354
Brian Silvermana29ebf92014-04-23 13:08:49 -0500355class PrimeProcessor(Processor):
Brian Silvermane6bada62014-05-04 16:16:54 -0700356 """A Processor subclass for building prime code."""
357
Brian Silvermanb3d50542014-04-23 14:28:55 -0500358 class Platform(Processor.Platform):
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700359 def __init__(self, architecture, folder, compiler, debug, sanitizer):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500360 super(PrimeProcessor.Platform, self).__init__()
361
Brian Silvermane6bada62014-05-04 16:16:54 -0700362 self.__architecture = architecture
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700363 self.__folder = folder
Brian Silvermane6bada62014-05-04 16:16:54 -0700364 self.__compiler = compiler
365 self.__debug = debug
366 self.__sanitizer = sanitizer
Brian Silvermana29ebf92014-04-23 13:08:49 -0500367
368 def __repr__(self):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700369 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s' \
370 ', sanitizer=%s)' \
Brian Silvermane6bada62014-05-04 16:16:54 -0700371 % (self.architecture(), self.compiler(), self.debug(),
372 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500373 def __str__(self):
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700374 return '%s-%s-%s%s-%s' % (self.architecture(), self.folder(),
375 self.compiler(),
376 '-debug' if self.debug() else '',
377 self.sanitizer())
Brian Silvermana29ebf92014-04-23 13:08:49 -0500378
379 def os(self):
380 return 'linux'
381 def gyp_platform(self):
Brian Silvermane6bada62014-05-04 16:16:54 -0700382 return '%s-%s-%s' % (self.os(), self.architecture(), self.compiler())
383 def architecture(self):
384 return self.__architecture
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700385 def folder(self):
386 return self.__folder
Brian Silvermane6bada62014-05-04 16:16:54 -0700387 def compiler(self):
388 return self.__compiler
389 def sanitizer(self):
390 return self.__sanitizer
391 def debug(self):
392 return self.__debug
Brian Silvermana29ebf92014-04-23 13:08:49 -0500393
Brian Silvermana29ebf92014-04-23 13:08:49 -0500394 def outname(self):
395 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500396
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700397 def priority(self):
398 r = 0
Brian Silverman99d22092015-02-18 01:10:05 -0500399 if self.compiler() == 'clang':
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700400 r += 100
401 if self.sanitizer() != 'none':
402 r -= 50
403 elif self.debug():
404 r -= 10
405 if self.architecture() == 'amd64':
406 r += 5
407 return r
408
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500409 def deploy(self, dry_run):
Brian Silverman5e8dd492015-03-01 17:53:59 -0500410 """Downloads code to the prime in a way that avoids clashing too badly with
411 starter (like the naive download everything one at a time)."""
412 if not self.architecture().endswith('_frc'):
413 raise Exception("Don't know how to download code to a %s." %
414 self.architecture())
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500415 SUM = 'md5sum'
Brian Silverman5e8dd492015-03-01 17:53:59 -0500416 TARGET_DIR = get_target_dir()
417 TEMP_DIR = get_temp_dir()
418 TARGET = 'admin@' + get_ip()
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500419
420 from_dir = os.path.join(self.outdir(), 'outputs')
421 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
422 stdin=open(os.devnull, 'r'),
423 cwd=from_dir)
424 to_download = subprocess.check_output(
425 ('ssh', TARGET,
Austin Schuhe77e9812015-02-16 02:58:17 -0800426 """rm -rf {TMPDIR} && mkdir -p {TMPDIR} && \\
427 mkdir -p {TO_DIR} && cd {TO_DIR} \\
Austin Schuh93426072014-10-21 22:22:06 -0700428 && echo '{SUMS}' | {SUM} -c \\
Brian Silvermanff485782014-06-18 19:59:09 -0700429 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*$/\\1/g'""".
430 format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums.decode('utf-8'),
431 SUM=SUM)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500432 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700433 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500434 return
435 self.do_deploy(
436 dry_run,
Brian Silvermanff485782014-06-18 19:59:09 -0700437 ('scp', '-o', 'Compression yes')
438 + 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 -0500439 + (('%s:%s' % (TARGET, TEMP_DIR)),))
440 if not dry_run:
Austin Schuhd3680052014-10-25 17:52:18 -0700441 mv_cmd = ['mv {TMPDIR}/* {TO_DIR} ']
Brian Silverman5e8dd492015-03-01 17:53:59 -0500442 mv_cmd.append('&& chmod u+s {TO_DIR}/starter_exe ')
Austin Schuhd3680052014-10-25 17:52:18 -0700443 mv_cmd.append('&& echo \'Done moving new executables into place\' ')
444 mv_cmd.append('&& bash -c \'sync && sync && sync\'')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500445 subprocess.check_call(
446 ('ssh', TARGET,
Austin Schuhd3680052014-10-25 17:52:18 -0700447 ''.join(mv_cmd).format(TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500448
Brian Silvermana4aff562014-05-02 17:43:50 -0700449 def build_env(self):
Brian Silvermanc9570932015-03-29 17:26:51 -0400450 OTHER_SYSROOT = '/usr/lib/llvm-3.5/'
Brian Silvermane6bada62014-05-04 16:16:54 -0700451 SYMBOLIZER_PATH = OTHER_SYSROOT + 'bin/llvm-symbolizer'
Brian Silvermana4aff562014-05-02 17:43:50 -0700452 r = {}
Brian Silvermane6bada62014-05-04 16:16:54 -0700453 if self.sanitizer() == 'address':
454 r['ASAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
Brian Silverman452aaec2014-05-05 16:52:18 -0700455 r['ASAN_OPTIONS'] = \
Brian Silverman407ca822014-06-05 18:36:41 -0700456 'detect_leaks=1:check_initialization_order=1:strict_init_order=1' \
Brian Silverman415e65d2014-06-21 22:39:28 -0700457 ':detect_stack_use_after_return=1:detect_odr_violation=2' \
458 ':allow_user_segv_handler=1'
Brian Silvermane6bada62014-05-04 16:16:54 -0700459 elif self.sanitizer() == 'memory':
460 r['MSAN_SYMBOLIZER_PATH'] = SYMBOLIZER_PATH
461 elif self.sanitizer() == 'thread':
462 r['TSAN_OPTIONS'] = 'external_symbolizer_path=' + SYMBOLIZER_PATH
Brian Silvermane4eb5d72015-04-07 00:45:31 -0400463 # This is apparently the default for newer versions, which disagrees
464 # with documentation, so just turn it on explicitly.
465 r['TSAN_OPTIONS'] += ':detect_deadlocks=1'
466 # Print more useful stacks for mutex locking order problems.
467 r['TSAN_OPTIONS'] += ':second_deadlock_stack=1'
Brian Silvermand3fac732014-05-03 16:03:46 -0700468
469 r['CCACHE_COMPRESS'] = 'yes'
Brian Silverman452aaec2014-05-05 16:52:18 -0700470 r['CCACHE_DIR'] = os.path.abspath(os.path.join(aos_path(), '..', 'output',
471 'ccache_dir'))
Brian Silvermand3fac732014-05-03 16:03:46 -0700472 r['CCACHE_HASHDIR'] = 'yes'
Brian Silverman20141f92015-01-05 17:39:01 -0800473 if self.compiler().startswith('clang'):
Brian Silvermand3fac732014-05-03 16:03:46 -0700474 # clang doesn't like being run directly on the preprocessed files.
475 r['CCACHE_CPP2'] = 'yes'
476 # Without this, ccache slows down because of the generated header files.
477 # The race condition that this opens up isn't a problem because the build
478 # system finishes modifying header files before compiling anything that
479 # uses them.
480 r['CCACHE_SLOPPINESS'] = 'include_file_mtime'
Brian Silvermand3fac732014-05-03 16:03:46 -0700481
Brian Silvermane6bada62014-05-04 16:16:54 -0700482 if self.architecture() == 'amd64':
Brian Silvermand3fac732014-05-03 16:03:46 -0700483 r['PATH'] = os.path.join(aos_path(), 'build', 'bin-ld.gold') + \
484 ':' + os.environ['PATH']
485
Brian Silvermana4aff562014-05-02 17:43:50 -0700486 return r
487
Brian Silverman9f330492015-03-01 17:37:02 -0500488 ARCHITECTURES = ('arm_frc', 'amd64')
489 COMPILERS = ('clang', 'gcc')
Brian Silverman47cd6f62014-05-03 10:35:52 -0700490 SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
491 SANITIZER_TEST_WARNINGS = {
492 'memory': (True,
493"""We don't have all of the libraries instrumented which leads to lots of false
Brian Silvermanc3740c32014-05-04 12:42:47 -0700494 errors with msan (especially stdlibc++).
495 TODO(brians): Figure out a way to deal with it."""),
Brian Silverman47cd6f62014-05-03 10:35:52 -0700496 }
Brian Silvermana5301e32014-05-03 10:51:49 -0700497 PIE_SANITIZERS = ('memory', 'thread')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500498
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700499 def __init__(self, folder, is_test, is_deploy):
Brian Silverman452aaec2014-05-05 16:52:18 -0700500 super(PrimeProcessor, self).__init__()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500501
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700502 self.__folder = folder
503
Brian Silvermana29ebf92014-04-23 13:08:49 -0500504 platforms = []
505 for architecture in PrimeProcessor.ARCHITECTURES:
506 for compiler in PrimeProcessor.COMPILERS:
507 for debug in [True, False]:
Brian Silverman9f330492015-03-01 17:37:02 -0500508 if architecture == 'amd64' and compiler == 'gcc':
Brian Silverman47cd6f62014-05-03 10:35:52 -0700509 # We don't have a compiler to use here.
510 continue
Brian Silvermana29ebf92014-04-23 13:08:49 -0500511 platforms.append(
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700512 self.Platform(architecture, folder, compiler, debug, 'none'))
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700513 for sanitizer in PrimeProcessor.SANITIZERS:
Brian Silverman20141f92015-01-05 17:39:01 -0800514 for compiler in ('clang',):
Brian Silverman47cd6f62014-05-03 10:35:52 -0700515 if compiler == 'gcc_4.8' and (sanitizer == 'undefined' or
516 sanitizer == 'integer' or
517 sanitizer == 'memory'):
518 # GCC 4.8 doesn't support these sanitizers.
519 continue
Brian Silvermane6bada62014-05-04 16:16:54 -0700520 if sanitizer == 'none':
521 # We already added sanitizer == 'none' above.
522 continue
523 platforms.append(
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700524 self.Platform('amd64', folder, compiler, True, sanitizer))
Brian Silvermane6bada62014-05-04 16:16:54 -0700525 self.__platforms = frozenset(platforms)
526
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500527 if is_test:
Brian Silvermane6bada62014-05-04 16:16:54 -0700528 default_platforms = self.select_platforms(architecture='amd64',
529 debug=True)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700530 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
531 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700532 default_platforms -= self.select_platforms(sanitizer=sanitizer)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500533 elif is_deploy:
Brian Silverman9f330492015-03-01 17:37:02 -0500534 default_platforms = self.select_platforms(architecture='arm_frc',
535 compiler='gcc',
Brian Silvermane6bada62014-05-04 16:16:54 -0700536 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500537 else:
Brian Silvermane6bada62014-05-04 16:16:54 -0700538 default_platforms = self.select_platforms(debug=False)
539 self.__default_platforms = frozenset(default_platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500540
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700541 def folder(self):
542 return self.__folder
Brian Silvermane6bada62014-05-04 16:16:54 -0700543 def platforms(self):
544 return self.__platforms
545 def default_platforms(self):
546 return self.__default_platforms
547
548 def download_externals(self, platforms):
549 to_download = set()
550 for architecture in PrimeProcessor.ARCHITECTURES:
Brian Silverman99895b92014-09-14 01:01:15 -0400551 pie_sanitizers = set()
Brian Silvermane6bada62014-05-04 16:16:54 -0700552 for sanitizer in PrimeProcessor.PIE_SANITIZERS:
Brian Silverman99895b92014-09-14 01:01:15 -0400553 pie_sanitizers.update(self.select_platforms(architecture=architecture,
554 sanitizer=sanitizer))
555 if platforms & pie_sanitizers:
556 to_download.add(architecture + '-fPIE')
557
Brian Silvermanc5f56952015-01-07 21:20:47 -0800558 if platforms & (self.select_platforms(architecture=architecture) -
Brian Silverman9f330492015-03-01 17:37:02 -0500559 pie_sanitizers):
Brian Silverman99895b92014-09-14 01:01:15 -0400560 to_download.add(architecture)
561
Brian Silvermane6bada62014-05-04 16:16:54 -0700562 for download_target in to_download:
563 call_download_externals(download_target)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500564
Brian Silverman452aaec2014-05-05 16:52:18 -0700565 def parse_platforms(self, platform_string):
566 if platform_string is None:
Brian Silvermane6bada62014-05-04 16:16:54 -0700567 return self.default_platforms()
568 r = self.default_platforms()
Brian Silverman452aaec2014-05-05 16:52:18 -0700569 for part in platform_string.split(','):
Brian Silverman1867aae2014-05-05 17:16:34 -0700570 if part == 'all':
571 r = self.platforms()
572 elif part[0] == '+':
Brian Silvermana29ebf92014-04-23 13:08:49 -0500573 r = r | self.select_platforms_string(part[1:])
574 elif part[0] == '-':
575 r = r - self.select_platforms_string(part[1:])
576 elif part[0] == '=':
577 r = self.select_platforms_string(part[1:])
578 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500579 selected = self.select_platforms_string(part)
Brian Silvermane6bada62014-05-04 16:16:54 -0700580 r = r - (self.platforms() - selected)
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500581 if not r:
582 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500583 return r
584
Brian Silverman452aaec2014-05-05 16:52:18 -0700585 def select_platforms(self, architecture=None, compiler=None, debug=None,
586 sanitizer=None):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500587 r = []
Brian Silvermane6bada62014-05-04 16:16:54 -0700588 for platform in self.platforms():
589 if architecture is None or platform.architecture() == architecture:
590 if compiler is None or platform.compiler() == compiler:
591 if debug is None or platform.debug() == debug:
592 if sanitizer is None or platform.sanitizer() == sanitizer:
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700593 r.append(platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500594 return set(r)
595
Brian Silverman452aaec2014-05-05 16:52:18 -0700596 def select_platforms_string(self, platforms_string):
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700597 architecture, compiler, debug, sanitizer = None, None, None, None
Brian Silverman452aaec2014-05-05 16:52:18 -0700598 for part in platforms_string.split('-'):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500599 if part in PrimeProcessor.ARCHITECTURES:
600 architecture = part
601 elif part in PrimeProcessor.COMPILERS:
602 compiler = part
603 elif part in ['debug', 'dbg']:
604 debug = True
605 elif part in ['release', 'nodebug', 'ndb']:
606 debug = False
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700607 elif part in PrimeProcessor.SANITIZERS:
608 sanitizer = part
Brian Silverman415e65d2014-06-21 22:39:28 -0700609 elif part == 'all':
610 architecture = compiler = debug = sanitizer = None
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700611 elif part == self.folder():
612 pass
Brian Silvermana29ebf92014-04-23 13:08:49 -0500613 else:
Brian Silverman452aaec2014-05-05 16:52:18 -0700614 raise Processor.UnknownPlatform(
Brian Silvermandf5348a2014-06-12 23:25:08 -0700615 '"%s" not recognized as a platform string component.' % part)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500616 return self.select_platforms(
617 architecture=architecture,
618 compiler=compiler,
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700619 debug=debug,
620 sanitizer=sanitizer)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500621
Brian Silverman9b7a6842014-05-05 16:19:11 -0700622 def check_installed(self, platforms, is_deploy):
623 packages = set(('lzip', 'm4', 'realpath'))
624 packages.add('ruby')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700625 packages.add('clang-3.5')
Brian Silverman718bca52015-01-07 21:23:36 -0800626 packages.add('clang-format-3.5')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700627 for platform in platforms:
Brian Silverman9f330492015-03-01 17:37:02 -0500628 if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
Brian Silverman9b7a6842014-05-05 16:19:11 -0700629 packages.add('clang-3.5')
Brian Silverman1867aae2014-05-05 17:16:34 -0700630 if platform.compiler() == 'gcc_4.8':
631 packages.add('libcloog-isl3:amd64')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700632 if is_deploy:
633 packages.add('openssh-client')
Brian Silverman9f330492015-03-01 17:37:02 -0500634 elif platform.architecture == 'arm_frc':
Brian Silvermanbae86d62014-09-14 01:05:31 -0400635 packages.add('gcc-4.9-arm-frc-linux-gnueabi')
636 packages.add('g++-4.9-arm-frc-linux-gnueabi')
Brian Silverman9b7a6842014-05-05 16:19:11 -0700637
638 self.do_check_installed(tuple(packages))
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700639
Brian Silverman6bca4722014-05-20 17:02:49 -0700640def strsignal(num):
641 # It ends up with SIGIOT instead otherwise, which is weird.
642 if num == signal.SIGABRT:
643 return 'SIGABRT'
644 # SIGCLD is a weird way to spell it.
645 if num == signal.SIGCHLD:
646 return 'SIGCHLD'
647
648 SIGNALS_TO_NAMES = dict((getattr(signal, n), n)
649 for n in dir(signal) if n.startswith('SIG')
650 and '_' not in n)
651 return SIGNALS_TO_NAMES.get(num, 'Unknown signal %d' % num)
652
Brian Silvermana29ebf92014-04-23 13:08:49 -0500653def main():
Brian Silvermandf5348a2014-06-12 23:25:08 -0700654 sys.argv.pop(0)
655 exec_name = sys.argv.pop(0)
656 def print_help(exit_status=None, message=None):
657 if message:
658 print(message)
659 sys.stdout.write(
Brian Silvermanb9e89602014-06-27 14:21:08 -0500660"""Usage: {name} [-j n] [action] [-n] [platform] [target|extra_flag]...
Brian Silvermandf5348a2014-06-12 23:25:08 -0700661Arguments:
662 -j, --jobs Explicitly specify how many jobs to run at a time.
663 Defaults to the number of processors + 2.
664 -n, --dry-run Don't actually do whatever.
665 Currently only meaningful for deploy.
666 action What to do. Defaults to build.
667 build: Build the code.
668 clean: Remove all the built output.
669 tests: Build and then run tests.
670 deploy: Build and then download.
Brian Silverman2c7564a2015-04-03 04:21:50 -0400671 environment: Dump the environment for building.
Brian Silvermandf5348a2014-06-12 23:25:08 -0700672 platform What variants of the code to build.
673 Defaults to something reasonable.
674 See below for details.
675 target... Which targets to build/test/etc.
676 Defaults to everything.
Brian Silvermanb9e89602014-06-27 14:21:08 -0500677 extra_flag... Extra flags associated with the targets.
678 --gtest_*: Arguments to pass on to tests.
Brian Silverman09480362015-03-29 17:42:24 -0400679 --print_logs, --log_file=*: More test arguments.
Brian Silvermana29ebf92014-04-23 13:08:49 -0500680
Brian Silvermandf5348a2014-06-12 23:25:08 -0700681Specifying targets:
682 Targets are combinations of architecture, compiler, and debug flags. Which
683 ones actually get run is built up as a set. It defaults to something
684 reasonable for the action (specified below).
685 The platform specification (the argument given to this script) is a comma-
686 separated sequence of hyphen-separated platforms, each with an optional
687 prefix.
688 Each selector (the things separated by commas) selects all of the platforms
689 which match all of its components. Its effect on the set of current platforms
690 depends on the prefix character.
691 Here are the prefix characters:
692 + Adds the selected platforms.
693 - Removes the selected platforms.
694 = Sets the current set to the selected platforms.
695 [none] Removes all non-selected platforms.
696 If this makes the current set empty, acts like =.
697 There is also the special psuedo-platform "all" which selects all platforms.
698 All of the available platforms:
699 {all_platforms}
700 Default platforms for deploying:
701 {deploy_platforms}
702 Default platforms for testing:
703 {test_platforms}
704 Default platforms for everything else:
705 {default_platforms}
Brian Silvermana29ebf92014-04-23 13:08:49 -0500706
Brian Silvermandf5348a2014-06-12 23:25:08 -0700707Examples of specifying targets:
708 build everything: "all"
709 only build things with clang: "clang"
710 build everything that uses GCC 4.8 (not just the defaults): "=gcc_4.8"
711 build all of the arm targets that use clang: "clang-arm" or "arm-clang"
712""".format(
713 name=exec_name,
Brian Silvermana0b281a2015-10-10 18:18:13 -0400714 all_platforms=str_platforms(PrimeProcessor('folder', False, False).platforms()),
715 deploy_platforms=str_platforms(PrimeProcessor('folder', False, True).default_platforms()),
716 test_platforms=str_platforms(PrimeProcessor('folder', True, False).default_platforms()),
717 default_platforms=str_platforms(PrimeProcessor('folder', False, False).default_platforms()),
Brian Silvermandf5348a2014-06-12 23:25:08 -0700718 ))
719 if exit_status is not None:
720 sys.exit(exit_status)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500721
Brian Silvermandf5348a2014-06-12 23:25:08 -0700722 def sort_platforms(platforms):
723 return sorted(
724 platforms, key=lambda platform: (-platform.priority(), str(platform)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500725
Brian Silvermandf5348a2014-06-12 23:25:08 -0700726 def str_platforms(platforms):
727 r = []
728 for platform in sort_platforms(platforms):
729 r.append(str(platform))
730 if len(r) > 1:
731 r[-1] = 'and ' + r[-1]
732 return ', '.join(r)
Brian Silverman20141f92015-01-05 17:39:01 -0800733
Brian Silvermandf5348a2014-06-12 23:25:08 -0700734 class Arguments(object):
735 def __init__(self):
736 self.jobs = os.sysconf('SC_NPROCESSORS_ONLN') + 2
737 self.action_name = 'build'
738 self.dry_run = False
739 self.targets = []
740 self.platform = None
Brian Silvermanb9e89602014-06-27 14:21:08 -0500741 self.extra_flags = []
Brian Silvermana29ebf92014-04-23 13:08:49 -0500742
Brian Silvermandf5348a2014-06-12 23:25:08 -0700743 args = Arguments()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500744
Brian Silvermandf5348a2014-06-12 23:25:08 -0700745 if len(sys.argv) < 2:
746 print_help(1, 'Not enough arguments')
747 args.processor = sys.argv.pop(0)
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700748 args.folder = sys.argv.pop(0)
Brian Silvermandf5348a2014-06-12 23:25:08 -0700749 args.main_gyp = sys.argv.pop(0)
Brian Silverman2c7564a2015-04-03 04:21:50 -0400750 VALID_ACTIONS = ['build', 'clean', 'deploy', 'tests', 'environment']
Brian Silvermandf5348a2014-06-12 23:25:08 -0700751 while sys.argv:
752 arg = sys.argv.pop(0)
753 if arg == '-j' or arg == '--jobs':
754 args.jobs = int(sys.argv.pop(0))
755 continue
756 if arg in VALID_ACTIONS:
757 args.action_name = arg
758 continue
759 if arg == '-n' or arg == '--dry-run':
760 if args.action_name != 'deploy':
761 print_help(1, '--dry-run is only valid for deploy')
762 args.dry_run = True
763 continue
764 if arg == '-h' or arg == '--help':
765 print_help(0)
Brian Silverman09480362015-03-29 17:42:24 -0400766 if (re.match('^--gtest_.*$', arg) or arg == '--print-logs' or
767 re.match('^--log_file=.*$', arg)):
Brian Silvermanb9e89602014-06-27 14:21:08 -0500768 if args.action_name == 'tests':
769 args.extra_flags.append(arg)
770 continue
771 else:
Brian Silverman09480362015-03-29 17:42:24 -0400772 print_help(1, '%s is only valid for tests' % arg)
Brian Silvermandf5348a2014-06-12 23:25:08 -0700773 if args.platform:
774 args.targets.append(arg)
775 else:
776 args.platform = arg
Brian Silvermana29ebf92014-04-23 13:08:49 -0500777
Brian Silverman20141f92015-01-05 17:39:01 -0800778 if args.processor == 'prime':
Brian Silvermanb691f5e2015-08-02 11:37:55 -0700779 processor = PrimeProcessor(args.folder,
780 args.action_name == 'tests',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500781 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500782 else:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700783 print_help(1, message='Unknown processor "%s".' % args.processor)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500784
Brian Silvermana29ebf92014-04-23 13:08:49 -0500785 unknown_platform_error = None
786 try:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700787 platforms = processor.parse_platforms(args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500788 except Processor.UnknownPlatform as e:
789 unknown_platform_error = e.message
Brian Silvermandf5348a2014-06-12 23:25:08 -0700790 args.targets.insert(0, args.platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500791 platforms = processor.parse_platforms(None)
792 if not platforms:
Brian Silvermandf5348a2014-06-12 23:25:08 -0700793 print_help(1, 'No platforms selected')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500794
Brian Silverman9b7a6842014-05-05 16:19:11 -0700795 processor.check_installed(platforms, args.action_name == 'deploy')
Brian Silvermane6bada62014-05-04 16:16:54 -0700796 processor.download_externals(platforms)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500797
798 class ToolsConfig(object):
799 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500800 self.variables = {'AOS': aos_path()}
801 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500802 for line in f:
803 if line[0] == '#':
804 pass
805 elif line.isspace():
806 pass
807 else:
808 new_name, new_value = line.rstrip().split('=')
809 for name, value in self.variables.items():
810 new_value = new_value.replace('${%s}' % name, value)
811 self.variables[new_name] = new_value
812 def __getitem__(self, key):
813 return self.variables[key]
814
815 tools_config = ToolsConfig()
816
817 def handle_clean_error(function, path, excinfo):
Brian Silverman452aaec2014-05-05 16:52:18 -0700818 _, _ = function, path
Brian Silvermana29ebf92014-04-23 13:08:49 -0500819 if issubclass(OSError, excinfo[0]):
820 if excinfo[1].errno == errno.ENOENT:
821 # Who cares if the file we're deleting isn't there?
822 return
823 raise excinfo[1]
824
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700825 def need_to_run_gyp(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700826 """Determines if we need to run gyp again or not.
827
828 The generated build files are supposed to re-run gyp again themselves, but
829 that doesn't work (or at least it used to not) and we sometimes want to
830 modify the results anyways.
831
832 Args:
833 platform: The platform to check for.
834 """
Brian Silvermanf0d3c782014-05-02 23:56:32 -0700835 if not os.path.exists(platform.build_ninja()):
836 return True
Brian Silvermane6bada62014-05-04 16:16:54 -0700837 if os.path.getmtime(__file__) > os.path.getmtime(platform.build_ninja()):
838 return True
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700839 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700840 # Looking through these folders takes a long time and isn't useful.
Brian Silverman452aaec2014-05-05 16:52:18 -0700841 if dirs.count('output'):
842 dirs.remove('output')
843 if dirs.count('.git'):
844 dirs.remove('.git')
Brian Silvermana4aff562014-05-02 17:43:50 -0700845 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700846 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
Brian Silverman452aaec2014-05-05 16:52:18 -0700847 + ('-newer', platform.build_ninja(),
848 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700849 stdin=open(os.devnull, 'r'))
850
851 def env(platform):
Brian Silvermane6bada62014-05-04 16:16:54 -0700852 """Makes sure we pass through important environmental variables.
853
854 Returns:
855 An environment suitable for passing to subprocess.Popen and friends.
856 """
Brian Silvermana4aff562014-05-02 17:43:50 -0700857 build_env = dict(platform.build_env())
Brian Silvermand3fac732014-05-03 16:03:46 -0700858 if not 'TERM' in build_env:
859 build_env['TERM'] = os.environ['TERM']
860 if not 'PATH' in build_env:
861 build_env['PATH'] = os.environ['PATH']
Brian Silvermana4aff562014-05-02 17:43:50 -0700862 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700863
Brian Silvermandf5348a2014-06-12 23:25:08 -0700864 sorted_platforms = sort_platforms(platforms)
865 user_output('Building %s...' % str_platforms(sorted_platforms))
Brian Silverman47cd6f62014-05-03 10:35:52 -0700866
867 if args.action_name == 'tests':
868 for sanitizer, warning in PrimeProcessor.SANITIZER_TEST_WARNINGS.items():
869 warned_about = platforms & processor.select_platforms(sanitizer=sanitizer)
870 if warned_about:
871 user_output(warning[1])
872 if warning[0]:
Brian Silvermane6bada62014-05-04 16:16:54 -0700873 # TODO(brians): Add a --force flag or something to override this?
Brian Silverman47cd6f62014-05-03 10:35:52 -0700874 user_output('Refusing to run tests for sanitizer %s.' % sanitizer)
875 exit(1)
876
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700877 num = 1
Brian Silvermanbd380fd2014-05-13 16:55:24 -0700878 for platform in sorted_platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700879 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500880 if args.action_name == 'clean':
881 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
Brian Silverman2c7564a2015-04-03 04:21:50 -0400882 elif args.action_name == 'environment':
883 user_output('Environment for building <<END')
884 for name, value in env(platform).items():
885 print('%s=%s' % (name, value))
886 print('END')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500887 else:
888 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700889 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500890 gyp = subprocess.Popen(
891 (tools_config['GYP'],
892 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500893 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500894 '--no-circular-check',
895 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500896 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500897 '-I/dev/stdin', '-Goutput_dir=output',
898 '-DOS=%s' % platform.os(),
899 '-DPLATFORM=%s' % platform.gyp_platform(),
Brian Silvermane6bada62014-05-04 16:16:54 -0700900 '-DARCHITECTURE=%s' % platform.architecture(),
901 '-DCOMPILER=%s' % platform.compiler().split('_')[0],
902 '-DFULL_COMPILER=%s' % platform.compiler(),
903 '-DDEBUG=%s' % ('yes' if platform.debug() else 'no'),
904 '-DSANITIZER=%s' % platform.sanitizer(),
Brian Silverman99895b92014-09-14 01:01:15 -0400905 '-DEXTERNALS_EXTRA=%s' %
Brian Silverman452aaec2014-05-05 16:52:18 -0700906 ('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
Brian Silverman9f330492015-03-01 17:37:02 -0500907 else '')) +
Brian Silvermanb3d50542014-04-23 14:28:55 -0500908 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500909 stdin=subprocess.PIPE)
910 gyp.communicate(("""
911{
912 'target_defaults': {
913 'configurations': {
914 '%s': {}
915 }
916 }
917}""" % platform.outname()).encode())
918 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700919 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500920 exit(1)
Brian Silvermane6bada62014-05-04 16:16:54 -0700921 processor.modify_ninja_file(platform.build_ninja())
Brian Silverman47cd6f62014-05-03 10:35:52 -0700922 user_output('Done running gyp')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500923 else:
Brian Silverman47cd6f62014-05-03 10:35:52 -0700924 user_output("Not running gyp")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500925
926 try:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700927 call = (tools_config['NINJA'],
Brian Silvermandf5348a2014-06-12 23:25:08 -0700928 '-C', platform.outdir()) + tuple(args.targets)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700929 if args.jobs:
930 call += ('-j', str(args.jobs))
931 subprocess.check_call(call,
Brian Silverman452aaec2014-05-05 16:52:18 -0700932 stdin=open(os.devnull, 'r'),
933 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500934 except subprocess.CalledProcessError as e:
935 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700936 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500937 raise e
938
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500939 if args.action_name == 'deploy':
940 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700941 elif args.action_name == 'tests':
942 dirname = os.path.join(platform.outdir(), 'tests')
Brian Silvermanc3740c32014-05-04 12:42:47 -0700943 done_queue = queue.Queue()
944 running = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700945 test_start_semaphore = threading.Semaphore(args.jobs)
946 if args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700947 to_run = []
Brian Silvermandf5348a2014-06-12 23:25:08 -0700948 for target in args.targets:
Brian Silvermanc3740c32014-05-04 12:42:47 -0700949 if target.endswith('_test'):
950 to_run.append(target)
951 else:
952 to_run = os.listdir(dirname)
953 for f in to_run:
Brian Silvermanb9e89602014-06-27 14:21:08 -0500954 thread = TestThread(os.path.join(dirname, f), tuple(args.extra_flags),
955 env(platform), done_queue,
Brian Silvermanc3740c32014-05-04 12:42:47 -0700956 test_start_semaphore)
957 running.append(thread)
958 thread.start()
959 try:
960 while running:
961 done = done_queue.get()
962 running.remove(done)
963 with test_output_lock:
964 test_output('Output from test %s:' % done.name)
Brian Silverman730bb012014-06-08 13:05:20 -0700965 try:
966 while True:
967 line = done.output.get(False)
968 if not sys.stdout.isatty():
969 # Remove color escape codes.
970 line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
971 sys.stdout.write(line)
972 except queue.Empty:
973 pass
974 if not done.returncode:
975 test_output('Test %s succeeded' % done.name)
976 else:
977 if done.returncode < 0:
978 sig = -done.returncode
979 test_output('Test %s was killed by signal %d (%s)' % \
980 (done.name, sig, strsignal(sig)))
981 elif done.returncode != 1:
982 test_output('Test %s exited with %d' % \
983 (done.name, done.returncode))
Brian Silvermanf2bbe092014-05-13 16:55:03 -0700984 else:
Brian Silverman730bb012014-06-08 13:05:20 -0700985 test_output('Test %s failed' % done.name)
986 user_output('Aborting because of test failure for %s.' % \
987 platform)
988 exit(1)
Brian Silvermanc3740c32014-05-04 12:42:47 -0700989 finally:
990 if running:
991 test_output('Killing other tests...')
Brian Silvermanbf0e1db2014-05-10 22:13:15 -0700992# Stop all of them before killing processes because otherwise stopping some of
993# them tends to let other ones that are waiting to start go.
994 for thread in running:
995 thread.stop()
Brian Silvermanc3740c32014-05-04 12:42:47 -0700996 for thread in running:
Brian Silverman48766e42014-12-29 21:37:04 -0800997 test_output('\tKilling %s' % thread.name)
998 thread.kill_process()
999 thread.kill_process()
1000 test_output('Waiting for other tests to die')
Brian Silvermanc3740c32014-05-04 12:42:47 -07001001 for thread in running:
Brian Silvermanc3740c32014-05-04 12:42:47 -07001002 thread.kill_process()
1003 thread.join()
1004 test_output('Done killing other tests')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -05001005
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -07001006 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
1007 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -05001008
1009if __name__ == '__main__':
1010 main()