Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import errno | 6 import errno |
| 7 import hashlib | 7 import hashlib |
| 8 import json | 8 import json |
| 9 import optparse | 9 import optparse |
| 10 import os | 10 import os |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 183 path: Full path to this file on the build system | 183 path: Full path to this file on the build system |
| 184 arch: Architecture of this file (e.g., x86-32) | 184 arch: Architecture of this file (e.g., x86-32) |
| 185 url: Relative path to file in the staged web directory. | 185 url: Relative path to file in the staged web directory. |
| 186 Used for specifying the "url" attribute in the nmf file.''' | 186 Used for specifying the "url" attribute in the nmf file.''' |
| 187 | 187 |
| 188 def __init__(self, name, path, url, arch=None): | 188 def __init__(self, name, path, url, arch=None): |
| 189 self.name = name | 189 self.name = name |
| 190 self.path = path | 190 self.path = path |
| 191 self.url = url | 191 self.url = url |
| 192 self.arch = arch | 192 self.arch = arch |
| 193 if arch is None: | 193 if not arch: |
| 194 self.arch = ParseElfHeader(path)[0] | 194 self.arch = ParseElfHeader(path)[0] |
| 195 | 195 |
| 196 def __repr__(self): | 196 def __repr__(self): |
| 197 return '<ArchFile %s>' % self.path | 197 return '<ArchFile %s>' % self.path |
| 198 | 198 |
| 199 def __str__(self): | 199 def __str__(self): |
| 200 '''Return the file path when invoked with the str() function''' | 200 '''Return the file path when invoked with the str() function''' |
| 201 return self.path | 201 return self.path |
| 202 | 202 |
| 203 | 203 |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 228 self.objdump = objdump | 228 self.objdump = objdump |
| 229 self.main_files = main_files or [] | 229 self.main_files = main_files or [] |
| 230 self.extra_files = extra_files or [] | 230 self.extra_files = extra_files or [] |
| 231 self.lib_path = lib_path or [] | 231 self.lib_path = lib_path or [] |
| 232 self.manifest = None | 232 self.manifest = None |
| 233 self.needed = {} | 233 self.needed = {} |
| 234 self.lib_prefix = lib_prefix or [] | 234 self.lib_prefix = lib_prefix or [] |
| 235 self.remap = remap or {} | 235 self.remap = remap or {} |
| 236 self.pnacl = main_files and main_files[0].endswith('pexe') | 236 self.pnacl = main_files and main_files[0].endswith('pexe') |
| 237 | 237 |
| 238 def GleanFromObjdump(self, files): | 238 for filename in self.main_files: |
| 239 if not os.path.exists(filename): | |
| 240 raise Error('Input file not found: %s' % filename) | |
| 241 if not os.path.isfile(filename): | |
| 242 raise Error('Input is not a file: %s' % filename) | |
| 243 | |
| 244 def GleanFromObjdump(self, files, arch): | |
| 239 '''Get architecture and dependency information for given files | 245 '''Get architecture and dependency information for given files |
| 240 | 246 |
| 241 Args: | 247 Args: |
| 242 files: A dict with key=filename and value=list or set of archs. E.g.: | 248 files: A list of files to examine. |
| 243 { '/path/to/my.nexe': ['x86-32'] | 249 [ '/path/to/my.nexe', |
| 244 '/path/to/lib64/libmy.so': ['x86-64'], | 250 '/path/to/lib64/libmy.so', |
| 245 '/path/to/mydata.so': ['x86-32', 'x86-64'], | 251 '/path/to/mydata.so', |
| 246 '/path/to/my.data': None } # Indicates all architectures | 252 '/path/to/my.data' ] |
| 253 arch: The architecure we are looking for, or None to accept any | |
| 254 architecture. | |
| 247 | 255 |
| 248 Returns: A tuple with the following members: | 256 Returns: A tuple with the following members: |
| 249 input_info: A dict with key=filename and value=ArchFile of input files. | 257 input_info: A dict with key=filename and value=ArchFile of input files. |
| 250 Includes the input files as well, with arch filled in if absent. | 258 Includes the input files as well, with arch filled in if absent. |
| 251 Example: { '/path/to/my.nexe': ArchFile(my.nexe), | 259 Example: { '/path/to/my.nexe': ArchFile(my.nexe), |
| 252 '/path/to/libfoo.so': ArchFile(libfoo.so) } | 260 '/path/to/libfoo.so': ArchFile(libfoo.so) } |
| 253 needed: A set of strings formatted as "arch/name". Example: | 261 needed: A set of strings formatted as "arch/name". Example: |
| 254 set(['x86-32/libc.so', 'x86-64/libgcc.so']) | 262 set(['x86-32/libc.so', 'x86-64/libgcc.so']) |
| 255 ''' | 263 ''' |
| 256 if not self.objdump: | 264 if not self.objdump: |
| 257 self.objdump = FindObjdumpExecutable() | 265 self.objdump = FindObjdumpExecutable() |
| 258 if not self.objdump: | 266 if not self.objdump: |
| 259 raise Error('No objdump executable found (see --help for more info)') | 267 raise Error('No objdump executable found (see --help for more info)') |
| 260 DebugPrint('GleanFromObjdump(%s)' % ([self.objdump, '-p'] + files.keys())) | 268 |
| 261 proc = subprocess.Popen([self.objdump, '-p'] + files.keys(), | 269 full_paths = set() |
| 262 stdout=subprocess.PIPE, | 270 for filename in files: |
| 271 if os.path.exists(filename): | |
| 272 full_paths.add(filename) | |
| 273 else: | |
| 274 for path in self.FindLibsInPath(filename): | |
| 275 full_paths.add(path) | |
| 276 | |
| 277 cmd = [self.objdump, '-p'] + list(full_paths) | |
| 278 DebugPrint('GleanFromObjdump[%s](%s)' % (arch, cmd)) | |
| 279 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, | |
| 263 stderr=subprocess.PIPE, bufsize=-1) | 280 stderr=subprocess.PIPE, bufsize=-1) |
| 281 | |
| 264 input_info = {} | 282 input_info = {} |
| 283 found_basenames = {} | |
|
binji
2013/05/28 22:52:41
= set(), you never use the value
Sam Clegg
2013/05/28 23:04:40
Its used below, unless I'm misunderstanding.
| |
| 265 needed = set() | 284 needed = set() |
| 266 output, err_output = proc.communicate() | 285 output, err_output = proc.communicate() |
| 267 if proc.returncode: | 286 if proc.returncode: |
| 268 raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' % | 287 raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' % |
| 269 (output, err_output, proc.returncode)) | 288 (output, err_output, proc.returncode)) |
| 270 | 289 |
| 271 for line in output.splitlines(True): | 290 for line in output.splitlines(True): |
| 272 # Objdump should display the architecture first and then the dependencies | 291 # Objdump should display the architecture first and then the dependencies |
| 273 # second for each file in the list. | 292 # second for each file in the list. |
| 274 matched = FormatMatcher.match(line) | 293 matched = FormatMatcher.match(line) |
| 275 if matched: | 294 if matched: |
| 276 filename = matched.group(1) | 295 filename = matched.group(1) |
| 277 arch = OBJDUMP_ARCH_MAP[matched.group(2)] | 296 file_arch = OBJDUMP_ARCH_MAP[matched.group(2)] |
| 278 if files[filename] is None or arch in files[filename]: | 297 if arch and file_arch != arch: |
| 279 name = os.path.basename(filename) | 298 continue |
| 280 input_info[filename] = ArchFile( | 299 name = os.path.basename(filename) |
| 281 arch=arch, | 300 found_basenames[name] = True |
| 282 name=name, | 301 input_info[filename] = ArchFile( |
| 283 path=filename, | 302 arch=file_arch, |
| 284 url='/'.join(self.lib_prefix + [ARCH_LOCATION[arch], name])) | 303 name=name, |
| 304 path=filename, | |
| 305 url='/'.join(self.lib_prefix + [ARCH_LOCATION[file_arch], name])) | |
| 285 matched = NeededMatcher.match(line) | 306 matched = NeededMatcher.match(line) |
| 286 if matched: | 307 if matched: |
| 287 if files[filename] is None or arch in files[filename]: | 308 if not arch or arch == file_arch: |
|
binji
2013/05/28 22:52:41
might be nicer to use
not(arch and arch != file_ar
binji
2013/05/28 22:52:41
now that I look at it closer, where is file_arch c
Sam Clegg
2013/05/28 23:04:40
Just removed this condition as the continue above
| |
| 288 needed.add('/'.join([arch, matched.group(1)])) | 309 match = '/'.join([file_arch, matched.group(1)]) |
| 310 needed.add(match) | |
| 311 Trace("NEEDED: %s" % match) | |
| 312 | |
| 313 for filename in files: | |
| 314 if os.path.basename(filename) not in found_basenames: | |
| 315 raise Error('Library not found [%s]: %s' % (arch, filename)) | |
|
binji
2013/05/28 22:52:41
seems like it would be useful to know which object
Sam Clegg
2013/05/28 23:04:40
Maybe. That would require another re-factor sinc
| |
| 316 | |
| 289 return input_info, needed | 317 return input_info, needed |
| 290 | 318 |
| 291 def FindLibsInPath(self, name): | 319 def FindLibsInPath(self, name): |
| 292 '''Finds the set of libraries matching |name| within lib_path | 320 '''Finds the set of libraries matching |name| within lib_path |
| 293 | 321 |
| 294 Args: | 322 Args: |
| 295 name: name of library to find | 323 name: name of library to find |
| 296 | 324 |
| 297 Returns: | 325 Returns: |
| 298 A list of system paths that match the given name within the lib_path''' | 326 A list of system paths that match the given name within the lib_path''' |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 316 | 344 |
| 317 if self.needed: | 345 if self.needed: |
| 318 return self.needed | 346 return self.needed |
| 319 | 347 |
| 320 DebugPrint('GetNeeded(%s)' % self.main_files) | 348 DebugPrint('GetNeeded(%s)' % self.main_files) |
| 321 | 349 |
| 322 dynamic = any(ParseElfHeader(f)[1] for f in self.main_files) | 350 dynamic = any(ParseElfHeader(f)[1] for f in self.main_files) |
| 323 | 351 |
| 324 if dynamic: | 352 if dynamic: |
| 325 examined = set() | 353 examined = set() |
| 326 all_files, unexamined = self.GleanFromObjdump( | 354 all_files, unexamined = self.GleanFromObjdump(self.main_files, None) |
| 327 dict([(f, None) for f in self.main_files])) | 355 for arch_file in all_files.itervalues(): |
| 328 for name, arch_file in all_files.items(): | 356 arch_file.url = arch_file.path |
| 329 arch_file.url = name | |
| 330 if unexamined: | 357 if unexamined: |
| 331 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) | 358 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) |
| 359 | |
| 332 while unexamined: | 360 while unexamined: |
| 333 files_to_examine = {} | 361 files_to_examine = {} |
| 362 | |
| 363 # Take all the currently unexamined files and group them | |
| 364 # by architecture. | |
| 334 for arch_name in unexamined: | 365 for arch_name in unexamined: |
| 335 arch, name = arch_name.split('/') | 366 arch, name = arch_name.split('/') |
| 336 for path in self.FindLibsInPath(name): | 367 files_to_examine.setdefault(arch, []).append(name) |
| 337 files_to_examine.setdefault(path, set()).add(arch) | 368 |
| 338 new_files, needed = self.GleanFromObjdump(files_to_examine) | 369 # Call GleanFromObjdump() for each architecture. |
| 339 all_files.update(new_files) | 370 needed = set() |
| 371 for arch, files in files_to_examine.iteritems(): | |
| 372 new_files, new_needed = self.GleanFromObjdump(files, arch) | |
| 373 all_files.update(new_files) | |
| 374 needed |= new_needed | |
| 375 | |
| 340 examined |= unexamined | 376 examined |= unexamined |
| 341 unexamined = needed - examined | 377 unexamined = needed - examined |
| 342 | 378 |
| 343 # With the runnable-ld.so scheme we have today, the proper name of | 379 # With the runnable-ld.so scheme we have today, the proper name of |
| 344 # the dynamic linker should be excluded from the list of files. | 380 # the dynamic linker should be excluded from the list of files. |
| 345 ldso = [LD_NACL_MAP[arch] for arch in set(OBJDUMP_ARCH_MAP.values())] | 381 ldso = [LD_NACL_MAP[arch] for arch in set(OBJDUMP_ARCH_MAP.values())] |
| 346 for name, arch_map in all_files.items(): | 382 for name, arch_file in all_files.items(): |
| 347 if arch_map.name in ldso: | 383 if arch_file.name in ldso: |
| 348 del all_files[name] | 384 del all_files[name] |
| 349 | 385 |
| 350 self.needed = all_files | 386 self.needed = all_files |
| 351 else: | 387 else: |
| 352 for filename in self.main_files: | 388 for filename in self.main_files: |
| 353 url = os.path.split(filename)[1] | 389 url = os.path.split(filename)[1] |
| 354 archfile = ArchFile(name=os.path.basename(filename), | 390 archfile = ArchFile(name=os.path.basename(filename), |
| 355 path=filename, url=url) | 391 path=filename, url=url) |
| 356 self.needed[filename] = archfile | 392 self.needed[filename] = archfile |
| 357 | 393 |
| 358 return self.needed | 394 return self.needed |
| 359 | 395 |
| 360 def StageDependencies(self, destination_dir): | 396 def StageDependencies(self, destination_dir): |
| 361 '''Copies over the dependencies into a given destination directory | 397 '''Copies over the dependencies into a given destination directory |
| 362 | 398 |
| 363 Each library will be put into a subdirectory that corresponds to the arch. | 399 Each library will be put into a subdirectory that corresponds to the arch. |
| 364 | 400 |
| 365 Args: | 401 Args: |
| 366 destination_dir: The destination directory for staging the dependencies | 402 destination_dir: The destination directory for staging the dependencies |
| 367 ''' | 403 ''' |
| 368 nexe_root = os.path.dirname(os.path.abspath(self.main_files[0])) | 404 nexe_root = os.path.dirname(os.path.abspath(self.main_files[0])) |
| 369 nexe_root = os.path.normcase(nexe_root) | 405 nexe_root = os.path.normcase(nexe_root) |
| 370 | 406 |
| 371 needed = self.GetNeeded() | 407 needed = self.GetNeeded() |
| 372 for source, arch_file in needed.items(): | 408 for arch_file in needed.itervalues(): |
| 373 urldest = arch_file.url | 409 urldest = arch_file.url |
| 410 source = arch_file.path | |
| 374 | 411 |
| 375 # for .nexe and .so files specified on the command line stage | 412 # for .nexe and .so files specified on the command line stage |
| 376 # them in paths relative to the .nexe (with the .nexe always | 413 # them in paths relative to the .nexe (with the .nexe always |
| 377 # being staged at the root). | 414 # being staged at the root). |
| 378 if source in self.main_files: | 415 if source in self.main_files: |
| 379 absdest = os.path.normcase(os.path.abspath(urldest)) | 416 absdest = os.path.normcase(os.path.abspath(urldest)) |
| 380 if absdest.startswith(nexe_root): | 417 if absdest.startswith(nexe_root): |
| 381 urldest = os.path.relpath(urldest, nexe_root) | 418 urldest = os.path.relpath(urldest, nexe_root) |
| 382 | 419 |
| 383 destination = os.path.join(destination_dir, urldest) | 420 destination = os.path.join(destination_dir, urldest) |
| (...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 589 def main(argv): | 626 def main(argv): |
| 590 parser = optparse.OptionParser( | 627 parser = optparse.OptionParser( |
| 591 usage='Usage: %prog [options] nexe [extra_libs...]') | 628 usage='Usage: %prog [options] nexe [extra_libs...]') |
| 592 parser.add_option('-o', '--output', dest='output', | 629 parser.add_option('-o', '--output', dest='output', |
| 593 help='Write manifest file to FILE (default is stdout)', | 630 help='Write manifest file to FILE (default is stdout)', |
| 594 metavar='FILE') | 631 metavar='FILE') |
| 595 parser.add_option('-D', '--objdump', dest='objdump', | 632 parser.add_option('-D', '--objdump', dest='objdump', |
| 596 help='Override the default "objdump" tool used to find ' | 633 help='Override the default "objdump" tool used to find ' |
| 597 'shared object dependencies', | 634 'shared object dependencies', |
| 598 metavar='TOOL') | 635 metavar='TOOL') |
| 599 parser.add_option('--no-default-libpath', | 636 parser.add_option('--no-default-libpath', action='store_true', |
| 600 help="Don't include the SDK default library paths") | 637 help="Don't include the SDK default library paths") |
| 601 parser.add_option('--debug-libs', action='store_true', | 638 parser.add_option('--debug-libs', action='store_true', |
| 602 help='Use debug library paths when constructing default ' | 639 help='Use debug library paths when constructing default ' |
| 603 'library path.') | 640 'library path.') |
| 604 parser.add_option('-L', '--library-path', dest='lib_path', | 641 parser.add_option('-L', '--library-path', dest='lib_path', |
| 605 action='append', default=[], | 642 action='append', default=[], |
| 606 help='Add DIRECTORY to library search path', | 643 help='Add DIRECTORY to library search path', |
| 607 metavar='DIRECTORY') | 644 metavar='DIRECTORY') |
| 608 parser.add_option('-P', '--path-prefix', dest='path_prefix', default='', | 645 parser.add_option('-P', '--path-prefix', dest='path_prefix', default='', |
| 609 help='A path to prepend to shared libraries in the .nmf', | 646 help='A path to prepend to shared libraries in the .nmf', |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 628 Trace.verbose = True | 665 Trace.verbose = True |
| 629 if options.debug_mode: | 666 if options.debug_mode: |
| 630 DebugPrint.debug_mode = True | 667 DebugPrint.debug_mode = True |
| 631 | 668 |
| 632 if options.toolchain is not None: | 669 if options.toolchain is not None: |
| 633 print 'warning: option -t/--toolchain is deprecated.' | 670 print 'warning: option -t/--toolchain is deprecated.' |
| 634 | 671 |
| 635 if len(args) < 1: | 672 if len(args) < 1: |
| 636 raise Error('No nexe files specified. See --help for more info') | 673 raise Error('No nexe files specified. See --help for more info') |
| 637 | 674 |
| 638 for filename in args: | |
| 639 if not os.path.exists(filename): | |
| 640 raise Error('Input file not found: %s' % filename) | |
| 641 if not os.path.isfile(filename): | |
| 642 raise Error('Input is not a file: %s' % filename) | |
| 643 | |
| 644 canonicalized = ParseExtraFiles(options.extra_files, sys.stderr) | 675 canonicalized = ParseExtraFiles(options.extra_files, sys.stderr) |
| 645 if canonicalized is None: | 676 if canonicalized is None: |
| 646 parser.error('Bad --extra-files (-x) argument syntax') | 677 parser.error('Bad --extra-files (-x) argument syntax') |
| 647 | 678 |
| 648 remap = {} | 679 remap = {} |
| 649 for ren in options.name: | 680 for ren in options.name: |
| 650 parts = ren.split(',') | 681 parts = ren.split(',') |
| 651 if len(parts) != 2: | 682 if len(parts) != 2: |
| 652 raise Error('Expecting --name=<orig_arch.so>,<new_name.so>') | 683 raise Error('Expecting --name=<orig_arch.so>,<new_name.so>') |
| 653 remap[parts[0]] = parts[1] | 684 remap[parts[0]] = parts[1] |
| 654 | 685 |
| 655 if options.path_prefix: | 686 if options.path_prefix: |
| 656 path_prefix = options.path_prefix.split('/') | 687 path_prefix = options.path_prefix.split('/') |
| 657 else: | 688 else: |
| 658 path_prefix = [] | 689 path_prefix = [] |
| 659 | 690 |
| 691 for libpath in options.lib_path: | |
| 692 if not os.path.exists(libpath): | |
| 693 raise Error('Specified library path does not exist: %s' % libpath) | |
| 694 if not os.path.isdir(libpath): | |
| 695 raise Error('Specified library is not a directory: %s' % libpath) | |
| 696 | |
| 660 if not options.no_default_libpath: | 697 if not options.no_default_libpath: |
| 661 # Add default libraries paths to the end of the search path. | 698 # Add default libraries paths to the end of the search path. |
| 662 config = options.debug_libs and 'Debug' or 'Release' | 699 config = options.debug_libs and 'Debug' or 'Release' |
| 663 options.lib_path += GetDefaultLibPath(config) | 700 options.lib_path += GetDefaultLibPath(config) |
| 664 | 701 |
| 665 nmf = NmfUtils(objdump=options.objdump, | 702 nmf = NmfUtils(objdump=options.objdump, |
| 666 main_files=args, | 703 main_files=args, |
| 667 lib_path=options.lib_path, | 704 lib_path=options.lib_path, |
| 668 extra_files=canonicalized, | 705 extra_files=canonicalized, |
| 669 lib_prefix=path_prefix, | 706 lib_prefix=path_prefix, |
| 670 remap=remap) | 707 remap=remap) |
| 671 | 708 |
| 672 nmf.GetManifest() | 709 nmf.GetManifest() |
| 673 if options.output is None: | 710 if not options.output: |
| 674 sys.stdout.write(nmf.GetJson()) | 711 sys.stdout.write(nmf.GetJson()) |
| 675 else: | 712 else: |
| 676 with open(options.output, 'w') as output: | 713 with open(options.output, 'w') as output: |
| 677 output.write(nmf.GetJson()) | 714 output.write(nmf.GetJson()) |
| 678 | 715 |
| 679 if options.stage_dependencies and not nmf.pnacl: | 716 if options.stage_dependencies and not nmf.pnacl: |
| 680 Trace('Staging dependencies...') | 717 Trace('Staging dependencies...') |
| 681 nmf.StageDependencies(options.stage_dependencies) | 718 nmf.StageDependencies(options.stage_dependencies) |
| 682 | 719 |
| 683 return 0 | 720 return 0 |
| 684 | 721 |
| 685 | 722 |
| 686 # Invoke this file directly for simple testing. | 723 # Invoke this file directly for simple testing. |
| 687 if __name__ == '__main__': | 724 if __name__ == '__main__': |
| 688 try: | 725 try: |
| 689 rtn = main(sys.argv[1:]) | 726 rtn = main(sys.argv[1:]) |
| 690 except Error, e: | 727 except Error, e: |
| 691 sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e)) | 728 sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e)) |
| 692 rtn = 1 | 729 rtn = 1 |
| 693 except KeyboardInterrupt: | 730 except KeyboardInterrupt: |
| 694 sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__)) | 731 sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__)) |
| 695 rtn = 1 | 732 rtn = 1 |
| 696 sys.exit(rtn) | 733 sys.exit(rtn) |
| OLD | NEW |