# Part of the A-A-P recipe executive: configure command handling

# Copyright (C) 2002-2003 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 Global
from Util import *
from Message import *
from Conftest import CheckFunc, CheckHeader, CheckType, CheckLib, CheckBuilder


class ConfContext:
    """
    Context for configure tests.  Used for the _conf scope.
    """
    def __init__(self, vardict):
        """
        The "_conf" scope is used for variables, so that things like $CC and
        $LIBS can use the value from the _conf scope.
        """
        self.vardict = vardict
        self.havedict = {}
        self.headerfilename = "confdefs.h"
        self.headerclean = 0        # set to 1 when headerfilename has been
                                    # cleared
        self.default_lang = None

    def Display(self, msg):
        msg_info(Global.globals, msg, msgm_cont)

    def Log(self, msg):
        msg_log(Global.globals, msg, msgm_cont)

    def AppendLIBS(self, lib_name_list):
        from Scope import find_recdict

        # Find the recdict where "LIBS" is defined and obtain the old value.
        # Also obtain the old value of $_conf.LIBS.
        rd = find_recdict(Global.globals, "LIBS")
        if rd:
            oldval = rd.get("LIBS")
        else:
            oldval = None
        conf_oldval = Global.globals["_conf"].data.get("LIBS")

        # Append library/libraries to the old value.
        if oldval:
            newval = oldval
        else:
            newval = ""
        for k in lib_name_list:
            if newval:
                newval = newval + " "
            newval = newval + ("-l%s" % k)

        # Set the new value in the same scope.  Also set $_conf.LIBS, so that
        # ":conf write recipe" will use the new value.
        if rd:
            rd["LIBS"] = newval
        Global.globals["_conf"].data["LIBS"] = newval

        return [rd, oldval, conf_oldval]

    def SetLIBS(self, newval):
        # Get the recdict, and old values from the list that AppendLIBS()
        # returned.
        rd = newval[0]
        oldval = newval[1]
        conf_oldval = newval[2]

        # Get the current value of $LIBS in ret_oldval and store the value from
        # newval.
        if rd:
            ret_oldval = rd.get("LIBS")
            rd["LIBS"] = oldval
        else:
            ret_oldval = None

        # Get the current value of $_conf.LIBS in ret_conf_oldval and store
        # the value from newval.
        ret_conf_oldval = Global.globals["_conf"].get("LIBS")
        if conf_oldval is None:
            del Global.globals["_conf"].data["LIBS"]
        else:
            Global.globals["_conf"].data["LIBS"] = conf_oldval

        return [rd, ret_oldval, ret_conf_oldval]


    def BuildProg(self, text, ext):
        self.Log("\n")          # add a line break after "Checking..."

        # source -> object
        src = "conftest" + ext
        trg = "conftest" + Global.globals["_no"].OBJSUF
        res = self.RunAction("compile", text, src, trg)

        if not res:
            # object -> program
            src = trg
            if ext == ".cpp":
                src = src + "{buildaction = cxx_build}"
            trg = "conftest" + Global.globals["_no"].EXESUF
            res = self.RunAction("build", None, src, trg)

        try_delete(trg)
        return res

    def CompileProg(self, text, ext):
        self.Log("\n")          # add a line break after "Checking..."

        # source -> object
        src = "conftest" + ext
        trg = "conftest" + Global.globals["_no"].OBJSUF
        res = self.RunAction("compile", text, src, trg)
        try_delete(trg)
        return res

    def RunAction(self, action, text, src, trg):
        """
        Run action "action" with source "src" and target "trg".
        When "text" is not None write it to "src".
        Afterwards "src" is always deleted.
        Returns an empty string for success, an error message for failure.
        """
        save_message = Global.globals.get("MESSAGE")
        Global.globals["MESSAGE"] = ""
        save_sys_cmd_log = Global.sys_cmd_log
        Global.sys_cmd_log = " "

        try:
            from Commands import aap_do
            if text:
                f = open(src, "w")
                f.write(text)
                f.close()
            aap_do(0, Global.globals, "%s {target = %s} %s"
                                                          % (action, trg, src))
            msg = ""

            # If there is no error but there is output, log it (e.g., for a
            # warning).
            if Global.sys_cmd_log != " ": 
                msg_log(Global.globals, Global.sys_cmd_log)

        except UserError:
            msg = ((_("Failed to %s test program:") % action)
                                               + Global.sys_cmd_log)

        if save_message is None:
            del Global.globals["MESSAGE"]
        else:
            Global.globals["MESSAGE"] = save_message
        Global.sys_cmd_log = save_sys_cmd_log

        try_delete(src)
        return msg



def init_conf_dict(confdict):
    """
    Called to init a ConfContext() object and add it to the "_conf" scope
    "confdict".
    """
    # "confdict" is used for context.vardict, so that the variables are
    # available as $_conf.VAR.
    context = ConfContext(confdict)

    # _conf.context is used to access the ConfContext object
    confdict["context"] = context

    # _conf.have is a shortcut to _conf.context.havedict
    confdict["have"] = context.havedict


def doconf(line_nr, recdict, optiondict, argdictlist):
    """
    Do the configure checks.  Implementation of the ":conf" command.
    """
    from Work import getrpstack
    from Process import recipe_error
    rpstack = getrpstack(recdict, line_nr)

    # Get the ConfContext object from the _conf scope.
    context = recdict["_conf"].context

    command = argdictlist[0]["name"]

    # Init the configure stuff when not done already.
    # TODO: Is it possible that the file is truncated with an ":execute"
    # command?
    if not context.headerclean and command != "init":
        _init_conf(context)

    if command in [ "header", "function", "type", "lib" ]:
        #
        # :conf header stdlib.h ...
        # :conf function snprintf ...
        # :conf type size_t ...
        # :conf lib iconv,iconv_open ...
        #
        if len(argdictlist) < 2:
            recipe_error(rpstack, _('":conf %s" requires at least one more argument') % command)
        found = 0
        for i in range(1, len(argdictlist)):
            testarg = argdictlist[i]["name"]
            headerarg = argdictlist[i].get("header")
            langarg = argdictlist[i].get("language")
            if not langarg:
                langarg = context.default_lang

            if command == "header":
                msg = CheckHeader(context, testarg,
                                        header = headerarg, language = langarg)
            elif command == "type":
                fallbackarg = argdictlist[i].get("fallback")
                msg = CheckType(context, testarg, fallback = fallbackarg,
                                        header = headerarg, language = langarg)
            elif command == "lib":
                call = argdictlist[i].get("call")
                comma = string.find(testarg, ",")
                if comma < 0:
                    if not call:
                        recipe_error(rpstack,
                                _('":conf lib" requires an argument in the form libname,funcname.'))
                    lib_name = testarg
                    func_name = None
                else:
                    lib_name = testarg[0:comma]
                    func_name = testarg[comma+1:]
                msg = CheckLib(context, lib_name, func_name, call = call,
                                        header = headerarg, language = langarg)
            else: # command == "function"
                msg = CheckFunc(context, testarg,
                                        header = headerarg, language = langarg)

            if not msg:
                found = 1
                if optiondict.get("oneof"):
                    break
            elif optiondict.get("required"):
                recipe_error(rpstack, _('required %s "%s" not found.')
                                                          % (command, testarg))
        if not found and optiondict.get("oneof"):
            from Dictlist import dictlist2str
            recipe_error(rpstack,
                    _('None of the %ss found for ":conf {oneof} %s"')
                    % (command,
                       dictlist2str(argdictlist, Expand(0, Expand.quote_aap))))

    elif command == "write":
        #
        # :conf write header config.h
        # :conf write recipe config.aap
        #
        from Dictlist import dictlist_expand
        dictlist_expand(argdictlist)
        if len(argdictlist) != 3:
            recipe_error(rpstack, _('":conf write" requires two arguments'))
        what = argdictlist[1]["name"]
        fname = argdictlist[2]["name"]

        if what == "header":
            # We copy confdefs.h to the target file.  This makes sure the same
            # file that was used for testing is used.
            from CopyMove import remote_copy_move
            remote_copy_move(rpstack, recdict, 1,
                             [ {"name" : context.headerfilename} ],
                             { "name" : fname }, {}, 0, errmsg = 1)
        elif what == "recipe":
            try:
                f = open(fname, "w")
            except StandardError, e:
                recipe_error(rpstack, _('Could not create recipe "%s": %s')
                                                             % (fname, str(e)))
            try:
                f.write("# Generated by Aap.  You are not supposed to edit this file.\n\n")
                for k in context.vardict.keys():
                    if not k in ["have", "context"] and context.vardict[k]:
                        f.write("%s = %s\n" % (k, context.vardict[k]))
                f.close()
            except StandardError, e:
                recipe_error(rpstack, _('Could not write to recipe "%s": %s')
                                                             % (fname, str(e)))
            else:
                msg_info(recdict, 'Written config recipe "%s"' % fname)

        else:
            recipe_error(rpstack, _('Unsupported argument for ":conf write": "%s"') % what)

    elif command == "init":
        #
        # :conf init
        #
        if len(argdictlist) > 1:
            recipe_error(rpstack, _('Too many arguments for ":conf init"'))
        _init_conf(context)

    elif command == "language":
        #
        # :conf language C++
        #
        if len(argdictlist) != 2:
            recipe_error(rpstack, _('":conf language" requires one argument'))
        context.default_lang = argdictlist[1]["name"]
        # check for a valid language
        msg = CheckBuilder(context, language = context.default_lang)
        if msg:
            msg_warning(recdict, _('Cannot compile a simple %s program: %s')
                                                 % (context.default_lang, msg))

    else:
        recipe_error(rpstack, _('Unsupported :conf argument: "%s"') % command)


def _init_conf(context):
    """
    Init the configure stuff.  This makes sure the "headerfilename" is empty.
    """
    try:
        f = open(context.headerfilename, "w+")
        f.write("/* Generated by Aap.  You are not supposed to edit this file. */\n\n")
        f.close()
    except StandardError, e:
        msg_warning(Global.globals, _("Could not make %s empty: %s")
                                        % (context.headerfilename, str(e)))
    context.headerclean = 1


def cleanup(recdict):
    """
    Cleanup after doing configure checks.
    """
    # Get the ConfContext object from the _conf scope.
    context = recdict["_conf"].context

    try_delete(context.headerfilename)

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