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

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 = {}
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
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
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
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)
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