1# Copyright Pedro Ferreira 2005.
2# Copyright Vladimir Prus 2007.
3# Distributed under the Boost
4# Software License, Version 1.0. (See accompanying
5# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
7bjam_interface = __import__('bjam')
8
9import operator
10import re
11
12import b2.build.property_set as property_set
13import b2.util
14
15class BjamAction:
16    """Class representing bjam action defined from Python."""
17
18    def __init__(self, action_name, function):
19        self.action_name = action_name
20        self.function = function
21
22    def __call__(self, targets, sources, property_set):
23
24        # Bjam actions defined from Python have only the command
25        # to execute, and no associated jam procedural code. So
26        # passing 'property_set' to it is not necessary.
27        bjam_interface.call("set-update-action", self.action_name,
28                            targets, sources, [])
29        if self.function:
30            self.function(targets, sources, property_set)
31
32class BjamNativeAction:
33    """Class representing bjam action defined by Jam code.
34
35    We still allow to associate a Python callable that will
36    be called when this action is installed on any target.
37    """
38
39    def __init__(self, action_name, function):
40        self.action_name = action_name
41        self.function = function
42
43    def __call__(self, targets, sources, property_set):
44        if self.function:
45            self.function(targets, sources, property_set)
46
47        p = []
48        if property_set:
49            p = property_set.raw()
50
51        b2.util.set_jam_action(self.action_name, targets, sources, p)
52
53action_modifiers = {"updated": 0x01,
54                    "together": 0x02,
55                    "ignore": 0x04,
56                    "quietly": 0x08,
57                    "piecemeal": 0x10,
58                    "existing": 0x20}
59
60class Engine:
61    """ The abstract interface to a build engine.
62
63    For now, the naming of targets, and special handling of some
64    target variables like SEARCH and LOCATE make this class coupled
65    to bjam engine.
66    """
67    def __init__ (self):
68        self.actions = {}
69
70    def add_dependency (self, targets, sources):
71        """Adds a dependency from 'targets' to 'sources'
72
73        Both 'targets' and 'sources' can be either list
74        of target names, or a single target name.
75        """
76        if isinstance (targets, str):
77            targets = [targets]
78        if isinstance (sources, str):
79            sources = [sources]
80
81        for target in targets:
82            for source in sources:
83                self.do_add_dependency (target, source)
84
85    def get_target_variable(self, targets, variable):
86        """Gets the value of `variable` on set on the first target in `targets`.
87
88        Args:
89            targets (str or list): one or more targets to get the variable from.
90            variable (str): the name of the variable
91
92        Returns:
93             the value of `variable` set on `targets` (list)
94
95        Example:
96
97            >>> ENGINE = get_manager().engine()
98            >>> ENGINE.set_target_variable(targets, 'MY-VAR', 'Hello World')
99            >>> ENGINE.get_target_variable(targets, 'MY-VAR')
100            ['Hello World']
101
102        Equivalent Jam code:
103
104            MY-VAR on $(targets) = "Hello World" ;
105            echo [ on $(targets) return $(MY-VAR) ] ;
106            "Hello World"
107        """
108        return bjam_interface.call('get-target-variable', targets, variable)
109
110    def set_target_variable (self, targets, variable, value, append=0):
111        """ Sets a target variable.
112
113        The 'variable' will be available to bjam when it decides
114        where to generate targets, and will also be available to
115        updating rule for that 'taret'.
116        """
117        if isinstance (targets, str):
118            targets = [targets]
119
120        for target in targets:
121            self.do_set_target_variable (target, variable, value, append)
122
123    def set_update_action (self, action_name, targets, sources, properties=property_set.empty()):
124        """ Binds a target to the corresponding update action.
125            If target needs to be updated, the action registered
126            with action_name will be used.
127            The 'action_name' must be previously registered by
128            either 'register_action' or 'register_bjam_action'
129            method.
130        """
131        assert(isinstance(properties, property_set.PropertySet))
132        if isinstance (targets, str):
133            targets = [targets]
134        self.do_set_update_action (action_name, targets, sources, properties)
135
136    def register_action (self, action_name, command, bound_list = [], flags = [],
137                         function = None):
138        """Creates a new build engine action.
139
140        Creates on bjam side an action named 'action_name', with
141        'command' as the command to be executed, 'bound_variables'
142        naming the list of variables bound when the command is executed
143        and specified flag.
144        If 'function' is not None, it should be a callable taking three
145        parameters:
146            - targets
147            - sources
148            - instance of the property_set class
149        This function will be called by set_update_action, and can
150        set additional target variables.
151        """
152        if self.actions.has_key(action_name):
153            raise "Bjam action %s is already defined" % action_name
154
155        assert(isinstance(flags, list))
156
157        bjam_flags = reduce(operator.or_,
158                            (action_modifiers[flag] for flag in flags), 0)
159
160        # We allow command to be empty so that we can define 'action' as pure
161        # python function that would do some conditional logic and then relay
162        # to other actions.
163        assert command or function
164        if command:
165            bjam_interface.define_action(action_name, command, bound_list, bjam_flags)
166
167        self.actions[action_name] = BjamAction(action_name, function)
168
169    def register_bjam_action (self, action_name, function=None):
170        """Informs self that 'action_name' is declared in bjam.
171
172        From this point, 'action_name' is a valid argument to the
173        set_update_action method. The action_name should be callable
174        in the global module of bjam.
175        """
176
177        # We allow duplicate calls to this rule for the same
178        # action name.  This way, jamfile rules that take action names
179        # can just register them without specially checking if
180        # action is already registered.
181        if not self.actions.has_key(action_name):
182            self.actions[action_name] = BjamNativeAction(action_name, function)
183
184    # Overridables
185
186
187    def do_set_update_action (self, action_name, targets, sources, property_set):
188        action = self.actions.get(action_name)
189        if not action:
190            raise Exception("No action %s was registered" % action_name)
191        action(targets, sources, property_set)
192
193    def do_set_target_variable (self, target, variable, value, append):
194        if append:
195            bjam_interface.call("set-target-variable", target, variable, value, "true")
196        else:
197            bjam_interface.call("set-target-variable", target, variable, value)
198
199    def do_add_dependency (self, target, source):
200        bjam_interface.call("DEPENDS", target, source)
201
202
203