blob: b3da95bc3e672827754b407c0c5af919112eb40c [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
18
19'''Converts a Gyp filename to its Bazel equivalent.
20
21Args:
22 gyp_file_name: The name of the gyp file this is contained in.
23 This is important for resolving relative paths etc.
24 file: The name of the file to deal with.
25'''
26def gyp_file_to_bazel(gyp_file_name, file):
27 if file.startswith('<(AOS)/'):
28 return '//aos' + file[6:]
29 elif file.startswith('<(DEPTH)/'):
30 return '//' + file[9:]
31 else:
32 if '(' in file:
33 raise RuntimeError('Bad variable in file "%s"' % file)
34 split_gyp = os.path.dirname(gyp_file_name).split('/')
35 rest = []
36 dotdots = 0
37 doing_dotdots = True
38 for component in file.split('/'):
39 if component == '..':
40 dotdots += 1
41 if not doing_dotdots:
42 raise RuntimeError('Bad use of .. in file "%s"' % file)
43 else:
44 doing_dotdots = False
45 rest.append(component)
46 return '/'.join(['/'] + split_gyp[:-dotdots] + rest)
47
48'''Converts a Gyp target to its Bazel equivalent.
49
50Args:
51 gyp_file_name: The name of the gyp file this is contained in.
52 This is important for resolving relative paths etc.
53 target: The name of the target to deal with.
54'''
55def gyp_target_to_bazel(gyp_file_name, target):
56 if not ':' in target:
57 if '.' in target:
58 raise RuntimeError('Have a filename instead of a target: "%s"' % target)
59 return ':' + target
60 if target[0] == ':':
61 return target
62
63 if target == '<(AOS)/build/aos.gyp:logging':
64 return '//aos/common/logging'
65
66 split = target.split(':')
67 if len(split) != 2:
68 raise RuntimeError('Not sure how to parse target "%s"' % target)
69
70 if split[0] == '<(EXTERNALS)':
71 return '//third_party/' + split[1]
72
73 split_path = split[0].rsplit('/', 1)
74 if len(split_path) != 2:
75 raise RuntimeError('TODO(Brian): Handle referring to this .gyp file as %s!' % split[0])
76 if not split_path[1].endswith('.gyp'):
77 raise RuntimeError('Not sure how to deal with gyp filename "%s"' % split[0])
78
79 folder = gyp_file_to_bazel(gyp_file_name, split_path[0])
80
81 if not folder.endswith(split_path[1][:-4]):
82 raise RuntimeError('Not sure how to deal with non-matching gyp file "%s"' % target)
83
84 return '%s:%s' % (folder, split[1])
85
86'''Represents a Bazel build target.
87
88Subclasses represent actual concrete things which are emitted into a BUILD
89file.'''
90class BuildTarget(object):
91 def __init__(self, type, name):
92 self.__type = type
93 self.__name = name
94
95 def add_dep(self, bazel_dep):
96 self.__deps.append(bazel_dep)
97
98 '''Returns a collections.OrderedDict with all of the attributes on the
99 Bazel rule this represents.
100
101 Subclasses are expected to override this and add their own attributes
102 in the appropriate order.'''
103 def attrs(self):
104 r = collections.OrderedDict()
105 r['name'] = self.__name
106 return r
107
108 '''Returns a set of load statements.
109
110 Subclasses are expected to override this and add their own loads.
111
112 Each element of the result is the arguments to a single load call.'''
113 def loads(self):
114 return set()
115
116 '''Returns the Bazel representation of a given object as an attribute
117 value.'''
118 def __to_bazel_string(o):
119 if isinstance(o, str):
120 return repr(o)
121 if hasattr(o, '__iter__'):
122 r = ['[']
123 for c in o:
124 r.append(' %s,' % BuildTarget.__to_bazel_string(c))
125 r.append(' ]')
126 return '\n'.join(r)
127 else:
128 return str(o)
129
130 def __str__(self):
131 r = [self.__type + '(']
132 for name, value in self.attrs().items():
133 if value:
134 r.append(' %s = %s,' % (name, BuildTarget.__to_bazel_string(value)))
135 r.append(')')
136 return '\n'.join(r)
137
138'''Represents a cc_* target.'''
139class CcBuildTarget(BuildTarget):
140 def __init__(self, type, name):
141 if not type.startswith('cc_'):
142 raise
143
144 super(CcBuildTarget, self).__init__(type, name)
145
146 self.__srcs = []
147 self.__deps = []
148
149 def add_src(self, src):
150 self.__srcs.append(src)
151
152 def add_dep(self, dep):
153 self.__deps.append(dep)
154
155 def attrs(self):
156 r = super(CcBuildTarget, self).attrs();
157 r['srcs'] = self.__srcs
158 r['deps'] = self.__deps
159 return r
160
161'''Represents a filegroup target.'''
162class FilegroupTarget(BuildTarget):
163 def __init__(self, name):
164 super(FilegroupTarget, self).__init__('filegroup', name)
165
166 self.__srcs = []
167
168 def add_src(self, src):
169 self.__srcs.append(src)
170
171 def attrs(self):
172 r = super(FilegroupTarget, self).attrs();
173 r['srcs'] = self.__srcs
174 return r
175
176'''Represents a queue_library target.'''
177class QueueTarget(BuildTarget):
178 def __init__(self, name):
179 super(QueueTarget, self).__init__('queue_library', name)
180
181 self.__srcs = []
182
183 def add_src(self, src):
184 self.__srcs.append(src)
185
186 def loads(self):
187 return set((('aos/build/queues', 'queue_library'),))
188
189 def attrs(self):
190 r = super(QueueTarget, self).attrs();
191 r['srcs'] = self.__srcs
192 return r
193
194def main(argv):
195 for d in argv:
196 build_targets = []
197
198 d = d.rstrip('/')
199 gyp_file_name = os.path.join(d, os.path.split(d)[-1] + '.gyp')
200 with open(gyp_file_name, 'r') as gyp_file:
201 gyp = yaml.load(gyp_file)
202 if 'targets' not in gyp:
203 print('No targets entry found in %s!' % gyp_file_name, file=sys.stderr)
204 return 1
205 if list(gyp.keys()) != ['targets']:
206 print('Unknown keys of %s from %s' % (gyp.keys(), gyp_file_name),
207 file=sys.stderr)
208 targets = gyp['targets']
209 for gyp_target in targets:
210 target = None
211 name = gyp_target['target_name']
212 type = gyp_target['type']
213 if (type in ['static_library', 'executable'] and
214 not 'includes' in gyp_target):
215 cc_type = {
216 'static_library': 'cc_library',
217 'executable': 'cc_binary',
218 }[type]
219 target = CcBuildTarget(cc_type, name)
220
221 for dep in gyp_target['dependencies']:
222 target.add_dep(gyp_target_to_bazel(gyp_file_name, dep))
223 for src in gyp_target['sources']:
224 target.add_src(src)
225 elif type == 'none':
226 target = FilegroupTarget(name)
227 for dep in gyp_target['dependencies']:
228 target.add_src(gyp_target_to_bazel(gyp_file_name, dep))
229 elif 'includes' in gyp_target:
230 includes = gyp_target['includes']
231 if len(includes) != 1:
232 raise RuntimeError(
233 'Not sure how to handle multiple includes in %s' % gyp_target)
234 include = gyp_file_to_bazel(gyp_file_name, includes[0])
235 if include == '//aos/build/queues.gypi':
236 vars = gyp_target['variables']
237 if 'header_path' not in vars:
238 raise RuntimeError(
239 'No header_path for target %s in %s' % (name, gyp_file_name))
240 if list(vars.keys()) != ['header_path']:
241 raise RuntimeError(
242 'Extra variables for target %s in %s' % (name, gyp_file_name))
243 if vars['header_path'] != os.path.dirname(gyp_file_name):
244 raise RuntimeError(
245 'Incorrect header_path for target %s in %s' % (name,
246 gyp_file_name))
247
248 target = QueueTarget(name)
249 for src in gyp_target['sources']:
250 if '/' in src:
251 raise RuntimeError(
252 '.q src %s in bad dir for target %s in %s' % (src,
253 name,
254 gyp_file_name))
255 target.add_src(src)
256 else:
257 raise RuntimeError(
258 'Unknown include %s for target %s in %s' % (include, name,
259 gyp_file_name))
260 else:
261 raise RuntimeError(
262 'Unknown type %s for target %s in %s' % (type, name, gyp_file_name))
263
264 if not target:
265 raise
266 build_targets.append(target)
267
268 with open(os.path.join(d, 'BUILD'), 'w') as build_file:
269 build_file.write(
270 'package(default_visibility = [\'//visibility:public\'])\n')
271 loads = set()
272 for t in build_targets:
273 loads |= t.loads()
274 if loads:
275 build_file.write('\n')
276 for load in sorted(loads):
277 build_file.write('load(%s)\n' % (', '.join([repr(part) for part
278 in load])))
279 for t in build_targets:
280 build_file.write('\n')
281 build_file.write(str(t))
282 build_file.write('\n')
283
284if __name__ == '__main__':
285 sys.exit(main(sys.argv[1:]))