#!/usr/bin/python

# get-libs: figure out which libraries a program needs to run, then install
# them from apt.

import argparse
import sys
import os
import subprocess
import re

def existing_path(path):
	if not os.path.exists(path):
		raise argparse.ArgumentError("{0} does not exist".format(path))
	return path

def which(program):
	import os
	def is_executable(path):
		return os.path.isfile(path) and os.access(path, os.X_OK)

	path, name = os.path.split(program)
	if path:
		if is_executable(program):
			return program
	else:
		for path in os.environ["PATH"].split(os.pathsep):
			path = path.strip('"')
			candidate = os.path.join(path, name)
			if is_executable(candidate):
				return candidate

	return None

parser = argparse.ArgumentParser(description="Library search and installation tool")
parser.add_argument("--ldd-path", type=existing_path, default=which("ldd"), help="path to ldd")
parser.add_argument("--apt-file-path", type=existing_path, default=which("apt-file"), help="path to apt-file")
parser.add_argument("--file-path", type=existing_path, default=which("file"), help="path to the file(1) utility")
parser.add_argument("--lib-path", "-l", type=existing_path, action="append", default=[], help="additional paths to libraries")
parser.add_argument("executable", type=existing_path, help="target executable file")
args = parser.parse_args()

if not args.ldd_path:
	print >> sys.stderr, "This utility requires that ldd be installed."
	sys.exit(1)

if not args.apt_file_path:
	print >> sys.stderr, "This utility requires that apt-file be installed."
	sys.exit(1)

if not args.file_path:
	print >> sys.stderr, "This utility requires that file(1) be installed."
	sys.exit(1)

def checkinfo(info, regex):
	pattern = re.compile(regex)
	for item in info:
		m = pattern.match(item)
		if m:
			return m
	return None

fileinfo = subprocess.check_output([args.file_path, args.executable]).split(': ')[1].split(', ')
if not checkinfo(fileinfo, "^ELF.*(executable|shared object)$"):
	print "{0} doesn't appear to be a ELF executable or shared object.".format(args.executable)
	sys.exit(2)

if not checkinfo(fileinfo, "^dynamically linked"):
	print "{0} doesn't appear to be dynamically linked.".format(args.executable)
	sys.exit(2)

archname = "(unknown)"
archsuffix = ""
if checkinfo(fileinfo, "^Intel 80386$"):
	archname = "32-bit"
	archsuffix = ":i386"

if checkinfo(fileinfo, "^x86-64$"):
	archname = "64-bit"
	archsuffix = ":amd64"

lddinfo = filter(None, re.split('[\n\t]+', subprocess.check_output([args.ldd_path, args.executable])))
if checkinfo(lddinfo, "not a dynamic executable"):
	print ("Your system doesn't have a version of ldd that can inspect {0}. Please "
			"install a {1}-compatible ldd (e.g. apt-get install libc6{2}) and re-run "
			"this utility.".format(args.executable, archname, archsuffix))
	sys.exit(3)

missinglibregex = re.compile(r"^(.*) => not found$")
missinglibs = [m.group(1) for m in [missinglibregex.match(info) for info in lddinfo] if m]
if not missinglibs:
	print "No additional libraries are required to execute {0}.".format(args.executable)
	print ("Additional libraries may be required for any shared objects loaded "
			"by {0}.".format(args.executable))
	sys.exit(0)
#print "The following missing libraries are required for {0}: {1}".format(args.executable, ', '.join(missinglibs))

candidates = {}

def search_for_library(paths, lib):
	for libpath in paths:
		for root, dirs, files in os.walk(libpath):
			if lib in files:
				return os.path.join(root, lib)
	return None

for lib in missinglibs:
	if search_for_library(args.lib_path, lib):
		print "Found {0} in a provided lib-path.".format(lib)
		continue
	candidates[lib] = []
	for line in subprocess.check_output([args.apt_file_path, 'search', lib]).split('\n'):
		if not line:
			continue
		line = line.split(': ')
		if len(line) < 2:
			continue
		dirname, filename = os.path.split(line[1])
		if filename == lib:
			candidates[lib].append(line[0])

maxlen = max([len(x) for x in candidates.keys()])
for library in candidates.keys():
	print "{1:{0}s}  : {2}".format(maxlen, library, " ".join(candidates[library]))
