blob: ac316ce2630c895af15878a8c634b30f152cc38c [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:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500194 selected = self.select_platforms_string(part)
195 r = r - (self.platforms - selected)
196 if not r:
197 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500198 return r
199
200 def select_platforms(self, architecture=None, compiler=None, debug=None):
201 r = []
202 for platform in self.platforms:
203 if architecture is None or platform.architecture == architecture:
204 if compiler is None or platform.compiler == compiler:
205 if debug is None or platform.debug == debug:
206 r.append(platform)
207 return set(r)
208
209 def select_platforms_string(self, string):
210 r = []
211 architecture, compiler, debug = None, None, None
212 for part in string.split('-'):
213 if part in PrimeProcessor.ARCHITECTURES:
214 architecture = part
215 elif part in PrimeProcessor.COMPILERS:
216 compiler = part
217 elif part in ['debug', 'dbg']:
218 debug = True
219 elif part in ['release', 'nodebug', 'ndb']:
220 debug = False
221 else:
222 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
223 return self.select_platforms(
224 architecture=architecture,
225 compiler=compiler,
226 debug=debug)
227
228def main():
229 class TryParsingAgain(Exception):
230 pass
231
232 class TryAgainArgumentParser(argparse.ArgumentParser):
233 def __init__(self, **kwargs):
234 super(TryAgainArgumentParser, self).__init__(**kwargs)
235
236 def error(self, message):
237 raise TryParsingAgain
238
239 def SetUpParser(parser, args):
240 def AddBuildArgs(parser):
241 parser.add_argument(
242 'target',
243 help='target to build',
244 nargs='*')
245 def AddCommonArgs(parser):
246 parser.add_argument(
247 'platforms',
248 help='platform(s) to act on',
249 nargs='?')
250
251 parser.add_argument('--processor', required=True, help='prime or crio')
252 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
253 subparsers = parser.add_subparsers(dest='action_name')
254
255 build_parser = subparsers.add_parser(
256 'build',
257 help='build the code (default)')
258 AddCommonArgs(build_parser)
259 AddBuildArgs(build_parser)
260
261 clean_parser = subparsers.add_parser(
262 'clean',
263 help='remove all output directories')
264 AddCommonArgs(clean_parser)
265
266 deploy_parser = subparsers.add_parser(
267 'deploy',
268 help='build and download the code')
269 AddCommonArgs(deploy_parser)
270 AddBuildArgs(deploy_parser)
271 deploy_parser.add_argument(
272 '-n', '--dry-run',
273 help="don't actually download anything",
274 action='store_true')
275
276 return parser.parse_args(args)
277
278 try:
279 parser = TryAgainArgumentParser()
280 args = SetUpParser(parser, sys.argv[1:])
281 except TryParsingAgain:
282 parser = argparse.ArgumentParser()
283 REQUIRED_ARGS_END = 5
284 args = SetUpParser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
285 sys.argv[(REQUIRED_ARGS_END):])
286
287 if args.processor == 'crio':
288 processor = CRIOProcessor()
289 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500290 processor = PrimeProcessor(args.action_name == 'tests',
291 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500292 else:
293 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
294
295 if 'target' in args:
296 targets = args.target[:]
297 else:
298 targets = []
299 unknown_platform_error = None
300 try:
301 platforms = processor.parse_platforms(args.platforms)
302 except Processor.UnknownPlatform as e:
303 unknown_platform_error = e.message
304 targets.append(args.platforms)
305 platforms = processor.parse_platforms(None)
306 if not platforms:
307 print("No platforms selected!", file=sys.stderr)
308 exit(1)
309
310 def download_externals(argument):
311 subprocess.check_call(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500312 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500313 argument),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500314 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500315
316 if processor.is_crio():
317 download_externals('crio')
318 else:
319 for architecture in PrimeProcessor.ARCHITECTURES:
320 if platforms & processor.select_platforms(architecture=architecture):
321 download_externals(architecture)
322
323 class ToolsConfig(object):
324 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500325 self.variables = {'AOS': aos_path()}
326 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500327 for line in f:
328 if line[0] == '#':
329 pass
330 elif line.isspace():
331 pass
332 else:
333 new_name, new_value = line.rstrip().split('=')
334 for name, value in self.variables.items():
335 new_value = new_value.replace('${%s}' % name, value)
336 self.variables[new_name] = new_value
337 def __getitem__(self, key):
338 return self.variables[key]
339
340 tools_config = ToolsConfig()
341
342 def handle_clean_error(function, path, excinfo):
343 if issubclass(OSError, excinfo[0]):
344 if excinfo[1].errno == errno.ENOENT:
345 # Who cares if the file we're deleting isn't there?
346 return
347 raise excinfo[1]
348
349 def need_to_run_gyp(platform):
350 try:
351 build_mtime = os.stat(platform.build_ninja()).st_mtime
352 except OSError as e:
353 if e.errno == errno.ENOENT:
354 return True
355 else:
356 raise e
357 pattern = re.compile('.*\.gyp[i]$')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500358 for dirname, _, files in os.walk(os.path.join(aos_path(), '..')):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500359 for f in [f for f in files if pattern.match(f)]:
360 if (os.stat(os.path.join(dirname, f)).st_mtime > build_mtime):
361 return True
362 return False
363
364 for platform in platforms:
365 print('Building %s...' % platform, file=sys.stderr)
366 if args.action_name == 'clean':
367 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
368 else:
369 if need_to_run_gyp(platform):
370 print('Running gyp...', file=sys.stderr)
371 gyp = subprocess.Popen(
372 (tools_config['GYP'],
373 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500374 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500375 '--no-circular-check',
376 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500377 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500378 '-I/dev/stdin', '-Goutput_dir=output',
379 '-DOS=%s' % platform.os(),
380 '-DPLATFORM=%s' % platform.gyp_platform(),
381 '-DARCHITECTURE=%s' % platform.architecture,
382 '-DCOMPILER=%s' % platform.compiler,
Brian Silvermanb3d50542014-04-23 14:28:55 -0500383 '-DDEBUG=%s' % ('yes' if platform.debug else 'no')) +
384 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500385 stdin=subprocess.PIPE)
386 gyp.communicate(("""
387{
388 'target_defaults': {
389 'configurations': {
390 '%s': {}
391 }
392 }
393}""" % platform.outname()).encode())
394 if gyp.returncode:
395 print("Running gyp failed!", file=sys.stderr)
396 exit(1)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500397 if processor.is_crio():
398 subprocess.check_call(
399 ('sed', '-i',
400 's/nm -gD/nm/g', platform.build_ninja()),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500401 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500402 print('Done running gyp.', file=sys.stderr)
403 else:
404 print("Not running gyp.", file=sys.stderr)
405
406 try:
Brian Silvermanb3d50542014-04-23 14:28:55 -0500407 build_env = dict(processor.build_env())
408 build_env['TERM'] = os.environ['TERM']
409 build_env['PATH'] = os.environ['PATH']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500410 subprocess.check_call(
411 (tools_config['NINJA'],
412 '-C', platform.outdir()) + tuple(targets),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500413 stdin=open(os.devnull, 'r'),
Brian Silvermanb3d50542014-04-23 14:28:55 -0500414 env=build_env)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500415 except subprocess.CalledProcessError as e:
416 if unknown_platform_error is not None:
417 print(unknown_platform_error, file=sys.stderr)
418 raise e
419
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500420 if args.action_name == 'deploy':
421 platform.deploy(args.dry_run)
422
423 # TODO(brians): tests
Brian Silvermana29ebf92014-04-23 13:08:49 -0500424 print('Done building %s...' % platform, file=sys.stderr)
425
426if __name__ == '__main__':
427 main()