blob: 6a728edc0ab6b8cb14f494b6ed9b78d37609b335 [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 Silvermanbe6cfe22014-04-27 08:06:27 -050070 def deploy(self, dry_run):
71 self.do_deploy(dry_run,
72 ('ncftpput', get_ip('robot'), '/',
73 os.path.join(self.outdir(), 'lib', 'FRC_UserProgram.out')))
74
Brian Silvermana29ebf92014-04-23 13:08:49 -050075 def __init__(self):
76 super(CRIOProcessor, self).__init__()
77
78 if 'WIND_BASE' in os.environ:
79 self.wind_base = os.environ['WIND_BASE']
80 else:
81 self.wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
82
83 def parse_platforms(self, string):
Brian Silvermanb3d50542014-04-23 14:28:55 -050084 if string is None or string == 'crio':
85 return (CRIOProcessor.Platform(False),)
86 elif string == 'crio-debug':
87 return (CRIOProcessor.Platform(True),)
88 else:
89 raise Processor.UnknownPlatform('Unknown cRIO platform "%s".' % string)
Brian Silvermana29ebf92014-04-23 13:08:49 -050090
91 def build_env(self):
92 return {'WIND_BASE': self.wind_base}
Brian Silvermanb3d50542014-04-23 14:28:55 -050093 def extra_gyp_flags(self):
94 return ('-DWIND_BASE=%s' % self.wind_base,)
95
Brian Silvermana29ebf92014-04-23 13:08:49 -050096 def is_crio(self): return True
97
98class PrimeProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -050099 class Platform(Processor.Platform):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500100 def __init__(self, architecture, compiler, debug):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500101 super(PrimeProcessor.Platform, self).__init__()
102
Brian Silvermana29ebf92014-04-23 13:08:49 -0500103 self.architecture = architecture
104 self.compiler = compiler
105 self.debug = debug
106
107 def __repr__(self):
108 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s)' \
109 % (self.architecture, self.compiler, self.debug)
110 def __str__(self):
111 return '%s-%s%s' % (self.architecture, self.compiler,
112 '-debug' if self.debug else '')
113
114 def os(self):
115 return 'linux'
116 def gyp_platform(self):
117 return '%s-%s-%s' % (self.os(), self.architecture, self.compiler)
118
Brian Silvermana29ebf92014-04-23 13:08:49 -0500119 def outname(self):
120 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500121
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500122 def deploy(self, dry_run):
123 """Downloads code to the prime in a way that avoids clashing too badly with starter
124 """
125 SUM = 'md5sum'
126 TARGET_DIR = '/home/driver/robot_code/bin'
127 TEMP_DIR = '/tmp/aos_downloader'
128 TARGET = 'driver@' + get_ip('prime')
129
130 from_dir = os.path.join(self.outdir(), 'outputs')
131 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
132 stdin=open(os.devnull, 'r'),
133 cwd=from_dir)
134 to_download = subprocess.check_output(
135 ('ssh', TARGET,
136 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
137 && echo '{SUMS}' | {SUM} --check --quiet
138 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".format(
139 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
140 if not to_download:
141 print("Nothing to download", file=sys.stderr)
142 return
143 self.do_deploy(
144 dry_run,
145 ('scp', '-o', 'Compression yes') + to_download
146 + (('%s:%s' % (TARGET, TEMP_DIR)),))
147 if not dry_run:
148 subprocess.check_call(
149 ('ssh', TARGET,
150 """mv {TMPDIR}/* {TO_DIR}
151 && echo 'Done moving new executables into place'
152 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
153 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
154
Brian Silvermana29ebf92014-04-23 13:08:49 -0500155 ARCHITECTURES = ['arm', 'amd64']
156 COMPILERS = ['clang', 'gcc']
157
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500158 def __init__(self, is_test, is_deploy):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500159 super(Processor, self).__init__()
160
161 platforms = []
162 for architecture in PrimeProcessor.ARCHITECTURES:
163 for compiler in PrimeProcessor.COMPILERS:
164 for debug in [True, False]:
165 platforms.append(
166 PrimeProcessor.Platform(architecture, compiler, debug))
167 self.platforms = frozenset(platforms)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500168 if is_test:
169 self.default_platforms = self.select_platforms(architecture='amd64', debug=True)
170 elif is_deploy:
171 # TODO(brians): Switch to deploying the code built with clang.
172 self.default_platforms = self.select_platforms(architecture='arm', compiler='gcc', debug=False)
173 else:
174 self.default_platforms = self.select_platforms(debug=False)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500175
176 def build_env(self):
177 return {}
Brian Silvermanb3d50542014-04-23 14:28:55 -0500178 def extra_gyp_flags(self):
179 return ()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500180 def is_crio(self): return False
181
182 def parse_platforms(self, string):
183 if string is None:
184 return self.default_platforms
185 r = self.default_platforms
186 for part in string.split(','):
187 if part[0] == '+':
188 r = r | self.select_platforms_string(part[1:])
189 elif part[0] == '-':
190 r = r - self.select_platforms_string(part[1:])
191 elif part[0] == '=':
192 r = self.select_platforms_string(part[1:])
193 else:
194 r = r - (self.platforms - self.select_platforms_string(part))
195 return r
196
197 def select_platforms(self, architecture=None, compiler=None, debug=None):
198 r = []
199 for platform in self.platforms:
200 if architecture is None or platform.architecture == architecture:
201 if compiler is None or platform.compiler == compiler:
202 if debug is None or platform.debug == debug:
203 r.append(platform)
204 return set(r)
205
206 def select_platforms_string(self, string):
207 r = []
208 architecture, compiler, debug = None, None, None
209 for part in string.split('-'):
210 if part in PrimeProcessor.ARCHITECTURES:
211 architecture = part
212 elif part in PrimeProcessor.COMPILERS:
213 compiler = part
214 elif part in ['debug', 'dbg']:
215 debug = True
216 elif part in ['release', 'nodebug', 'ndb']:
217 debug = False
218 else:
219 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
220 return self.select_platforms(
221 architecture=architecture,
222 compiler=compiler,
223 debug=debug)
224
225def main():
226 class TryParsingAgain(Exception):
227 pass
228
229 class TryAgainArgumentParser(argparse.ArgumentParser):
230 def __init__(self, **kwargs):
231 super(TryAgainArgumentParser, self).__init__(**kwargs)
232
233 def error(self, message):
234 raise TryParsingAgain
235
236 def SetUpParser(parser, args):
237 def AddBuildArgs(parser):
238 parser.add_argument(
239 'target',
240 help='target to build',
241 nargs='*')
242 def AddCommonArgs(parser):
243 parser.add_argument(
244 'platforms',
245 help='platform(s) to act on',
246 nargs='?')
247
248 parser.add_argument('--processor', required=True, help='prime or crio')
249 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
250 subparsers = parser.add_subparsers(dest='action_name')
251
252 build_parser = subparsers.add_parser(
253 'build',
254 help='build the code (default)')
255 AddCommonArgs(build_parser)
256 AddBuildArgs(build_parser)
257
258 clean_parser = subparsers.add_parser(
259 'clean',
260 help='remove all output directories')
261 AddCommonArgs(clean_parser)
262
263 deploy_parser = subparsers.add_parser(
264 'deploy',
265 help='build and download the code')
266 AddCommonArgs(deploy_parser)
267 AddBuildArgs(deploy_parser)
268 deploy_parser.add_argument(
269 '-n', '--dry-run',
270 help="don't actually download anything",
271 action='store_true')
272
273 return parser.parse_args(args)
274
275 try:
276 parser = TryAgainArgumentParser()
277 args = SetUpParser(parser, sys.argv[1:])
278 except TryParsingAgain:
279 parser = argparse.ArgumentParser()
280 REQUIRED_ARGS_END = 5
281 args = SetUpParser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
282 sys.argv[(REQUIRED_ARGS_END):])
283
284 if args.processor == 'crio':
285 processor = CRIOProcessor()
286 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500287 processor = PrimeProcessor(args.action_name == 'tests',
288 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500289 else:
290 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
291
292 if 'target' in args:
293 targets = args.target[:]
294 else:
295 targets = []
296 unknown_platform_error = None
297 try:
298 platforms = processor.parse_platforms(args.platforms)
299 except Processor.UnknownPlatform as e:
300 unknown_platform_error = e.message
301 targets.append(args.platforms)
302 platforms = processor.parse_platforms(None)
303 if not platforms:
304 print("No platforms selected!", file=sys.stderr)
305 exit(1)
306
307 def download_externals(argument):
308 subprocess.check_call(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500309 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500310 argument),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500311 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500312
313 if processor.is_crio():
314 download_externals('crio')
315 else:
316 for architecture in PrimeProcessor.ARCHITECTURES:
317 if platforms & processor.select_platforms(architecture=architecture):
318 download_externals(architecture)
319
320 class ToolsConfig(object):
321 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500322 self.variables = {'AOS': aos_path()}
323 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500324 for line in f:
325 if line[0] == '#':
326 pass
327 elif line.isspace():
328 pass
329 else:
330 new_name, new_value = line.rstrip().split('=')
331 for name, value in self.variables.items():
332 new_value = new_value.replace('${%s}' % name, value)
333 self.variables[new_name] = new_value
334 def __getitem__(self, key):
335 return self.variables[key]
336
337 tools_config = ToolsConfig()
338
339 def handle_clean_error(function, path, excinfo):
340 if issubclass(OSError, excinfo[0]):
341 if excinfo[1].errno == errno.ENOENT:
342 # Who cares if the file we're deleting isn't there?
343 return
344 raise excinfo[1]
345
346 def need_to_run_gyp(platform):
347 try:
348 build_mtime = os.stat(platform.build_ninja()).st_mtime
349 except OSError as e:
350 if e.errno == errno.ENOENT:
351 return True
352 else:
353 raise e
354 pattern = re.compile('.*\.gyp[i]$')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500355 for dirname, _, files in os.walk(os.path.join(aos_path(), '..')):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500356 for f in [f for f in files if pattern.match(f)]:
357 if (os.stat(os.path.join(dirname, f)).st_mtime > build_mtime):
358 return True
359 return False
360
361 for platform in platforms:
362 print('Building %s...' % platform, file=sys.stderr)
363 if args.action_name == 'clean':
364 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
365 else:
366 if need_to_run_gyp(platform):
367 print('Running gyp...', file=sys.stderr)
368 gyp = subprocess.Popen(
369 (tools_config['GYP'],
370 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500371 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500372 '--no-circular-check',
373 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500374 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500375 '-I/dev/stdin', '-Goutput_dir=output',
376 '-DOS=%s' % platform.os(),
377 '-DPLATFORM=%s' % platform.gyp_platform(),
378 '-DARCHITECTURE=%s' % platform.architecture,
379 '-DCOMPILER=%s' % platform.compiler,
Brian Silvermanb3d50542014-04-23 14:28:55 -0500380 '-DDEBUG=%s' % ('yes' if platform.debug else 'no')) +
381 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500382 stdin=subprocess.PIPE)
383 gyp.communicate(("""
384{
385 'target_defaults': {
386 'configurations': {
387 '%s': {}
388 }
389 }
390}""" % platform.outname()).encode())
391 if gyp.returncode:
392 print("Running gyp failed!", file=sys.stderr)
393 exit(1)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500394 if processor.is_crio():
395 subprocess.check_call(
396 ('sed', '-i',
397 's/nm -gD/nm/g', platform.build_ninja()),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500398 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500399 print('Done running gyp.', file=sys.stderr)
400 else:
401 print("Not running gyp.", file=sys.stderr)
402
403 try:
Brian Silvermanb3d50542014-04-23 14:28:55 -0500404 build_env = dict(processor.build_env())
405 build_env['TERM'] = os.environ['TERM']
406 build_env['PATH'] = os.environ['PATH']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500407 subprocess.check_call(
408 (tools_config['NINJA'],
409 '-C', platform.outdir()) + tuple(targets),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500410 stdin=open(os.devnull, 'r'),
Brian Silvermanb3d50542014-04-23 14:28:55 -0500411 env=build_env)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500412 except subprocess.CalledProcessError as e:
413 if unknown_platform_error is not None:
414 print(unknown_platform_error, file=sys.stderr)
415 raise e
416
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500417 if args.action_name == 'deploy':
418 platform.deploy(args.dry_run)
419
420 # TODO(brians): tests
Brian Silvermana29ebf92014-04-23 13:08:49 -0500421 print('Done building %s...' % platform, file=sys.stderr)
422
423if __name__ == '__main__':
424 main()