#! /usr/bin/python
# -*- coding: utf-8 -*-
# kate: space-indent on; indent-width 2; mixedindent off; indent-mode python;

# Copyright (C) 2009 Amand 'alrj' Tihon <amand.tihon@alrj.org>
#
# This file is part of bold, the Byte Optimized Linker.
#
# You can redistribute this file and/or modify it under the terms of the
# GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License or (at your option) any later version.

__author__ = "Amand Tihon <amand.tihon@alrj.org>"
__version__ = "0.2.0"


from Bold.linker import BoldLinker
from Bold.errors import *
from optparse import OptionParser
import os, sys


class BoldOptionParser(OptionParser):
  """Bold option parser."""
  global __version__
  _usage_message = "%prog [options] objfile..."
  _version_message = "%%prog version %s" % __version__
  _description_message = """A limited ELF linker for x86_64. It is
intended to create very small executables with the least possible overhead."""

  def __init__(self):
    OptionParser.__init__(self, usage=self._usage_message,
      version=self._version_message, description=self._description_message,
      add_help_option=True, prog="bold")

    self.set_defaults(entry=None, outfile="a.out", raw=False, ccall=False,
      align=False)

    self.add_option("-e", "--entry", action="store", dest="entry",
      metavar="SYMBOL", help="Set the entry point (default: _start)")

    self.add_option("-l", "--library", action="append", dest="shlibs",
      metavar="LIBNAME", help="Search for library LIBNAME")

    self.add_option("-L", "--library-path", action="append", dest="libpath",
      metavar="DIRECTORY",
      help="Add DIRECTORY to library search path. (Ignored, for compatibility only.")

    self.add_option("-o", "--output", action="store", dest="outfile",
      metavar="FILE", help="Set output file name (default: a.out)")

    self.add_option("--raw", action="store_true", dest="raw",
      help="Don't include the builtin external symbols resolution code")

    self.add_option("-c", "--ccall", action="store_true", dest="ccall",
      help="Make external symbol callable by C (default: no)")

    self.add_option("-a", "--align-ccall", action="store_true", dest="align",
      help="Align C callable symbols with actual functions pointers")


def main():
  parser = BoldOptionParser()
  options, args = parser.parse_args()

  if not args:
    print >>sys.stderr, "No input files"
    return 1

  # Take a copy of args
  objects = args[:]

  if options.align and not options.ccall:
    print >>sys.stderr, "Making external symbols callable by C because of -a."
    options.ccall = True

  if options.ccall and options.raw:
    # ccall implies that we include the symbol resolution code...
    print >>sys.stderr, "Including symbol resolution code because of -c."
    options.raw = False

  if not options.raw:
    for d in ['.', 'runtime', '/usr/lib/bold/', '/usr/local/lib/bold']:
      f = os.path.join(d, 'bold_ibh-x86_64.o')
      if os.path.isfile(f):
        objects.append(f)
        break
    else:
      print >>sys.stderr, "Could not find bold_ibh-x86_64.o."
      return 1


  # Try reordering objects ?

  linker = BoldLinker()

  for infile in objects:
    try:
      linker.add_object(infile)
    except UnsupportedObject, e:
      print >>sys.stderr, e
      return 1
    except IOError, e:
      print >>sys.stderr, e
      return 1


  if options.shlibs:
    for shlib in options.shlibs:
      try:
        linker.add_shlib(shlib)
      except LibNotFound, e:
        print >>sys.stderr, e
        return 1

  if options.entry is not None:
    linker.entry_point = options.entry
  else:
    if not options.raw:
      linker.entry_point = "_bold__ibh_start"

  try:
    linker.build_symbols_tables()

    linker.check_external()

    linker.build_external(with_jump=options.ccall, align_jump=options.align)

    linker.link()
  except UndefinedSymbol, e:
    print >>sys.stderr, e
    return 1
  except RedefinedSymbol, e:
    print >>sys.stderr, e
    return 1

  # Remove the file if it was present
  try:
    os.unlink(options.outfile)
  except os.error, e:
    if e.errno == 2: # No such file
      pass

  try:
    o = open(options.outfile, "wb")
  except IOError, e:
    print >>sys.stderr, e
    return 1

  linker.tofile(o)
  o.close()
  
  try:
    os.chmod(options.outfile, 0755)
  except IOError, e:
    print >>sys.stderr, e
    return 1

  return 0


if __name__ == "__main__":
  try:
    rcode = main()
  except Exception, e:
    raise
    print >>sys.stderr, "Unhandled error:", e
    rcode = 1

  exit(rcode)

