blob: b954379766257481cbdb52061e4aac854e3075ce [file] [log] [blame]
Brian Silvermana29ebf92014-04-23 13:08:49 -05001#!/usr/bin/python3
2
3import argparse
4import sys
5import subprocess
6import re
7import os
8import os.path
9import string
10import shutil
11import errno
12
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050013def aos_path():
14 return os.path.join(os.path.dirname(__file__), '..')
15
16def get_ip(device):
17 FILENAME = os.path.normpath(os.path.join(aos_path(), '..', 'output', 'ip_base.txt'))
18 if not os.access(FILENAME, os.R_OK):
19 os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
20 with open(FILENAME, 'w') as f:
21 f.write('10.9.71')
22 with open(FILENAME, 'r') as f:
23 base = f.readline()
24 if device == 'prime':
25 return base + '.179'
26 elif device == 'robot':
27 return base + '.2'
28 else:
29 raise Exception('Unknown device %s to get an IP address for.' % device)
30
Brian Silvermana9b1e5c2014-04-30 18:08:04 -070031def user_output(message):
32 print('build.py: ' + message, file=sys.stderr)
33
Brian Silvermana29ebf92014-04-23 13:08:49 -050034class Processor(object):
35 class UnknownPlatform(Exception):
36 def __init__(self, message):
37 self.message = message
38
Brian Silvermanb3d50542014-04-23 14:28:55 -050039 class Platform(object):
40 def outdir(self):
41 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050042 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -050043 def build_ninja(self):
44 return os.path.join(self.outdir(), 'build.ninja')
45
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050046 def do_deploy(self, dry_run, command):
47 real_command = (('echo',) + command) if dry_run else command
48 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -050049
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -070050 # TODO(brians): Verify that this (and its callers) catch everything from a
51 # fresh install.
52 def do_check_installed(self, other_packages):
53 all_packages = () + other_packages
54 try:
55 result = subprocess.check_output(
56 ('dpkg-query', '--show') + all_packages,
57 stdin=open(os.devnull, 'r'),
58 stderr=subprocess.STDOUT)
59 except subprocess.CalledProcessError as e:
60 user_output('Some packages not installed:\n'
61 + e.output.decode('utf-8').rstrip())
62 exit(1)
63
Brian Silvermana29ebf92014-04-23 13:08:49 -050064class CRIOProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -050065 class Platform(Processor.Platform):
Brian Silvermana4aff562014-05-02 17:43:50 -070066 def __init__(self, debug, wind_base):
Brian Silvermanb3d50542014-04-23 14:28:55 -050067 super(CRIOProcessor.Platform, self).__init__()
68
69 self.debug = debug
Brian Silvermana4aff562014-05-02 17:43:50 -070070 self.wind_base = wind_base
Brian Silvermanb3d50542014-04-23 14:28:55 -050071
72 def __repr__(self):
73 return 'CRIOProcessor.Platform(debug=%s)' % self.debug
74 def __str__(self):
75 return 'crio%s' % ('-debug' if self.debug else '')
76
77 def outname(self):
78 return 'crio-debug' if self.debug else 'crio'
79 def os(self):
80 return 'vxworks'
81 def gyp_platform(self):
82 return 'crio'
83 def architecture(self):
84 return 'ppc'
85 def compiler(self):
86 return 'gcc'
87
Brian Silvermane48c09a2014-04-30 18:04:58 -070088 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050089 def deploy(self, dry_run):
90 self.do_deploy(dry_run,
91 ('ncftpput', get_ip('robot'), '/',
92 os.path.join(self.outdir(), 'lib', 'FRC_UserProgram.out')))
93
Brian Silvermana4aff562014-05-02 17:43:50 -070094 def build_env(self):
95 return {'WIND_BASE': self.wind_base}
96
Brian Silvermana29ebf92014-04-23 13:08:49 -050097 def __init__(self):
98 super(CRIOProcessor, self).__init__()
99
100 if 'WIND_BASE' in os.environ:
101 self.wind_base = os.environ['WIND_BASE']
102 else:
103 self.wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
104
105 def parse_platforms(self, string):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500106 if string is None or string == 'crio':
Brian Silvermana4aff562014-05-02 17:43:50 -0700107 return (CRIOProcessor.Platform(False, self.wind_base),)
108 elif string == 'crio-debug' or string == 'debug':
109 return (CRIOProcessor.Platform(True, self.wind_base),)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500110 else:
111 raise Processor.UnknownPlatform('Unknown cRIO platform "%s".' % string)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500112
Brian Silvermanb3d50542014-04-23 14:28:55 -0500113 def extra_gyp_flags(self):
114 return ('-DWIND_BASE=%s' % self.wind_base,)
115
Brian Silvermana29ebf92014-04-23 13:08:49 -0500116 def is_crio(self): return True
117
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700118 def check_installed(self):
119 # TODO(brians): Add powerpc-wrs-vxworks (a new enough version too).
120 self.do_check_installed(
121 ('ncftp',))
122
Brian Silvermana29ebf92014-04-23 13:08:49 -0500123class PrimeProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500124 class Platform(Processor.Platform):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500125 def __init__(self, architecture, compiler, debug):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500126 super(PrimeProcessor.Platform, self).__init__()
127
Brian Silvermana29ebf92014-04-23 13:08:49 -0500128 self.architecture = architecture
129 self.compiler = compiler
130 self.debug = debug
131
132 def __repr__(self):
133 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s)' \
134 % (self.architecture, self.compiler, self.debug)
135 def __str__(self):
136 return '%s-%s%s' % (self.architecture, self.compiler,
137 '-debug' if self.debug else '')
138
139 def os(self):
140 return 'linux'
141 def gyp_platform(self):
142 return '%s-%s-%s' % (self.os(), self.architecture, self.compiler)
143
Brian Silvermana29ebf92014-04-23 13:08:49 -0500144 def outname(self):
145 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500146
Brian Silvermane48c09a2014-04-30 18:04:58 -0700147 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500148 def deploy(self, dry_run):
149 """Downloads code to the prime in a way that avoids clashing too badly with starter
150 """
151 SUM = 'md5sum'
152 TARGET_DIR = '/home/driver/robot_code/bin'
153 TEMP_DIR = '/tmp/aos_downloader'
154 TARGET = 'driver@' + get_ip('prime')
155
156 from_dir = os.path.join(self.outdir(), 'outputs')
157 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
158 stdin=open(os.devnull, 'r'),
159 cwd=from_dir)
160 to_download = subprocess.check_output(
161 ('ssh', TARGET,
162 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
163 && echo '{SUMS}' | {SUM} --check --quiet
164 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".format(
165 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
166 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700167 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500168 return
169 self.do_deploy(
170 dry_run,
171 ('scp', '-o', 'Compression yes') + to_download
172 + (('%s:%s' % (TARGET, TEMP_DIR)),))
173 if not dry_run:
174 subprocess.check_call(
175 ('ssh', TARGET,
176 """mv {TMPDIR}/* {TO_DIR}
177 && echo 'Done moving new executables into place'
178 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
179 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
180
Brian Silvermana4aff562014-05-02 17:43:50 -0700181 def build_env(self):
182 r = {}
183 if self.compiler == 'clang':
184 r['LD_LIBRARY_PATH'] = '/opt/clang-3.5/lib64'
185 r['ASAN_SYMBOLIZER_PATH'] = '/opt/clang-3.5/bin/llvm-symbolizer'
186 r['ASAN_OPTIONS'] = 'detect_leaks=1:check_initialization_order=1:strict_init_order=1'
187 return r
188
Brian Silvermana29ebf92014-04-23 13:08:49 -0500189 ARCHITECTURES = ['arm', 'amd64']
190 COMPILERS = ['clang', 'gcc']
191
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500192 def __init__(self, is_test, is_deploy):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500193 super(Processor, self).__init__()
194
195 platforms = []
196 for architecture in PrimeProcessor.ARCHITECTURES:
197 for compiler in PrimeProcessor.COMPILERS:
198 for debug in [True, False]:
199 platforms.append(
200 PrimeProcessor.Platform(architecture, compiler, debug))
201 self.platforms = frozenset(platforms)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500202 if is_test:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700203 self.default_platforms = self.select_platforms(architecture='amd64',
204 debug=True)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500205 elif is_deploy:
206 # TODO(brians): Switch to deploying the code built with clang.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700207 self.default_platforms = self.select_platforms(architecture='arm',
208 compiler='gcc',
209 debug=False)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500210 else:
211 self.default_platforms = self.select_platforms(debug=False)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500212
Brian Silvermanb3d50542014-04-23 14:28:55 -0500213 def extra_gyp_flags(self):
214 return ()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500215 def is_crio(self): return False
216
217 def parse_platforms(self, string):
218 if string is None:
219 return self.default_platforms
220 r = self.default_platforms
221 for part in string.split(','):
222 if part[0] == '+':
223 r = r | self.select_platforms_string(part[1:])
224 elif part[0] == '-':
225 r = r - self.select_platforms_string(part[1:])
226 elif part[0] == '=':
227 r = self.select_platforms_string(part[1:])
228 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500229 selected = self.select_platforms_string(part)
230 r = r - (self.platforms - selected)
231 if not r:
232 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500233 return r
234
235 def select_platforms(self, architecture=None, compiler=None, debug=None):
236 r = []
237 for platform in self.platforms:
238 if architecture is None or platform.architecture == architecture:
239 if compiler is None or platform.compiler == compiler:
240 if debug is None or platform.debug == debug:
241 r.append(platform)
242 return set(r)
243
244 def select_platforms_string(self, string):
245 r = []
246 architecture, compiler, debug = None, None, None
247 for part in string.split('-'):
248 if part in PrimeProcessor.ARCHITECTURES:
249 architecture = part
250 elif part in PrimeProcessor.COMPILERS:
251 compiler = part
252 elif part in ['debug', 'dbg']:
253 debug = True
254 elif part in ['release', 'nodebug', 'ndb']:
255 debug = False
256 else:
257 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
258 return self.select_platforms(
259 architecture=architecture,
260 compiler=compiler,
261 debug=debug)
262
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700263 def check_installed(self):
264 self.do_check_installed(
265 ('clang-3.5', 'gcc-4.7-arm-linux-gnueabihf',
266 'g++-4.7-arm-linux-gnueabihf', 'openssh-client'))
267
Brian Silvermana29ebf92014-04-23 13:08:49 -0500268def main():
269 class TryParsingAgain(Exception):
270 pass
271
272 class TryAgainArgumentParser(argparse.ArgumentParser):
273 def __init__(self, **kwargs):
274 super(TryAgainArgumentParser, self).__init__(**kwargs)
275
276 def error(self, message):
277 raise TryParsingAgain
278
279 def SetUpParser(parser, args):
280 def AddBuildArgs(parser):
281 parser.add_argument(
282 'target',
283 help='target to build',
284 nargs='*')
285 def AddCommonArgs(parser):
286 parser.add_argument(
287 'platforms',
288 help='platform(s) to act on',
289 nargs='?')
290
291 parser.add_argument('--processor', required=True, help='prime or crio')
292 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
293 subparsers = parser.add_subparsers(dest='action_name')
294
295 build_parser = subparsers.add_parser(
296 'build',
297 help='build the code (default)')
298 AddCommonArgs(build_parser)
299 AddBuildArgs(build_parser)
300
301 clean_parser = subparsers.add_parser(
302 'clean',
303 help='remove all output directories')
304 AddCommonArgs(clean_parser)
305
306 deploy_parser = subparsers.add_parser(
307 'deploy',
308 help='build and download the code')
309 AddCommonArgs(deploy_parser)
310 AddBuildArgs(deploy_parser)
311 deploy_parser.add_argument(
312 '-n', '--dry-run',
313 help="don't actually download anything",
314 action='store_true')
315
Brian Silvermane48c09a2014-04-30 18:04:58 -0700316 tests_parser = subparsers.add_parser(
317 'tests',
318 help='run tests')
319 AddCommonArgs(tests_parser)
320 AddBuildArgs(tests_parser)
321
Brian Silvermana29ebf92014-04-23 13:08:49 -0500322 return parser.parse_args(args)
323
324 try:
325 parser = TryAgainArgumentParser()
326 args = SetUpParser(parser, sys.argv[1:])
327 except TryParsingAgain:
328 parser = argparse.ArgumentParser()
329 REQUIRED_ARGS_END = 5
330 args = SetUpParser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
331 sys.argv[(REQUIRED_ARGS_END):])
332
333 if args.processor == 'crio':
334 processor = CRIOProcessor()
335 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500336 processor = PrimeProcessor(args.action_name == 'tests',
337 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500338 else:
339 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700340 processor.check_installed()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500341
342 if 'target' in args:
343 targets = args.target[:]
344 else:
345 targets = []
346 unknown_platform_error = None
347 try:
348 platforms = processor.parse_platforms(args.platforms)
349 except Processor.UnknownPlatform as e:
350 unknown_platform_error = e.message
351 targets.append(args.platforms)
352 platforms = processor.parse_platforms(None)
353 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700354 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500355 exit(1)
356
357 def download_externals(argument):
358 subprocess.check_call(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500359 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500360 argument),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500361 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500362
363 if processor.is_crio():
364 download_externals('crio')
365 else:
366 for architecture in PrimeProcessor.ARCHITECTURES:
367 if platforms & processor.select_platforms(architecture=architecture):
368 download_externals(architecture)
369
370 class ToolsConfig(object):
371 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500372 self.variables = {'AOS': aos_path()}
373 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500374 for line in f:
375 if line[0] == '#':
376 pass
377 elif line.isspace():
378 pass
379 else:
380 new_name, new_value = line.rstrip().split('=')
381 for name, value in self.variables.items():
382 new_value = new_value.replace('${%s}' % name, value)
383 self.variables[new_name] = new_value
384 def __getitem__(self, key):
385 return self.variables[key]
386
387 tools_config = ToolsConfig()
388
389 def handle_clean_error(function, path, excinfo):
390 if issubclass(OSError, excinfo[0]):
391 if excinfo[1].errno == errno.ENOENT:
392 # Who cares if the file we're deleting isn't there?
393 return
394 raise excinfo[1]
395
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700396 def need_to_run_gyp(platform):
397 dirs = os.listdir(os.path.join(aos_path(), '..'))
Brian Silvermana4aff562014-05-02 17:43:50 -0700398 # Looking through these folders takes a long time and isn't useful.
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700399 dirs.remove('output')
Brian Silvermana4aff562014-05-02 17:43:50 -0700400 dirs.remove('.git')
401 return not not subprocess.check_output(
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700402 ('find',) + tuple(os.path.join(aos_path(), '..', d) for d in dirs)
403 + ('-newer', platform.build_ninja(),
404 '(', '-name', '*.gyp', '-or', '-name', '*.gypi', ')'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700405 stdin=open(os.devnull, 'r'))
406
407 def env(platform):
408 build_env = dict(platform.build_env())
409 build_env['TERM'] = os.environ['TERM']
410 build_env['PATH'] = os.environ['PATH']
411 return build_env
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700412
413 num = 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500414 for platform in platforms:
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700415 user_output('Building %s (%d/%d)...' % (platform, num, len(platforms)))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500416 if args.action_name == 'clean':
417 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
418 else:
419 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700420 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500421 gyp = subprocess.Popen(
422 (tools_config['GYP'],
423 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500424 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500425 '--no-circular-check',
426 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500427 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500428 '-I/dev/stdin', '-Goutput_dir=output',
429 '-DOS=%s' % platform.os(),
430 '-DPLATFORM=%s' % platform.gyp_platform(),
431 '-DARCHITECTURE=%s' % platform.architecture,
432 '-DCOMPILER=%s' % platform.compiler,
Brian Silvermanb3d50542014-04-23 14:28:55 -0500433 '-DDEBUG=%s' % ('yes' if platform.debug else 'no')) +
434 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500435 stdin=subprocess.PIPE)
436 gyp.communicate(("""
437{
438 'target_defaults': {
439 'configurations': {
440 '%s': {}
441 }
442 }
443}""" % platform.outname()).encode())
444 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700445 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500446 exit(1)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500447 if processor.is_crio():
448 subprocess.check_call(
449 ('sed', '-i',
450 's/nm -gD/nm/g', platform.build_ninja()),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500451 stdin=open(os.devnull, 'r'))
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700452 user_output('Done running gyp.')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500453 else:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700454 user_output("Not running gyp.")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500455
456 try:
457 subprocess.check_call(
458 (tools_config['NINJA'],
459 '-C', platform.outdir()) + tuple(targets),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500460 stdin=open(os.devnull, 'r'),
Brian Silvermana4aff562014-05-02 17:43:50 -0700461 env=env(platform))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500462 except subprocess.CalledProcessError as e:
463 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700464 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500465 raise e
466
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500467 if args.action_name == 'deploy':
468 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700469 elif args.action_name == 'tests':
470 dirname = os.path.join(platform.outdir(), 'tests')
471 for f in targets or os.listdir(dirname):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700472 user_output('Running test %s...' % f)
Brian Silvermana4aff562014-05-02 17:43:50 -0700473 subprocess.check_call(
474 os.path.join(dirname, f),
475 env=env(platform))
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700476 user_output('Test %s succeeded' % f)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500477
Brian Silvermanc2d8e5a2014-05-01 18:33:12 -0700478 user_output('Done building %s (%d/%d)' % (platform, num, len(platforms)))
479 num += 1
Brian Silvermana29ebf92014-04-23 13:08:49 -0500480
481if __name__ == '__main__':
482 main()