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 |