| 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 = set() |
| 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.add(name) |
| 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 match = '/'.join([file_arch, matched.group(1)]) |
| 288 needed.add('/'.join([arch, matched.group(1)])) | 309 needed.add(match) |
| 310 Trace("NEEDED: %s" % match) |
| 311 |
| 312 for filename in files: |
| 313 if os.path.basename(filename) not in found_basenames: |
| 314 raise Error('Library not found [%s]: %s' % (arch, filename)) |
| 315 |
| 289 return input_info, needed | 316 return input_info, needed |
| 290 | 317 |
| 291 def FindLibsInPath(self, name): | 318 def FindLibsInPath(self, name): |
| 292 '''Finds the set of libraries matching |name| within lib_path | 319 '''Finds the set of libraries matching |name| within lib_path |
| 293 | 320 |
| 294 Args: | 321 Args: |
| 295 name: name of library to find | 322 name: name of library to find |
| 296 | 323 |
| 297 Returns: | 324 Returns: |
| 298 A list of system paths that match the given name within the lib_path''' | 325 A list of system paths that match the given name within the lib_path''' |
| (...skipping 17 matching lines...) Expand all Loading... |
| 316 | 343 |
| 317 if self.needed: | 344 if self.needed: |
| 318 return self.needed | 345 return self.needed |
| 319 | 346 |
| 320 DebugPrint('GetNeeded(%s)' % self.main_files) | 347 DebugPrint('GetNeeded(%s)' % self.main_files) |
| 321 | 348 |
| 322 dynamic = any(ParseElfHeader(f)[1] for f in self.main_files) | 349 dynamic = any(ParseElfHeader(f)[1] for f in self.main_files) |
| 323 | 350 |
| 324 if dynamic: | 351 if dynamic: |
| 325 examined = set() | 352 examined = set() |
| 326 all_files, unexamined = self.GleanFromObjdump( | 353 all_files, unexamined = self.GleanFromObjdump(self.main_files, None) |
| 327 dict([(f, None) for f in self.main_files])) | 354 for arch_file in all_files.itervalues(): |
| 328 for name, arch_file in all_files.items(): | 355 arch_file.url = arch_file.path |
| 329 arch_file.url = name | |
| 330 if unexamined: | 356 if unexamined: |
| 331 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) | 357 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) |
| 358 |
| 332 while unexamined: | 359 while unexamined: |
| 333 files_to_examine = {} | 360 files_to_examine = {} |
| 361 |
| 362 # Take all the currently unexamined files and group them |
| 363 # by architecture. |
| 334 for arch_name in unexamined: | 364 for arch_name in unexamined: |
| 335 arch, name = arch_name.split('/') | 365 arch, name = arch_name.split('/') |
| 336 for path in self.FindLibsInPath(name): | 366 files_to_examine.setdefault(arch, []).append(name) |
| 337 files_to_examine.setdefault(path, set()).add(arch) | 367 |
| 338 new_files, needed = self.GleanFromObjdump(files_to_examine) | 368 # Call GleanFromObjdump() for each architecture. |
| 339 all_files.update(new_files) | 369 needed = set() |
| 370 for arch, files in files_to_examine.iteritems(): |
| 371 new_files, new_needed = self.GleanFromObjdump(files, arch) |
| 372 all_files.update(new_files) |
| 373 needed |= new_needed |
| 374 |
| 340 examined |= unexamined | 375 examined |= unexamined |
| 341 unexamined = needed - examined | 376 unexamined = needed - examined |
| 342 | 377 |
| 343 # With the runnable-ld.so scheme we have today, the proper name of | 378 # 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. | 379 # 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())] | 380 ldso = [LD_NACL_MAP[arch] for arch in set(OBJDUMP_ARCH_MAP.values())] |
| 346 for name, arch_map in all_files.items(): | 381 for name, arch_file in all_files.items(): |
| 347 if arch_map.name in ldso: | 382 if arch_file.name in ldso: |
| 348 del all_files[name] | 383 del all_files[name] |
| 349 | 384 |
| 350 self.needed = all_files | 385 self.needed = all_files |
| 351 else: | 386 else: |
| 352 for filename in self.main_files: | 387 for filename in self.main_files: |
| 353 url = os.path.split(filename)[1] | 388 url = os.path.split(filename)[1] |
| 354 archfile = ArchFile(name=os.path.basename(filename), | 389 archfile = ArchFile(name=os.path.basename(filename), |
| 355 path=filename, url=url) | 390 path=filename, url=url) |
| 356 self.needed[filename] = archfile | 391 self.needed[filename] = archfile |
| 357 | 392 |
| 358 return self.needed | 393 return self.needed |
| 359 | 394 |
| 360 def StageDependencies(self, destination_dir): | 395 def StageDependencies(self, destination_dir): |
| 361 '''Copies over the dependencies into a given destination directory | 396 '''Copies over the dependencies into a given destination directory |
| 362 | 397 |
| 363 Each library will be put into a subdirectory that corresponds to the arch. | 398 Each library will be put into a subdirectory that corresponds to the arch. |
| 364 | 399 |
| 365 Args: | 400 Args: |
| 366 destination_dir: The destination directory for staging the dependencies | 401 destination_dir: The destination directory for staging the dependencies |
| 367 ''' | 402 ''' |
| 368 nexe_root = os.path.dirname(os.path.abspath(self.main_files[0])) | 403 nexe_root = os.path.dirname(os.path.abspath(self.main_files[0])) |
| 369 nexe_root = os.path.normcase(nexe_root) | 404 nexe_root = os.path.normcase(nexe_root) |
| 370 | 405 |
| 371 needed = self.GetNeeded() | 406 needed = self.GetNeeded() |
| 372 for source, arch_file in needed.items(): | 407 for arch_file in needed.itervalues(): |
| 373 urldest = arch_file.url | 408 urldest = arch_file.url |
| 409 source = arch_file.path |
| 374 | 410 |
| 375 # for .nexe and .so files specified on the command line stage | 411 # for .nexe and .so files specified on the command line stage |
| 376 # them in paths relative to the .nexe (with the .nexe always | 412 # them in paths relative to the .nexe (with the .nexe always |
| 377 # being staged at the root). | 413 # being staged at the root). |
| 378 if source in self.main_files: | 414 if source in self.main_files: |
| 379 absdest = os.path.normcase(os.path.abspath(urldest)) | 415 absdest = os.path.normcase(os.path.abspath(urldest)) |
| 380 if absdest.startswith(nexe_root): | 416 if absdest.startswith(nexe_root): |
| 381 urldest = os.path.relpath(urldest, nexe_root) | 417 urldest = os.path.relpath(urldest, nexe_root) |
| 382 | 418 |
| 383 destination = os.path.join(destination_dir, urldest) | 419 destination = os.path.join(destination_dir, urldest) |
| (...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 589 def main(argv): | 625 def main(argv): |
| 590 parser = optparse.OptionParser( | 626 parser = optparse.OptionParser( |
| 591 usage='Usage: %prog [options] nexe [extra_libs...]') | 627 usage='Usage: %prog [options] nexe [extra_libs...]') |
| 592 parser.add_option('-o', '--output', dest='output', | 628 parser.add_option('-o', '--output', dest='output', |
| 593 help='Write manifest file to FILE (default is stdout)', | 629 help='Write manifest file to FILE (default is stdout)', |
| 594 metavar='FILE') | 630 metavar='FILE') |
| 595 parser.add_option('-D', '--objdump', dest='objdump', | 631 parser.add_option('-D', '--objdump', dest='objdump', |
| 596 help='Override the default "objdump" tool used to find ' | 632 help='Override the default "objdump" tool used to find ' |
| 597 'shared object dependencies', | 633 'shared object dependencies', |
| 598 metavar='TOOL') | 634 metavar='TOOL') |
| 599 parser.add_option('--no-default-libpath', | 635 parser.add_option('--no-default-libpath', action='store_true', |
| 600 help="Don't include the SDK default library paths") | 636 help="Don't include the SDK default library paths") |
| 601 parser.add_option('--debug-libs', action='store_true', | 637 parser.add_option('--debug-libs', action='store_true', |
| 602 help='Use debug library paths when constructing default ' | 638 help='Use debug library paths when constructing default ' |
| 603 'library path.') | 639 'library path.') |
| 604 parser.add_option('-L', '--library-path', dest='lib_path', | 640 parser.add_option('-L', '--library-path', dest='lib_path', |
| 605 action='append', default=[], | 641 action='append', default=[], |
| 606 help='Add DIRECTORY to library search path', | 642 help='Add DIRECTORY to library search path', |
| 607 metavar='DIRECTORY') | 643 metavar='DIRECTORY') |
| 608 parser.add_option('-P', '--path-prefix', dest='path_prefix', default='', | 644 parser.add_option('-P', '--path-prefix', dest='path_prefix', default='', |
| 609 help='A path to prepend to shared libraries in the .nmf', | 645 help='A path to prepend to shared libraries in the .nmf', |
| (...skipping 18 matching lines...) Expand all Loading... |
| 628 Trace.verbose = True | 664 Trace.verbose = True |
| 629 if options.debug_mode: | 665 if options.debug_mode: |
| 630 DebugPrint.debug_mode = True | 666 DebugPrint.debug_mode = True |
| 631 | 667 |
| 632 if options.toolchain is not None: | 668 if options.toolchain is not None: |
| 633 print 'warning: option -t/--toolchain is deprecated.' | 669 print 'warning: option -t/--toolchain is deprecated.' |
| 634 | 670 |
| 635 if len(args) < 1: | 671 if len(args) < 1: |
| 636 raise Error('No nexe files specified. See --help for more info') | 672 raise Error('No nexe files specified. See --help for more info') |
| 637 | 673 |
| 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) | 674 canonicalized = ParseExtraFiles(options.extra_files, sys.stderr) |
| 645 if canonicalized is None: | 675 if canonicalized is None: |
| 646 parser.error('Bad --extra-files (-x) argument syntax') | 676 parser.error('Bad --extra-files (-x) argument syntax') |
| 647 | 677 |
| 648 remap = {} | 678 remap = {} |
| 649 for ren in options.name: | 679 for ren in options.name: |
| 650 parts = ren.split(',') | 680 parts = ren.split(',') |
| 651 if len(parts) != 2: | 681 if len(parts) != 2: |
| 652 raise Error('Expecting --name=<orig_arch.so>,<new_name.so>') | 682 raise Error('Expecting --name=<orig_arch.so>,<new_name.so>') |
| 653 remap[parts[0]] = parts[1] | 683 remap[parts[0]] = parts[1] |
| 654 | 684 |
| 655 if options.path_prefix: | 685 if options.path_prefix: |
| 656 path_prefix = options.path_prefix.split('/') | 686 path_prefix = options.path_prefix.split('/') |
| 657 else: | 687 else: |
| 658 path_prefix = [] | 688 path_prefix = [] |
| 659 | 689 |
| 690 for libpath in options.lib_path: |
| 691 if not os.path.exists(libpath): |
| 692 raise Error('Specified library path does not exist: %s' % libpath) |
| 693 if not os.path.isdir(libpath): |
| 694 raise Error('Specified library is not a directory: %s' % libpath) |
| 695 |
| 660 if not options.no_default_libpath: | 696 if not options.no_default_libpath: |
| 661 # Add default libraries paths to the end of the search path. | 697 # Add default libraries paths to the end of the search path. |
| 662 config = options.debug_libs and 'Debug' or 'Release' | 698 config = options.debug_libs and 'Debug' or 'Release' |
| 663 options.lib_path += GetDefaultLibPath(config) | 699 options.lib_path += GetDefaultLibPath(config) |
| 664 | 700 |
| 665 nmf = NmfUtils(objdump=options.objdump, | 701 nmf = NmfUtils(objdump=options.objdump, |
| 666 main_files=args, | 702 main_files=args, |
| 667 lib_path=options.lib_path, | 703 lib_path=options.lib_path, |
| 668 extra_files=canonicalized, | 704 extra_files=canonicalized, |
| 669 lib_prefix=path_prefix, | 705 lib_prefix=path_prefix, |
| 670 remap=remap) | 706 remap=remap) |
| 671 | 707 |
| 672 nmf.GetManifest() | 708 nmf.GetManifest() |
| 673 if options.output is None: | 709 if not options.output: |
| 674 sys.stdout.write(nmf.GetJson()) | 710 sys.stdout.write(nmf.GetJson()) |
| 675 else: | 711 else: |
| 676 with open(options.output, 'w') as output: | 712 with open(options.output, 'w') as output: |
| 677 output.write(nmf.GetJson()) | 713 output.write(nmf.GetJson()) |
| 678 | 714 |
| 679 if options.stage_dependencies and not nmf.pnacl: | 715 if options.stage_dependencies and not nmf.pnacl: |
| 680 Trace('Staging dependencies...') | 716 Trace('Staging dependencies...') |
| 681 nmf.StageDependencies(options.stage_dependencies) | 717 nmf.StageDependencies(options.stage_dependencies) |
| 682 | 718 |
| 683 return 0 | 719 return 0 |
| 684 | 720 |
| 685 | 721 |
| 686 # Invoke this file directly for simple testing. | 722 # Invoke this file directly for simple testing. |
| 687 if __name__ == '__main__': | 723 if __name__ == '__main__': |
| 688 try: | 724 try: |
| 689 rtn = main(sys.argv[1:]) | 725 rtn = main(sys.argv[1:]) |
| 690 except Error, e: | 726 except Error, e: |
| 691 sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e)) | 727 sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e)) |
| 692 rtn = 1 | 728 rtn = 1 |
| 693 except KeyboardInterrupt: | 729 except KeyboardInterrupt: |
| 694 sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__)) | 730 sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__)) |
| 695 rtn = 1 | 731 rtn = 1 |
| 696 sys.exit(rtn) | 732 sys.exit(rtn) |
| OLD | NEW |