#! /usr/bin/env python
# Part of the A-A-P recipe executive: The main function.

# Copyright (C) 2002 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING

# Main Python code that executes an A-A-P recipe

import sys
from traceback import print_exception

# Each phase of executing a recipe is done by one module
from DoAddDef import doadddef
from DoArgs import doargs
from DoBuild import dobuild
from DoRead import doread

from Cache import dump_cache
from Error import *
from Sign import sign_write_all
from Util import *
import Global
from Message import *

# Globals
exit_status = 0		    # exit status; default is zero (success)
exit_info = None	    # exception stack when something went wrong.

def error_msg(recdict, msg):
    """Print an error message and set the exit status to one."""
    global exit_status
    msg_error(recdict, msg)
    if exit_status == 0:
	exit_status = 1

profiling = 0

def do_the_work(argv, find_recipe, commands):
    """Common function for main() and execute().
       "argv" is the list of command line arguments (excluding the program
       name).
       When "find_recipe" is non-zero, search for a recipe to load.
       When "commands" is None, execute the specified or default target(s).
       When "commands" is empty, do nothing.
       When "commands" is a non-empty string, execute these commands.
       Returns an error message and a work object.
       exit_status is set to a non-zero value when something failed.
    """
    global exit_status, exit_info
    exit_status = 0
    exit_info = None

    # We require Python 1.5 or later.
    if sys.version[0] == '1' and int(sys.version[2]) < 5:
	exit_status = 1
	return "A-A-P requires Python version 1.5 or later.", None

    # Need to know the directory of this module.  But __file__ isn't defined
    # when executed directly, then use argv[0] instead, see below.
    if not Global.aap_rootdir:
	Global.aap_rootdir = os.path.abspath(os.path.dirname(__file__))

    # Check if the bug in pre.py of Python 2.2.1 is present.  It was
    # distributed with Red Hat 8.0, thus it happens frequently enough to
    # require this check.
    try:
	import pre
	msg = pre.sub("(a)", "\\1", "a")
    except TypeError:
	exit_status = 1
	return 'Detected an error in the Python "pre" library.\nSee "%s/README.txt" how to fix this.' % Global.aap_rootdir, None

    # Internationalisation inits: setlocale and gettext.
    i18n_init()

    #
    # Do the main work.
    #
    msg = None
    work = None

    try:
	# 1. Process the command line arguments.
	Global.cmd_args = doargs(argv)

	if Global.cmd_args.has_option("verbose"):
	    Global.cmd_args.printit()
	
	# When profiling is requested and it wasn't started yet, start all over
	# with profiling enabled.
	global profiling
	if not profiling and Global.cmd_args.has_option("profile"):
	    profiling = 1
	    import profile
	    prof = profile.Profile()
	    try:
		res = prof.runcall(do_the_work, argv, find_recipe, commands)
	    finally:
		prof.dump_stats(Global.cmd_args.options.get("profile"))
	    return res


	# 2. Read the recipe and included recipes.  Assignments and commands
	#    are executed directly, rules and dependencies are stored.
	#    "work" is a Work object with collected items from the recipe.
	work = doread(find_recipe)

	# 3. Add the default rules and dependencies
	doadddef(work)

	# 4. Build each target or execute the commands.
	if commands:
	    from ParsePos import ParsePos
	    from RecPos import RecPos
	    from Process import Process

	    fp = ParsePos([ RecPos(_('execute()'), 0) ],
						      string = commands + '\n')
	    Process(fp, work.recdict)
	elif commands is None:
	    dobuild(work)

    except NormalExit:	# planned exit
	pass
    except SystemExit, r:
	exit_status = r
	msg = _("Aborted")
    except KeyboardInterrupt:
	exit_status = 1
	msg = _("Interrupted")
    except UserError, e:
	exit_status = 1
    	msg = e.args
    except SyntaxError, e:
	exit_status = 1
	exit_info = sys.exc_info()
	msg = _("Syntax error") + str(e)
    except:
	exit_status = 1
	exit_info = sys.exc_info()
    	msg = _("Internal Error")

    if work:
	# Dump entries for the downloaded files.
	dump_cache(work.recdict)

	# Dump the sign files.
	sign_write_all(work.recdict)

	# Close any subshell for installing packages.
	from Port import close_sushell
	close_sushell(work.recdict)

    return msg, work


def main():
    """
    The main function to execute an A-A-P recipe.
    """
    msg, work = do_the_work(sys.argv[1:], 1, None)
    if msg:
	if work:
	    error_msg(work.recdict, msg)
	else:
	    error_msg(None, msg)
	if exit_info:
	    print_exception(exit_info[0], exit_info[1], exit_info[2])

    sys.exit(exit_status)


# When executed directly, call the main function.
if __name__ == '__main__':
    # We need to know the location of our modules (find ccskim there).
    try:
	Global.aap_rootdir = os.path.dirname(os.path.realpath(sys.argv[0]))
    except:
	# Doesn't have os.path.realpath(), it's new in Python 2.2
	Global.aap_rootdir = os.path.dirname(os.path.abspath(sys.argv[0]))

    # When started with a relative path and changing directories we still need
    # to be able to find our modules.
    sys.path.append(Global.aap_rootdir)

    main()


#
# Other programs may call this funtion to execute one or more recipe commands.
# For example: execute(":do view thisfile")
#
def execute(commands, argv = [], find_recipe = 0):
    """Execute recipe commands "commands".
       "argv" is a list of command line arguments.
       "find_recipe" is non-zero to find a default recipe.
       Returns an error message or None."""
    msg, work = do_the_work(argv, find_recipe, commands)

    if exit_info:
	print msg
	print_exception(exit_info[0], exit_info[1], exit_info[2])

    msg_stoplog()

    return msg


#
# Other programs may call this function to fetch a list of files.
# Uses the "main.aap" recipe, unless "argv" specifies another recipe to use.
#
def fetch(fnames, argv = []):
    """Fetch files in list "fnames".
       "argv" is a list of command line arguments.
       Will search for a default recipe if none is specified.
       Returns an error message or None."""
    from Dictlist import list2str
    return execute(":fetch %s" % list2str(fnames), argv, 1)


#
# Other programs may call this function to obtain the list of nodes and
# recdict.  It was added to be used by the IDE.
# Uses the "main.aap" recipe, unless "argv" specifies another recipe to use.
#
def get_nodelist(argv = []):
    """Obtain the information from the recipe specified with "argv" or the
       default recipe.
       Return a tuple with the list of nodes and a dictionary for the global
       variables."""
    msg, work = do_the_work(argv, 1, '')

    if msg:
	if work:
	    error_msg(work.recdict, msg)
	else:
	    error_msg(None, msg)
	if exit_info:
	    print_exception(exit_info[0], exit_info[1], exit_info[2])
    
    msg_stoplog()

    if not work:
	return [], {}
    return work.nodes.values(), work.recdict

# vim: set sw=4 sts=4 tw=79 fo+=l:
