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