blob: dd93026ad272650b001c5ec5fe25979059a1940f [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 Silvermana29ebf92014-04-23 13:08:49 -050031class Processor(object):
32 class UnknownPlatform(Exception):
33 def __init__(self, message):
34 self.message = message
35
Brian Silvermanb3d50542014-04-23 14:28:55 -050036 class Platform(object):
37 def outdir(self):
38 return os.path.join(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050039 aos_path(), '..', 'output', self.outname())
Brian Silvermanb3d50542014-04-23 14:28:55 -050040 def build_ninja(self):
41 return os.path.join(self.outdir(), 'build.ninja')
42
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050043 def do_deploy(self, dry_run, command):
44 real_command = (('echo',) + command) if dry_run else command
45 subprocess.check_call(real_command, stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -050046
47class CRIOProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -050048 class Platform(Processor.Platform):
49 def __init__(self, debug):
50 super(CRIOProcessor.Platform, self).__init__()
51
52 self.debug = debug
53
54 def __repr__(self):
55 return 'CRIOProcessor.Platform(debug=%s)' % self.debug
56 def __str__(self):
57 return 'crio%s' % ('-debug' if self.debug else '')
58
59 def outname(self):
60 return 'crio-debug' if self.debug else 'crio'
61 def os(self):
62 return 'vxworks'
63 def gyp_platform(self):
64 return 'crio'
65 def architecture(self):
66 return 'ppc'
67 def compiler(self):
68 return 'gcc'
69
Brian Silvermane48c09a2014-04-30 18:04:58 -070070 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050071 def deploy(self, dry_run):
72 self.do_deploy(dry_run,
73 ('ncftpput', get_ip('robot'), '/',
74 os.path.join(self.outdir(), 'lib', 'FRC_UserProgram.out')))
75
Brian Silvermana29ebf92014-04-23 13:08:49 -050076 def __init__(self):
77 super(CRIOProcessor, self).__init__()
78
79 if 'WIND_BASE' in os.environ:
80 self.wind_base = os.environ['WIND_BASE']
81 else:
82 self.wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
83
84 def parse_platforms(self, string):
Brian Silvermanb3d50542014-04-23 14:28:55 -050085 if string is None or string == 'crio':
86 return (CRIOProcessor.Platform(False),)
87 elif string == 'crio-debug':
88 return (CRIOProcessor.Platform(True),)
89 else:
90 raise Processor.UnknownPlatform('Unknown cRIO platform "%s".' % string)
Brian Silvermana29ebf92014-04-23 13:08:49 -050091
92 def build_env(self):
93 return {'WIND_BASE': self.wind_base}
Brian Silvermanb3d50542014-04-23 14:28:55 -050094 def extra_gyp_flags(self):
95 return ('-DWIND_BASE=%s' % self.wind_base,)
96
Brian Silvermana29ebf92014-04-23 13:08:49 -050097 def is_crio(self): return True
98
99class PrimeProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500100 class Platform(Processor.Platform):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500101 def __init__(self, architecture, compiler, debug):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500102 super(PrimeProcessor.Platform, self).__init__()
103
Brian Silvermana29ebf92014-04-23 13:08:49 -0500104 self.architecture = architecture
105 self.compiler = compiler
106 self.debug = debug
107
108 def __repr__(self):
109 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s)' \
110 % (self.architecture, self.compiler, self.debug)
111 def __str__(self):
112 return '%s-%s%s' % (self.architecture, self.compiler,
113 '-debug' if self.debug else '')
114
115 def os(self):
116 return 'linux'
117 def gyp_platform(self):
118 return '%s-%s-%s' % (self.os(), self.architecture, self.compiler)
119
Brian Silvermana29ebf92014-04-23 13:08:49 -0500120 def outname(self):
121 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500122
Brian Silvermane48c09a2014-04-30 18:04:58 -0700123 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500124 def deploy(self, dry_run):
125 """Downloads code to the prime in a way that avoids clashing too badly with starter
126 """
127 SUM = 'md5sum'
128 TARGET_DIR = '/home/driver/robot_code/bin'
129 TEMP_DIR = '/tmp/aos_downloader'
130 TARGET = 'driver@' + get_ip('prime')
131
132 from_dir = os.path.join(self.outdir(), 'outputs')
133 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
134 stdin=open(os.devnull, 'r'),
135 cwd=from_dir)
136 to_download = subprocess.check_output(
137 ('ssh', TARGET,
138 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
139 && echo '{SUMS}' | {SUM} --check --quiet
140 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".format(
141 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
142 if not to_download:
143 print("Nothing to download", file=sys.stderr)
144 return
145 self.do_deploy(
146 dry_run,
147 ('scp', '-o', 'Compression yes') + to_download
148 + (('%s:%s' % (TARGET, TEMP_DIR)),))
149 if not dry_run:
150 subprocess.check_call(
151 ('ssh', TARGET,
152 """mv {TMPDIR}/* {TO_DIR}
153 && echo 'Done moving new executables into place'
154 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
155 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
156
Brian Silvermana29ebf92014-04-23 13:08:49 -0500157 ARCHITECTURES = ['arm', 'amd64']
158 COMPILERS = ['clang', 'gcc']
159
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500160 def __init__(self, is_test, is_deploy):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500161 super(Processor, self).__init__()
162
163 platforms = []
164 for architecture in PrimeProcessor.ARCHITECTURES:
165 for compiler in PrimeProcessor.COMPILERS:
166 for debug in [True, False]:
167 platforms.append(
168 PrimeProcessor.Platform(architecture, compiler, debug))
169 self.platforms = frozenset(platforms)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500170 if is_test:
171 self.default_platforms = self.select_platforms(architecture='amd64', debug=True)
172 elif is_deploy:
173 # TODO(brians): Switch to deploying the code built with clang.
174 self.default_platforms = self.select_platforms(architecture='arm', compiler='gcc', debug=False)
175 else:
176 self.default_platforms = self.select_platforms(debug=False)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500177
178 def build_env(self):
179 return {}
Brian Silvermanb3d50542014-04-23 14:28:55 -0500180 def extra_gyp_flags(self):
181 return ()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500182 def is_crio(self): return False
183
184 def parse_platforms(self, string):
185 if string is None:
186 return self.default_platforms
187 r = self.default_platforms
188 for part in string.split(','):
189 if part[0] == '+':
190 r = r | self.select_platforms_string(part[1:])
191 elif part[0] == '-':
192 r = r - self.select_platforms_string(part[1:])
193 elif part[0] == '=':
194 r = self.select_platforms_string(part[1:])
195 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500196 selected = self.select_platforms_string(part)
197 r = r - (self.platforms - selected)
198 if not r:
199 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500200 return r
201
202 def select_platforms(self, architecture=None, compiler=None, debug=None):
203 r = []
204 for platform in self.platforms:
205 if architecture is None or platform.architecture == architecture:
206 if compiler is None or platform.compiler == compiler:
207 if debug is None or platform.debug == debug:
208 r.append(platform)
209 return set(r)
210
211 def select_platforms_string(self, string):
212 r = []
213 architecture, compiler, debug = None, None, None
214 for part in string.split('-'):
215 if part in PrimeProcessor.ARCHITECTURES:
216 architecture = part
217 elif part in PrimeProcessor.COMPILERS:
218 compiler = part
219 elif part in ['debug', 'dbg']:
220 debug = True
221 elif part in ['release', 'nodebug', 'ndb']:
222 debug = False
223 else:
224 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
225 return self.select_platforms(
226 architecture=architecture,
227 compiler=compiler,
228 debug=debug)
229
230def main():
231 class TryParsingAgain(Exception):
232 pass
233
234 class TryAgainArgumentParser(argparse.ArgumentParser):
235 def __init__(self, **kwargs):
236 super(TryAgainArgumentParser, self).__init__(**kwargs)
237
238 def error(self, message):
239 raise TryParsingAgain
240
241 def SetUpParser(parser, args):
242 def AddBuildArgs(parser):
243 parser.add_argument(
244 'target',
245 help='target to build',
246 nargs='*')
247 def AddCommonArgs(parser):
248 parser.add_argument(
249 'platforms',
250 help='platform(s) to act on',
251 nargs='?')
252
253 parser.add_argument('--processor', required=True, help='prime or crio')
254 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
255 subparsers = parser.add_subparsers(dest='action_name')
256
257 build_parser = subparsers.add_parser(
258 'build',
259 help='build the code (default)')
260 AddCommonArgs(build_parser)
261 AddBuildArgs(build_parser)
262
263 clean_parser = subparsers.add_parser(
264 'clean',
265 help='remove all output directories')
266 AddCommonArgs(clean_parser)
267
268 deploy_parser = subparsers.add_parser(
269 'deploy',
270 help='build and download the code')
271 AddCommonArgs(deploy_parser)
272 AddBuildArgs(deploy_parser)
273 deploy_parser.add_argument(
274 '-n', '--dry-run',
275 help="don't actually download anything",
276 action='store_true')
277
Brian Silvermane48c09a2014-04-30 18:04:58 -0700278 tests_parser = subparsers.add_parser(
279 'tests',
280 help='run tests')
281 AddCommonArgs(tests_parser)
282 AddBuildArgs(tests_parser)
283
Brian Silvermana29ebf92014-04-23 13:08:49 -0500284 return parser.parse_args(args)
285
286 try:
287 parser = TryAgainArgumentParser()
288 args = SetUpParser(parser, sys.argv[1:])
289 except TryParsingAgain:
290 parser = argparse.ArgumentParser()
291 REQUIRED_ARGS_END = 5
292 args = SetUpParser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
293 sys.argv[(REQUIRED_ARGS_END):])
294
295 if args.processor == 'crio':
296 processor = CRIOProcessor()
297 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500298 processor = PrimeProcessor(args.action_name == 'tests',
299 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500300 else:
301 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
302
303 if 'target' in args:
304 targets = args.target[:]
305 else:
306 targets = []
307 unknown_platform_error = None
308 try:
309 platforms = processor.parse_platforms(args.platforms)
310 except Processor.UnknownPlatform as e:
311 unknown_platform_error = e.message
312 targets.append(args.platforms)
313 platforms = processor.parse_platforms(None)
314 if not platforms:
315 print("No platforms selected!", file=sys.stderr)
316 exit(1)
317
318 def download_externals(argument):
319 subprocess.check_call(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500320 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500321 argument),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500322 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500323
324 if processor.is_crio():
325 download_externals('crio')
326 else:
327 for architecture in PrimeProcessor.ARCHITECTURES:
328 if platforms & processor.select_platforms(architecture=architecture):
329 download_externals(architecture)
330
331 class ToolsConfig(object):
332 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500333 self.variables = {'AOS': aos_path()}
334 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500335 for line in f:
336 if line[0] == '#':
337 pass
338 elif line.isspace():
339 pass
340 else:
341 new_name, new_value = line.rstrip().split('=')
342 for name, value in self.variables.items():
343 new_value = new_value.replace('${%s}' % name, value)
344 self.variables[new_name] = new_value
345 def __getitem__(self, key):
346 return self.variables[key]
347
348 tools_config = ToolsConfig()
349
350 def handle_clean_error(function, path, excinfo):
351 if issubclass(OSError, excinfo[0]):
352 if excinfo[1].errno == errno.ENOENT:
353 # Who cares if the file we're deleting isn't there?
354 return
355 raise excinfo[1]
356
357 def need_to_run_gyp(platform):
358 try:
359 build_mtime = os.stat(platform.build_ninja()).st_mtime
360 except OSError as e:
361 if e.errno == errno.ENOENT:
362 return True
363 else:
364 raise e
Brian Silvermane48c09a2014-04-30 18:04:58 -0700365 pattern = re.compile('.*\.gypi?$')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500366 for dirname, _, files in os.walk(os.path.join(aos_path(), '..')):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500367 for f in [f for f in files if pattern.match(f)]:
368 if (os.stat(os.path.join(dirname, f)).st_mtime > build_mtime):
369 return True
370 return False
371
372 for platform in platforms:
373 print('Building %s...' % platform, file=sys.stderr)
374 if args.action_name == 'clean':
375 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
376 else:
377 if need_to_run_gyp(platform):
378 print('Running gyp...', file=sys.stderr)
379 gyp = subprocess.Popen(
380 (tools_config['GYP'],
381 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500382 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500383 '--no-circular-check',
384 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500385 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500386 '-I/dev/stdin', '-Goutput_dir=output',
387 '-DOS=%s' % platform.os(),
388 '-DPLATFORM=%s' % platform.gyp_platform(),
389 '-DARCHITECTURE=%s' % platform.architecture,
390 '-DCOMPILER=%s' % platform.compiler,
Brian Silvermanb3d50542014-04-23 14:28:55 -0500391 '-DDEBUG=%s' % ('yes' if platform.debug else 'no')) +
392 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500393 stdin=subprocess.PIPE)
394 gyp.communicate(("""
395{
396 'target_defaults': {
397 'configurations': {
398 '%s': {}
399 }
400 }
401}""" % platform.outname()).encode())
402 if gyp.returncode:
403 print("Running gyp failed!", file=sys.stderr)
404 exit(1)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500405 if processor.is_crio():
406 subprocess.check_call(
407 ('sed', '-i',
408 's/nm -gD/nm/g', platform.build_ninja()),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500409 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500410 print('Done running gyp.', file=sys.stderr)
411 else:
412 print("Not running gyp.", file=sys.stderr)
413
414 try:
Brian Silvermanb3d50542014-04-23 14:28:55 -0500415 build_env = dict(processor.build_env())
416 build_env['TERM'] = os.environ['TERM']
417 build_env['PATH'] = os.environ['PATH']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500418 subprocess.check_call(
419 (tools_config['NINJA'],
420 '-C', platform.outdir()) + tuple(targets),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500421 stdin=open(os.devnull, 'r'),
Brian Silvermanb3d50542014-04-23 14:28:55 -0500422 env=build_env)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500423 except subprocess.CalledProcessError as e:
424 if unknown_platform_error is not None:
425 print(unknown_platform_error, file=sys.stderr)
426 raise e
427
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500428 if args.action_name == 'deploy':
429 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700430 elif args.action_name == 'tests':
431 dirname = os.path.join(platform.outdir(), 'tests')
432 for f in targets or os.listdir(dirname):
433 print('Running test %s...' % f, file=sys.stderr)
434 subprocess.check_call(os.path.join(dirname, f))
435 print('Test %s succeeded' % f, file=sys.stderr)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500436
Brian Silvermana29ebf92014-04-23 13:08:49 -0500437 print('Done building %s...' % platform, file=sys.stderr)
438
439if __name__ == '__main__':
440 main()