# Part of the A-A-P recipe executive: Read the toplevel recipe and process it

# 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

import os
import os.path
import string
import sys

import Args
from Work import Work
from Util import *
from Error import *
import Global
from ParsePos import ParsePos
from Process import Process
from RecPos import RecPos
from Message import *
from Dictlist import list2str


first_target = 1	# becomes zero when the first target has been found
recipe_active = {}	# recipes that are currently being read


def goto_dir(recdict, dir):
    """Change to directory "dir" and give a message about it."""
    if dir != os.getcwd():
	# Note: This message is not translated, so that a parser for the
	# messages isn't confused by various languages.
	msg_changedir(recdict, dir)
	os.chdir(dir)

def recipe_dir(recdict, name):
    """When the directory of the recipe "name" is not the current directory,
       print the name of the directory and make it the current one.
       "name" must be an absolute path name.
       Return the recipe name relative to the new directory."""
    dir, tail = os.path.split(name)
    goto_dir(recdict, dir)
    return tail


def read_recipe_dir(dirname, recdict):
    """Check directory "dirname" for recipes and read them all."""
    from glob import glob
    for f in glob(os.path.join(dirname, "*.aap")):
	read_recipe([], f, recdict)


def read_recipe(rpstack, sname, recdict, optional = 0, reread = 0):
    """
    Read a recipe, turn it into Python and execute the result.
    This will fill the "recdict" dictionary with the stuff found in the recipe.
    Included and child recipes will be loaded recursively.
    This doesn't execute the build commands in the recipe(s) yet.
    """

    name = full_fname(sname)
    if not reread and recipe_active.has_key(name):
	msg_warning(recdict,
			 _('Skipping recipe already being read: "%s"') % sname)
    else:
	try:
	    file = open(name, "r")
	except:
	    if optional:
		msg_extra(recdict, _('Recipe cannot be read: "%s"') % sname)
		return
	    raise UserError, _('Cannot open "%s" for reading.') % sname

	if reread:
	    msg_extra(recdict, _('Restart reading recipe "') + sname + '"')
	else:
	    msg_extra(recdict, _('Reading recipe "') + sname + '"')

	recipe_active[name] = 1
	rpstack.append(RecPos(name))

	# create an object to contain the file position
	fp = ParsePos(rpstack, file = file)

	#
	# Parse and execute the recipe.
	# Process() also closes the file.
	#
	try:
	    Process(fp, recdict)
	    del recipe_active[name]
	    msg_extra(recdict, _('Finished reading recipe "') + sname + '"')
	except OriginUpdate:
	    # Encountered a ":fetch" command that updated the recipe file and
	    # executed the new one, then thows OriginUpdate to cancel executing
	    # the old one.
	    pass

	del rpstack[-1]


def doread_init(work, recdict):
    """Create the empty "work" and recdict.
       Read A-A-P, system and user default recipes."""
    dir, tail = os.path.split(__file__)
    read_recipe([], os.path.join(dir, "default.aap"), recdict, optional = 1)
    for dir in default_dirs():
	read_recipe_dir(os.path.join(dir, "startup"), recdict)

    recdict["targetarg"] = list2str(Global.cmd_args.targets)

    # Mark all dependencies defined here as startup ones.
    for dep in work.dependencies:
	dep.startup = 1


def doread(find_recipe):
    """
    Read the main recipe and process it (but don't execute the build rules).
    This will fill a Work object with the stuff found in the recipe.
    When "find_recipe" is zero, don't search for a "main.aap" recipe to use.
    """

    # Create the empty "work" and recdict.
    work = Work()
    recdict = work.recdict

    # read A-A-P, system and user default recipes
    doread_init(work, recdict)

    #
    # Decide which recipe to execute.
    #
    if Global.cmd_args.has_option("recipe"):
	# Use the recipe specified with "-f recipe"
	if Global.cmd_args.has_option("search-up"):
	    msg_warning(recdict,
			     _("Recipe argument overrules --search-up option"))
	recipe = os.path.abspath(Global.cmd_args.options.get("recipe"))
    elif find_recipe:
	if Global.cmd_args.has_option("search-up"):
	    # Search the directory tree upwards for a "main.aap" recipe.
	    dir = os.getcwd()
	    while dir:
		recipe = os.path.join(dir, "main.aap")
		if os.path.isfile(recipe):
		    break
		dir, tail = os.path.split(dir)
		if not tail:
		    dir = ''
	    if not dir:
		raise UserError, _("Cannot find a main.aap file.")
	else:
	    # Use the default recipe "main.aap".
	    recipe = os.path.abspath("main.aap")
    else:
	recipe = None

    if find_recipe or recipe:
	if not os.path.isfile(recipe):
	    raise UserError, _('Recipe file "%s" not found') % recipe

	# Go to the directory of the recipe.  We never return to the current
	# dir.
	recipe = recipe_dir(recdict, recipe)
	work.top_recipe = recipe

	post_cd_init(recdict, work)

	# Read and parse the recipe, results are added to "recdict".
	read_recipe([], recipe, recdict)
    else:
	# Use the current directory.
	post_cd_init(recdict, work)

    return work


def post_cd_init(recdict, work):
    """Inits that can only be done when the top directory has been set."""
    # Need an absolute path to avoid ":cd" changes the meaning.
    recdict["PKGDIR"] = os.path.abspath("pack")

    # Open the message log here.
    msg_startlog(recdict)

    # Remember the top directory.
    work.top_dir = os.getcwd()

    # Find Python modules in the directory of the recipe.
    sys.path.append(work.top_dir)


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