text: #!/usr/bin/env python
  # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  # Use of this source code is governed by a BSD-style license that can be
  # found in the LICENSE file.
  #
  """Creates an import library from an import description file."""
  import ast
  import logging
  import optparse
  import os
  import os.path
  import shutil
  import subprocess
  import sys
  import tempfile
  
  _USAGE = """\
  Usage: %prog [options] [imports-file]
  
  Creates an import library from imports-file.
  
  Note: this script uses the microsoft assembler (ml.exe) and the library tool
      (lib.exe), both of which must be in path.
  """
  
  _ASM_STUB_HEADER = """\
  ; This file is autogenerated by create_importlib_win.py, do not edit.
  .386
  .MODEL FLAT, C
  .CODE
  
  ; Stubs to provide mangled names to lib.exe for the
  ; correct generation of import libs.
  """
  
  _DEF_STUB_HEADER = """\
  ; This file is autogenerated by create_importlib_win.py, do not edit.
  
  ; Export declarations for generating import libs.
  """
  
  _LOGGER = logging.getLogger()
  
  
  class _Error(Exception):
      pass
  
  
  class _ImportLibraryGenerator(object):
  
      def __init__(self, temp_dir):
          self._temp_dir = temp_dir
  
      def _Shell(self, cmd, **kw):
          ret = subprocess.call(cmd, **kw)
          _LOGGER.info('Running "%s" returned %d.', cmd, ret)
          if ret != 0:
              raise _Error('Command "%s" returned %d.' % (cmd, ret))
  
      def _ReadImportsFile(self, imports_file):
          # Slurp the imports file.
          return ast.literal_eval(open(imports_file).read())
  
      def _WriteStubsFile(self, import_names, output_file):
          output_file.write(_ASM_STUB_HEADER)
  
          for name in import_names:
              output_file.write('%s PROC\n' % name)
              output_file.write('%s ENDP\n' % name)
  
          output_file.write('END\n')
  
      def _WriteDefFile(self, dll_name, import_names, output_file):
          output_file.write(_DEF_STUB_HEADER)
          output_file.write('NAME %s\n' % dll_name)
          output_file.write('EXPORTS\n')
          for name in import_names:
              name = name.split('@')[0]
              output_file.write('  %s\n' % name)
  
      def _CreateObj(self, dll_name, imports):
          """Writes an assembly file containing empty declarations.
  
      For each imported function of the form:
  
      AddClipboardFormatListener@4 PROC
      AddClipboardFormatListener@4 ENDP
  
      The resulting object file is then supplied to lib.exe with a .def file
      declaring the corresponding non-adorned exports as they appear on the
      exporting DLL, e.g.
  
      EXPORTS
        AddClipboardFormatListener
  
      In combination, the .def file and the .obj file cause lib.exe to generate
      an x86 import lib with public symbols named like
      "__imp__AddClipboardFormatListener@4", binding to exports named like
      "AddClipboardFormatListener".
  
      All of this is perpetrated in a temporary directory, as the intermediate
      artifacts are quick and easy to produce, and of no interest to anyone
      after the fact."""
  
          # Create an .asm file to provide stdcall-like stub names to lib.exe.
          asm_name = dll_name + '.asm'
          _LOGGER.info('Writing asm file "%s".', asm_name)
          with open(os.path.join(self._temp_dir, asm_name), 'wb') as stubs_file:
              self._WriteStubsFile(imports, stubs_file)
  
          # Invoke on the assembler to compile it to .obj.
          obj_name = dll_name + '.obj'
          cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name]
          self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull))
  
          return obj_name
  
      def _CreateImportLib(self, dll_name, imports, architecture, output_file):
          """Creates an import lib binding imports to dll_name for architecture.
  
      On success, writes the import library to output file.
      """
          obj_file = None
  
          # For x86 architecture we have to provide an object file for correct
          # name mangling between the import stubs and the exported functions.
          if architecture == 'x86':
              obj_file = self._CreateObj(dll_name, imports)
  
          # Create the corresponding .def file. This file has the non stdcall-adorned
          # names, as exported by the destination DLL.
          def_name = dll_name + '.def'
          _LOGGER.info('Writing def file "%s".', def_name)
          with open(os.path.join(self._temp_dir, def_name), 'wb') as def_file:
              self._WriteDefFile(dll_name, imports, def_file)
  
          # Invoke on lib.exe to create the import library.
          # We generate everything into the temporary directory, as the .exp export
          # files will be generated at the same path as the import library, and we
          # don't want those files potentially gunking the works.
          dll_base_name, ext = os.path.splitext(dll_name)
          lib_name = dll_base_name + '.lib'
          cmdline = [
              'lib.exe',
              '/machine:%s' % architecture,
              '/def:%s' % def_name,
              '/out:%s' % lib_name
          ]
          if obj_file:
              cmdline.append(obj_file)
  
          self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull))
  
          # Copy the .lib file to the output directory.
          shutil.copyfile(os.path.join(self._temp_dir, lib_name), output_file)
          _LOGGER.info('Created "%s".', output_file)
  
      def CreateImportLib(self, imports_file, output_file):
          # Read the imports file.
          imports = self._ReadImportsFile(imports_file)
  
          # Creates the requested import library in the output directory.
          self._CreateImportLib(imports['dll_name'], imports['imports'],
                                imports.get('architecture', 'x86'), output_file)
  
  
  def main():
      parser = optparse.OptionParser(usage=_USAGE)
      parser.add_option(
          '-o', '--output-file', help='Specifies the output file path.')
      parser.add_option(
          '-k',
          '--keep-temp-dir',
          action='store_true',
          help='Keep the temporary directory.')
      parser.add_option(
          '-v', '--verbose', action='store_true', help='Verbose logging.')
  
      options, args = parser.parse_args()
  
      if len(args) != 1:
          parser.error('You must provide an imports file.')
  
      if not options.output_file:
          parser.error('You must provide an output file.')
  
      options.output_file = os.path.abspath(options.output_file)
  
      if options.verbose:
          logging.basicConfig(level=logging.INFO)
      else:
          logging.basicConfig(level=logging.WARN)
  
      temp_dir = tempfile.mkdtemp()
      _LOGGER.info('Created temporary directory "%s."', temp_dir)
      try:
          # Create a generator and create the import lib.
          generator = _ImportLibraryGenerator(temp_dir)
  
          ret = generator.CreateImportLib(args[0], options.output_file)
      except Exception, e:
          _LOGGER.exception('Failed to create import lib.')
          ret = 1
      finally:
          if not options.keep_temp_dir:
              shutil.rmtree(temp_dir)
              _LOGGER.info('Deleted temporary directory "%s."', temp_dir)
  
      return ret
  
  
  if __name__ == '__main__':
      sys.exit(main())
  