Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      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
      4 # found in the LICENSE file.
      5 
      6 import errno
      7 import json
      8 import optparse
      9 import os
     10 import re
     11 import shutil
     12 import struct
     13 import subprocess
     14 import sys
     15 
     16 import getos
     17 import quote
     18 
     19 if sys.version_info < (2, 6, 0):
     20   sys.stderr.write("python 2.6 or later is required run this script\n")
     21   sys.exit(1)
     22 
     23 NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$')
     24 FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$')
     25 
     26 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
     27 
     28 OBJDUMP_ARCH_MAP = {
     29     # Names returned by Linux's objdump:
     30     'elf64-x86-64': 'x86-64',
     31     'elf32-i386': 'x86-32',
     32     'elf32-little': 'arm',
     33     'elf32-littlearm': 'arm',
     34     # Names returned by x86_64-nacl-objdump:
     35     'elf64-nacl': 'x86-64',
     36     'elf32-nacl': 'x86-32',
     37 }
     38 
     39 ARCH_LOCATION = {
     40     'x86-32': 'lib32',
     41     'x86-64': 'lib64',
     42     'arm': 'lib',
     43 }
     44 
     45 
     46 # These constants are used within nmf files.
     47 RUNNABLE_LD = 'runnable-ld.so'  # Name of the dynamic loader
     48 MAIN_NEXE = 'main.nexe'  # Name of entry point for execution
     49 PROGRAM_KEY = 'program'  # Key of the program section in an nmf file
     50 URL_KEY = 'url'  # Key of the url field for a particular file in an nmf file
     51 FILES_KEY = 'files'  # Key of the files section in an nmf file
     52 PNACL_OPTLEVEL_KEY = 'optlevel' # key for PNaCl optimization level
     53 PORTABLE_KEY = 'portable' # key for portable section of manifest
     54 TRANSLATE_KEY = 'pnacl-translate' # key for translatable objects
     55 
     56 
     57 # The proper name of the dynamic linker, as kept in the IRT.  This is
     58 # excluded from the nmf file by convention.
     59 LD_NACL_MAP = {
     60     'x86-32': 'ld-nacl-x86-32.so.1',
     61     'x86-64': 'ld-nacl-x86-64.so.1',
     62     'arm': None,
     63 }
     64 
     65 
     66 def DebugPrint(message):
     67   if DebugPrint.debug_mode:
     68     sys.stderr.write('%s\n' % message)
     69 
     70 
     71 DebugPrint.debug_mode = False  # Set to True to enable extra debug prints
     72 
     73 
     74 def MakeDir(dirname):
     75   """Just like os.makedirs but doesn't generate errors when dirname
     76   already exists.
     77   """
     78   if os.path.isdir(dirname):
     79     return
     80 
     81   Trace("mkdir: %s" % dirname)
     82   try:
     83     os.makedirs(dirname)
     84   except OSError as exception_info:
     85     if exception_info.errno != errno.EEXIST:
     86       raise
     87 
     88 
     89 class Error(Exception):
     90   '''Local Error class for this file.'''
     91   pass
     92 
     93 
     94 def ParseElfHeader(path):
     95   """Determine properties of a nexe by parsing elf header.
     96   Return tuple of architecture and boolean signalling whether
     97   the executable is dynamic (has INTERP header) or static.
     98   """
     99   # From elf.h:
    100   # typedef struct
    101   # {
    102   #   unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
    103   #   Elf64_Half e_type; /* Object file type */
    104   #   Elf64_Half e_machine; /* Architecture */
    105   #   ...
    106   # } Elf32_Ehdr;
    107   elf_header_format = '16s2H'
    108   elf_header_size = struct.calcsize(elf_header_format)
    109 
    110   with open(path, 'rb') as f:
    111     header = f.read(elf_header_size)
    112 
    113   try:
    114     header = struct.unpack(elf_header_format, header)
    115   except struct.error:
    116     raise Error("error parsing elf header: %s" % path)
    117   e_ident, _, e_machine = header[:3]
    118 
    119   elf_magic = '\x7fELF'
    120   if e_ident[:4] != elf_magic:
    121     raise Error('Not a valid NaCL executable: %s' % path)
    122 
    123   e_machine_mapping = {
    124     3 : 'x86-32',
    125     40 : 'arm',
    126     62 : 'x86-64'
    127   }
    128   if e_machine not in e_machine_mapping:
    129     raise Error('Unknown machine type: %s' % e_machine)
    130 
    131   # Set arch based on the machine type in the elf header
    132   arch = e_machine_mapping[e_machine]
    133 
    134   # Now read the full header in either 64bit or 32bit mode
    135   dynamic = IsDynamicElf(path, arch == 'x86-64')
    136   return arch, dynamic
    137 
    138 
    139 def IsDynamicElf(path, is64bit):
    140   """Examine an elf file to determine if it is dynamically
    141   linked or not.
    142   This is determined by searching the program headers for
    143   a header of type PT_INTERP.
    144   """
    145   if is64bit:
    146     elf_header_format = '16s2HI3QI3H'
    147   else:
    148     elf_header_format = '16s2HI3II3H'
    149 
    150   elf_header_size = struct.calcsize(elf_header_format)
    151 
    152   with open(path, 'rb') as f:
    153     header = f.read(elf_header_size)
    154     header = struct.unpack(elf_header_format, header)
    155     p_header_offset = header[5]
    156     p_header_entry_size = header[9]
    157     num_p_header = header[10]
    158     f.seek(p_header_offset)
    159     p_headers = f.read(p_header_entry_size*num_p_header)
    160 
    161   # Read the first word of each Phdr to find out its type.
    162   #
    163   # typedef struct
    164   # {
    165   #   Elf32_Word  p_type;     /* Segment type */
    166   #   ...
    167   # } Elf32_Phdr;
    168   elf_phdr_format = 'I'
    169   PT_INTERP = 3
    170 
    171   while p_headers:
    172     p_header = p_headers[:p_header_entry_size]
    173     p_headers = p_headers[p_header_entry_size:]
    174     phdr_type = struct.unpack(elf_phdr_format, p_header[:4])[0]
    175     if phdr_type == PT_INTERP:
    176       return True
    177 
    178   return False
    179 
    180 
    181 class ArchFile(object):
    182   '''Simple structure containing information about
    183 
    184   Attributes:
    185     name: Name of this file
    186     path: Full path to this file on the build system
    187     arch: Architecture of this file (e.g., x86-32)
    188     url: Relative path to file in the staged web directory.
    189         Used for specifying the "url" attribute in the nmf file.'''
    190 
    191   def __init__(self, name, path, url, arch=None):
    192     self.name = name
    193     self.path = path
    194     self.url = url
    195     self.arch = arch
    196     if not arch:
    197       self.arch = ParseElfHeader(path)[0]
    198 
    199   def __repr__(self):
    200     return '<ArchFile %s>' % self.path
    201 
    202   def __str__(self):
    203     '''Return the file path when invoked with the str() function'''
    204     return self.path
    205 
    206 
    207 class NmfUtils(object):
    208   '''Helper class for creating and managing nmf files
    209 
    210   Attributes:
    211     manifest: A JSON-structured dict containing the nmf structure
    212     needed: A dict with key=filename and value=ArchFile (see GetNeeded)
    213   '''
    214 
    215   def __init__(self, main_files=None, objdump=None,
    216                lib_path=None, extra_files=None, lib_prefix=None,
    217                remap=None, pnacl_optlevel=None):
    218     '''Constructor
    219 
    220     Args:
    221       main_files: List of main entry program files.  These will be named
    222           files->main.nexe for dynamic nexes, and program for static nexes
    223       objdump: path to x86_64-nacl-objdump tool (or Linux equivalent)
    224       lib_path: List of paths to library directories
    225       extra_files: List of extra files to include in the nmf
    226       lib_prefix: A list of path components to prepend to the library paths,
    227           both for staging the libraries and for inclusion into the nmf file.
    228           Examples:  ['..'], ['lib_dir']
    229       remap: Remaps the library name in the manifest.
    230       pnacl_optlevel: Optimization level for PNaCl translation.
    231       '''
    232     self.objdump = objdump
    233     self.main_files = main_files or []
    234     self.extra_files = extra_files or []
    235     self.lib_path = lib_path or []
    236     self.manifest = None
    237     self.needed = {}
    238     self.lib_prefix = lib_prefix or []
    239     self.remap = remap or {}
    240     self.pnacl = main_files and main_files[0].endswith('pexe')
    241     self.pnacl_optlevel = pnacl_optlevel
    242 
    243     for filename in self.main_files:
    244       if not os.path.exists(filename):
    245         raise Error('Input file not found: %s' % filename)
    246       if not os.path.isfile(filename):
    247         raise Error('Input is not a file: %s' % filename)
    248 
    249   def GleanFromObjdump(self, files, arch):
    250     '''Get architecture and dependency information for given files
    251 
    252     Args:
    253       files: A list of files to examine.
    254           [ '/path/to/my.nexe',
    255             '/path/to/lib64/libmy.so',
    256             '/path/to/mydata.so',
    257             '/path/to/my.data' ]
    258       arch: The architecure we are looking for, or None to accept any
    259             architecture.
    260 
    261     Returns: A tuple with the following members:
    262       input_info: A dict with key=filename and value=ArchFile of input files.
    263           Includes the input files as well, with arch filled in if absent.
    264           Example: { '/path/to/my.nexe': ArchFile(my.nexe),
    265                      '/path/to/libfoo.so': ArchFile(libfoo.so) }
    266       needed: A set of strings formatted as "arch/name".  Example:
    267           set(['x86-32/libc.so', 'x86-64/libgcc.so'])
    268     '''
    269     if not self.objdump:
    270       self.objdump = FindObjdumpExecutable()
    271       if not self.objdump:
    272         raise Error('No objdump executable found (see --help for more info)')
    273 
    274     full_paths = set()
    275     for filename in files:
    276       if os.path.exists(filename):
    277         full_paths.add(filename)
    278       else:
    279         for path in self.FindLibsInPath(filename):
    280           full_paths.add(path)
    281 
    282     cmd = [self.objdump, '-p'] + list(full_paths)
    283     DebugPrint('GleanFromObjdump[%s](%s)' % (arch, cmd))
    284     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
    285                             stderr=subprocess.PIPE, bufsize=-1)
    286 
    287     input_info = {}
    288     found_basenames = set()
    289     needed = set()
    290     output, err_output = proc.communicate()
    291     if proc.returncode:
    292       raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' %
    293                   (output, err_output, proc.returncode))
    294 
    295     for line in output.splitlines(True):
    296       # Objdump should display the architecture first and then the dependencies
    297       # second for each file in the list.
    298       matched = FormatMatcher.match(line)
    299       if matched:
    300         filename = matched.group(1)
    301         file_arch = OBJDUMP_ARCH_MAP[matched.group(2)]
    302         if arch and file_arch != arch:
    303           continue
    304         name = os.path.basename(filename)
    305         found_basenames.add(name)
    306         input_info[filename] = ArchFile(
    307             arch=file_arch,
    308             name=name,
    309             path=filename,
    310             url='/'.join(self.lib_prefix + [ARCH_LOCATION[file_arch], name]))
    311       matched = NeededMatcher.match(line)
    312       if matched:
    313         match = '/'.join([file_arch, matched.group(1)])
    314         needed.add(match)
    315         Trace("NEEDED: %s" % match)
    316 
    317     for filename in files:
    318       if os.path.basename(filename) not in found_basenames:
    319         raise Error('Library not found [%s]: %s' % (arch, filename))
    320 
    321     return input_info, needed
    322 
    323   def FindLibsInPath(self, name):
    324     '''Finds the set of libraries matching |name| within lib_path
    325 
    326     Args:
    327       name: name of library to find
    328 
    329     Returns:
    330       A list of system paths that match the given name within the lib_path'''
    331     files = []
    332     for dirname in self.lib_path:
    333       filename = os.path.join(dirname, name)
    334       if os.path.exists(filename):
    335         files.append(filename)
    336     if not files:
    337       raise Error('cannot find library %s' % name)
    338     return files
    339 
    340   def GetNeeded(self):
    341     '''Collect the list of dependencies for the main_files
    342 
    343     Returns:
    344       A dict with key=filename and value=ArchFile of input files.
    345           Includes the input files as well, with arch filled in if absent.
    346           Example: { '/path/to/my.nexe': ArchFile(my.nexe),
    347                      '/path/to/libfoo.so': ArchFile(libfoo.so) }'''
    348 
    349     if self.needed:
    350       return self.needed
    351 
    352     DebugPrint('GetNeeded(%s)' % self.main_files)
    353 
    354     dynamic = any(ParseElfHeader(f)[1] for f in self.main_files)
    355 
    356     if dynamic:
    357       examined = set()
    358       all_files, unexamined = self.GleanFromObjdump(self.main_files, None)
    359       for arch_file in all_files.itervalues():
    360         arch_file.url = arch_file.path
    361         if unexamined:
    362           unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD]))
    363 
    364       while unexamined:
    365         files_to_examine = {}
    366 
    367         # Take all the currently unexamined files and group them
    368         # by architecture.
    369         for arch_name in unexamined:
    370           arch, name = arch_name.split('/')
    371           files_to_examine.setdefault(arch, []).append(name)
    372 
    373         # Call GleanFromObjdump() for each architecture.
    374         needed = set()
    375         for arch, files in files_to_examine.iteritems():
    376           new_files, new_needed = self.GleanFromObjdump(files, arch)
    377           all_files.update(new_files)
    378           needed |= new_needed
    379 
    380         examined |= unexamined
    381         unexamined = needed - examined
    382 
    383       # With the runnable-ld.so scheme we have today, the proper name of
    384       # the dynamic linker should be excluded from the list of files.
    385       ldso = [LD_NACL_MAP[arch] for arch in set(OBJDUMP_ARCH_MAP.values())]
    386       for name, arch_file in all_files.items():
    387         if arch_file.name in ldso:
    388           del all_files[name]
    389 
    390       self.needed = all_files
    391     else:
    392       for filename in self.main_files:
    393         url = os.path.split(filename)[1]
    394         archfile = ArchFile(name=os.path.basename(filename),
    395                             path=filename, url=url)
    396         self.needed[filename] = archfile
    397 
    398     return self.needed
    399 
    400   def StageDependencies(self, destination_dir):
    401     '''Copies over the dependencies into a given destination directory
    402 
    403     Each library will be put into a subdirectory that corresponds to the arch.
    404 
    405     Args:
    406       destination_dir: The destination directory for staging the dependencies
    407     '''
    408     nexe_root = os.path.dirname(os.path.abspath(self.main_files[0]))
    409     nexe_root = os.path.normcase(nexe_root)
    410 
    411     needed = self.GetNeeded()
    412     for arch_file in needed.itervalues():
    413       urldest = arch_file.url
    414       source = arch_file.path
    415 
    416       # for .nexe and .so files specified on the command line stage
    417       # them in paths relative to the .nexe (with the .nexe always
    418       # being staged at the root).
    419       if source in self.main_files:
    420         absdest = os.path.normcase(os.path.abspath(urldest))
    421         if absdest.startswith(nexe_root):
    422           urldest = os.path.relpath(urldest, nexe_root)
    423 
    424       destination = os.path.join(destination_dir, urldest)
    425 
    426       if (os.path.normcase(os.path.abspath(source)) ==
    427           os.path.normcase(os.path.abspath(destination))):
    428         continue
    429 
    430       # make sure target dir exists
    431       MakeDir(os.path.dirname(destination))
    432 
    433       Trace('copy: %s -> %s' % (source, destination))
    434       shutil.copy2(source, destination)
    435 
    436   def _GeneratePNaClManifest(self):
    437     manifest = {}
    438     manifest[PROGRAM_KEY] = {}
    439     manifest[PROGRAM_KEY][PORTABLE_KEY] = {}
    440     translate_dict =  {
    441       "url": os.path.basename(self.main_files[0]),
    442     }
    443     if self.pnacl_optlevel is not None:
    444       translate_dict[PNACL_OPTLEVEL_KEY] = self.pnacl_optlevel
    445     manifest[PROGRAM_KEY][PORTABLE_KEY][TRANSLATE_KEY] = translate_dict
    446     self.manifest = manifest
    447 
    448   def _GenerateManifest(self):
    449     '''Create a JSON formatted dict containing the files
    450 
    451     NaCl will map url requests based on architecture.  The startup NEXE
    452     can always be found under the top key PROGRAM.  Additional files are under
    453     the FILES key further mapped by file name.  In the case of 'runnable' the
    454     PROGRAM key is populated with urls pointing the runnable-ld.so which acts
    455     as the startup nexe.  The application itself is then placed under the
    456     FILES key mapped as 'main.exe' instead of the original name so that the
    457     loader can find it. '''
    458     manifest = { FILES_KEY: {}, PROGRAM_KEY: {} }
    459 
    460     needed = self.GetNeeded()
    461 
    462     runnable = any(n.endswith(RUNNABLE_LD) for n in needed)
    463 
    464     extra_files_kv = [(key, ArchFile(name=key,
    465                                      arch=arch,
    466                                      path=url,
    467                                      url=url))
    468                       for key, arch, url in self.extra_files]
    469 
    470     nexe_root = os.path.dirname(os.path.abspath(self.main_files[0]))
    471 
    472     for need, archinfo in needed.items() + extra_files_kv:
    473       urlinfo = { URL_KEY: archinfo.url }
    474       name = archinfo.name
    475 
    476       # If starting with runnable-ld.so, make that the main executable.
    477       if runnable:
    478         if need.endswith(RUNNABLE_LD):
    479           manifest[PROGRAM_KEY][archinfo.arch] = urlinfo
    480           continue
    481 
    482       if need in self.main_files:
    483         # Ensure that the .nexe and .so names are relative to the root
    484         # of where the .nexe lives.
    485         if os.path.abspath(urlinfo[URL_KEY]).startswith(nexe_root):
    486           urlinfo[URL_KEY] = os.path.relpath(urlinfo[URL_KEY], nexe_root)
    487 
    488         if need.endswith(".nexe"):
    489           # Place it under program if we aren't using the runnable-ld.so.
    490           if not runnable:
    491             manifest[PROGRAM_KEY][archinfo.arch] = urlinfo
    492             continue
    493           # Otherwise, treat it like another another file named main.nexe.
    494           name = MAIN_NEXE
    495 
    496       name = self.remap.get(name, name)
    497       fileinfo = manifest[FILES_KEY].get(name, {})
    498       fileinfo[archinfo.arch] = urlinfo
    499       manifest[FILES_KEY][name] = fileinfo
    500     self.manifest = manifest
    501 
    502   def GetManifest(self):
    503     '''Returns a JSON-formatted dict containing the NaCl dependencies'''
    504     if not self.manifest:
    505       if self.pnacl:
    506         self._GeneratePNaClManifest()
    507       else:
    508         self._GenerateManifest()
    509     return self.manifest
    510 
    511   def GetJson(self):
    512     '''Returns the Manifest as a JSON-formatted string'''
    513     pretty_string = json.dumps(self.GetManifest(), indent=2)
    514     # json.dumps sometimes returns trailing whitespace and does not put
    515     # a newline at the end.  This code fixes these problems.
    516     pretty_lines = pretty_string.split('\n')
    517     return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n'
    518 
    519 
    520 def Trace(msg):
    521   if Trace.verbose:
    522     sys.stderr.write(str(msg) + '\n')
    523 
    524 Trace.verbose = False
    525 
    526 
    527 def ParseExtraFiles(encoded_list, err):
    528   """Parse the extra-files list and return a canonicalized list of
    529   [key, arch, url] triples.  The |encoded_list| should be a list of
    530   strings of the form 'key:url' or 'key:arch:url', where an omitted
    531   'arch' is taken to mean 'portable'.
    532 
    533   All entries in |encoded_list| are checked for syntax errors before
    534   returning.  Error messages are written to |err| (typically
    535   sys.stderr) so that the user has actionable feedback for fixing all
    536   errors, rather than one at a time.  If there are any errors, None is
    537   returned instead of a list, since an empty list is a valid return
    538   value.
    539   """
    540   seen_error = False
    541   canonicalized = []
    542   for ix in range(len(encoded_list)):
    543     kv = encoded_list[ix]
    544     unquoted = quote.unquote(kv, ':')
    545     if len(unquoted) == 3:
    546       if unquoted[1] != ':':
    547         err.write('Syntax error for key:value tuple ' +
    548                   'for --extra-files argument: ' + kv + '\n')
    549         seen_error = True
    550       else:
    551         canonicalized.append([unquoted[0], 'portable', unquoted[2]])
    552     elif len(unquoted) == 5:
    553       if unquoted[1] != ':' or unquoted[3] != ':':
    554         err.write('Syntax error for key:arch:url tuple ' +
    555                   'for --extra-files argument: ' +
    556                   kv + '\n')
    557         seen_error = True
    558       else:
    559         canonicalized.append([unquoted[0], unquoted[2], unquoted[4]])
    560     else:
    561       err.write('Bad key:arch:url tuple for --extra-files: ' + kv + '\n')
    562   if seen_error:
    563     return None
    564   return canonicalized
    565 
    566 
    567 def GetSDKRoot():
    568   """Determine current NACL_SDK_ROOT, either via the environment variable
    569   itself, or by attempting to derive it from the location of this script.
    570   """
    571   sdk_root = os.environ.get('NACL_SDK_ROOT')
    572   if not sdk_root:
    573     sdk_root = os.path.dirname(SCRIPT_DIR)
    574     if not os.path.exists(os.path.join(sdk_root, 'toolchain')):
    575       return None
    576 
    577   return sdk_root
    578 
    579 
    580 def FindObjdumpExecutable():
    581   """Derive path to objdump executable to use for determining shared
    582   object dependencies.
    583   """
    584   sdk_root = GetSDKRoot()
    585   if not sdk_root:
    586     return None
    587 
    588   osname = getos.GetPlatform()
    589   toolchain = os.path.join(sdk_root, 'toolchain', '%s_x86_glibc' % osname)
    590   objdump = os.path.join(toolchain, 'bin', 'x86_64-nacl-objdump')
    591   if osname == 'win':
    592     objdump += '.exe'
    593 
    594   if not os.path.exists(objdump):
    595     sys.stderr.write('WARNING: failed to find objdump in default '
    596                      'location: %s' % objdump)
    597     return None
    598 
    599   return objdump
    600 
    601 
    602 def GetDefaultLibPath(config):
    603   """Derive default library path to use when searching for shared
    604   objects.  This currently include the toolchain library folders
    605   as well as the top level SDK lib folder and the naclports lib
    606   folder.  We include both 32-bit and 64-bit library paths.
    607   """
    608   assert(config in ('Debug', 'Release'))
    609   sdk_root = GetSDKRoot()
    610   if not sdk_root:
    611     # TOOD(sbc): output a warning here?  We would also need to suppress
    612     # the warning when run from the chromium build.
    613     return []
    614 
    615   osname = getos.GetPlatform()
    616   libpath = [
    617     # Core toolchain libraries
    618     'toolchain/%s_x86_glibc/x86_64-nacl/lib' % osname,
    619     'toolchain/%s_x86_glibc/x86_64-nacl/lib32' % osname,
    620     # naclports installed libraries
    621     'toolchain/%s_x86_glibc/x86_64-nacl/usr/lib' % osname,
    622     'toolchain/%s_x86_glibc/i686-nacl/usr/lib' % osname,
    623     # SDK bundle libraries
    624     'lib/glibc_x86_32/%s' % config,
    625     'lib/glibc_x86_64/%s' % config,
    626     # naclports bundle libraries
    627     'ports/lib/glibc_x86_32/%s' % config,
    628     'ports/lib/glibc_x86_64/%s' % config,
    629   ]
    630   libpath = [os.path.normpath(p) for p in libpath]
    631   libpath = [os.path.join(sdk_root, p) for p in libpath]
    632   return libpath
    633 
    634 
    635 def main(argv):
    636   parser = optparse.OptionParser(
    637       usage='Usage: %prog [options] nexe [extra_libs...]')
    638   parser.add_option('-o', '--output', dest='output',
    639                     help='Write manifest file to FILE (default is stdout)',
    640                     metavar='FILE')
    641   parser.add_option('-D', '--objdump', dest='objdump',
    642                     help='Override the default "objdump" tool used to find '
    643                          'shared object dependencies',
    644                     metavar='TOOL')
    645   parser.add_option('--no-default-libpath', action='store_true',
    646                     help="Don't include the SDK default library paths")
    647   parser.add_option('--debug-libs', action='store_true',
    648                     help='Use debug library paths when constructing default '
    649                          'library path.')
    650   parser.add_option('-L', '--library-path', dest='lib_path',
    651                     action='append', default=[],
    652                     help='Add DIRECTORY to library search path',
    653                     metavar='DIRECTORY')
    654   parser.add_option('-P', '--path-prefix', dest='path_prefix', default='',
    655                     help='A path to prepend to shared libraries in the .nmf',
    656                     metavar='DIRECTORY')
    657   parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies',
    658                     help='Destination directory for staging libraries',
    659                     metavar='DIRECTORY')
    660   parser.add_option('-t', '--toolchain', help='Legacy option, do not use')
    661   parser.add_option('-n', '--name', dest='name',
    662                     help='Rename FOO as BAR',
    663                     action='append', default=[], metavar='FOO,BAR')
    664   parser.add_option('-x', '--extra-files',
    665                     help=('Add extra key:file tuple to the "files"' +
    666                           ' section of the .nmf'),
    667                     action='append', default=[], metavar='FILE')
    668   parser.add_option('-O', '--pnacl-optlevel',
    669                     help='Set the optimization level to N in PNaCl manifests',
    670                     metavar='N')
    671   parser.add_option('-v', '--verbose',
    672                     help='Verbose output', action='store_true')
    673   parser.add_option('-d', '--debug-mode',
    674                     help='Debug mode', action='store_true')
    675   options, args = parser.parse_args(argv)
    676   if options.verbose:
    677     Trace.verbose = True
    678   if options.debug_mode:
    679     DebugPrint.debug_mode = True
    680 
    681   if options.toolchain is not None:
    682     sys.stderr.write('warning: option -t/--toolchain is deprecated.\n')
    683 
    684   if len(args) < 1:
    685     parser.error('No nexe files specified.  See --help for more info')
    686 
    687   canonicalized = ParseExtraFiles(options.extra_files, sys.stderr)
    688   if canonicalized is None:
    689     parser.error('Bad --extra-files (-x) argument syntax')
    690 
    691   remap = {}
    692   for ren in options.name:
    693     parts = ren.split(',')
    694     if len(parts) != 2:
    695       parser.error('Expecting --name=<orig_arch.so>,<new_name.so>')
    696     remap[parts[0]] = parts[1]
    697 
    698   if options.path_prefix:
    699     path_prefix = options.path_prefix.split('/')
    700   else:
    701     path_prefix = []
    702 
    703   for libpath in options.lib_path:
    704     if not os.path.exists(libpath):
    705       sys.stderr.write('Specified library path does not exist: %s\n' % libpath)
    706     elif not os.path.isdir(libpath):
    707       sys.stderr.write('Specified library is not a directory: %s\n' % libpath)
    708 
    709   if not options.no_default_libpath:
    710     # Add default libraries paths to the end of the search path.
    711     config = options.debug_libs and 'Debug' or 'Release'
    712     options.lib_path += GetDefaultLibPath(config)
    713 
    714   pnacl_optlevel = None
    715   if options.pnacl_optlevel is not None:
    716     pnacl_optlevel = int(options.pnacl_optlevel)
    717     if pnacl_optlevel < 0 or pnacl_optlevel > 3:
    718       sys.stderr.write(
    719           'warning: PNaCl optlevel %d is unsupported (< 0 or > 3)\n' %
    720           pnacl_optlevel)
    721 
    722   nmf = NmfUtils(objdump=options.objdump,
    723                  main_files=args,
    724                  lib_path=options.lib_path,
    725                  extra_files=canonicalized,
    726                  lib_prefix=path_prefix,
    727                  remap=remap,
    728                  pnacl_optlevel=pnacl_optlevel)
    729 
    730   nmf.GetManifest()
    731   if not options.output:
    732     sys.stdout.write(nmf.GetJson())
    733   else:
    734     with open(options.output, 'w') as output:
    735       output.write(nmf.GetJson())
    736 
    737   if options.stage_dependencies and not nmf.pnacl:
    738     Trace('Staging dependencies...')
    739     nmf.StageDependencies(options.stage_dependencies)
    740 
    741   return 0
    742 
    743 
    744 if __name__ == '__main__':
    745   try:
    746     rtn = main(sys.argv[1:])
    747   except Error, e:
    748     sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e))
    749     rtn = 1
    750   except KeyboardInterrupt:
    751     sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__))
    752     rtn = 1
    753   sys.exit(rtn)
    754