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

Side by Side Diff: native_client_sdk/src/tools/create_nmf.py

Issue 15891011: [NaCl SDK] Fix create_nmf handling of missing libraries. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 6 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | native_client_sdk/src/tools/tests/create_nmf_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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)
OLDNEW
« no previous file with comments | « no previous file | native_client_sdk/src/tools/tests/create_nmf_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698