blob: 8b7790f277523fdd3fb34e80de12b70290aad712 [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
13class Processor(object):
14 class UnknownPlatform(Exception):
15 def __init__(self, message):
16 self.message = message
17
18 def aos_path():
19 return os.path.join(os.path.dirname(__file__), '..')
20
21class CRIOProcessor(Processor):
22 def __init__(self):
23 super(CRIOProcessor, self).__init__()
24
25 if 'WIND_BASE' in os.environ:
26 self.wind_base = os.environ['WIND_BASE']
27 else:
28 self.wind_base = '/usr/local/powerpc-wrs-vxworks/wind_base'
29
30 def parse_platforms(self, string):
31 if string is not None and string != 'crio':
32 raise Processor.UnknownPlatform('Unknown cRIO platform "%s"!' % string, file=sys.stderr)
33 return CRIOPlatform()
34
35 def build_env(self):
36 return {'WIND_BASE': self.wind_base}
37 def is_crio(self): return True
38
39class PrimeProcessor(Processor):
40 class Platform(object):
41 def __init__(self, architecture, compiler, debug):
42 self.architecture = architecture
43 self.compiler = compiler
44 self.debug = debug
45
46 def __repr__(self):
47 return 'PrimeProcessor.Platform(architecture=%s, compiler=%s, debug=%s)' \
48 % (self.architecture, self.compiler, self.debug)
49 def __str__(self):
50 return '%s-%s%s' % (self.architecture, self.compiler,
51 '-debug' if self.debug else '')
52
53 def os(self):
54 return 'linux'
55 def gyp_platform(self):
56 return '%s-%s-%s' % (self.os(), self.architecture, self.compiler)
57
58 def outdir(self):
59 return os.path.join(
60 Processor.aos_path(), '..', 'output', self.outname())
61 def outname(self):
62 return str(self)
63 def build_ninja(self):
64 return os.path.join(self.outdir(), 'build.ninja')
65
66 ARCHITECTURES = ['arm', 'amd64']
67 COMPILERS = ['clang', 'gcc']
68
69 def __init__(self):
70 super(Processor, self).__init__()
71
72 platforms = []
73 for architecture in PrimeProcessor.ARCHITECTURES:
74 for compiler in PrimeProcessor.COMPILERS:
75 for debug in [True, False]:
76 platforms.append(
77 PrimeProcessor.Platform(architecture, compiler, debug))
78 self.platforms = frozenset(platforms)
79 self.default_platforms = self.select_platforms(debug=False)
80
81 def build_env(self):
82 return {}
83 def is_crio(self): return False
84
85 def parse_platforms(self, string):
86 if string is None:
87 return self.default_platforms
88 r = self.default_platforms
89 for part in string.split(','):
90 if part[0] == '+':
91 r = r | self.select_platforms_string(part[1:])
92 elif part[0] == '-':
93 r = r - self.select_platforms_string(part[1:])
94 elif part[0] == '=':
95 r = self.select_platforms_string(part[1:])
96 else:
97 r = r - (self.platforms - self.select_platforms_string(part))
98 return r
99
100 def select_platforms(self, architecture=None, compiler=None, debug=None):
101 r = []
102 for platform in self.platforms:
103 if architecture is None or platform.architecture == architecture:
104 if compiler is None or platform.compiler == compiler:
105 if debug is None or platform.debug == debug:
106 r.append(platform)
107 return set(r)
108
109 def select_platforms_string(self, string):
110 r = []
111 architecture, compiler, debug = None, None, None
112 for part in string.split('-'):
113 if part in PrimeProcessor.ARCHITECTURES:
114 architecture = part
115 elif part in PrimeProcessor.COMPILERS:
116 compiler = part
117 elif part in ['debug', 'dbg']:
118 debug = True
119 elif part in ['release', 'nodebug', 'ndb']:
120 debug = False
121 else:
122 raise Processor.UnknownPlatform('Unknown platform string component "%s".' % part)
123 return self.select_platforms(
124 architecture=architecture,
125 compiler=compiler,
126 debug=debug)
127
128def main():
129 class TryParsingAgain(Exception):
130 pass
131
132 class TryAgainArgumentParser(argparse.ArgumentParser):
133 def __init__(self, **kwargs):
134 super(TryAgainArgumentParser, self).__init__(**kwargs)
135
136 def error(self, message):
137 raise TryParsingAgain
138
139 def SetUpParser(parser, args):
140 def AddBuildArgs(parser):
141 parser.add_argument(
142 'target',
143 help='target to build',
144 nargs='*')
145 def AddCommonArgs(parser):
146 parser.add_argument(
147 'platforms',
148 help='platform(s) to act on',
149 nargs='?')
150
151 parser.add_argument('--processor', required=True, help='prime or crio')
152 parser.add_argument('--main_gyp', required=True, help='main .gyp file')
153 subparsers = parser.add_subparsers(dest='action_name')
154
155 build_parser = subparsers.add_parser(
156 'build',
157 help='build the code (default)')
158 AddCommonArgs(build_parser)
159 AddBuildArgs(build_parser)
160
161 clean_parser = subparsers.add_parser(
162 'clean',
163 help='remove all output directories')
164 AddCommonArgs(clean_parser)
165
166 deploy_parser = subparsers.add_parser(
167 'deploy',
168 help='build and download the code')
169 AddCommonArgs(deploy_parser)
170 AddBuildArgs(deploy_parser)
171 deploy_parser.add_argument(
172 '-n', '--dry-run',
173 help="don't actually download anything",
174 action='store_true')
175
176 return parser.parse_args(args)
177
178 try:
179 parser = TryAgainArgumentParser()
180 args = SetUpParser(parser, sys.argv[1:])
181 except TryParsingAgain:
182 parser = argparse.ArgumentParser()
183 REQUIRED_ARGS_END = 5
184 args = SetUpParser(parser, sys.argv[1:REQUIRED_ARGS_END] + ['build'] +
185 sys.argv[(REQUIRED_ARGS_END):])
186
187 if args.processor == 'crio':
188 processor = CRIOProcessor()
189 elif args.processor == 'prime':
190 processor = PrimeProcessor()
191 else:
192 parser.exit(status=1, message='Unknown processor "%s".' % args.processor)
193
194 if 'target' in args:
195 targets = args.target[:]
196 else:
197 targets = []
198 unknown_platform_error = None
199 try:
200 platforms = processor.parse_platforms(args.platforms)
201 except Processor.UnknownPlatform as e:
202 unknown_platform_error = e.message
203 targets.append(args.platforms)
204 platforms = processor.parse_platforms(None)
205 if not platforms:
206 print("No platforms selected!", file=sys.stderr)
207 exit(1)
208
209 def download_externals(argument):
210 subprocess.check_call(
211 (os.path.join(Processor.aos_path(), 'build', 'download_externals.sh'),
212 argument),
213 stdin=open('/dev/null', 'r'))
214
215 if processor.is_crio():
216 download_externals('crio')
217 else:
218 for architecture in PrimeProcessor.ARCHITECTURES:
219 if platforms & processor.select_platforms(architecture=architecture):
220 download_externals(architecture)
221
222 class ToolsConfig(object):
223 def __init__(self):
224 self.variables = {'AOS': Processor.aos_path()}
225 with open(os.path.join(Processor.aos_path(), 'build', 'tools_config'), 'r') as f:
226 for line in f:
227 if line[0] == '#':
228 pass
229 elif line.isspace():
230 pass
231 else:
232 new_name, new_value = line.rstrip().split('=')
233 for name, value in self.variables.items():
234 new_value = new_value.replace('${%s}' % name, value)
235 self.variables[new_name] = new_value
236 def __getitem__(self, key):
237 return self.variables[key]
238
239 tools_config = ToolsConfig()
240
241 def handle_clean_error(function, path, excinfo):
242 if issubclass(OSError, excinfo[0]):
243 if excinfo[1].errno == errno.ENOENT:
244 # Who cares if the file we're deleting isn't there?
245 return
246 raise excinfo[1]
247
248 def need_to_run_gyp(platform):
249 try:
250 build_mtime = os.stat(platform.build_ninja()).st_mtime
251 except OSError as e:
252 if e.errno == errno.ENOENT:
253 return True
254 else:
255 raise e
256 pattern = re.compile('.*\.gyp[i]$')
257 for dirname, _, files in os.walk(os.path.join(Processor.aos_path(), '..')):
258 for f in [f for f in files if pattern.match(f)]:
259 if (os.stat(os.path.join(dirname, f)).st_mtime > build_mtime):
260 return True
261 return False
262
263 for platform in platforms:
264 print('Building %s...' % platform, file=sys.stderr)
265 if args.action_name == 'clean':
266 shutil.rmtree(platform.outdir(), onerror=handle_clean_error)
267 else:
268 if need_to_run_gyp(platform):
269 print('Running gyp...', file=sys.stderr)
270 gyp = subprocess.Popen(
271 (tools_config['GYP'],
272 '--check',
273 '--depth=%s' % os.path.join(Processor.aos_path(), '..'),
274 '--no-circular-check',
275 '-f', 'ninja',
276 '-I%s' % os.path.join(Processor.aos_path(), 'build', 'aos.gypi'),
277 '-I/dev/stdin', '-Goutput_dir=output',
278 '-DOS=%s' % platform.os(),
279 '-DPLATFORM=%s' % platform.gyp_platform(),
280 '-DARCHITECTURE=%s' % platform.architecture,
281 '-DCOMPILER=%s' % platform.compiler,
282 '-DDEBUG=%s' % ('yes' if platform.debug else 'no'),
283 args.main_gyp),
284 stdin=subprocess.PIPE)
285 gyp.communicate(("""
286{
287 'target_defaults': {
288 'configurations': {
289 '%s': {}
290 }
291 }
292}""" % platform.outname()).encode())
293 if gyp.returncode:
294 print("Running gyp failed!", file=sys.stderr)
295 exit(1)
296 print('Done running gyp.', file=sys.stderr)
297 else:
298 print("Not running gyp.", file=sys.stderr)
299
300 try:
301 subprocess.check_call(
302 (tools_config['NINJA'],
303 '-C', platform.outdir()) + tuple(targets),
304 stdin=open('/dev/null', 'r'))
305 except subprocess.CalledProcessError as e:
306 if unknown_platform_error is not None:
307 print(unknown_platform_error, file=sys.stderr)
308 raise e
309
310 # TODO(brians): deploy and tests
311 print('Done building %s...' % platform, file=sys.stderr)
312
313if __name__ == '__main__':
314 main()