# 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.

import sys, os
import parse, config

# Python 2.3 doesn't have the built-in set keyword.
import sets
set = sets.Set

##
## Packages

def lookup_binary(name, list_class = parse.BinaryList):
	"""Returns the Binary object matching NAME.  If one is not found,
	   it first looks into the config.virtual map."""

	binary_list = list_class.instance()

	binary = binary_list.by_binary.get(name)
	if binary:
		return binary
	else:
		realname = config.virtual.get(name)
		if not realname:
			raise Exception, "Unknown virtual package: %s" % name

		return binary_list.by_binary[realname]

def contains_virtual(name, container):
	"""Returns true if NAME is in CONTAINER.  A config.virtual lookup
	   is done first if necessary.  (Helper function used by
	   toolchain_contains and scratchbox_contains.)"""

	if name in container:
		return True
	else:
		realname = config.virtual.get(name)
		if realname:
			return realname in container
		else:
			return False

def toolchain_contains(name):
	"""Returns true if NAME (or the corresponding real package in the
	   case of a virtual package) is included in the Scratchbox
	   toolchain."""

	sbox_list = parse.ScratchboxList.instance()
	return contains_virtual(name, sbox_list.toolchain_names)

def scratchbox_contains(name):
	"""Returns true if NAME (or the corresponding real package in the
	   case of a virtual package) is included in the Scratchbox
	   toolchain or is a build tool provided by Scratchbox."""

	sbox_list = parse.ScratchboxList.instance()
	return contains_virtual(name, sbox_list.all_names)

def remove_toolchain_binaries(original):
	"""Returns a copy of the ORIGINAL set with packages provided by
	   Scratchbox's toolchain removed."""

	stripped = set()
	for binary in original:
		if not toolchain_contains(binary.package):
			stripped.add(binary)
	return stripped

def add_depends(binaries):
	"""Adds the (run-time) dependencies of the Binary objects in
	   BINARIES to BINARIES."""

	while True:
		new = set()

		for binary in binaries:
			for name in binary.all_depends():
				dep = lookup_binary(name)
				if dep not in binaries:
					new.add(dep)

		if not new:
			break

		binaries.update(new)

	return binaries

def get_binaries(options, params):
	"""Returns a list of binary packages including all of their
	   run-time dependencies.  All Essential packages and package
	   names listed in PARAMS are included.  If "--no-deps" is in
	   OPTIONS, the dependencies are not included.  If "--no-sbox" is
	   in OPTIONS, the packages in Scratchbox's toolchain are removed
	   from the list."""

	binary_list = parse.BinaryList.instance()

	binaries = set()

	if "--no-essential" not in options:
		binaries = binary_list.essential

	for name in params:
		binary = lookup_binary(name)
		binaries.add(binary)

	if "--no-deps" not in options:
		binaries = add_depends(binaries)
	if "--no-sbox" not in options:
		binaries = remove_toolchain_binaries(binaries)

	return binaries

def get_sources(binaries):
	"""Returns a list of source packages that correspond to the
	   Binary objects in BINARIES."""

	source_list = parse.SourceList.instance()

	sources = set()
	for binary in binaries:
		try:
			source = source_list.by_source[binary.source]
			sources.add(source)
		except KeyError:
			print "no source for: "+binary.source

	return sources

##
## Miscellaneous

def parse_deadends(options):
	"""Returns a list of package names that are specified in the
	   OPTIONS string vector, encoded in strings of the form
	   "--deadends=<package>,<package>,...".  (This is probably a
	   wrong place for this function, etc.)"""

	deadends = set()

	for opt in options:
		if opt.startswith("--deadends="):
			key, value = opt.split("=", 1)
			for name in value.split(","):
				deadends.add(name)

	return deadends

##
## OS

def spawn(command, args, directory = None, vars = None):
	"""Same as os.spawnvp(os.P_WAIT, COMMAND, ARGS) but change the
	   current working directory of the child process to DIRECTORY
	   and add VARS to its environment."""

	pid = os.fork()
	if pid == 0:
		if directory:
			os.chdir(directory)

		if vars:
			os.environ.update(vars)

		os.execvp(command, args)

		print >>sys.stderr, "Failed to execute", command
		os.abort()
	else:
		pid, status = os.waitpid(pid, 0)
		return status

