"""
Executes a set of implementations as a program.
"""

# Copyright (C) 2006, Thomas Leonard
# See the README file for details, or visit http://0install.net.

import os, sys
from logging import debug, info

from zeroinstall.injector.model import Interface, SafeException, EnvironmentBinding, DistributionImplementation, ZeroInstallImplementation
from zeroinstall.injector.iface_cache import iface_cache

def do_env_binding(binding, path):
	os.environ[binding.name] = binding.get_value(path,
					os.environ.get(binding.name, None))
	info("%s=%s", binding.name, os.environ[binding.name])

def execute(policy, prog_args, dry_run = False, main = None, wrapper = None):
	"""Execute program. On success, doesn't return. On failure, raises an Exception.
	Returns normally only for a successful dry run.
	
	@precondition: C{policy.ready and policy.get_uncached_implementations() == []}
	"""
	iface = iface_cache.get_interface(policy.root)
		
	for needed_iface in policy.implementation:
		impl = policy.implementation[needed_iface]
		assert impl
		for dep in impl.requires:
			dep_iface = iface_cache.get_interface(dep.interface)
			for b in dep.bindings:
				if isinstance(b, EnvironmentBinding):
					dep_impl = policy.get_implementation(dep_iface)
					if isinstance(dep_impl, ZeroInstallImplementation):
						do_env_binding(b, _get_implementation_path(dep_impl.id))
					else:
						debug("Implementation %s is native; no binding needed", dep_impl)
	
	root_impl = policy.get_implementation(iface)
	_execute(root_impl, prog_args, dry_run, main, wrapper)

def _get_implementation_path(id):
	if id.startswith('/'): return id
	return iface_cache.stores.lookup(id)

def execute_selections(selections, prog_args, dry_run = False, main = None, wrapper = None):
	"""Execute program. On success, doesn't return. On failure, raises an Exception.
	Returns normally only for a successful dry run.
	
	@since: 0.27
	@precondition: All implementations are in the cache.
	"""
	sels = selections.selections
	for selection in sels.values():
		for dep in selection.dependencies:
			for b in dep.bindings:
				if isinstance(b, EnvironmentBinding):
					dep_impl = sels[dep.interface]
					if dep_impl.id.startswith('package:'):
						debug("Implementation %s is native; no binding needed", dep_impl)
					else:
						do_env_binding(b, _get_implementation_path(dep_impl.id))
	
	root_impl = sels[selections.interface]
	_execute(root_impl, prog_args, dry_run, main, wrapper)

def test_selections(selections, prog_args, dry_run, main, wrapper = None):
	"""Run the program in a child process, collecting stdout and stderr.
	@return: the output produced by the process
	@since: 0.27
	"""
	args = []
	import tempfile
	output = tempfile.TemporaryFile(prefix = '0launch-test')
	try:
		child = os.fork()
		if child == 0:
			# We are the child
			try:
				try:
					os.dup2(output.fileno(), 1)
					os.dup2(output.fileno(), 2)
					execute_selections(selections, prog_args, dry_run, main)
				except:
					import traceback
					traceback.print_exc()
			finally:
				sys.stdout.flush()
				sys.stderr.flush()
				os._exit(1)

		info("Waiting for test process to finish...")

		pid, status = os.waitpid(child, 0)
		assert pid == child

		output.seek(0)
		results = output.read()
		if status != 0:
			results += "Error from child process: exit code = %d" % status
	finally:
		output.close()
	
	return results

def _execute(root_impl, prog_args, dry_run, main, wrapper):
	assert root_impl is not None

	if root_impl.id.startswith('package:'):
		main = main or root_impl.main
		prog_path = main
	else:
		if main is None:
			main = root_impl.main
		elif main.startswith('/'):
			main = main[1:]
		elif root_impl.main:
			main = os.path.join(os.path.dirname(root_impl.main), main)
		if main:
			prog_path = os.path.join(_get_implementation_path(root_impl.id), main)

	if main is None:
		raise SafeException("Implementation '%s' cannot be executed directly; it is just a library "
				    "to be used by other programs (or missing 'main' attribute)" %
				    root_impl)

	if not os.path.exists(prog_path):
		raise SafeException("File '%s' does not exist.\n"
				"(implementation '%s' + program '%s')" %
				(prog_path, root_impl.id, main))
	if wrapper:
		prog_args = ['-c', wrapper + ' "$@"', '-', prog_path] + list(prog_args)
		prog_path = '/bin/sh'

	if dry_run:
		print "Would execute:", prog_path, ' '.join(prog_args)
	else:
		info("Executing: %s", prog_path)
		sys.stdout.flush()
		sys.stderr.flush()
		try:
			os.execl(prog_path, prog_path, *prog_args)
		except OSError, ex:
			raise SafeException("Failed to run '%s': %s" % (prog_path, str(ex)))


syntax highlighted by Code2HTML, v. 0.9.1