#!/usr/bin/env python
# 
#   chkdep
#  
#   Copyright (c) 2007, 2010 by Miklos Vajna <vmiklos@frugalware.org>
#   Copyright (c) 2007 by Marcus Habermehl <bmh1980de@yahoo.de>
#  
#   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 subprocess, tempfile, shutil, os, stat, re, pacman, getopt, sys

from modulefinder import ModuleFinder

def usage():
	os.system("man chkdep")

getdeps_deps = []
def getdeps(root):
	i = pacman.db_getpkgcache(db)
	while i:
		pkg = pacman.void_to_PM_PKG(pacman.list_getdata(i))
		pkgname = pacman.void_to_char(pacman.pkg_getinfo(pkg, pacman.PKG_NAME))
		if pkgname == root:
			j = pacman.void_to_PM_LIST(pacman.pkg_getinfo(pkg, pacman.PKG_DEPENDS))
			while j:
				dep = pacman.void_to_char(pacman.list_getdata(j)).split("<")[0].split(">")[0].split("=")[0]
				if dep not in getdeps_deps:
					getdeps_deps.append(dep)
					getdeps(dep)
				j = pacman.list_next(j)
			break
		i = pacman.list_next(i)
	return getdeps_deps

def rmdupdeps(deps):
	global getdeps_deps
	depdeps = []
	newdeps = []
	for i in deps:
		if i not in ignorepkgs:
			if i in depdeps:
				if trace:
					print("Ignoring %s as it is already a dependency of some other dependency." % i)
				continue
			gotdeps = [x for x in getdeps(i) if x != i]
			getdeps_deps = []
			if trace:
				print("Depends for %s: %s." % (i, gotdeps))
			depdeps.extend(gotdeps)
	for i in deps:
		if i not in depdeps and i not in ignorepkgs:
			newdeps.append(i)
		elif i in depdeps and trace:
			print("Ignoring %s as it is already a dependency of some other dependency." % i)
		elif i in ignorepkgs and trace:
			print("Ignoring %s as requested." % i)
	return newdeps

def detect_owner(lib):
	global quiet
	if not len(lib):
		return
	if lib.find(fpmroot) == 0:
		return
	pkg = pacman.void_to_PM_PKG(pacman.list_getdata(pacman.pkg_getowners(lib)))
	owner = pacman.void_to_char(pacman.pkg_getinfo(pkg, pacman.PKG_NAME))
	if not owner and not quiet:
		print("WARNING: No package found containing %s!" % lib, file=sys.stderr)
	elif owner not in deps:
		if trace:
			print("%s is an owner for %s" % (owner, lib))
		deps.append(owner)

class Checks:
	def python(self, file):
		ftype = os.popen("file -b %s" % file).read().rstrip()
		if not "python script" in ftype and not file.endswith(".py"):
			return
		libs = []
		modules = []
		regex1 = r"import ([a-zA-Z0-9_\.]*)"
		regex2 = r"from ([a-zA-Z0-9_\.]*) import"
		fd = open(file, "r")
		for line in fd.readlines():
			module = str()
			if re.match(regex1, line):
				module = re.match(regex1, line).groups()[0].rstrip()
			elif re.match(regex2, line):
				module = re.match(regex2, line).groups()[0].rstrip()
			else:
				continue
			if not module:
				continue
			if not module in libs:
				modules.append(module)
		fd.close()

		for module in modules:
			mod_file = str()
			mod_repl = module.replace(".", "/")
			for path in sys.path:
				if os.path.isfile("%s/%s.py" % (path, mod_repl)):
					mod_file = "%s/%s.py" % (path, mod_repl)
					break
				elif os.path.isfile("%s/%s.so" % (path, mod_repl)):
					mod_file = "%s/%s.so" % (path, mod_repl)
					break
				elif os.path.isfile("%s/%s/__init__.py" % (path, mod_repl)):
					mod_file = "%s/%s/__init__.py" % (path, mod_repl)
					break
			libs.append(mod_file)
		return libs

	def elf(self, file):
		try:
			if not os.stat(file)[stat.ST_MODE] & stat.S_IXUSR:
				return
		except OSError:
			return
		libs = []
		sock = os.popen("ldd %s 2>%s" % (file, os.devnull))
		while True:
			i = sock.readline()
			if not i:
				break
			if i.find("=>") == -1:
				continue
			lib = re.sub(r".* => (.*) \(.*", r"\1", i.strip())
			if ' ' in lib:
				# not all the deps are installed
				lib = lib.split(' ')[0]
			if len(lib):
				if trace:
					print("'%s' links to '%s'." % (file, lib))
				libs.append(lib)
		sock.close()
		return libs

	def mono(self, file):
		dlls = []
		ret = []
		sock = os.popen("monodis --assemblyref %s" % file)

		while True:
			i = sock.readline()
			if not i:
				break
			if i.find("Name=") == -1:
				continue
			dll = i.strip().split('Name=')[1]
			if dll not in dlls:
				dlls.append(dll)
		sock.close()

		for i in dlls:
			sock = os.popen('MONO_LOG_LEVEL="debug" mono --aot %s' % i)
			while True:
				j = sock.readline()
				if not j:
					break
				if j.find(i) != -1:
					break
			sock.close()
			ret.append((re.sub(r".*'(.*)'.*", r"\1", j.strip())))
		return ret
	def perl(self, file):
		mods = []
		ret = []
		sock = open(file)
		while True:
			modules = []
			i = sock.readline()
			if not i:
				break
			if not re.match(r"^use [^ ]+::", i):
				continue
			lib = re.sub(r"^use ([^ ]+::[^ ]+)( |;).*", r"\1", i.strip())
			mods.append(lib)
		sock.close()
		for i in mods:
			sock = os.popen("perldoc -l %s" % i)
			ret.append(sock.readline().strip())
			sock.close()
		return ret

checks = Checks()

pacman.initialize("/")
db = pacman.db_register("local")
ignorepkgs = []
deps = []

dir = None
method="elf"
pkg = None
quiet = False
trace = False

try:
	opts, args = getopt.getopt(sys.argv[1:], "d:p:qn:m:tvi", ["dir=", "package=", "quiet", "ignore=", "method=", "trace", "version"])
except getopt.GetoptError:
	usage()
	sys.exit(1)

for opt, arg in opts:
	if opt in ("-d", "--dir"):
		dir = arg
	if opt in ("-p", "--package"):
		pkg = arg
	if opt in ("-q", "--quiet"):
		quiet = True
	if opt in ("-m", "--method"):
		method = arg
	if opt in ("-n", "--name"):
		ignorepkgs.append(arg)
	if opt in ("-t", "--trace"):
		trace = True
	if opt in ("-v", "--version"):
		print("chkdep %s" % __version__)
		sys.exit(0)

if not dir and not pkg:
	usage()
	sys.exit(1)

checker = getattr(checks, method)

if "FAKEROOTKEY" in list(os.environ.keys()):
	ignorepkgs.append("fakeroot")

if pkg:
	fpmroot = tempfile.mkdtemp()
	cwd = os.getcwd()
	os.chdir(fpmroot)
	if subprocess.call(["bsdtar", "xf", os.path.join(cwd, pkg)]) != 0:
		raise Exception("bsdtar failed")
	os.chdir(cwd)

if dir:
	fpmroot = dir

depfiles = []
for root, dirs, files in os.walk(fpmroot):
	for file in files:
		ret = checker(os.path.join(root, file))
		if ret:
			for i in ret:
				if i not in depfiles:
					if trace:
						print("Found %s as a new dependency file." % i)
					depfiles.append(i)
for i in depfiles:
	detect_owner(i)

if pkg:
	shutil.rmtree(fpmroot)


print("Full DEPENDS:	depends=('" + "' '".join(deps) + "')")
deps = rmdupdeps(deps)
print("Cleaned up DEPENDS:   depends=('" + "' '".join(deps) + "')")
