blob: aa30eb9e91ea5d021aa4706cf93aa5ed5a1f2bd7 [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):
Brian Silvermanf480a612015-09-13 02:22:01 -0400187 unique_deps = []
188 for dep in self.__deps:
189 if dep not in unique_deps:
190 unique_deps.append(dep)
Brian Silverman8c374e02015-09-06 23:02:21 -0400191 r = super(CcBuildTarget, self).attrs();
192 r['srcs'] = self.__srcs
Brian Silverman100534c2015-09-07 15:51:23 -0400193 r['hdrs'] = self.__hdrs
194 r['tags'] = self.__tags
Brian Silvermanf480a612015-09-13 02:22:01 -0400195 r['deps'] = unique_deps
Brian Silverman8c374e02015-09-06 23:02:21 -0400196 return r
197
198'''Represents a filegroup target.'''
199class FilegroupTarget(BuildTarget):
200 def __init__(self, name):
201 super(FilegroupTarget, self).__init__('filegroup', name)
202
203 self.__srcs = []
204
205 def add_src(self, src):
206 self.__srcs.append(src)
207
208 def attrs(self):
209 r = super(FilegroupTarget, self).attrs();
210 r['srcs'] = self.__srcs
211 return r
212
213'''Represents a queue_library target.'''
214class QueueTarget(BuildTarget):
215 def __init__(self, name):
216 super(QueueTarget, self).__init__('queue_library', name)
217
218 self.__srcs = []
Brian Silverman100534c2015-09-07 15:51:23 -0400219 self.__deps = []
Brian Silverman8c374e02015-09-06 23:02:21 -0400220
221 def add_src(self, src):
222 self.__srcs.append(src)
223
Brian Silverman100534c2015-09-07 15:51:23 -0400224 def add_dep(self, dep):
225 self.__deps.append(dep)
226
Brian Silverman8c374e02015-09-06 23:02:21 -0400227 def loads(self):
Brian Silverman100534c2015-09-07 15:51:23 -0400228 return set((('/aos/build/queues', 'queue_library'),))
Brian Silverman8c374e02015-09-06 23:02:21 -0400229
230 def attrs(self):
231 r = super(QueueTarget, self).attrs();
232 r['srcs'] = self.__srcs
Brian Silverman100534c2015-09-07 15:51:23 -0400233 r['deps'] = self.__deps
Brian Silverman8c374e02015-09-06 23:02:21 -0400234 return r
235
Brian Silverman100534c2015-09-07 15:51:23 -0400236def _warn_attr(keys_to_handle, name, gyp_file_name, attr):
237 if attr in keys_to_handle:
238 print('Target %s in %s has %s' % (name, gyp_file_name, attr),
239 file=sys.stderr)
240 keys_to_handle.remove(attr)
241
Brian Silverman8c374e02015-09-06 23:02:21 -0400242def main(argv):
243 for d in argv:
244 build_targets = []
245
246 d = d.rstrip('/')
247 gyp_file_name = os.path.join(d, os.path.split(d)[-1] + '.gyp')
248 with open(gyp_file_name, 'r') as gyp_file:
249 gyp = yaml.load(gyp_file)
250 if 'targets' not in gyp:
251 print('No targets entry found in %s!' % gyp_file_name, file=sys.stderr)
252 return 1
253 if list(gyp.keys()) != ['targets']:
254 print('Unknown keys of %s from %s' % (gyp.keys(), gyp_file_name),
255 file=sys.stderr)
256 targets = gyp['targets']
257 for gyp_target in targets:
258 target = None
Brian Silverman100534c2015-09-07 15:51:23 -0400259 keys_to_handle = set(gyp_target.keys())
260 if 'export_dependent_settings' in gyp_target:
261 keys_to_handle.remove('export_dependent_settings')
Brian Silverman8c374e02015-09-06 23:02:21 -0400262 name = gyp_target['target_name']
Brian Silverman100534c2015-09-07 15:51:23 -0400263 keys_to_handle.remove('target_name')
264 _warn_attr(keys_to_handle, name, gyp_file_name, 'actions')
265 _warn_attr(keys_to_handle, name, gyp_file_name, 'conditions')
266 _warn_attr(keys_to_handle, name, gyp_file_name, 'copies')
267 _warn_attr(keys_to_handle, name, gyp_file_name, 'hard_dependency')
268 _warn_attr(keys_to_handle, name, gyp_file_name,
269 'direct_dependent_settings')
270
271 # These are getting moved to the right place manually.
272 if gyp_file_name == 'aos/common/common.gyp':
273 if name == 'condition' or name == 'mutex' or name == 'event':
274 continue
275 # By building ..., this becomes irrelevant.
276 if gyp_file_name == 'frc971/frc971.gyp':
277 if name == 'All':
278 continue
279
280 if 'variables' in gyp_target:
281 if 'no_rsync' in gyp_target['variables']:
282 del gyp_target['variables']['no_rsync']
283
Brian Silverman8c374e02015-09-06 23:02:21 -0400284 type = gyp_target['type']
Brian Silverman100534c2015-09-07 15:51:23 -0400285 keys_to_handle.remove('type')
Brian Silverman8c374e02015-09-06 23:02:21 -0400286 if (type in ['static_library', 'executable'] and
287 not 'includes' in gyp_target):
288 cc_type = {
289 'static_library': 'cc_library',
290 'executable': 'cc_binary',
291 }[type]
Brian Silverman100534c2015-09-07 15:51:23 -0400292 if re.match('.*_test$', name) and cc_type == 'cc_binary':
293 cc_type = 'cc_test'
Brian Silverman8c374e02015-09-06 23:02:21 -0400294 target = CcBuildTarget(cc_type, name)
295
Brian Silverman100534c2015-09-07 15:51:23 -0400296 if 'dependencies' in gyp_target:
297 for dep in gyp_target['dependencies']:
298 target.add_dep(gyp_target_to_bazel(gyp_file_name, dep))
299 keys_to_handle.remove('dependencies')
300 if 'sources' in gyp_target:
301 for src in gyp_target['sources']:
302 # In //aos/common:queue_types, this will get dealt with manually
303 # along with the actions.
304 if src == '<(print_field_cc)':
305 continue
306
307 if '/' in src:
308 raise RuntimeError(
309 'Bad folder for %s in target %s in %s' % (src, name,
310 gyp_file_name))
311
312 target.add_src(src)
313
314 # This is sort of a heuristic: if there's a header file matching
315 # the source file, add it as an hdr. This is going to require some
316 # manual cleanup, but it'll be close.
317 src_filename = os.path.join(os.path.dirname(gyp_file_name), src)
318 if not os.path.exists(src_filename):
319 raise RuntimeError(
320 'Can not find source %s in target %s' % (src_filename,
321 name))
322 header = src_filename.rsplit('.', 2)[0] + '.h'
323 if os.path.exists(header):
324 target.add_hdr(src.rsplit('.', 2)[0] + '.h')
325 keys_to_handle.remove('sources')
326 if 'variables' in gyp_target:
327 vars = gyp_target['variables']
328 if 'is_special_test' in vars:
329 if vars['is_special_test'] != 1:
330 raise RuntimeError(
331 'Unexpected is_special_test value in target %s' % name)
332 target.add_tag('manual')
333 del vars['is_special_test']
Brian Silverman8c374e02015-09-06 23:02:21 -0400334 elif type == 'none':
335 target = FilegroupTarget(name)
336 for dep in gyp_target['dependencies']:
337 target.add_src(gyp_target_to_bazel(gyp_file_name, dep))
Brian Silverman100534c2015-09-07 15:51:23 -0400338 keys_to_handle.remove('dependencies')
Brian Silverman8c374e02015-09-06 23:02:21 -0400339 elif 'includes' in gyp_target:
340 includes = gyp_target['includes']
Brian Silverman100534c2015-09-07 15:51:23 -0400341 keys_to_handle.remove('includes')
Brian Silverman8c374e02015-09-06 23:02:21 -0400342 if len(includes) != 1:
343 raise RuntimeError(
344 'Not sure how to handle multiple includes in %s' % gyp_target)
345 include = gyp_file_to_bazel(gyp_file_name, includes[0])
346 if include == '//aos/build/queues.gypi':
347 vars = gyp_target['variables']
Brian Silverman100534c2015-09-07 15:51:23 -0400348 keys_to_handle.remove('variables')
Brian Silverman8c374e02015-09-06 23:02:21 -0400349 if 'header_path' not in vars:
350 raise RuntimeError(
351 'No header_path for target %s in %s' % (name, gyp_file_name))
352 if list(vars.keys()) != ['header_path']:
353 raise RuntimeError(
354 'Extra variables for target %s in %s' % (name, gyp_file_name))
355 if vars['header_path'] != os.path.dirname(gyp_file_name):
356 raise RuntimeError(
357 'Incorrect header_path for target %s in %s' % (name,
358 gyp_file_name))
359
360 target = QueueTarget(name)
361 for src in gyp_target['sources']:
362 if '/' in src:
363 raise RuntimeError(
364 '.q src %s in bad dir for target %s in %s' % (src,
365 name,
366 gyp_file_name))
367 target.add_src(src)
Brian Silverman100534c2015-09-07 15:51:23 -0400368 keys_to_handle.remove('sources')
369 if 'dependencies' in gyp_target:
370 for dep in gyp_target['dependencies']:
371 target.add_dep(gyp_target_to_bazel(gyp_file_name, dep))
372 keys_to_handle.remove('dependencies')
Brian Silverman8c374e02015-09-06 23:02:21 -0400373 else:
374 raise RuntimeError(
375 'Unknown include %s for target %s in %s' % (include, name,
376 gyp_file_name))
377 else:
378 raise RuntimeError(
379 'Unknown type %s for target %s in %s' % (type, name, gyp_file_name))
380
381 if not target:
382 raise
Brian Silverman100534c2015-09-07 15:51:23 -0400383
384 if (gyp_file_name == 'y2015/http_status/http_status.gyp' and
385 name == 'http_status'):
386 # We'll handle these manually.
387 keys_to_handle.remove('include_dirs')
388 if (gyp_file_name == 'aos/common/common.gyp' and
389 name == 'queue_types'):
390 # These will get handled manually as part of dealing with the
391 # actions.
392 keys_to_handle.remove('variables')
393
394 # If there were variables but they all got deleted, then we don't
395 # actually have any more to handle.
396 if 'variables' in keys_to_handle and not gyp_target['variables']:
397 keys_to_handle.remove('variables')
398 if keys_to_handle:
399 raise RuntimeError(
400 'Unhandled keys for target %s in %s: %s' % (name, gyp_file_name,
401 keys_to_handle))
Brian Silverman8c374e02015-09-06 23:02:21 -0400402 build_targets.append(target)
403
Brian Silverman100534c2015-09-07 15:51:23 -0400404 if not build_targets:
405 print('No output targets for %s' % d, file=sys.stderr)
406 continue
407
Brian Silverman8c374e02015-09-06 23:02:21 -0400408 with open(os.path.join(d, 'BUILD'), 'w') as build_file:
409 build_file.write(
410 'package(default_visibility = [\'//visibility:public\'])\n')
411 loads = set()
412 for t in build_targets:
413 loads |= t.loads()
414 if loads:
415 build_file.write('\n')
416 for load in sorted(loads):
417 build_file.write('load(%s)\n' % (', '.join([repr(part) for part
418 in load])))
419 for t in build_targets:
420 build_file.write('\n')
421 build_file.write(str(t))
422 build_file.write('\n')
423
424if __name__ == '__main__':
425 sys.exit(main(sys.argv[1:]))