/*++

Copyright (c) 2017 Minoca Corp.

    This file is licensed under the terms of the GNU General Public License
    version 3. Alternative licensing terms are available. Contact
    info@minocacorp.com for details. See the LICENSE file at the root of this
    project for complete licensing information.

Module Name:

    make.ck

Abstract:

    This module performs Makefile output generation for the Minoca build
    generator.

Author:

    Evan Green 1-Feb-2016

Environment:

    Chalk

--*/

//
// ------------------------------------------------------------------- Includes
//

from io import open;

//
// --------------------------------------------------------------------- Macros
//

//
// ---------------------------------------------------------------- Definitions
//

//
// ------------------------------------------------------ Data Type Definitions
//

//
// ----------------------------------------------- Internal Function Prototypes
//

function
_makePrintConfig (
    file,
    config,
    variables,
    target
    );

function
_makePrintDefaultTargets (
    file,
    config,
    targets
    );

function
_makePrintInputs (
    file,
    config,
    inputs
    );

function
_makePrintBuildDirectoriesTarget (
    file,
    config,
    directories
    );

function
_makePrintMakefileTarget (
    file,
    config,
    scripts
    );

function
_makePrintRebuildCommand (
    file,
    config
    );

function
_makePrintTargetFile (
    file,
    config,
    target
    );

function
_makePrintPath (
    file,
    config,
    path
    );

function
_makePrintConfigValue (
    file,
    value
    );

function
_makePrintWithVariableConversion (
    file,
    value
    );

//
// -------------------------------------------------------------------- Globals
//

var _makeLineContinuation = " \\\n        ";

//
// ------------------------------------------------------------------ Functions
//

class MakeVariableTransformer {
    function
    __get (
        key
        )

    /*++

    Routine Description:

        This routine performs a "get" operation on this fake dictionary, which
        really returns a dressed up version of the key.

    Arguments:

        key - Supplies the key to get.

    Return Value:

        Retuns the variable specified for a Makefile.

    --*/

    {

        if ((key == "IN") || (key == "in")) {
            return "$+";

        } else if ((key == "OUT") || (key == "out")) {
            return "$@";
        }

        return "$(%s)" % key;
    }

    function
    __slice (
        key
        )

    /*++

    Routine Description:

        This routine executes the slice operator, which is called when square
        brackets are used.

    Arguments:

        key - Supplies the key to get.

    Return Value:

        Retuns the variable specified for a Makefile.

    --*/

    {

        return this.__get(key);
    }
}

function
buildMakefile (
    config,
    entries
    )

/*++

Routine Description:

    This routine creates a Makefile from the given Minoca build generator
    specification.

Arguments:

    config - Supplies the application configuration

    entries - Supplies a dictionary containing the tools, targets, pools, and
        build directories.

Return Value:

    0 on success.

    1 on failure.

--*/

{

    var file;
    var makefilePath;
    var module;
    var targetsList = entries.targetsList;
    var tool;
    var tools;

    if (config.output_file) {
        makefilePath = config.output_file;

    } else {
        makefilePath = config.output + "/Makefile";
        makefilePath =
               makefilePath.template({config.input_variable: config.input}, 0);
    }

    if (config.verbose) {
        Core.print("Creating %s" % makefilePath);
    }

    file = open(makefilePath, "wb");

    //
    // TODO: Put the current time in the file.
    //

    file.write("# Makefile automatically generated by Minoca mingen\n");
    file.write("# Define high level variables\n");
    if (config.unanchored) {
        file.write("%s := %s # Fill this in\n" % [config.input_variable, ""]);
        file.write("%s := %s # Fill this in\n" % [config.output_variable, ""]);

    } else {
        file.write("%s := %s\n" % [config.input_variable, config.input]);
        file.write("%s := %s\n" % [config.output_variable, config.output]);
    }

    _makePrintConfig(file, config, config.vars, null);
    file.write("\n# Define tools:\n");
    tools = entries.tools;
    for (tool in tools) {
        tool = tools[tool];
        if (!tool.active) {
            continue;
        }

        file.write("TOOL_%s = " % tool.name);
        if (tool.description) {
            file.write("@echo ");
            _makePrintWithVariableConversion(file, tool.description);
            file.write(" ; \\\n    ");
        }

        _makePrintWithVariableConversion(file, tool.command);
        file.write("\n\n");
    }

    _makePrintDefaultTargets(file, config, targetsList);

    //
    // Loop over every target. Targets are bunched together by module due to
    // the way they're added to the list.
    //

    for (target in targetsList) {
        if (!target.active) {
            continue;
        }

        if (target.module != module) {
            module = target.module;
            if (module == "") {
                file.write("# Define root targets\n");

            } else {
                file.write("# Define targets for %s\n" % module);
            }
        }

        tool = target.get("tool");
        if (tool == "phony") {
            file.write(".PHONY: ");
            _makePrintTargetFile(file, config, target);
            file.write("\n");
        }

        //
        // Add the configs for this target, then the target itself, then its
        // inputs.
        //

        _makePrintConfig(file, config, target.config, target);
        _makePrintTargetFile(file, config, target);
        file.write(": ");
        _makePrintInputs(file, config, target.inputs);

        //
        // Add the implicit and order-only inputs if there are any. Make
        // doesn't have the concept of implicit inputs, where these are
        // normal prerequisites that don't show up on the command line.
        // So lump them in with order-only prerequisites. This might cause
        // some situations where make decides not to rebuild targets it
        // should, but it's the best that can be done for these types.
        //

        if ((target.implicit.length() + target.orderonly.length()) != 0) {
            file.write(" | " + _makeLineContinuation);
            _makePrintInputs(file, config, target.implicit);
            if ((target.implicit.length() != 0) &&
                (target.orderonly.length() != 0)) {

                file.write(_makeLineContinuation);
            }

            _makePrintInputs(file, config, target.orderonly);
        }

        //
        // Use the tool as the recipe for the target.
        //

        if ((tool != null) && (tool != "phony")) {
            file.write("\n\t$(TOOL_%s)" % tool);
        }

        file.write("\n\n");
    }

    _makePrintBuildDirectoriesTarget(file, config, entries.buildDirectories);
    if (config.generator) {
        _makePrintMakefileTarget(file, config, entries.scripts);
    }

    file.close();
    return;
}

//
// --------------------------------------------------------- Internal Functions
//

function
_makePrintConfig (
    file,
    config,
    variables,
    target
    )

/*++

Routine Description:

    This routine writes a target's variables dictionary to the output file.

Arguments:

    file - Supplies the output file being written.

    config - Supplies the application configuration

    variables - Supplies the variables to print.

    target - Supplies an optional target being printed.

Return Value:

    None.

--*/

{

    for (key in variables) {
        if (target) {
            _makePrintTargetFile(file, config, target);
            file.write(": ");
        }

        file.write("%s := " % key.__str());
        _makePrintConfigValue(file, variables[key]);
        file.write("\n");
    }

    return;
}

function
_makePrintDefaultTargets (
    file,
    config,
    targets
    )

/*++

Routine Description:

    This routine prints any targets marked as default.

Arguments:

    file - Supplies the file being printed to.

    config - Supplies the application configuration

    targets - Supplies the set of targets.

Return Value:

    None.

--*/

{

    var printedBanner = false;

    for (target in targets) {
        if ((target.active) && (target.get("default"))) {
            if (!printedBanner) {
                file.write("# Default target\n");
                printedBanner = true;
            }

            _makePrintTargetFile(file, config, target);
            file.write(":\n");
        }
    }

    if (printedBanner) {
        file.write("\n");
    }

    return;
}

function
_makePrintInputs (
    file,
    config,
    inputs
    )

/*++

Routine Description:

    This routine prints one of the inputs list for a target.

Arguments:

    file - Supplies the file being printed to.

    config - Supplies the application configuration

    inputs - Supplies the list of inputs to print.

Return Value:

    None.

--*/

{

    var index;
    var input;
    var length;

    length = inputs.length();
    for (index = 0; index < length; index += 1) {
        input = inputs[index];
        if (input is String) {
            _makePrintPath(file, config, input);

        } else {
            _makePrintTargetFile(file, config, input);
        }

        if (index != length - 1) {
            file.write(_makeLineContinuation);
        }
    }

    return;
}

function
_makePrintBuildDirectoriesTarget (
    file,
    config,
    directories
    )

/*++

Routine Description:

    This routine emits the built in target that ensures the directories for
    all build files exist.

Arguments:

    file - Supplies the file being printed to.

    config - Supplies the application configuration

    directories - Supplies the list of build directories.

Return Value:

    None.

--*/

{

    var first = true;

    file.write("# Built-in build directories target.\n");
    file.write(".builddirs:\n");
    for (directory in directories) {
        file.write("\t@echo \"");
        _makePrintPath(file, config, directory);
        if (first) {
            file.write("\" > ");
            first = false;

        } else {
            file.write("\" >> ");
        }

        file.write(".builddirs\n");
        file.write("\tmkdir -p \"");
        _makePrintPath(file, config, directory);
        file.write("\"\n");
    }

    file.write("\n");
}

function
_makePrintMakefileTarget (
    file,
    config,
    scripts
    )

/*++

Routine Description:

    This routine emits the built in target that rebuilds the Makefile itself
    based on the source scripts.

Arguments:

    file - Supplies the file being printed to.

    config - Supplies the application configuration

    scripts - Supplies a pointer to the list of scripts.

Return Value:

    None.

--*/

{

    var outputFile = config.output_file;

    if (!outputFile) {
        outputFile = "Makefile";
    }

    file.write("# Built-in Makefile target.\n%s: " % outputFile);
    for (script in scripts) {
        file.write(_makeLineContinuation);
        file.write("$(%s)/%s" % [config.input_variable, script]);
    }

    file.write("\n\t");
    _makePrintRebuildCommand(file, config);
    file.write("\n");
    return;
}

function
_makePrintRebuildCommand (
    file,
    config
    )

/*++

Routine Description:

    This routine prints the command that can be used to rebuild the
    configuration.

Arguments:

    file - Supplies the file being printed to.

    config - Supplies the application configuration

Return Value:

    None.

--*/

{

    var argv0 = config.argv[0];
    var args;

    if (argv0.contains(".ck")) {
        argv0 = "chalk " + argv0;
    }

    //
    // Putting input, output, and format first will cause later specifications
    // of the same type to be ignored.
    //

    file.write("%s --input=\"$(%s)\" --output=\"$(%s)\" --format=%s" %
               [argv0,
                config.input_variable,
                config.output_variable,
                "make"]);

    args = config.argv[1...-1];
    for (arg in args) {
        file.write(" " + arg);
    }

    return;
}

function
_makePrintTargetFile (
    file,
    config,
    target
    )

/*++

Routine Description:

    This routine writes a target's output file name to the output file.

Arguments:

    file - Supplies the output file being written.

    config - Supplies the application configuration

    target - Supplies an optional target being printed.

Return Value:

    None.

--*/

{

    var tool = target.get("tool");;

    if ((tool != null) && (tool == "phony")) {
        _makePrintWithVariableConversion(file, target.output);
        return;
    }

    _makePrintPath(file, config, target.output);
    return;
}

function
_makePrintPath (
    file,
    config,
    path
    )

/*++

Routine Description:

    This routine prints a path.

Arguments:

    file - Supplies the output file being written.

    config - Supplies the application configuration

    path - Supplies the path to print.

Return Value:

    None.

--*/

{

    _makePrintWithVariableConversion(file, path);
    return;
}

function
_makePrintConfigValue (
    file,
    value
    )

/*++

Routine Description:

    This routine prints a configuration value.

Arguments:

    file - Supplies a pointer to the file to print to.

    value - Supplies a pointer to the object to print.

Return Value:

    0 on success.

    -1 if some entries were skipped.

--*/

{

    var index;
    var length;

    if (value is List) {
        length = value.length();
        for (index = 0; index < length; index += 1) {
            _makePrintConfigValue(file, value[index]);
            if (index != length - 1) {
                file.write(" ");
            }
        }

    } else if (value is Int) {
        file.write("%d" % value);

    } else if (value is String) {
        _makePrintWithVariableConversion(file, value);
    }

    return;
}

function
_makePrintWithVariableConversion (
    file,
    value
    )

/*++

Routine Description:

    This routine prints a string to the output file, converting variable
    expressions into proper make format.

Arguments:

    file - Supplies a pointer to the file to print to.

    value - Supplies the value to convert.

Return Value:

    None.

--*/

{

    var result;

    try {

        //
        // Pre-escape all literal dollar signs.
        //

        value = value.replace("$$", "$$$$", -1);
        result = value.template(MakeVariableTransformer(), false);

    } except ValueError {
        Core.raise(ValueError("Error transforming string: \"%s\"" % value));
    }

    file.write(result);
    return;
}

