Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(263)

Side by Side Diff: chromium/scripts/generate_gyp.py

Issue 9290059: Initial commit of all previous Chrome build scripts. (Closed) Base URL: http://git.chromium.org/chromium/third_party/ffmpeg.git@master
Patch Set: Drop deprecated subfolder. Created 8 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """Creates a GYP include file for building FFmpeg from source.
8
9 The way this works is a bit silly but it's easier than reverse engineering
10 FFmpeg's configure scripts and Makefiles. It scans through build directories for
11 object files then does a reverse lookup against the FFmpeg source tree to find
12 the corresponding C or assembly file.
13
14 Running build_ffmpeg.sh for ia32, x64, arm, and arm-neon platforms is required
15 prior to running this script. The arm and arm-neon platforms assume a
16 Chromium OS build environment.
17
18 Step 1: Have a Chromium OS checkout (refer to http://dev.chromium.org)
19 mkdir chromeos
20 repo init ...
21 repo sync
22
23 Step 2: Check out deps/third_party inside Chromium OS
24 cd path/to/chromeos
25 mkdir deps
26 cd deps
27 svn co svn://svn.chromium.org/chrome/trunk/deps/third_party/ffmpeg
28 svn co svn://svn.chromium.org/chrome/trunk/deps/third_party/libvpx
29
30 Step 3: Build for ia32/x64 platforms outside chroot
31 cd path/to/chromeos/deps/ffmpeg
32 ./build_ffmpeg.sh linux ia32 path/to/chromeos/deps
33 ./build_ffmpeg.sh linux x64 path/to/chromeos/deps
34
35 Step 4: Build and enter Chromium OS chroot:
36 cd path/to/chromeos/src/scripts
37 ./make_chroot
38 ./enter_chroot.sh
39
40 Step 5: Setup build environment for ARM:
41 ./setup_board --board arm-generic
42
43 Step 6: Build for arm/arm-neon platforms inside chroot
44 ./build_ffmpeg.sh linux arm path/to/chromeos/deps
45 ./build_ffmpeg.sh linux arm-neon path/to/chromeos/deps
46
47 Step 7: Exit chroot and generate gyp file
48 exit
49 cd path/to/chromeos/deps/ffmpeg
50 ./generate_gyp.py --source_dir=source --build_dir=.
51
52 Phew!
53
54 While this seems insane, reverse engineering and maintaining a gyp file by hand
55 is significantly more painful.
56 """
57
58 __author__ = 'scherkus@chromium.org (Andrew Scherkus)'
59
60 import datetime
61 import itertools
62 import optparse
63 import os
64 import string
65 import sys
66
67 GYP_HEADER = """# Copyright (c) %d The Chromium Authors. All rights reserved.
68 # Use of this source code is governed by a BSD-style license that can be
69 # found in the LICENSE file.
70
71 # NOTE: this file is autogenerated by deps/third_party/ffmpeg/generate_gyp.py
72
73 {
74 'conditions': [
75 """ % (datetime.datetime.now().year)
76
77 GYP_FOOTER = """ ], # conditions
78 }
79 """
80
81 GYP_CONDITIONAL_SOURCE_STANZA_BEGIN = """ ['%s', {
82 'sources': [
83 """
84 GYP_CONDITIONAL_SOURCE_STANZA_ITEM = """ 'patched-ffmpeg/%s',
85 """
86 GYP_CONDITIONAL_SOURCE_STANZA_END = """ ],
87 }], # %s
88 """
89
90 # Controls GYP conditional stanza generation.
91 SUPPORTED_ARCHITECTURES = ['ia32', 'x64', 'arm', 'arm-neon']
92 SUPPORTED_TARGETS = ['Chromium', 'Chrome', 'ChromiumOS', 'ChromeOS']
93
94 def NormalizeFilename(name):
95 """ Removes leading path separators in an attempt to normalize paths."""
96 return string.lstrip(name, os.sep)
97
98
99 def CleanObjectFiles(object_files):
100 """Removes unneeded object files due to linker errors, binary size, etc...
101
102 Args:
103 object_files: List of object files that needs cleaning.
104 """
105 blacklist = [
106 'libavcodec/inverse.o', # Includes libavutil/inverse.c
107
108 # The following files are removed to trim down on binary size.
109 # TODO(ihf): Warning, it is *easy* right now to remove more files
110 # than is healthy and end up with a library that the linker does
111 # not complain about but that can't be loaded. Add some verification!
112 'libavcodec/dirac.o',
113 'libavcodec/resample.o',
114 'libavcodec/resample2.o',
115 'libavcodec/x86/dnxhd_mmx.o',
116 'libavformat/os_support.o',
117 'libavformat/sdp.o',
118 'libavutil/adler32.o',
119 'libavutil/aes.o',
120 'libavutil/des.o',
121 'libavutil/error.o',
122 'libavutil/file.o',
123 'libavutil/lls.o',
124 'libavutil/rc4.o',
125 'libavutil/sha.o',
126 'libavutil/tree.o',
127 ]
128 for name in blacklist:
129 if name in object_files:
130 object_files.remove(name)
131 return object_files
132
133
134 def GetSourceFiles(source_dir):
135 """Returns a list of source files for the given source directory.
136
137 Args:
138 source_dir: Path to build a source mapping for.
139
140 Returns:
141 A python list of source file paths.
142 """
143
144 def IsSourceDir(d):
145 return d not in ['.git', '.svn']
146
147 def IsSourceFile(f):
148 _, ext = os.path.splitext(f)
149 return ext in ['.c', '.S', '.asm']
150
151 source_files = []
152 for root, dirs, files in os.walk(source_dir):
153 dirs = filter(IsSourceDir, dirs)
154 files = filter(IsSourceFile, files)
155
156 # Strip leading source_dir from root.
157 root = root[len(source_dir):]
158 source_files.extend([NormalizeFilename(os.path.join(root, name)) for name in
159 files])
160 return source_files
161
162
163 def GetObjectFiles(build_dir):
164 """Returns a list of object files for the given build directory.
165
166 Args:
167 build_dir: Path to build an object file list for.
168
169 Returns:
170 A python list of object files paths.
171 """
172 object_files = []
173 for root, dirs, files in os.walk(build_dir):
174 # Strip leading build_dir from root.
175 root = root[len(build_dir):]
176
177 for name in files:
178 _, ext = os.path.splitext(name)
179 if ext == '.o':
180 name = NormalizeFilename(os.path.join(root, name))
181 object_files.append(name)
182 CleanObjectFiles(object_files)
183 return object_files
184
185
186 def GetObjectToSourceMapping(source_files):
187 """Returns a map of object file paths to source file paths.
188
189 Args:
190 source_files: List of source file paths.
191
192 Returns:
193 Map with object file paths as keys and source file paths as values.
194 """
195 object_to_sources = {}
196 for name in source_files:
197 basename, ext = os.path.splitext(name)
198 key = basename + '.o'
199 object_to_sources[key] = name
200 return object_to_sources
201
202
203 def GetSourceFileSet(object_to_sources, object_files):
204 """Determines set of source files given object files.
205
206 Args:
207 object_to_sources: A dictionary of object to source file paths.
208 object_files: A list of object file paths.
209
210 Returns:
211 A python set of source files requried to build said objects.
212 """
213 source_set = set()
214 for name in object_files:
215 # Intentially raise a KeyError if lookup fails since something is messed
216 # up with our source and object lists.
217 source_set.add(object_to_sources[name])
218 return source_set
219
220
221 class SourceSet(object):
222 """A SourceSet represents a set of source files that are built on the given
223 set of architectures and targets.
224 """
225
226 def __init__(self, sources, architectures, targets):
227 """Creates a SourceSet.
228
229 Args:
230 sources: a python set of source files
231 architectures: a python set of architectures (i.e., arm, x64)
232 targets: a python set of targets (i.e., Chromium, Chrome)
233 """
234 self.sources = sources
235 self.architectures = architectures
236 self.targets = targets
237
238 def __repr__(self):
239 return '{%s, %s, %s}' % (self.sources, self.architectures, self.targets)
240
241 def __eq__(self, other):
242 return (self.sources == other.sources and
243 self.architectures == other.architectures and
244 self.targets == other.targets)
245
246 def Intersect(self, other):
247 """Return a new SourceSet containing the set of source files common to both
248 this and the other SourceSet.
249
250 The resulting SourceSet represents the union of the architectures and
251 targets of this and the other SourceSet.
252 """
253 return SourceSet(self.sources & other.sources,
254 self.architectures | other.architectures,
255 self.targets | other.targets)
256
257 def Difference(self, other):
258 """Return a new SourceSet containing the set of source files not present in
259 the other SourceSet.
260
261 The resulting SourceSet represents the intersection of the architectures and
262 targets of this and the other SourceSet.
263 """
264 return SourceSet(self.sources - other.sources,
265 self.architectures & other.architectures,
266 self.targets & other.targets)
267
268 def IsEmpty(self):
269 """An empty SourceSet is defined as containing no source files or no
270 architecture/target (i.e., a set of files that aren't built on anywhere).
271 """
272 return (len(self.sources) == 0 or len(self.architectures) == 0 or
273 len(self.targets) == 0)
274
275 def GenerateGypStanza(self):
276 """Generates a gyp conditional stanza representing this source set.
277
278 TODO(scherkus): Having all this special case condition optimizing logic in
279 here feels a bit dirty, but hey it works. Perhaps refactor if it starts
280 getting out of hand.
281
282 Returns:
283 A string of gyp code.
284 """
285
286 # Only build a non-trivial conditional if it's a subset of all supported
287 # architectures.
288 arch_conditions = []
289 if self.architectures == set(SUPPORTED_ARCHITECTURES):
290 arch_conditions.append('1')
291 else:
292 for arch in self.architectures:
293 if arch == 'arm-neon':
294 arch_conditions.append('(target_arch == "arm" and arm_neon == 1)')
295 else:
296 arch_conditions.append('target_arch == "%s"' % arch)
297
298
299 # Only build a non-trivial conditional if it's a subset of all supported
300 # targets.
301 branding_conditions = []
302 if self.targets == set(SUPPORTED_TARGETS):
303 branding_conditions.append('1')
304 else:
305 for branding in self.targets:
306 branding_conditions.append('ffmpeg_branding == "%s"' % branding)
307
308 conditions = '(%s) and (%s)' % (' or '.join(arch_conditions),
309 ' or '.join(branding_conditions))
310
311 stanza = []
312 stanza += GYP_CONDITIONAL_SOURCE_STANZA_BEGIN % (conditions)
313 for name in sorted(self.sources):
314 stanza += GYP_CONDITIONAL_SOURCE_STANZA_ITEM % (name)
315 stanza += GYP_CONDITIONAL_SOURCE_STANZA_END % (conditions)
316 return ''.join(stanza)
317
318
319 def CreatePairwiseDisjointSets(sets):
320 """ Given a list of SourceSet objects, returns the pairwise disjoint sets.
321
322 NOTE: This isn't the most efficient algorithm, but given how infrequent we
323 need to run this and how small the input size is we'll leave it as is.
324 """
325
326 disjoint_sets = list(sets)
327
328 new_sets = True
329 while new_sets:
330 new_sets = False
331 for pair in itertools.combinations(disjoint_sets, 2):
332 intersection = pair[0].Intersect(pair[1])
333
334 # Both pairs are already disjoint, nothing to do.
335 if intersection.IsEmpty():
336 continue
337
338 # Add the resulting intersection set.
339 new_sets = True
340 disjoint_sets.append(intersection)
341
342 # Calculate the resulting differences for this pair of sets.
343 #
344 # If the differences are an empty set, remove them from the list of sets,
345 # otherwise update the set itself.
346 for p in pair:
347 i = disjoint_sets.index(p)
348 difference = p.Difference(intersection)
349 if difference.IsEmpty():
350 del disjoint_sets[i]
351 else:
352 disjoint_sets[i] = difference
353
354 # Restart the calculation since the list of disjoint sets has changed.
355 break
356
357 return disjoint_sets
358
359
360 def ParseOptions():
361 """Parses the options and terminates program if they are not sane.
362
363 Returns:
364 The pair (optparse.OptionValues, [string]), that is the output of
365 a successful call to parser.parse_args().
366 """
367 parser = optparse.OptionParser(
368 usage='usage: %prog [options] DIR')
369
370 parser.add_option('-s',
371 '--source_dir',
372 dest='source_dir',
373 default=None,
374 metavar='DIR',
375 help='FFmpeg source directory containing patched-ffmpeg')
376
377 parser.add_option('-b',
378 '--build_dir',
379 dest='build_dir',
380 default='.',
381 metavar='DIR',
382 help='Build root containing build.x64.linux, etc...')
383
384 options, args = parser.parse_args()
385
386 if not options.source_dir:
387 parser.error('No FFmpeg source directory specified')
388
389 if not os.path.exists(os.path.join(options.source_dir, 'patched-ffmpeg')):
390 parser.error('FFmpeg source directory does not contain patched-ffmpeg')
391
392 if not options.build_dir:
393 parser.error('No build root directory specified')
394
395 return options, args
396
397
398 def main():
399 options, args = ParseOptions()
400
401 # Generate map of FFmpeg source files.
402 source_dir = os.path.join(options.source_dir, 'patched-ffmpeg')
403 source_files = GetSourceFiles(source_dir)
404 object_to_sources = GetObjectToSourceMapping(source_files)
405
406 # Open for writing.
407 output_name = os.path.join(options.source_dir, 'ffmpeg_generated.gypi')
408 fd = open(output_name, 'w')
409 fd.write(GYP_HEADER)
410
411 sets = []
412
413 for arch in SUPPORTED_ARCHITECTURES:
414 for target in SUPPORTED_TARGETS:
415 # Construct build directory in the form of build.$arch.linux/$target.
416 #
417 # NOTE: FFmpeg doesn't have any mac-specific files, so instead of
418 # complicating the entire process worrying about multiple OSes, we'll
419 # assume that linux is good enough for now.
420 name = ''.join(['build.', arch, '.linux'])
421 build_dir = os.path.join(options.build_dir, name, target)
422 if not os.path.exists(build_dir):
423 print "Build directory not found: %s" % build_dir
424 sys.exit(1)
425
426 object_files = GetObjectFiles(build_dir)
427
428 # Generate the set of source files to build said target.
429 s = GetSourceFileSet(object_to_sources, object_files)
430 sets.append(SourceSet(s, set([arch]), set([target])))
431
432 # Generate conditional stanza for each disjoint source set.
433 for s in CreatePairwiseDisjointSets(sets):
434 fd.write(s.GenerateGypStanza())
435
436 fd.write(GYP_FOOTER)
437 fd.close()
438
439 if __name__ == '__main__':
440 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698