# Copyright 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)

# Declares main target 'generate' used to produce targets by calling a
# user-provided rule that takes and produces virtual targets.

import "class" : new ;
import errors ;
import feature ;
import param ;
import project ;
import property ;
import property-set ;
import targets ;
import regex ;


feature.feature generating-rule : : free ;


class generated-target-class : basic-target
{
    import errors ;
    import indirect ;
    import virtual-target ;

    rule __init__ ( name : project : sources * : requirements *
        : default-build * : usage-requirements * )
    {
        basic-target.__init__ $(name) : $(project) : $(sources)
            : $(requirements) : $(default-build) : $(usage-requirements) ;

        if ! [ $(self.requirements).get <generating-rule> ]
        {
            errors.user-error "The generate rule requires the <generating-rule>"
                "property to be set" ;
        }
    }

    rule construct ( name : sources * : property-set )
    {
        local result ;
        local gr = [ $(property-set).get <generating-rule> ] ;

        # FIXME: this is a copy-paste from virtual-target.jam. We should add a
        # utility rule to call a rule like this.
        local rule-name = [ MATCH ^@(.*) : $(gr) ] ;
        if $(rule-name)
        {
            if $(gr[2])
            {
                local target-name = [ full-name ] ;
                errors.user-error "Multiple <generating-rule> properties"
                    "encountered for target $(target-name)." ;
            }

            result = [ indirect.call $(rule-name) $(self.project) $(name)
                : $(property-set) : $(sources) ] ;

            if ! $(result)
            {
                ECHO "warning: Unable to construct" [ full-name ] ;
            }
        }

        local ur ;
        local targets ;

        if $(result)
        {
            if  [ class.is-a $(result[1]) : property-set ]
            {
                ur = $(result[1]) ;
                targets = $(result[2-]) ;
            }
            else
            {
                ur = [ property-set.empty ] ;
                targets = $(result) ;
            }
        }
        # FIXME: the following loop should be doable using sequence.transform or
        # some similar utility rule.
        local rt ;
        for local t in $(targets)
        {
            rt += [ virtual-target.register $(t) ] ;
        }
        return $(ur) $(rt) ;
    }
}


rule generate ( name : sources * : requirements * : default-build *
    : usage-requirements * )
{
    param.handle-named-params
        sources requirements default-build usage-requirements ;
    local project = [ project.current ] ;

    targets.main-target-alternative
        [ new generated-target-class $(name) : $(project)
            : [ targets.main-target-sources $(sources) : $(name) ]
            : [ targets.main-target-requirements $(requirements) : $(project) ]
            : [ targets.main-target-default-build $(default-build) : $(project) ]
            : [ targets.main-target-usage-requirements $(usage-requirements) : $(project) ]
        ] ;
}

IMPORT $(__name__) : generate : : generate ;
