• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8As well as creating the nmf file this tool can also find and stage
9any shared libraries dependencies that the executables might have.
10"""
11
12import errno
13import json
14import optparse
15import os
16import posixpath
17import shutil
18import sys
19
20import getos
21
22if 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
26SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
27LIB_DIR = os.path.join(SCRIPT_DIR, 'lib')
28
29sys.path.append(LIB_DIR)
30
31import elf
32import get_shared_deps
33import quote
34
35
36ARCH_LOCATION = {
37    'x86-32': 'lib32',
38    'x86-64': 'lib64',
39    'arm': 'lib',
40}
41
42
43# These constants are used within nmf files.
44RUNNABLE_LD = 'runnable-ld.so'  # Name of the dynamic loader
45MAIN_NEXE = 'main.nexe'  # Name of entry point for execution
46PROGRAM_KEY = 'program'  # Key of the program section in an nmf file
47URL_KEY = 'url'  # Key of the url field for a particular file in an nmf file
48FILES_KEY = 'files'  # Key of the files section in an nmf file
49PNACL_OPTLEVEL_KEY = 'optlevel' # key for PNaCl optimization level
50PORTABLE_KEY = 'portable' # key for portable section of manifest
51TRANSLATE_KEY = 'pnacl-translate' # key for translatable objects
52TRANSLATE_DEBUG_KEY = 'pnacl-debug' # key for translatable debug objects
53
54
55def DebugPrint(message):
56  if DebugPrint.debug_mode:
57    sys.stderr.write('%s\n' % message)
58
59
60DebugPrint.debug_mode = False  # Set to True to enable extra debug prints
61
62
63def 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
76def 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
87def 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
103def 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
117def 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
132def 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
140class Error(Exception):
141  """Local Error class for this file."""
142  pass
143
144
145class 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
172class 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
421def Trace(msg):
422  if Trace.verbose:
423    sys.stderr.write(str(msg) + '\n')
424
425Trace.verbose = False
426
427
428def 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
468def 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
481def 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
503def 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
544def 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
688if __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