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/.bc executables.
      7 
      8 As well as creating the nmf file this tool can also find and stage
      9 any shared libraries dependencies that the executables might have.
     10 """
     11 
     12 import errno
     13 import json
     14 import optparse
     15 import os
     16 import posixpath
     17 import shutil
     18 import sys
     19 
     20 import getos
     21 
     22 if sys.version_info < (2, 6, 0):
     23   sys.stderr.write("python 2.6 or later is required run this script\n")
     24   sys.exit(1)
     25 
     26 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
     27 LIB_DIR = os.path.join(SCRIPT_DIR, 'lib')
     28 
     29 sys.path.append(LIB_DIR)
     30 
     31 import elf
     32 import get_shared_deps
     33 import quote
     34 
     35 
     36 ARCH_LOCATION = {
     37     'x86-32': 'lib32',
     38     'x86-64': 'lib64',
     39     'arm': 'lib',
     40 }
     41 
     42 
     43 # These constants are used within nmf files.
     44 RUNNABLE_LD = 'runnable-ld.so'  # Name of the dynamic loader
     45 MAIN_NEXE = 'main.nexe'  # Name of entry point for execution
     46 PROGRAM_KEY = 'program'  # Key of the program section in an nmf file
     47 URL_KEY = 'url'  # Key of the url field for a particular file in an nmf file
     48 FILES_KEY = 'files'  # Key of the files section in an nmf file
     49 PNACL_OPTLEVEL_KEY = 'optlevel' # key for PNaCl optimization level
     50 PORTABLE_KEY = 'portable' # key for portable section of manifest
     51 TRANSLATE_KEY = 'pnacl-translate' # key for translatable objects
     52 TRANSLATE_DEBUG_KEY = 'pnacl-debug' # key for translatable debug objects
     53 
     54 
     55 def DebugPrint(message):
     56   if DebugPrint.debug_mode:
     57     sys.stderr.write('%s\n' % message)
     58 
     59 
     60 DebugPrint.debug_mode = False  # Set to True to enable extra debug prints
     61 
     62 
     63 def SplitPath(path):
     64   """Returns all components of a path as a list.
     65 
     66   e.g.
     67   'foo/bar/baz.blah' => ['foo', 'bar', 'baz.blah']
     68   """
     69   result = []
     70   while path:
     71     path, part = os.path.split(path)
     72     result.append(part)
     73   return result[::-1]  # Reverse.
     74 
     75 
     76 def MakePosixPath(path):
     77   """Converts from the native format to posixpath format.
     78 
     79   e.g. on Windows, "foo\\bar\\baz.blah" => "foo/bar/baz.blah"
     80   on Mac/Linux this is a no-op.
     81   """
     82   if os.path == posixpath:
     83     return path
     84   return posixpath.join(*SplitPath(path))
     85 
     86 
     87 def PosixRelPath(path, start):
     88   """Takes two paths in native format, and produces a relative path in posix
     89   format.
     90 
     91   e.g.
     92   For Windows: "foo\\bar\\baz.blah", "foo" => "bar/baz.blah"
     93   For Mac/Linux: "foo/bar/baz.blah", "foo" => "bar/baz.blah"
     94 
     95   NOTE: This function uses os.path.realpath to create a canonical path for
     96   |path| and |start|.
     97   """
     98   real_path = os.path.realpath(path)
     99   real_start = os.path.realpath(start)
    100   return MakePosixPath(os.path.relpath(real_path, real_start))
    101 
    102 
    103 def DirectoryTreeContainsFile(dirname, filename):
    104   """Returns True if a file is in a directory, or any of that directory's
    105   subdirectories recursively.
    106 
    107   e.g.
    108   DirectoryTreeContainsFile("foo", "foo/quux.txt") => True
    109   DirectoryTreeContainsFile("foo", "foo/bar/baz/blah.txt") => True
    110   DirectoryTreeContainsFile("foo", "bar/blah.txt") => False
    111   """
    112   real_dirname = os.path.realpath(dirname)
    113   real_filename = os.path.realpath(filename)
    114   return real_filename.startswith(real_dirname)
    115 
    116 
    117 def MakeDir(dirname):
    118   """Just like os.makedirs but doesn't generate errors when dirname
    119   already exists.
    120   """
    121   if os.path.isdir(dirname):
    122     return
    123 
    124   Trace("mkdir: %s" % dirname)
    125   try:
    126     os.makedirs(dirname)
    127   except OSError as exception_info:
    128     if exception_info.errno != errno.EEXIST:
    129       raise
    130 
    131 
    132 def ParseElfHeader(path):
    133   """Wrap elf.ParseElfHeader to return raise this module's Error on failure."""
    134   try:
    135     return elf.ParseElfHeader(path)
    136   except elf.Error, e:
    137     raise Error(str(e))
    138 
    139 
    140 class Error(Exception):
    141   """Local Error class for this file."""
    142   pass
    143 
    144 
    145 class ArchFile(object):
    146   """Simple structure containing information about an architecture-specific
    147      file.
    148 
    149   Attributes:
    150     name: Name of this file
    151     path: Full path to this file on the build system
    152     arch: Architecture of this file (e.g., x86-32)
    153     url: Relative path to file in the staged web directory.
    154         Used for specifying the "url" attribute in the nmf file."""
    155 
    156   def __init__(self, name, path, url=None, arch=None):
    157     self.name = name
    158     self.path = path
    159     self.url = url
    160     self.arch = arch
    161     if not arch:
    162       self.arch = ParseElfHeader(path)[0]
    163 
    164   def __repr__(self):
    165     return '<ArchFile %s>' % self.path
    166 
    167   def __str__(self):
    168     """Return the file path when invoked with the str() function"""
    169     return self.path
    170 
    171 
    172 class NmfUtils(object):
    173   """Helper class for creating and managing nmf files"""
    174 
    175   def __init__(self, main_files=None, objdump=None,
    176                lib_path=None, extra_files=None, lib_prefix=None,
    177                nexe_prefix=None, no_arch_prefix=None, remap=None,
    178                pnacl_optlevel=None, pnacl_debug_optlevel=None,
    179                nmf_root=None):
    180     """Constructor
    181 
    182     Args:
    183       main_files: List of main entry program files.  These will be named
    184           files->main.nexe for dynamic nexes, and program for static nexes
    185       objdump: path to x86_64-nacl-objdump tool (or Linux equivalent)
    186       lib_path: List of paths to library directories
    187       extra_files: List of extra files to include in the nmf
    188       lib_prefix: A path prefix to prepend to the library paths, both for
    189           staging the libraries and for inclusion into the nmf file.
    190           Example: '../lib_dir'
    191       nexe_prefix: Like lib_prefix, but is prepended to the nexes instead.
    192       no_arch_prefix: Don't prefix shared libraries by lib32/lib64.
    193       remap: Remaps the library name in the manifest.
    194       pnacl_optlevel: Optimization level for PNaCl translation.
    195       pnacl_debug_optlevel: Optimization level for debug PNaCl translation.
    196       nmf_root: Directory of the NMF. All urls are relative to this directory.
    197     """
    198     assert len(main_files) > 0
    199     self.objdump = objdump
    200     self.main_files = main_files
    201     self.extra_files = extra_files or []
    202     self.lib_path = lib_path or []
    203     self.manifest = None
    204     self.needed = None
    205     self.lib_prefix = lib_prefix or ''
    206     self.nexe_prefix = nexe_prefix or ''
    207     self.no_arch_prefix = no_arch_prefix
    208     self.remap = remap or {}
    209     self.pnacl = main_files[0].endswith(('.pexe', '.bc'))
    210     self.pnacl_optlevel = pnacl_optlevel
    211     self.pnacl_debug_optlevel = pnacl_debug_optlevel
    212     if nmf_root is not None:
    213       self.nmf_root = nmf_root
    214     else:
    215       # To match old behavior, if there is no nmf_root, use the directory of
    216       # the first nexe found in main_files.
    217       self.nmf_root = os.path.dirname(main_files[0])
    218 
    219     for filename in self.main_files:
    220       if not os.path.exists(filename):
    221         raise Error('Input file not found: %s' % filename)
    222       if not os.path.isfile(filename):
    223         raise Error('Input is not a file: %s' % filename)
    224 
    225   def GetNeeded(self):
    226     """Collect the list of dependencies for the main_files
    227 
    228     Returns:
    229       A dict with key=filename and value=ArchFile of input files.
    230           Includes the input files as well, with arch filled in if absent.
    231           Example: { '/path/to/my.nexe': ArchFile(my.nexe),
    232                      '/path/to/libfoo.so': ArchFile(libfoo.so) }"""
    233 
    234     if self.needed:
    235       return self.needed
    236 
    237     DebugPrint('GetNeeded(%s)' % self.main_files)
    238 
    239     if not self.objdump:
    240       self.objdump = FindObjdumpExecutable()
    241 
    242     try:
    243       all_files = get_shared_deps.GetNeeded(self.main_files, self.objdump,
    244                                             self.lib_path)
    245     except get_shared_deps.NoObjdumpError:
    246       raise Error('No objdump executable found (see --help for more info)')
    247     except get_shared_deps.Error, e:
    248       raise Error(str(e))
    249 
    250     self.needed = {}
    251 
    252     # all_files is a dictionary mapping filename to architecture. self.needed
    253     # should be a dictionary of filename to ArchFile.
    254     for filename, arch in all_files.iteritems():
    255       name = os.path.basename(filename)
    256       self.needed[filename] = ArchFile(name=name, path=filename, arch=arch)
    257 
    258     self._SetArchFileUrls()
    259 
    260     return self.needed
    261 
    262   def _SetArchFileUrls(self):
    263     """Fill in the url member of all ArchFiles in self.needed.
    264 
    265     All urls are relative to the nmf_root. In addition, architecture-specific
    266     files are relative to the .nexe with the matching architecture. This is
    267     useful when making a multi-platform packaged app, so each architecture's
    268     files are in a different directory.
    269     """
    270     # self.GetNeeded() should have already been called.
    271     assert self.needed is not None
    272 
    273     main_nexes = [f for f in self.main_files if f.endswith('.nexe')]
    274 
    275     # map from each arch to its corresponding main nexe.
    276     arch_to_main_dir = {}
    277     for main_file in main_nexes:
    278       arch, _ = ParseElfHeader(main_file)
    279       main_dir = os.path.dirname(main_file)
    280       main_dir = PosixRelPath(main_dir, self.nmf_root)
    281       if main_dir == '.':
    282         main_dir = ''
    283       arch_to_main_dir[arch] = main_dir
    284 
    285     for arch_file in self.needed.itervalues():
    286       prefix = ''
    287       if DirectoryTreeContainsFile(self.nmf_root, arch_file.path):
    288         # This file is already in the nmf_root tree, so it does not need to be
    289         # staged. Just make the URL relative to the .nmf.
    290         url = PosixRelPath(arch_file.path, self.nmf_root)
    291       else:
    292         # This file is outside of the nmf_root subtree, so it needs to be
    293         # staged. Its path should be relative to the main .nexe with the same
    294         # architecture.
    295         prefix = arch_to_main_dir[arch_file.arch]
    296         url = os.path.basename(arch_file.path)
    297 
    298       if arch_file.name.endswith('.nexe'):
    299         prefix = posixpath.join(prefix, self.nexe_prefix)
    300       elif self.no_arch_prefix:
    301         prefix = posixpath.join(prefix, self.lib_prefix)
    302       else:
    303         prefix = posixpath.join(
    304             prefix, self.lib_prefix, ARCH_LOCATION[arch_file.arch])
    305       arch_file.url = posixpath.join(prefix, url)
    306 
    307   def StageDependencies(self, destination_dir):
    308     """Copies over the dependencies into a given destination directory
    309 
    310     Each library will be put into a subdirectory that corresponds to the arch.
    311 
    312     Args:
    313       destination_dir: The destination directory for staging the dependencies
    314     """
    315     assert self.needed is not None
    316     for arch_file in self.needed.itervalues():
    317       source = arch_file.path
    318       destination = os.path.join(destination_dir, arch_file.url)
    319 
    320       if (os.path.normcase(os.path.realpath(source)) ==
    321           os.path.normcase(os.path.realpath(destination))):
    322         continue
    323 
    324       # make sure target dir exists
    325       MakeDir(os.path.dirname(destination))
    326 
    327       Trace('copy: %s -> %s' % (source, destination))
    328       shutil.copy2(source, destination)
    329 
    330   def _GeneratePNaClManifest(self):
    331     manifest = {}
    332     manifest[PROGRAM_KEY] = {}
    333     manifest[PROGRAM_KEY][PORTABLE_KEY] = {}
    334     portable = manifest[PROGRAM_KEY][PORTABLE_KEY]
    335     for filename in self.main_files:
    336       translate_dict =  {
    337           'url': os.path.basename(filename),
    338       }
    339       if filename.endswith('.pexe'):
    340         if self.pnacl_optlevel is not None:
    341           translate_dict[PNACL_OPTLEVEL_KEY] = self.pnacl_optlevel
    342         if TRANSLATE_KEY in portable:
    343           raise Error('Multiple .pexe files')
    344         portable[TRANSLATE_KEY] = translate_dict
    345       elif filename.endswith('.bc'):
    346         if self.pnacl_debug_optlevel is not None:
    347           translate_dict[PNACL_OPTLEVEL_KEY] = self.pnacl_debug_optlevel
    348         if TRANSLATE_DEBUG_KEY in portable:
    349           raise Error('Multiple .bc files')
    350         portable[TRANSLATE_DEBUG_KEY] = translate_dict
    351       else:
    352         raise Error('Unexpected executable type: %s' % filename)
    353     self.manifest = manifest
    354 
    355   def _GenerateManifest(self):
    356     """Create a JSON formatted dict containing the files
    357 
    358     NaCl will map url requests based on architecture.  The startup NEXE
    359     can always be found under the top key PROGRAM.  Additional files are under
    360     the FILES key further mapped by file name.  In the case of 'runnable' the
    361     PROGRAM key is populated with urls pointing the runnable-ld.so which acts
    362     as the startup nexe.  The application itself is then placed under the
    363     FILES key mapped as 'main.exe' instead of the original name so that the
    364     loader can find it.
    365     """
    366     manifest = { FILES_KEY: {}, PROGRAM_KEY: {} }
    367 
    368     needed = self.GetNeeded()
    369 
    370     runnable = any(n.endswith(RUNNABLE_LD) for n in needed)
    371 
    372     extra_files_kv = [(key, ArchFile(name=key,
    373                                      arch=arch,
    374                                      path=url,
    375                                      url=url))
    376                       for key, arch, url in self.extra_files]
    377 
    378     for need, archinfo in needed.items() + extra_files_kv:
    379       urlinfo = { URL_KEY: archinfo.url }
    380       name = archinfo.name
    381 
    382       # If starting with runnable-ld.so, make that the main executable.
    383       if runnable:
    384         if need.endswith(RUNNABLE_LD):
    385           manifest[PROGRAM_KEY][archinfo.arch] = urlinfo
    386           continue
    387 
    388       if need in self.main_files:
    389         if need.endswith(".nexe"):
    390           # Place it under program if we aren't using the runnable-ld.so.
    391           if not runnable:
    392             manifest[PROGRAM_KEY][archinfo.arch] = urlinfo
    393             continue
    394           # Otherwise, treat it like another another file named main.nexe.
    395           name = MAIN_NEXE
    396 
    397       name = self.remap.get(name, name)
    398       fileinfo = manifest[FILES_KEY].get(name, {})
    399       fileinfo[archinfo.arch] = urlinfo
    400       manifest[FILES_KEY][name] = fileinfo
    401     self.manifest = manifest
    402 
    403   def GetManifest(self):
    404     """Returns a JSON-formatted dict containing the NaCl dependencies"""
    405     if not self.manifest:
    406       if self.pnacl:
    407         self._GeneratePNaClManifest()
    408       else:
    409         self._GenerateManifest()
    410     return self.manifest
    411 
    412   def GetJson(self):
    413     """Returns the Manifest as a JSON-formatted string"""
    414     pretty_string = json.dumps(self.GetManifest(), indent=2)
    415     # json.dumps sometimes returns trailing whitespace and does not put
    416     # a newline at the end.  This code fixes these problems.
    417     pretty_lines = pretty_string.split('\n')
    418     return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n'
    419 
    420 
    421 def Trace(msg):
    422   if Trace.verbose:
    423     sys.stderr.write(str(msg) + '\n')
    424 
    425 Trace.verbose = False
    426 
    427 
    428 def ParseExtraFiles(encoded_list, err):
    429   """Parse the extra-files list and return a canonicalized list of
    430   [key, arch, url] triples.  The |encoded_list| should be a list of
    431   strings of the form 'key:url' or 'key:arch:url', where an omitted
    432   'arch' is taken to mean 'portable'.
    433 
    434   All entries in |encoded_list| are checked for syntax errors before
    435   returning.  Error messages are written to |err| (typically
    436   sys.stderr) so that the user has actionable feedback for fixing all
    437   errors, rather than one at a time.  If there are any errors, None is
    438   returned instead of a list, since an empty list is a valid return
    439   value.
    440   """
    441   seen_error = False
    442   canonicalized = []
    443   for ix in range(len(encoded_list)):
    444     kv = encoded_list[ix]
    445     unquoted = quote.unquote(kv, ':')
    446     if len(unquoted) == 3:
    447       if unquoted[1] != ':':
    448         err.write('Syntax error for key:value tuple ' +
    449                   'for --extra-files argument: ' + kv + '\n')
    450         seen_error = True
    451       else:
    452         canonicalized.append([unquoted[0], 'portable', unquoted[2]])
    453     elif len(unquoted) == 5:
    454       if unquoted[1] != ':' or unquoted[3] != ':':
    455         err.write('Syntax error for key:arch:url tuple ' +
    456                   'for --extra-files argument: ' +
    457                   kv + '\n')
    458         seen_error = True
    459       else:
    460         canonicalized.append([unquoted[0], unquoted[2], unquoted[4]])
    461     else:
    462       err.write('Bad key:arch:url tuple for --extra-files: ' + kv + '\n')
    463   if seen_error:
    464     return None
    465   return canonicalized
    466 
    467 
    468 def GetSDKRoot():
    469   """Determine current NACL_SDK_ROOT, either via the environment variable
    470   itself, or by attempting to derive it from the location of this script.
    471   """
    472   sdk_root = os.environ.get('NACL_SDK_ROOT')
    473   if not sdk_root:
    474     sdk_root = os.path.dirname(SCRIPT_DIR)
    475     if not os.path.exists(os.path.join(sdk_root, 'toolchain')):
    476       return None
    477 
    478   return sdk_root
    479 
    480 
    481 def FindObjdumpExecutable():
    482   """Derive path to objdump executable to use for determining shared
    483   object dependencies.
    484   """
    485   sdk_root = GetSDKRoot()
    486   if not sdk_root:
    487     return None
    488 
    489   osname = getos.GetPlatform()
    490   toolchain = os.path.join(sdk_root, 'toolchain', '%s_x86_glibc' % osname)
    491   objdump = os.path.join(toolchain, 'bin', 'x86_64-nacl-objdump')
    492   if osname == 'win':
    493     objdump += '.exe'
    494 
    495   if not os.path.exists(objdump):
    496     sys.stderr.write('WARNING: failed to find objdump in default '
    497                      'location: %s' % objdump)
    498     return None
    499 
    500   return objdump
    501 
    502 
    503 def GetDefaultLibPath(config):
    504   """Derive default library path to use when searching for shared
    505   objects.  This currently include the toolchain library folders
    506   as well as the top level SDK lib folder and the naclports lib
    507   folder.  We include both 32-bit and 64-bit library paths.
    508   """
    509   assert(config in ('Debug', 'Release'))
    510   sdk_root = GetSDKRoot()
    511   if not sdk_root:
    512     # TOOD(sbc): output a warning here?  We would also need to suppress
    513     # the warning when run from the chromium build.
    514     return []
    515 
    516   osname = getos.GetPlatform()
    517   libpath = [
    518     # Core toolchain libraries
    519     'toolchain/%s_x86_glibc/x86_64-nacl/lib' % osname,
    520     'toolchain/%s_x86_glibc/x86_64-nacl/lib32' % osname,
    521     # naclports installed libraries
    522     'toolchain/%s_x86_glibc/x86_64-nacl/usr/lib' % osname,
    523     'toolchain/%s_x86_glibc/i686-nacl/usr/lib' % osname,
    524     # SDK bundle libraries
    525     'lib/glibc_x86_32/%s' % config,
    526     'lib/glibc_x86_64/%s' % config,
    527     # naclports bundle libraries
    528     'ports/lib/glibc_x86_32/%s' % config,
    529     'ports/lib/glibc_x86_64/%s' % config,
    530   ]
    531 
    532   bionic_dir = 'toolchain/%s_arm_bionic' % osname
    533   if os.path.isdir(os.path.join(sdk_root, bionic_dir)):
    534     libpath += [
    535       '%s/arm-nacl/lib' % bionic_dir,
    536       '%s/arm-nacl/usr/lib' % bionic_dir,
    537       'lib/bionic_arm/%s' % config,
    538     ]
    539   libpath = [os.path.normpath(p) for p in libpath]
    540   libpath = [os.path.join(sdk_root, p) for p in libpath]
    541   return libpath
    542 
    543 
    544 def main(argv):
    545   parser = optparse.OptionParser(
    546       usage='Usage: %prog [options] nexe [extra_libs...]', description=__doc__)
    547   parser.add_option('-o', '--output', dest='output',
    548                     help='Write manifest file to FILE (default is stdout)',
    549                     metavar='FILE')
    550   parser.add_option('-D', '--objdump', dest='objdump',
    551                     help='Override the default "objdump" tool used to find '
    552                          'shared object dependencies',
    553                     metavar='TOOL')
    554   parser.add_option('--no-default-libpath', action='store_true',
    555                     help="Don't include the SDK default library paths")
    556   parser.add_option('--debug-libs', action='store_true',
    557                     help='Use debug library paths when constructing default '
    558                          'library path.')
    559   parser.add_option('-L', '--library-path', dest='lib_path',
    560                     action='append', default=[],
    561                     help='Add DIRECTORY to library search path',
    562                     metavar='DIRECTORY')
    563   parser.add_option('-P', '--path-prefix', dest='path_prefix', default='',
    564                     help='Deprecated. An alias for --lib-prefix.',
    565                     metavar='DIRECTORY')
    566   parser.add_option('-p', '--lib-prefix', dest='lib_prefix', default='',
    567                     help='A path to prepend to shared libraries in the .nmf',
    568                     metavar='DIRECTORY')
    569   parser.add_option('-N', '--nexe-prefix', dest='nexe_prefix', default='',
    570                     help='A path to prepend to nexes in the .nmf',
    571                     metavar='DIRECTORY')
    572   parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies',
    573                     help='Destination directory for staging libraries',
    574                     metavar='DIRECTORY')
    575   parser.add_option('--no-arch-prefix', action='store_true',
    576                     help='Don\'t put shared libraries in the lib32/lib64 '
    577                     'directories. Instead, they will be put in the same '
    578                     'directory as the .nexe that matches its architecture.')
    579   parser.add_option('-t', '--toolchain', help='Legacy option, do not use')
    580   parser.add_option('-n', '--name', dest='name',
    581                     help='Rename FOO as BAR',
    582                     action='append', default=[], metavar='FOO,BAR')
    583   parser.add_option('-x', '--extra-files',
    584                     help=('Add extra key:file tuple to the "files"' +
    585                           ' section of the .nmf'),
    586                     action='append', default=[], metavar='FILE')
    587   parser.add_option('-O', '--pnacl-optlevel',
    588                     help='Set the optimization level to N in PNaCl manifests',
    589                     metavar='N')
    590   parser.add_option('--pnacl-debug-optlevel',
    591                     help='Set the optimization level to N for debugging '
    592                          'sections in PNaCl manifests',
    593                     metavar='N')
    594   parser.add_option('-v', '--verbose',
    595                     help='Verbose output', action='store_true')
    596   parser.add_option('-d', '--debug-mode',
    597                     help='Debug mode', action='store_true')
    598 
    599   # To enable bash completion for this command first install optcomplete
    600   # and then add this line to your .bashrc:
    601   #  complete -F _optcomplete create_nmf.py
    602   try:
    603     import optcomplete
    604     optcomplete.autocomplete(parser)
    605   except ImportError:
    606     pass
    607 
    608   options, args = parser.parse_args(argv)
    609   if options.verbose:
    610     Trace.verbose = True
    611   if options.debug_mode:
    612     DebugPrint.debug_mode = True
    613 
    614   if options.toolchain is not None:
    615     sys.stderr.write('warning: option -t/--toolchain is deprecated.\n')
    616 
    617   if len(args) < 1:
    618     parser.error('No nexe files specified.  See --help for more info')
    619 
    620   canonicalized = ParseExtraFiles(options.extra_files, sys.stderr)
    621   if canonicalized is None:
    622     parser.error('Bad --extra-files (-x) argument syntax')
    623 
    624   remap = {}
    625   for ren in options.name:
    626     parts = ren.split(',')
    627     if len(parts) != 2:
    628       parser.error('Expecting --name=<orig_arch.so>,<new_name.so>')
    629     remap[parts[0]] = parts[1]
    630 
    631   if options.path_prefix:
    632     options.lib_prefix = options.path_prefix
    633 
    634   for libpath in options.lib_path:
    635     if not os.path.exists(libpath):
    636       sys.stderr.write('Specified library path does not exist: %s\n' % libpath)
    637     elif not os.path.isdir(libpath):
    638       sys.stderr.write('Specified library is not a directory: %s\n' % libpath)
    639 
    640   if not options.no_default_libpath:
    641     # Add default libraries paths to the end of the search path.
    642     config = options.debug_libs and 'Debug' or 'Release'
    643     options.lib_path += GetDefaultLibPath(config)
    644     for path in options.lib_path:
    645       Trace('libpath: %s' % path)
    646 
    647   pnacl_optlevel = None
    648   if options.pnacl_optlevel is not None:
    649     pnacl_optlevel = int(options.pnacl_optlevel)
    650     if pnacl_optlevel < 0 or pnacl_optlevel > 3:
    651       sys.stderr.write(
    652           'warning: PNaCl optlevel %d is unsupported (< 0 or > 3)\n' %
    653           pnacl_optlevel)
    654   if options.pnacl_debug_optlevel is not None:
    655     pnacl_debug_optlevel = int(options.pnacl_debug_optlevel)
    656   else:
    657     pnacl_debug_optlevel = pnacl_optlevel
    658 
    659   nmf_root = None
    660   if options.output:
    661     nmf_root = os.path.dirname(options.output)
    662 
    663   nmf = NmfUtils(objdump=options.objdump,
    664                  main_files=args,
    665                  lib_path=options.lib_path,
    666                  extra_files=canonicalized,
    667                  lib_prefix=options.lib_prefix,
    668                  nexe_prefix=options.nexe_prefix,
    669                  no_arch_prefix=options.no_arch_prefix,
    670                  remap=remap,
    671                  pnacl_optlevel=pnacl_optlevel,
    672                  pnacl_debug_optlevel=pnacl_debug_optlevel,
    673                  nmf_root=nmf_root)
    674 
    675   if not options.output:
    676     sys.stdout.write(nmf.GetJson())
    677   else:
    678     with open(options.output, 'w') as output:
    679       output.write(nmf.GetJson())
    680 
    681   if options.stage_dependencies and not nmf.pnacl:
    682     Trace('Staging dependencies...')
    683     nmf.StageDependencies(options.stage_dependencies)
    684 
    685   return 0
    686 
    687 
    688 if __name__ == '__main__':
    689   try:
    690     rtn = main(sys.argv[1:])
    691   except Error, e:
    692     sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e))
    693     rtn = 1
    694   except KeyboardInterrupt:
    695     sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__))
    696     rtn = 1
    697   sys.exit(rtn)
    698