| OLD | NEW |
| (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() |
| OLD | NEW |