#!/scratchbox/tools/bin/python

# Copyright 2005  Movial
#
# 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.

# lib/show.py -- Command-line utility for outputting package dependency
# lists and trees.

import sys
import parse, util

# Python 2.3 doesn't have the built-in set and frozenset keywords.
import sets
set = sets.Set
frozenset = sets.ImmutableSet

##
## Dependencies

def print_binaries(options, params):
	"""Print a list of binaries and their run-time dependencies."""

	binaries = util.get_binaries(options, params)

	l = list(binaries)
	l.sort()

	for binary in l:
		print "%s:" % binary.package,
		for name in binary.all_depends():
			if "--no-sbox" in options or not util.toolchain_contains(name):
				print name,
		print

##
## Build-dependency tree

recursion_tag = "RECURSION"
deadend_tag   = "DEADEND"
seen_tag      = "..."

def resolve_sources(no_sbox, deadends, name, root, trail, seen):
	source_list = parse.SourceList.instance()

	for dep in source_list.by_source[name].all_build_depends():
		if no_sbox or not util.scratchbox_contains(dep):
			name = util.lookup_binary(dep).source
			if name in root:
				continue

			if name in trail:
				leaf = recursion_tag
			elif name in deadends:
				leaf = deadend_tag
			elif name in seen:
				leaf = seen_tag
			else:
				seen.add(name)

				leaf = {}
				resolve_sources(no_sbox, deadends, name, leaf, trail | frozenset([name]), seen)

			root[name] = leaf

def output_sources(root, prefix):
	pre = prefix[:]
	for source in root:
		str = pre + " - " + source
		pre = " " * len(pre)

		leaf = root[source]
		if leaf:
			if leaf is recursion_tag or leaf is deadend_tag or leaf is seen_tag:
				print str, "-", leaf
			else:
				output_sources(leaf, str)
		else:
			print str

def print_sources(options, params):
	"""Print a tree of source packages and their build-dependencies."""

	no_sbox = "--no-sbox" in options
	
	sbox_list = parse.ScratchboxList.instance()
	deadends = sbox_list.all_names

	tree = {}
	seen = set()
	sources = util.get_sources(util.get_binaries(options, params))

	pos = 0
	num = len(sources)

	for source in sources:
		pos += 1
		print >>sys.stderr, "%d/%d\r" % (pos, num),
		sys.stderr.flush()

		if source.package in deadends:
			root = deadend_tag
		else:
			root = {}
			resolve_sources(no_sbox, deadends, source.package, root, frozenset(), seen)
		tree[source] = root

	print >>sys.stderr

	for source in tree:
		leaf = tree[source]
		if leaf:
			if leaf is deadend_tag:
				print source, "-", leaf
			else:
				output_sources(leaf, source.package)
		else:
			print source

	print >>sys.stderr, "Seen packages: %d" % len(seen)

##
## Missing build-dependencies

def resolve_missing(no_sbox, deadends, binary, missing):
	source_list = parse.SourceList.instance()

	new = []
	for name in source_list.by_source[binary.source].all_build_depends():
		if no_sbox or not util.scratchbox_contains(name):
			dep = util.lookup_binary(name)
			if dep not in missing:
				missing.add(dep)
				if dep.source not in deadends:
					new.append(dep)

	for name in new:
		resolve_missing(no_sbox, deadends, name, missing)

def print_missing(options, params):
	"""Print a list of source packages that are not provided by
	   Scratchbox but are needed for building binary packages."""

	no_sbox = "--no-sbox" in options
	deadends = util.parse_deadends(options)

	missing = set()
	for binary in util.get_binaries(options, params):
		if binary.source not in deadends:
			resolve_missing(no_sbox, deadends, binary, missing)

	lines = []
	for binary in missing:
		lines.append("%s/%s" % (binary.source, binary.package))

	lines.sort()

	for line in lines:
		print line

##
## Main

actions = {
	"binaries": (print_binaries, "[--no-deps] [--no-sbox] <packages>"),
	"sources":  (print_sources,  "[--no-sbox] [--no-deps] [--deadends=<packages>] <packages>"),
	"missing":  (print_missing,  "[--no-deps] [--no-sbox] <packages>")
}

def usage():
	print >>sys.stderr, "Usage:", sys.argv[0], "<action> [<options>] [<parameters>]"
	print >>sys.stderr

	print >>sys.stderr, "Actions:"
	for action in actions:
		func, sig = actions[action]
		print >>sys.stderr, "    %s\t%s" % (action, sig)
	print >>sys.stderr

	sys.exit(1)

def main(args):
	if len(args) < 1:
		usage()

	action = args[0]
	if action == "-h" or action.endswith("help"):
		usage()

	if action not in actions:
		usage()

	func, sig = actions.get(action)

	options = []
	params = []

	for arg in args[1:]:
		if arg[0] == "-":
			options.append(arg)
		else:
			params.append(arg)

	try:
		func(options, params)

	except KeyboardInterrupt:
		sys.exit(1)

if __name__ == "__main__":
	main(sys.argv[1:])

