blob: 85f783501671d8d31f9f9153a585e8ce52484d78 [file] [log] [blame]
Brian Silverman8c374e02015-09-06 23:02:21 -04001#!/usr/bin/python3
2
3# This script converts simple Gyp files to their Bazel equivalents.
4# It is not intended to be particularly extensible; it is only going to be used
5# once over all of our code and then forgotten about.
6# This "only has to work on exactly the code we have now" property also means
7# it's pretty picky about many things and doesn't try to handle anything
8# beyond what we actually have.
9#
10# It takes a list of folders to deal with.
11#
12# Running this script requires PyYAML <pyyaml.org> to be installed.
13
14import sys
15import os
16import yaml
17import collections
Brian Silverman100534c2015-09-07 15:51:23 -040018import re
Brian Silverman8c374e02015-09-06 23:02:21 -040019
20'''Converts a Gyp filename to its Bazel equivalent.
21
22Args:
23 gyp_file_name: The name of the gyp file this is contained in.
24 This is important for resolving relative paths etc.
25 file: The name of the file to deal with.
26'''
27def gyp_file_to_bazel(gyp_file_name, file):
28 if file.startswith('<(AOS)/'):
29 return '//aos' + file[6:]
30 elif file.startswith('<(DEPTH)/'):
31 return '//' + file[9:]
32 else:
33 if '(' in file:
34 raise RuntimeError('Bad variable in file "%s"' % file)
35 split_gyp = os.path.dirname(gyp_file_name).split('/')
36 rest = []
37 dotdots = 0
38 doing_dotdots = True
39 for component in file.split('/'):
40 if component == '..':
41 dotdots += 1
42 if not doing_dotdots:
43 raise RuntimeError('Bad use of .. in file "%s"' % file)
44 else:
45 doing_dotdots = False
46 rest.append(component)
47 return '/'.join(['/'] + split_gyp[:-dotdots] + rest)
48
49'''Converts a Gyp target to its Bazel equivalent.
50
51Args:
52 gyp_file_name: The name of the gyp file this is contained in.
53 This is important for resolving relative paths etc.
54 target: The name of the target to deal with.
55'''
56def gyp_target_to_bazel(gyp_file_name, target):
57 if not ':' in target:
58 if '.' in target:
59 raise RuntimeError('Have a filename instead of a target: "%s"' % target)
60 return ':' + target
61 if target[0] == ':':
62 return target
63
Brian Silverman100534c2015-09-07 15:51:23 -040064 # These thin wrappers won't be copied.
Brian Silverman8c374e02015-09-06 23:02:21 -040065 if target == '<(AOS)/build/aos.gyp:logging':
66 return '//aos/common/logging'
Brian Silverman100534c2015-09-07 15:51:23 -040067 if target == '<(AOS)/build/aos.gyp:logging_interface':
68 return '//aos/common/logging:logging_interface'
69
70 # These are getting moved to the right place manually.
71 if target == '<(AOS)/common/common.gyp:condition':
72 return '//aos/linux_code/ipc_lib:condition'
73 if target == '<(AOS)/common/common.gyp:mutex':
74 return '//aos/linux_code/ipc_lib:mutex'
75 if target == '<(AOS)/common/common.gyp:event':
76 return '//aos/linux_code/ipc_lib:event'
77
78 # By building ..., we can mostly ignore these.
79 if (target == '<(AOS)/build/aos_all.gyp:Prime' or
80 target == '../../frc971/frc971.gyp:All'):
81 return '//aos:prime_binaries'
Brian Silverman8c374e02015-09-06 23:02:21 -040082
83 split = target.split(':')
84 if len(split) != 2:
85 raise RuntimeError('Not sure how to parse target "%s"' % target)
86
87 if split[0] == '<(EXTERNALS)':
88 return '//third_party/' + split[1]
89
90 split_path = split[0].rsplit('/', 1)
91 if len(split_path) != 2:
92 raise RuntimeError('TODO(Brian): Handle referring to this .gyp file as %s!' % split[0])
93 if not split_path[1].endswith('.gyp'):
94 raise RuntimeError('Not sure how to deal with gyp filename "%s"' % split[0])
95
96 folder = gyp_file_to_bazel(gyp_file_name, split_path[0])
97
98 if not folder.endswith(split_path[1][:-4]):
99 raise RuntimeError('Not sure how to deal with non-matching gyp file "%s"' % target)
100
101 return '%s:%s' % (folder, split[1])
102
103'''Represents a Bazel build target.
104
105Subclasses represent actual concrete things which are emitted into a BUILD
106file.'''
107class BuildTarget(object):
108 def __init__(self, type, name):
109 self.__type = type
110 self.__name = name
111
112 def add_dep(self, bazel_dep):
113 self.__deps.append(bazel_dep)
114
Brian Silverman100534c2015-09-07 15:51:23 -0400115 def _type(self):
116 return self.__type
117
Brian Silverman8c374e02015-09-06 23:02:21 -0400118 '''Returns a collections.OrderedDict with all of the attributes on the
119 Bazel rule this represents.
120
121 Subclasses are expected to override this and add their own attributes
122 in the appropriate order.'''
123 def attrs(self):
124 r = collections.OrderedDict()
125 r['name'] = self.__name
126 return r
127
128 '''Returns a set of load statements.
129
130 Subclasses are expected to override this and add their own loads.
131
132 Each element of the result is the arguments to a single load call.'''
133 def loads(self):
134 return set()
135
136 '''Returns the Bazel representation of a given object as an attribute
137 value.'''
138 def __to_bazel_string(o):
139 if isinstance(o, str):
140 return repr(o)
141 if hasattr(o, '__iter__'):
142 r = ['[']
143 for c in o:
144 r.append(' %s,' % BuildTarget.__to_bazel_string(c))
145 r.append(' ]')
146 return '\n'.join(r)
147 else:
148 return str(o)
149
150 def __str__(self):
151 r = [self.__type + '(']
152 for name, value in self.attrs().items():
153 if value:
154 r.append(' %s = %s,' % (name, BuildTarget.__to_bazel_string(value)))
155 r.append(')')
156 return '\n'.join(r)
157
158'''Represents a cc_* target.'''
159class CcBuildTarget(BuildTarget):
160 def __init__(self, type, name):
161 if not type.startswith('cc_'):
162 raise
163
164 super(CcBuildTarget, self).__init__(type, name)
165
166 self.__srcs = []
Brian Silverman100534c2015-09-07 15:51:23 -0400167 self.__hdrs = []
Brian Silverman8c374e02015-09-06 23:02:21 -0400168 self.__deps = []
Brian Silverman100534c2015-09-07 15:51:23 -0400169 self.__tags = []
Brian Silverman8c374e02015-09-06 23:02:21 -0400170
171 def add_src(self, src):
172 self.__srcs.append(src)
173
Brian Silverman100534c2015-09-07 15:51:23 -0400174 def add_hdr(self, hdr):
175 self.__hdrs.append(hdr)
176
Brian Silverman8c374e02015-09-06 23:02:21 -0400177 def add_dep(self, dep):
178 self.__deps.append(dep)
179
Brian Silverman100534c2015-09-07 15:51:23 -0400180 def add_tag(self, tag):
181 if self._type() != 'cc_test':
182 raise RuntimeError(
183 'Trying to add tag %s to non-test type %s' % (tag, self._type()))
184 self.__tags.append(tag)
185
Brian Silverman8c374e02015-09-06 23:02:21 -0400186 def attrs(self):
187 r = super(CcBuildTarget, self).attrs();
188 r['srcs'] = self.__srcs
Brian Silverman100534c2015-09-07 15:51:23 -0400189 r['hdrs'] = self.__hdrs
190 r['tags'] = self.__tags
Brian Silverman8c374e02015-09-06 23:02:21 -0400191 r['deps'] = self.__deps
192 return r
193
194'''Represents a filegroup target.'''
195class FilegroupTarget(BuildTarget):
196 def __init__(self, name):
197 super(FilegroupTarget, self).__init__('filegroup', name)
198
199 self.__srcs = []
200
201 def add_src(self, src):
202 self.__srcs.append(src)
203
204 def attrs(self):
205 r = super(FilegroupTarget, self).attrs();
206 r['srcs'] = self.__srcs
207 return r
208
209'''Represents a queue_library target.'''
210class QueueTarget(BuildTarget):
211 def __init__(self, name):
212 super(QueueTarget, self).__init__('queue_library', name)
213
214 self.__srcs = []
Brian Silverman100534c2015-09-07 15:51:23 -0400215 self.__deps = []
Brian Silverman8c374e02015-09-06 23:02:21 -0400216
217 def add_src(self, src):
218 self.__srcs.append(src)
219
Brian Silverman100534c2015-09-07 15:51:23 -0400220 def add_dep(self, dep):
221 self.__deps.append(dep)
222
Brian Silverman8c374e02015-09-06 23:02:21 -0400223 def loads(self):
Brian Silverman100534c2015-09-07 15:51:23 -0400224 return set((('/aos/build/queues', 'queue_library'),))
Brian Silverman8c374e02015-09-06 23:02:21 -0400225
226 def attrs(self):
227 r = super(QueueTarget, self).attrs();
228 r['srcs'] = self.__srcs
Brian Silverman100534c2015-09-07 15:51:23 -0400229 r['deps'] = self.__deps
Brian Silverman8c374e02015-09-06 23:02:21 -0400230 return r
231
Brian Silverman100534c2015-09-07 15:51:23 -0400232def _warn_attr(keys_to_handle, name, gyp_file_name, attr):
233 if attr in keys_to_handle:
234 print('Target %s in %s has %s' % (name, gyp_file_name, attr),
235 file=sys.stderr)
236 keys_to_handle.remove(attr)
237
Brian Silverman8c374e02015-09-06 23:02:21 -0400238def main(argv):
239 for d in argv:
240 build_targets = []
241
242 d = d.rstrip('/')
243 gyp_file_name = os.path.join(d, os.path.split(d)[-1] + '.gyp')
244 with open(gyp_file_name, 'r') as gyp_file:
245 gyp = yaml.load(gyp_file)
246 if 'targets' not in gyp:
247 print('No targets entry found in %s!' % gyp_file_name, file=sys.stderr)
248 return 1
249 if list(gyp.keys()) != ['targets']:
250 print('Unknown keys of %s from %s' % (gyp.keys(), gyp_file_name),
251 file=sys.stderr)
252 targets = gyp['targets']
253 for gyp_target in targets:
254 target = None
Brian Silverman100534c2015-09-07 15:51:23 -0400255 keys_to_handle = set(gyp_target.keys())
256 if 'export_dependent_settings' in gyp_target:
257 keys_to_handle.remove('export_dependent_settings')
Brian Silverman8c374e02015-09-06 23:02:21 -0400258 name = gyp_target['target_name']
Brian Silverman100534c2015-09-07 15:51:23 -0400259 keys_to_handle.remove('target_name')
260 _warn_attr(keys_to_handle, name, gyp_file_name, 'actions')
261 _warn_attr(keys_to_handle, name, gyp_file_name, 'conditions')
262 _warn_attr(keys_to_handle, name, gyp_file_name, 'copies')
263 _warn_attr(keys_to_handle, name, gyp_file_name, 'hard_dependency')
264 _warn_attr(keys_to_handle, name, gyp_file_name,
265 'direct_dependent_settings')
266
267 # These are getting moved to the right place manually.
268 if gyp_file_name == 'aos/common/common.gyp':
269 if name == 'condition' or name == 'mutex' or name == 'event':
270 continue
271 # By building ..., this becomes irrelevant.
272 if gyp_file_name == 'frc971/frc971.gyp':
273 if name == 'All':
274 continue
275
276 if 'variables' in gyp_target:
277 if 'no_rsync' in gyp_target['variables']:
278 del gyp_target['variables']['no_rsync']
279
Brian Silverman8c374e02015-09-06 23:02:21 -0400280 type = gyp_target['type']
Brian Silverman100534c2015-09-07 15:51:23 -0400281 keys_to_handle.remove('type')
Brian Silverman8c374e02015-09-06 23:02:21 -0400282 if (type in ['static_library', 'executable'] and
283 not 'includes' in gyp_target):
284 cc_type = {
285 'static_library': 'cc_library',
286 'executable': 'cc_binary',
287 }[type]
Brian Silverman100534c2015-09-07 15:51:23 -0400288 if re.match('.*_test$', name) and cc_type == 'cc_binary':
289 cc_type = 'cc_test'
Brian Silverman8c374e02015-09-06 23:02:21 -0400290 target = CcBuildTarget(cc_type, name)
291
Brian Silverman100534c2015-09-07 15:51:23 -0400292 if 'dependencies' in gyp_target:
293 for dep in gyp_target['dependencies']:
294 target.add_dep(gyp_target_to_bazel(gyp_file_name, dep))
295 keys_to_handle.remove('dependencies')
296 if 'sources' in gyp_target:
297 for src in gyp_target['sources']:
298 # In //aos/common:queue_types, this will get dealt with manually
299 # along with the actions.
300 if src == '<(print_field_cc)':
301 continue
302
303 if '/' in src:
304 raise RuntimeError(
305 'Bad folder for %s in target %s in %s' % (src, name,
306 gyp_file_name))
307
308 target.add_src(src)
309
310 # This is sort of a heuristic: if there's a header file matching
311 # the source file, add it as an hdr. This is going to require some
312 # manual cleanup, but it'll be close.
313 src_filename = os.path.join(os.path.dirname(gyp_file_name), src)
314 if not os.path.exists(src_filename):
315 raise RuntimeError(
316 'Can not find source %s in target %s' % (src_filename,
317 name))
318 header = src_filename.rsplit('.', 2)[0] + '.h'
319 if os.path.exists(header):
320 target.add_hdr(src.rsplit('.', 2)[0] + '.h')
321 keys_to_handle.remove('sources')
322 if 'variables' in gyp_target:
323 vars = gyp_target['variables']
324 if 'is_special_test' in vars:
325 if vars['is_special_test'] != 1:
326 raise RuntimeError(
327 'Unexpected is_special_test value in target %s' % name)
328 target.add_tag('manual')
329 del vars['is_special_test']
Brian Silverman8c374e02015-09-06 23:02:21 -0400330 elif type == 'none':
331 target = FilegroupTarget(name)
332 for dep in gyp_target['dependencies']:
333 target.add_src(gyp_target_to_bazel(gyp_file_name, dep))
Brian Silverman100534c2015-09-07 15:51:23 -0400334 keys_to_handle.remove('dependencies')
Brian Silverman8c374e02015-09-06 23:02:21 -0400335 elif 'includes' in gyp_target:
336 includes = gyp_target['includes']
Brian Silverman100534c2015-09-07 15:51:23 -0400337 keys_to_handle.remove('includes')
Brian Silverman8c374e02015-09-06 23:02:21 -0400338 if len(includes) != 1:
339 raise RuntimeError(
340 'Not sure how to handle multiple includes in %s' % gyp_target)
341 include = gyp_file_to_bazel(gyp_file_name, includes[0])
342 if include == '//aos/build/queues.gypi':
343 vars = gyp_target['variables']
Brian Silverman100534c2015-09-07 15:51:23 -0400344 keys_to_handle.remove('variables')
Brian Silverman8c374e02015-09-06 23:02:21 -0400345 if 'header_path' not in vars:
346 raise RuntimeError(
347 'No header_path for target %s in %s' % (name, gyp_file_name))
348 if list(vars.keys()) != ['header_path']:
349 raise RuntimeError(
350 'Extra variables for target %s in %s' % (name, gyp_file_name))
351 if vars['header_path'] != os.path.dirname(gyp_file_name):
352 raise RuntimeError(
353 'Incorrect header_path for target %s in %s' % (name,
354 gyp_file_name))
355
356 target = QueueTarget(name)
357 for src in gyp_target['sources']:
358 if '/' in src:
359 raise RuntimeError(
360 '.q src %s in bad dir for target %s in %s' % (src,
361 name,
362 gyp_file_name))
363 target.add_src(src)
Brian Silverman100534c2015-09-07 15:51:23 -0400364 keys_to_handle.remove('sources')
365 if 'dependencies' in gyp_target:
366 for dep in gyp_target['dependencies']:
367 target.add_dep(gyp_target_to_bazel(gyp_file_name, dep))
368 keys_to_handle.remove('dependencies')
Brian Silverman8c374e02015-09-06 23:02:21 -0400369 else:
370 raise RuntimeError(
371 'Unknown include %s for target %s in %s' % (include, name,
372 gyp_file_name))
373 else:
374 raise RuntimeError(
375 'Unknown type %s for target %s in %s' % (type, name, gyp_file_name))
376
377 if not target:
378 raise
Brian Silverman100534c2015-09-07 15:51:23 -0400379
380 if (gyp_file_name == 'y2015/http_status/http_status.gyp' and
381 name == 'http_status'):
382 # We'll handle these manually.
383 keys_to_handle.remove('include_dirs')
384 if (gyp_file_name == 'aos/common/common.gyp' and
385 name == 'queue_types'):
386 # These will get handled manually as part of dealing with the
387 # actions.
388 keys_to_handle.remove('variables')
389
390 # If there were variables but they all got deleted, then we don't
391 # actually have any more to handle.
392 if 'variables' in keys_to_handle and not gyp_target['variables']:
393 keys_to_handle.remove('variables')
394 if keys_to_handle:
395 raise RuntimeError(
396 'Unhandled keys for target %s in %s: %s' % (name, gyp_file_name,
397 keys_to_handle))
Brian Silverman8c374e02015-09-06 23:02:21 -0400398 build_targets.append(target)
399
Brian Silverman100534c2015-09-07 15:51:23 -0400400 if not build_targets:
401 print('No output targets for %s' % d, file=sys.stderr)
402 continue
403
Brian Silverman8c374e02015-09-06 23:02:21 -0400404 with open(os.path.join(d, 'BUILD'), 'w') as build_file:
405 build_file.write(
406 'package(default_visibility = [\'//visibility:public\'])\n')
407 loads = set()
408 for t in build_targets:
409 loads |= t.loads()
410 if loads:
411 build_file.write('\n')
412 for load in sorted(loads):
413 build_file.write('load(%s)\n' % (', '.join([repr(part) for part
414 in load])))
415 for t in build_targets:
416 build_file.write('\n')
417 build_file.write(str(t))
418 build_file.write('\n')
419
420if __name__ == '__main__':
421 sys.exit(main(sys.argv[1:]))