# Copyright (C) Rene Rivera 2002. Permission to copy, use, modify, sell and
# distribute this software is granted provided this copyright notice appears in
# all copies. This software is provided "as is" without express or implied
# warranty, and with no claim as to its suitability for any purpose.

# Represents the projet-root of a collection of projects. This maintains
# information about the root, and pointers to the project defined within
# the root. Each project-root gets it's own module to put things into. For
# instance declared constants which also get interned into each loaded project.

import modules ;

# Load the project-root file for the given directory. The directory can
# be either the project-root itself, or any subdirectory. Fails if it can't
# find the project-root. We return the project-root module.
#
rule load (
    dir # The directory to obtain the project-root for.
    )
{
    # Find the project-root.jam corresponding to this directory.
    #
    local location = [ os.path.parent
         [ os.path.make [ find-to-root $(dir) : project-root.jam ] ] ] ;

    # No project-root file found.
    #
    if ! $(location)
    {
        EXIT
            "Failed to find the project root for:" $(dir)"." "Did not"
            "find a project-root.jam file there or in any of its parent"
            "directories."
            "Please consult the documentation at 'http://www.boost.org'." ;
    }

    local module-name = project-root<$(location)> ;

    # Only bother with the rest if the project-root isn't loaded yet.
    #
    if ! [ modules.binding $(module-name) ]
    {
        # Give the new module all the rules from project-root-context
        modules.clone-rules project-root-context $(module-name) ;

        # Remember the project-root, and some info about it.
        #
        .roots += $(module-name) ;
        modules.poke $(module-name) : .module : $(module-name) ;
        modules.poke $(module-name) : .location : $(location) ;

        # Load it within a module specifically for the project root.
        # The module system handles checking for multiple includes.
        #
        modules.load $(module-name)
            : project-root.jam : [ os.path.native $(location) ] ;

    }

    # Return the module for the project.
    #
    return $(module-name) ;
}

# Print out all the project-roots.
#
rule print ( )
{
    import sequence ;
    import print ;

    print.section "Project Roots" ;
    local roots = [ sequence.insertion-sort $(.roots) ] ;
    for local root in $(roots)
    {
        $(root).print ;
    }
}

module project-root-context
{

# Module context for each project-root loaded.

# The module name of the project-root.
.module = ;

# The location of the project-root, as a path.
.location = ;

# The set of projects registered in this project-root.
.projects = ;

# The set of interned constants for this project-root.
.constants = ;

# Accessor to .module.
#
rule module-name ( )
{
    return $(.module) ;
}

# Accessor to .location.
#
rule location ( )
{
    return $(.location) ;
}

# Register a project under this project-root. This does any setup
# in the module of the project, including interning the project-root
# constants. Multiple calls on the same project are allowed and will
# not disturb the previous calls.
#
rule register-project (
    project-module # The module of the project to register.
    )
{
    if ! $(project-module) in $(.projects)
    {
        .projects += $(project-module) ;
        intern-constants $(project-module) ;
    }
    modules.poke $(project-module) : .project-root-module : $(.module) ;
}

# Declare and set a project global constant. Project global constants are
# normal variables but should not be changed. They are applied to each
# Jamfile that is loaded under it's corresponding project-root.
#
rule constant (
    name # Variable name of the constant.
    : value # Value of the constant.
    )
{
    $(name) = $(value) ;
    .constants += $(name) ;
}

# Intern the constants from this project-root into the calling context.
#
rule intern-constants (
    context ? # The module to intern into the current module.
    )
{
    local intern-module = $(context) ;
    intern-module ?= [ CALLER_MODULE ] ;
    for local c in $(*constants)
    {
        modules.poke $(intern-module) : $(c) : $($(c)) ;
    }
}

# Print out info about this project root. Calls print on the
# individual projects in this project-root.
#
rule print ( )
{
    import sequence ;
    import print ;

    # Needed to get deterministic order of output, which makes testing simpler
    local rule compare_projects ( p1 p2 )
    {
        local id1 = [ $(p1).id ] ;
        local id2 = [ $(p2).id ] ;
        if $(id1) < $(id2)
        {
            return true ;
        }
        else
        {
            if $(id1) = $(id2) && $(p1) < $(p2)
            {
                return true ;
            }
        }
    }

    print.section "'"$(.location)"'" Module for project-root is "'"$(.module)"'" ;
    if $(.constants)
    {
        print.section Constants ;
        print.list-start ;
        local constants = [ sequence.insertion-sort $(.constants) ] ;
        for local c in $(constants)
        {
            print.list-item $(c) "=" $($(c)) ;
        }
        print.list-end ;
    }
    if $(.projects)
    {
        print.section Projects ;
        local projects = [ sequence.insertion-sort $(.projects) : compare_projects ] ;
        for local p in $(projects)
        {
            $(p).print ;
        }
    }
}

}
