#!/usr/bin/env python

# finkdep - Find the Fink packages that provide the dependencies of an
# object file
#
# Copyright (c) 2005-2008 Sebastien Maret <bmaret@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.

import getopt, sys, os, string

versionNum = "0.1"

# Default settings

verbose = False
nIndent = 2           # Number of white space for indentation in the output
maxLineLength = 60    # Maximum line length for the info output
findMethod = "grep"   # Fink package finding method. Can be either dpkg or grep
finkPrefix = "/sw"    # Fink prefix (needed when searching with grep)

# Display usage

def usage():
    print """Usage: finkdep [OPTION] FILE
Find the Fink packages that provide the dependencies of an object file
   
Options:
    -h, --help          display this help
    -v, --version       display version
    -V, --verbose       cause finkdep to say what it is doing"""

# Display version number

def version():
   print 'This is finkdep, version %s' % versionNum
   print """Copyright (c) 2005 Sebastien Maret.
This program is a free software. You are welcome to redistribute
it under the terms of the GNU General Public License."""

# Find the shared librairies used by the object file

def getShlibs(objFile):

    shlibs = []
    
    p = os.popen('otool -L %s' % objFile)
    lines = map(string.strip, p.readlines())
    p.close()

    for line in lines[1:]:
        shlibs.append(line.split()[0])
    
    return shlibs

# Find the symbolic link pointing on a shared library

def getLinkShlib(shlib):

    shlibDirName = os.path.dirname(shlib) + '/'
    shlibBaseName = os.path.basename(shlib)  

    splitBaseName = shlibBaseName.split('.') 
    shlibExt = splitBaseName.pop() # Should be dylib
    
    if (os.path.islink(shlibDirName + splitBaseName[0] + '.' + shlibExt)):
        return shlibDirName + splitBaseName[0] + '.' + shlibExt
    elif ((len(splitBaseName) > 1) and
          (os.path.islink(shlibDirName + splitBaseName[0] + '.' + \
                          splitBaseName[1] + '.' + shlibExt))):
        return shlibDirName + splitBaseName[0] + '.' + \
               splitBaseName[1] + '.' + shlibExt
    else:
        return None
    
# Find the Fink package that provides a library

def getFinkPackage(shlib):

    # Find package with dpkg -S
    if findMethod == "dpkg":
        p = os.popen('dpkg -S %s' % shlib)
        line = p.read()
        FinkPackage = line.split(':')[0]
        p.close()

    # Find package with grep on .list files (much faster)
    elif findMethod == "grep":
        grepCmd = "grep %s %s/var/lib/dpkg/info/*.list" % (shlib, finkPrefix)
        p = os.popen(grepCmd)
        line = p.read()
        FinkPackage = os.path.basename(line.split(':')[0]).split('.')[0]
        p.close()

    else:
        print "finkdep: Unknown find method."
        sys.exit(2)
        
    return FinkPackage

# Format the output

def formatOutput(depends):

    output = ""
    line = ""
    indent = ""
    for i in range(nIndent): indent += " "
    if len(depends) > 0:
        line += indent
        for i in range(len(depends)):
            depend = depends[i]
            if len(line) + len(depend) + 1 <= maxLineLength:
                line += depend
                if i != len(depends) - 1: line += ", "
            else:
                line += "\n"
                output += line
                line = ""
                line += indent
                line += depend
                if i != len(depends) - 1 : line += ", "
        output += line + "\n"
    return output

# Get options, arguments and binary names from the command line

try:
   options, objFiles = getopt.getopt(sys.argv[1:], "hvV",
                                     ["help", "version", "verbose"])
except getopt.GetoptError:   
   sys.stdout.write("""finkdep: Unknown option.
Try 'finkdep --help' for more information.
""")
   sys.exit(2)

for opt, arg in options:

   # Print version if -v is set
   
   if opt in ("-v", "--version"):
      version()
      sys.exit()
      
   # Print help if the option -h is set or if no input 
   # file is given
         
   if opt in ("-h", "--help"):
      usage()
      sys.exit()

   # Set the verbose flag is option -V is set

   if opt in ("-V", "--verbose"):
       verbose = True

# Find the Fink packages that provides the dependencies of object files

depends = []
buildDepends = []

if len(objFiles) == 1:
    for objFile in objFiles:
        if verbose: sys.stdout.write("Searching linked librairies... ")
        shlibs = getShlibs(objFile)
        if verbose: sys.stdout.write("done.\n")
        for shlib in shlibs:
            
            # X11 libraries
            
            if shlib[:10] == '/usr/X11R6':
                depend = 'x11-shlibs'
                if depends.count(depend) == 0: depends.append(depend)
                buildDepend = 'x11-dev'
                if buildDepends.count(buildDepend) == 0: buildDepends.append(buildDepend)

            # Other system librairies

            elif shlib[:4] == '/usr':
                continue
            elif shlib[:7] == '/System':
                continue

            # General case
                
            else:

                # Search for the Fink package providing the library
                
                if verbose: sys.stdout.write("Searching for the Fink package providing %s... " % shlib)
                depend = getFinkPackage(shlib)
                if verbose: sys.stdout.write("%s\n" % depend)
                if depends.count(depend) == 0: depends.append(depend)

                # Search for the symbolic name pointing on it
                
                if verbose: sys.stdout.write("Searching for the symbolic link pointing on %s... " \
                                             % os.path.basename(shlib))
                linkShlib = getLinkShlib(shlib)
                if linkShlib: 
                    if verbose: sys.stdout.write("%s\n" % os.path.basename(linkShlib))
                    if verbose: sys.stdout.write("Searching for the Fink package providing %s... " \
                                                 % os.path.basename(linkShlib))
                    buildDepend = getFinkPackage(linkShlib)
                    if verbose: sys.stdout.write("%s\n" % buildDepend)
                    if buildDepends.count(buildDepend) == 0: buildDepends.append(buildDepend)
                else:
                    if verbose: sys.stdout.write("not found.\n")
                    sys.stderr.write("finkdep: Could not find the symbolic link " + \
                                     "pointing on %s\n" % shlib)

    # Sort and print the dependencies

    depends.sort()
    buildDepends.sort()

    sys.stdout.write("Depends: <<\n")
    if len(depends) > 0: sys.stdout.write(formatOutput(depends))
    sys.stdout.write("<<\n")

    sys.stdout.write("BuildDepends: <<\n")
    if len(depends) > 0: sys.stdout.write(formatOutput(buildDepends))
    sys.stdout.write("<<\n")
    
else:
    sys.stderr.write("""finkdep: No or more than one argument given.
Try 'finkdep --help' for more information.
""")
    sys.exit(2)
 
sys.exit()
