This file is indexed.

/usr/share/pyshared/bitten/recipe.py is in trac-bitten-slave 0.6+final-3.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2010 Edgewall Software
# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://bitten.edgewall.org/wiki/License.

"""Execution of build recipes.

This module provides various classes that can be used to process build recipes,
most importantly the `Recipe` class.
"""

import inspect
import keyword
import logging
import os
import time
try:
    set
except NameError:
    from sets import Set as set

from pkg_resources import WorkingSet
from bitten.build import BuildError, TimeoutError
from bitten.build.config import Configuration
from bitten.util import xmlio

__all__ = ['Context', 'Recipe', 'Step', 'InvalidRecipeError']
__docformat__ = 'restructuredtext en'

log = logging.getLogger('bitten.recipe')


class InvalidRecipeError(Exception):
    """Exception raised when a recipe is not valid."""


class Context(object):
    """The context in which a build is executed."""

    step = None # The current step
    generator = None # The current generator (namespace#name)

    def __init__(self, basedir, config=None, vars=None):
        """Initialize the context.
        
        :param basedir: a string containing the working directory for the build.
                        (may be a pattern for replacement ex: 'build_${build}'
        :param config: the build slave configuration
        :type config: `Configuration`
        """        
        self.config = config or Configuration()
        self.vars = vars or {}
        self.output = []
        self.basedir = os.path.realpath(self.config.interpolate(basedir,
                                                                **self.vars))
        self.vars['basedir'] = self.basedir.replace('\\', '\\\\')

    def run(self, step, namespace, name, attr):
        """Run the specified recipe command.
        
        :param step: the build step that the command belongs to
        :param namespace: the namespace URI of the command
        :param name: the local tag name of the command
        :param attr: a dictionary containing the attributes defined on the
                     command element
        """
        self.step = step

        try:
            function = None
            qname = '#'.join(filter(None, [namespace, name]))
            if namespace:
                group = 'bitten.recipe_commands'
                for entry_point in WorkingSet().iter_entry_points(group, qname):
                    function = entry_point.load()
                    break
            elif name == 'report':
                function = Context.report_file
            elif name == 'attach':
                function = Context.attach
            if not function:
                raise InvalidRecipeError('Unknown recipe command %s' % qname)

            def escape(name):
                name = name.replace('-', '_')
                if keyword.iskeyword(name) or name in __builtins__:
                    name = name + '_'
                return name
            args = dict([(escape(name),
                          self.config.interpolate(attr[name], **self.vars))
                         for name in attr])
            function_args, has_kwargs = inspect.getargspec(function)[0:3:2]
            for arg in args:
                if not (arg in function_args or has_kwargs):
                    raise InvalidRecipeError(
                            "Unsupported argument '%s' for command %s" % \
                            (arg, qname))

            self.generator = qname
            log.debug('Executing %s with arguments: %s', function, args)
            function(self, **args)

        finally:
            self.generator = None
            self.step = None

    def error(self, message):
        """Record an error message.
        
        :param message: a string containing the error message.
        """
        self.output.append((Recipe.ERROR, None, self.generator, message))

    def log(self, xml):
        """Record log output.
        
        :param xml: an XML fragment containing the log messages
        """
        self.output.append((Recipe.LOG, None, self.generator, xml))

    def report(self, category, xml):
        """Record report data.
        
        :param category: the name of category of the report
        :param xml: an XML fragment containing the report data
        """
        self.output.append((Recipe.REPORT, category, self.generator, xml))

    def report_file(self, category=None, file_=None):
        """Read report data from a file and record it.
        
        :param category: the name of the category of the report
        :param file\_: the path to the file containing the report data, relative
                       to the base directory
        """
        filename = self.resolve(file_)
        try:
            fileobj = file(filename, 'r')
            try:
                xml_elem = xmlio.Fragment()
                for child in xmlio.parse(fileobj).children():
                    child_elem = xmlio.Element(child.name, **dict([
                        (name, value) for name, value in child.attr.items()
                        if value is not None
                    ]))
                    xml_elem.append(child_elem[
                        [xmlio.Element(grandchild.name)[grandchild.gettext()]
                        for grandchild in child.children()]
                    ])
                self.output.append((Recipe.REPORT, category, None, xml_elem))
            finally:
                fileobj.close()
        except xmlio.ParseError, e:
            self.error('Failed to parse %s report at %s: %s'
                       % (category, filename, e))
        except IOError, e:
            self.error('Failed to read %s report at %s: %s'
                       % (category, filename, e))

    def attach(self, file_=None, description=None, resource=None):
        """Attach a file to the build or build configuration.
        
        :param file\_: the path to the file to attach, relative to
                       base directory.
        :param description: description saved with attachment
        :param resource: which resource to attach the file to,
                   either 'build' (default) or 'config'
        """
        # Attachments are not added as inline xml, so only adding
        # the details for later processing.
        if not file_:
            self.error('No attachment file specified.')
            return
        xml_elem = xmlio.Element('file', filename=file_,
                                description=description or '',
                                resource=resource or 'build')
        self.output.append((Recipe.ATTACH, None, None, xml_elem))

    def resolve(self, *path):
        """Return the path of a file relative to the base directory.
        
        Accepts any number of positional arguments, which are joined using the
        system path separator to form the path.
        """
        return os.path.normpath(os.path.join(self.basedir, *path))


class Step(object):
    """Represents a single step of a build recipe.

    Iterate over an object of this class to get the commands to execute, and
    their keyword arguments.
    """

    def __init__(self, elem, onerror_default):
        """Create the step.
        
        :param elem: the XML element representing the step
        :type elem: `ParsedElement`
        """
        self._elem = elem
        self.id = elem.attr['id']
        self.description = elem.attr.get('description')
        self.onerror = elem.attr.get('onerror', onerror_default)
        assert self.onerror in ('fail', 'ignore', 'continue')

    def __repr__(self):
        return '<%s %r>' % (type(self).__name__, self.id)

    def execute(self, ctxt):
        """Execute this step in the given context.
        
        :param ctxt: the build context
        :type ctxt: `Context`
        """
        last_finish = time.time()
        for child in self._elem:
            try:
                ctxt.run(self, child.namespace, child.name, child.attr)
            except (BuildError, InvalidRecipeError, TimeoutError), e:
                ctxt.error(e)
        if time.time() < last_finish + 1:
            # Add a delay to make sure steps appear in correct order
            time.sleep(1)

        errors = []
        while ctxt.output:
            type, category, generator, output = ctxt.output.pop(0)
            yield type, category, generator, output
            if type == Recipe.ERROR:
                errors.append((generator, output))
        if errors:
            for _t, error in errors:
                log.error(error)
            if self.onerror != 'ignore':
                raise BuildError("Build step '%s' failed" % self.id)
            log.warning("Continuing despite errors in step '%s'", self.id)


class Recipe(object):
    """A build recipe.
    
    Iterate over this object to get the individual build steps in the order
    they have been defined in the recipe file.
    """

    ERROR = 'error'
    LOG = 'log'
    REPORT = 'report'
    ATTACH = 'attach'

    def __init__(self, xml, basedir=os.getcwd(), config=None):
        """Create the recipe.
        
        :param xml: the XML document representing the recipe
        :type xml: `ParsedElement`
        :param basedir: the base directory for the build
        :param config: the slave configuration (optional)
        :type config: `Configuration`
        """
        assert isinstance(xml, xmlio.ParsedElement)
        vars = dict([(name, value) for name, value in xml.attr.items()
                     if not name.startswith('xmlns')])
        self.ctxt = Context(basedir, config, vars)
        self._root = xml
    	self.onerror_default = vars.get('onerror', 'fail')
        assert self.onerror_default in ('fail', 'ignore', 'continue')

    def __iter__(self):
        """Iterate over the individual steps of the recipe."""
        for child in self._root.children('step'):
            yield Step(child, self.onerror_default)

    def validate(self):
        """Validate the recipe.
        
        This method checks a number of constraints:
         - the name of the root element must be "build"
         - the only permitted child elements or the root element with the name
           "step"
         - the recipe must contain at least one step
         - step elements must have a unique "id" attribute
         - a step must contain at least one nested command
         - commands must not have nested content

        :raise InvalidRecipeError: in case any of the above contraints is
                                   violated
        """
        if self._root.name != 'build':
            raise InvalidRecipeError('Root element must be <build>')
        steps = list(self._root.children())
        if not steps:
            raise InvalidRecipeError('Recipe defines no build steps')

        step_ids = set()
        for step in steps:
            if step.name != 'step':
                raise InvalidRecipeError('Only <step> elements allowed at '
                                         'top level of recipe')
            if not step.attr.get('id'):
                raise InvalidRecipeError('Steps must have an "id" attribute')

            if step.attr['id'] in step_ids:
                raise InvalidRecipeError('Duplicate step ID "%s"' %
                                         step.attr['id'])
            step_ids.add(step.attr['id'])

            cmds = list(step.children())
            if not cmds:
                raise InvalidRecipeError('Step "%s" has no recipe commands' %
                                         step.attr['id'])
            for cmd in cmds:
                if len(list(cmd.children())):
                    raise InvalidRecipeError('Recipe command <%s> has nested '
                                             'content' % cmd.name)