blob: ff955cdab449a9138a56468d8b2c851f20ef90db [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
50class CRIOProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -050051 class Platform(Processor.Platform):
52 def __init__(self, debug):
53 super(CRIOProcessor.Platform, self).__init__()
54
55 self.debug = debug
56
57 def __repr__(self):
58 return 'CRIOProcessor.Platform(debug=%s)' % self.debug
59 def __str__(self):
60 return 'crio%s' % ('-debug' if self.debug else '')
61
62 def outname(self):
63 return 'crio-debug' if self.debug else 'crio'
64 def os(self):
65 return 'vxworks'
66 def gyp_platform(self):
67 return 'crio'
68 def architecture(self):
69 return 'ppc'
70 def compiler(self):
71 return 'gcc'
72
Brian Silvermane48c09a2014-04-30 18:04:58 -070073 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -050074 def deploy(self, dry_run):
75 self.do_deploy(dry_run,
76 ('ncftpput', get_ip('robot'), '/',
77 os.path.join(self.outdir(), 'lib', 'FRC_UserProgram.out')))
78
Brian Silvermana29ebf92014-04-23 13:08:49 -050079 def __init__(self):
80 super(CRIOProcessor, self).__init__()
81
82 if 'WIND_BASE' in os.environ:
83 self.wind_base = os.environ['WIND_BASE']
84 else:
85 self.wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
86
87 def parse_platforms(self, string):
Brian Silvermanb3d50542014-04-23 14:28:55 -050088 if string is None or string == 'crio':
89 return (CRIOProcessor.Platform(False),)
90 elif string == 'crio-debug':
91 return (CRIOProcessor.Platform(True),)
92 else:
93 raise Processor.UnknownPlatform('Unknown cRIO platform "%s".' % string)
Brian Silvermana29ebf92014-04-23 13:08:49 -050094
95 def build_env(self):
96 return {'WIND_BASE': self.wind_base}
Brian Silvermanb3d50542014-04-23 14:28:55 -050097 def extra_gyp_flags(self):
98 return ('-DWIND_BASE=%s' % self.wind_base,)
99
Brian Silvermana29ebf92014-04-23 13:08:49 -0500100 def is_crio(self): return True
101
102class PrimeProcessor(Processor):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500103 class Platform(Processor.Platform):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500104 def __init__(self, architecture, compiler, debug):
Brian Silvermanb3d50542014-04-23 14:28:55 -0500105 super(PrimeProcessor.Platform, self).__init__()
106
Brian Silvermana29ebf92014-04-23 13:08:49 -0500107 self.architecture = architecture
108 self.compiler = compiler
109 self.debug = debug
110
111 def __repr__(self):
112 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s)' \
113 % (self.architecture, self.compiler, self.debug)
114 def __str__(self):
115 return '%s-%s%s' % (self.architecture, self.compiler,
116 '-debug' if self.debug else '')
117
118 def os(self):
119 return 'linux'
120 def gyp_platform(self):
121 return '%s-%s-%s' % (self.os(), self.architecture, self.compiler)
122
Brian Silvermana29ebf92014-04-23 13:08:49 -0500123 def outname(self):
124 return str(self)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500125
Brian Silvermane48c09a2014-04-30 18:04:58 -0700126 # TODO(brians): test this
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500127 def deploy(self, dry_run):
128 """Downloads code to the prime in a way that avoids clashing too badly with starter
129 """
130 SUM = 'md5sum'
131 TARGET_DIR = '/home/driver/robot_code/bin'
132 TEMP_DIR = '/tmp/aos_downloader'
133 TARGET = 'driver@' + get_ip('prime')
134
135 from_dir = os.path.join(self.outdir(), 'outputs')
136 sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
137 stdin=open(os.devnull, 'r'),
138 cwd=from_dir)
139 to_download = subprocess.check_output(
140 ('ssh', TARGET,
141 """rm -rf {TMPDIR} && mkdir {TMPDIR} && cd {TO_DIR}
142 && echo '{SUMS}' | {SUM} --check --quiet
143 |& grep -F FAILED | sed 's/^\\(.*\\): FAILED.*"'$'"/\\1/g'""".format(
144 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR, SUMS=sums, SUM=SUM)))
145 if not to_download:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700146 user_output("Nothing to download")
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500147 return
148 self.do_deploy(
149 dry_run,
150 ('scp', '-o', 'Compression yes') + to_download
151 + (('%s:%s' % (TARGET, TEMP_DIR)),))
152 if not dry_run:
153 subprocess.check_call(
154 ('ssh', TARGET,
155 """mv {TMPDIR}/* {TO_DIR}
156 && echo 'Done moving new executables into place'
157 && ionice -c 3 bash -c 'sync && sync && sync'""".format(
158 TMPDIR=TEMP_DIR, TO_DIR=TARGET_DIR)))
159
Brian Silvermana29ebf92014-04-23 13:08:49 -0500160 ARCHITECTURES = ['arm', 'amd64']
161 COMPILERS = ['clang', 'gcc']
162
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500163 def __init__(self, is_test, is_deploy):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500164 super(Processor, self).__init__()
165
166 platforms = []
167 for architecture in PrimeProcessor.ARCHITECTURES:
168 for compiler in PrimeProcessor.COMPILERS:
169 for debug in [True, False]:
170 platforms.append(
171 PrimeProcessor.Platform(architecture, compiler, debug))
172 self.platforms = frozenset(platforms)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500173 if is_test:
174 self.default_platforms = self.select_platforms(architecture='amd64', debug=True)
175 elif is_deploy:
176 # TODO(brians): Switch to deploying the code built with clang.
177 self.default_platforms = self.select_platforms(architecture='arm', compiler='gcc', debug=False)
178 else:
179 self.default_platforms = self.select_platforms(debug=False)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500180
181 def build_env(self):
182 return {}
Brian Silvermanb3d50542014-04-23 14:28:55 -0500183 def extra_gyp_flags(self):
184 return ()
Brian Silvermana29ebf92014-04-23 13:08:49 -0500185 def is_crio(self): return False
186
187 def parse_platforms(self, string):
188 if string is None:
189 return self.default_platforms
190 r = self.default_platforms
191 for part in string.split(','):
192 if part[0] == '+':
193 r = r | self.select_platforms_string(part[1:])
194 elif part[0] == '-':
195 r = r - self.select_platforms_string(part[1:])
196 elif part[0] == '=':
197 r = self.select_platforms_string(part[1:])
198 else:
Brian Silverman7cd5ad42014-04-27 08:11:30 -0500199 selected = self.select_platforms_string(part)
200 r = r - (self.platforms - selected)
201 if not r:
202 r = selected
Brian Silvermana29ebf92014-04-23 13:08:49 -0500203 return r
204
205 def select_platforms(self, architecture=None, compiler=None, debug=None):
206 r = []
207 for platform in self.platforms:
208 if architecture is None or platform.architecture == architecture:
209 if compiler is None or platform.compiler == compiler:
210 if debug is None or platform.debug == debug:
211 r.append(platform)
212 return set(r)
213
214 def select_platforms_string(self, string):
215 r = []
216 architecture, compiler, debug = None, None, None
217 for part in string.split('-'):
218 if part in PrimeProcessor.ARCHITECTURES:
219 architecture = part
220 elif part in PrimeProcessor.COMPILERS:
221 compiler = part
222 elif part in ['debug', 'dbg']:
223 debug = True
224 elif part in ['release', 'nodebug', 'ndb']:
225 debug = False
226 else:
227 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
228 return self.select_platforms(
229 architecture=architecture,
230 compiler=compiler,
231 debug=debug)
232
233def main():
234 class TryParsingAgain(Exception):
235 pass
236
237 class TryAgainArgumentParser(argparse.ArgumentParser):
238 def __init__(self, **kwargs):
239 super(TryAgainArgumentParser, self).__init__(**kwargs)
240
241 def error(self, message):
242 raise TryParsingAgain
243
244 def SetUpParser(parser, args):
245 def AddBuildArgs(parser):
246 parser.add_argument(
247 'target',
248 help='target to build',
249 nargs='*')
250 def AddCommonArgs(parser):
251 parser.add_argument(
252 'platforms',
253 help='platform(s) to act on',
254 nargs='?')
255
256 parser.add_argument('--processor', required=True, help='prime or crio')
257 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
258 subparsers = parser.add_subparsers(dest='action_name')
259
260 build_parser = subparsers.add_parser(
261 'build',
262 help='build the code (default)')
263 AddCommonArgs(build_parser)
264 AddBuildArgs(build_parser)
265
266 clean_parser = subparsers.add_parser(
267 'clean',
268 help='remove all output directories')
269 AddCommonArgs(clean_parser)
270
271 deploy_parser = subparsers.add_parser(
272 'deploy',
273 help='build and download the code')
274 AddCommonArgs(deploy_parser)
275 AddBuildArgs(deploy_parser)
276 deploy_parser.add_argument(
277 '-n', '--dry-run',
278 help="don't actually download anything",
279 action='store_true')
280
Brian Silvermane48c09a2014-04-30 18:04:58 -0700281 tests_parser = subparsers.add_parser(
282 'tests',
283 help='run tests')
284 AddCommonArgs(tests_parser)
285 AddBuildArgs(tests_parser)
286
Brian Silvermana29ebf92014-04-23 13:08:49 -0500287 return parser.parse_args(args)
288
289 try:
290 parser = TryAgainArgumentParser()
291 args = SetUpParser(parser, sys.argv[1:])
292 except TryParsingAgain:
293 parser = argparse.ArgumentParser()
294 REQUIRED_ARGS_END = 5
295 args = SetUpParser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
296 sys.argv[(REQUIRED_ARGS_END):])
297
298 if args.processor == 'crio':
299 processor = CRIOProcessor()
300 elif args.processor == 'prime':
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500301 processor = PrimeProcessor(args.action_name == 'tests',
302 args.action_name == 'deploy')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500303 else:
304 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
305
306 if 'target' in args:
307 targets = args.target[:]
308 else:
309 targets = []
310 unknown_platform_error = None
311 try:
312 platforms = processor.parse_platforms(args.platforms)
313 except Processor.UnknownPlatform as e:
314 unknown_platform_error = e.message
315 targets.append(args.platforms)
316 platforms = processor.parse_platforms(None)
317 if not platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700318 user_output("No platforms selected!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500319 exit(1)
320
321 def download_externals(argument):
322 subprocess.check_call(
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500323 (os.path.join(aos_path(), 'build', 'download_externals.sh'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500324 argument),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500325 stdin=open(os.devnull, 'r'))
Brian Silvermana29ebf92014-04-23 13:08:49 -0500326
327 if processor.is_crio():
328 download_externals('crio')
329 else:
330 for architecture in PrimeProcessor.ARCHITECTURES:
331 if platforms & processor.select_platforms(architecture=architecture):
332 download_externals(architecture)
333
334 class ToolsConfig(object):
335 def __init__(self):
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500336 self.variables = {'AOS': aos_path()}
337 with open(os.path.join(aos_path(), 'build', 'tools_config'), 'r') as f:
Brian Silvermana29ebf92014-04-23 13:08:49 -0500338 for line in f:
339 if line[0] == '#':
340 pass
341 elif line.isspace():
342 pass
343 else:
344 new_name, new_value = line.rstrip().split('=')
345 for name, value in self.variables.items():
346 new_value = new_value.replace('${%s}' % name, value)
347 self.variables[new_name] = new_value
348 def __getitem__(self, key):
349 return self.variables[key]
350
351 tools_config = ToolsConfig()
352
353 def handle_clean_error(function, path, excinfo):
354 if issubclass(OSError, excinfo[0]):
355 if excinfo[1].errno == errno.ENOENT:
356 # Who cares if the file we're deleting isn't there?
357 return
358 raise excinfo[1]
359
360 def need_to_run_gyp(platform):
361 try:
362 build_mtime = os.stat(platform.build_ninja()).st_mtime
363 except OSError as e:
364 if e.errno == errno.ENOENT:
365 return True
366 else:
367 raise e
Brian Silvermane48c09a2014-04-30 18:04:58 -0700368 pattern = re.compile('.*\.gypi?$')
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500369 for dirname, _, files in os.walk(os.path.join(aos_path(), '..')):
Brian Silvermana29ebf92014-04-23 13:08:49 -0500370 for f in [f for f in files if pattern.match(f)]:
371 if (os.stat(os.path.join(dirname, f)).st_mtime > build_mtime):
372 return True
373 return False
374
375 for platform in platforms:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700376 user_output('Building %s...' % platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500377 if args.action_name == 'clean':
378 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
379 else:
380 if need_to_run_gyp(platform):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700381 user_output('Running gyp...')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500382 gyp = subprocess.Popen(
383 (tools_config['GYP'],
384 '--check',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500385 '--depth=%s' % os.path.join(aos_path(), '..'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500386 '--no-circular-check',
387 '-f', 'ninja',
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500388 '-I%s' % os.path.join(aos_path(), 'build', 'aos.gypi'),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500389 '-I/dev/stdin', '-Goutput_dir=output',
390 '-DOS=%s' % platform.os(),
391 '-DPLATFORM=%s' % platform.gyp_platform(),
392 '-DARCHITECTURE=%s' % platform.architecture,
393 '-DCOMPILER=%s' % platform.compiler,
Brian Silvermanb3d50542014-04-23 14:28:55 -0500394 '-DDEBUG=%s' % ('yes' if platform.debug else 'no')) +
395 processor.extra_gyp_flags() + (args.main_gyp,),
Brian Silvermana29ebf92014-04-23 13:08:49 -0500396 stdin=subprocess.PIPE)
397 gyp.communicate(("""
398{
399 'target_defaults': {
400 'configurations': {
401 '%s': {}
402 }
403 }
404}""" % platform.outname()).encode())
405 if gyp.returncode:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700406 user_output("Running gyp failed!")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500407 exit(1)
Brian Silvermanb3d50542014-04-23 14:28:55 -0500408 if processor.is_crio():
409 subprocess.check_call(
410 ('sed', '-i',
411 's/nm -gD/nm/g', platform.build_ninja()),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500412 stdin=open(os.devnull, 'r'))
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700413 user_output('Done running gyp.')
Brian Silvermana29ebf92014-04-23 13:08:49 -0500414 else:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700415 user_output("Not running gyp.")
Brian Silvermana29ebf92014-04-23 13:08:49 -0500416
417 try:
Brian Silvermanb3d50542014-04-23 14:28:55 -0500418 build_env = dict(processor.build_env())
419 build_env['TERM'] = os.environ['TERM']
420 build_env['PATH'] = os.environ['PATH']
Brian Silvermana29ebf92014-04-23 13:08:49 -0500421 subprocess.check_call(
422 (tools_config['NINJA'],
423 '-C', platform.outdir()) + tuple(targets),
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500424 stdin=open(os.devnull, 'r'),
Brian Silvermanb3d50542014-04-23 14:28:55 -0500425 env=build_env)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500426 except subprocess.CalledProcessError as e:
427 if unknown_platform_error is not None:
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700428 user_output(unknown_platform_error)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500429 raise e
430
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500431 if args.action_name == 'deploy':
432 platform.deploy(args.dry_run)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700433 elif args.action_name == 'tests':
434 dirname = os.path.join(platform.outdir(), 'tests')
435 for f in targets or os.listdir(dirname):
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700436 user_output('Running test %s...' % f)
Brian Silvermane48c09a2014-04-30 18:04:58 -0700437 subprocess.check_call(os.path.join(dirname, f))
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700438 user_output('Test %s succeeded' % f)
Brian Silvermanbe6cfe22014-04-27 08:06:27 -0500439
Brian Silvermana9b1e5c2014-04-30 18:08:04 -0700440 user_output('Done building %s' % platform)
Brian Silvermana29ebf92014-04-23 13:08:49 -0500441
442if __name__ == '__main__':
443 main()