Ticket #87: 124.patch

File 124.patch, 127.4 KB (added by dustin, 2 years ago)

all #124 bugs in properties branch

  • buildbot/buildset.py

    Mon Apr 14 14:53:43 EDT 2008  dustin@v.igoro.us
      * #124:docs.patch
      Add properties to the documentation, along with a nontrivial reorganization
      of the scheduler section of the documentation.
    Mon Apr 14 12:06:01 EDT 2008  dustin@v.igoro.us
      * wrap-long-doc-line-2.patch
      Wrap another long line
    Mon Apr 14 12:03:44 EDT 2008  dustin@v.igoro.us
      * wrap-long-doc-line.patch
      Wrap an overly long line in the documentation.
    Sun Apr 13 16:26:53 EDT 2008  dustin@v.igoro.us
      * #124:display-properties-web-status.patch
      Display build properties in the build status page.
    Sun Apr 13 15:55:26 EDT 2008  dustin@v.igoro.us
      * #124:remove-custom-props.patch
      Remove custom properties, which are now largely redundant, and on which the
      properties interface was modeled.
    Sun Apr 13 14:42:31 EDT 2008  dustin@v.igoro.us
      * #124:schedulers-provide-properties.patch
      Make the scheduler classes actually provide properties to the buildsets
      they submit.  Triggerable schedulers also do a nice job of configurably
      propagating properties from the triggering build.
    Sat Apr 12 20:32:42 EDT 2008  dustin@v.igoro.us
      * #124:global-properties.patch
      Support global properties, defined with c['properties'] = {} in master.cfg
    Sat Apr 12 19:46:58 EDT 2008  dustin@v.igoro.us
      * #124:scheduler-properties.patch
      Arrange for properties to come down from schedulers, via BuildStep and
      BuildRequest objects.  custom_props are still present, in parallel, but
      will go eventually. Triggered builds no longer propagate custom props.
    Sat Apr 12 18:11:03 EDT 2008  dustin@v.igoro.us
      * #124:getProperty-returns-property-only.patch
      Change Property.getProperty to just return the property value, since
      having getProperty return different things on different objects is
      confusing.
    Sat Apr 12 16:58:59 EDT 2008  dustin@v.igoro.us
      * #124:properties-class.patch
      Add and use a Properties class, refactor the way properties are rendered,
      and update unit tests accordingly.
    Sat Apr 12 01:50:01 EDT 2008  dustin@v.igoro.us
      * #124:remove-customBuildProperties-unused.patch
      Remove unused and undocumented customBuildProperties config.
      This did not allow users to specify global properties; rather, it
      specified descriptions for properties that were not used anywhere in
      the codebase.
    diff -rN -u old-124/buildbot/buildset.py new-124/buildbot/buildset.py
    old new  
    1  
    21from buildbot.process import base 
    32from buildbot.status import builder 
     3from buildbot.process.properties import Properties 
    44 
    55 
    66class BuildSet: 
     
    1111    (source.changes=list).""" 
    1212 
    1313    def __init__(self, builderNames, source, reason=None, bsid=None, 
    14                  scheduler=None, custom_props=None): 
     14                 properties=None): 
    1515        """ 
    1616        @param source: a L{buildbot.sourcestamp.SourceStamp} 
    1717        """ 
     
    1919        self.source = source 
    2020        self.reason = reason 
    2121 
    22         if not custom_props: custom_props = {} 
    23         self.custom_props = custom_props 
     22        self.properties = Properties() 
     23        if properties: self.properties.updateFromProperties(properties) 
    2424 
    2525        self.stillHopeful = True 
    2626        self.status = bss = builder.BuildSetStatus(source, reason, 
    2727                                                   builderNames, bsid) 
    28         self.scheduler = scheduler 
    2928 
    3029    def waitUntilSuccess(self): 
    3130        return self.status.waitUntilSuccess() 
     
    4140        # create the requests 
    4241        for b in builders: 
    4342            req = base.BuildRequest(self.reason, self.source, b.name,  
    44                                     scheduler=self.scheduler, 
    45                                     custom_props=self.custom_props) 
     43                                    properties=self.properties) 
    4644            reqs.append((b, req)) 
    4745            self.requests.append(req) 
    4846            d = req.waitUntilFinished() 
  • buildbot/buildslave.py

    diff -rN -u old-124/buildbot/buildslave.py new-124/buildbot/buildslave.py
    old new  
    1111from buildbot.status.builder import SlaveStatus 
    1212from buildbot.status.mail import MailNotifier 
    1313from buildbot.interfaces import IBuildSlave 
     14from buildbot.process.properties import Properties 
    1415 
    1516class BuildSlave(NewCredPerspective, service.MultiService): 
    1617    """This is the master-side representative for a remote buildbot slave. 
     
    2627    implements(IBuildSlave) 
    2728 
    2829    def __init__(self, name, password, max_builds=None, 
    29                  notify_on_missing=[], missing_timeout=3600): 
     30                 notify_on_missing=[], missing_timeout=3600, 
     31                 properties={}): 
    3032        """ 
    3133        @param name: botname this machine will supply when it connects 
    3234        @param password: password this machine will supply when 
     
    3436        @param max_builds: maximum number of simultaneous builds that will 
    3537                           be run concurrently on this buildslave (the 
    3638                           default is None for no limit) 
     39        @param properties: properties that will be applied to builds run on  
     40                           this slave 
     41        @type properties: dictionary 
    3742        """ 
    3843        service.MultiService.__init__(self) 
    3944        self.slavename = name 
     
    4449        self.slave_commands = None 
    4550        self.slavebuilders = [] 
    4651        self.max_builds = max_builds 
     52 
     53        self.properties = Properties() 
     54        self.properties.update(properties, "BuildSlave") 
     55        self.properties.setProperty("slavename", name, "BuildSlave") 
     56 
    4757        self.lastMessageReceived = 0 
    4858        if isinstance(notify_on_missing, str): 
    4959            notify_on_missing = [notify_on_missing] 
  • buildbot/clients/debug.py

    diff -rN -u old-124/buildbot/clients/debug.py new-124/buildbot/clients/debug.py
    old new  
    105105            if revision == '': 
    106106                revision = None 
    107107        reason = "debugclient 'Request Build' button pushed" 
    108         custom_props = {} 
     108        properties = {} 
    109109        d = self.remote.callRemote("requestBuild", 
    110                                    name, reason, branch, revision, custom_props) 
     110                                   name, reason, branch, revision, properties) 
    111111        d.addErrback(self.err) 
    112112 
    113113    def do_ping(self, widget): 
  • buildbot/interfaces.py

    diff -rN -u old-124/buildbot/interfaces.py new-124/buildbot/interfaces.py
    old new  
    3838class IScheduler(Interface): 
    3939    """I watch for Changes in the source tree and decide when to trigger 
    4040    Builds. I create BuildSet objects and submit them to the BuildMaster. I 
    41     am a service, and the BuildMaster is always my parent.""" 
     41    am a service, and the BuildMaster is always my parent. 
     42     
     43    @ivar properties: properties to be applied to all builds started by this 
     44    scheduler 
     45    @type properties: L<buildbot.process.properties.Properties> 
     46    """ 
    4247 
    4348    def addChange(change): 
    4449        """A Change has just been dispatched by one of the ChangeSources. 
  • buildbot/master.py

    diff -rN -u old-124/buildbot/master.py new-124/buildbot/master.py
    old new  
    2727from buildbot.sourcestamp import SourceStamp 
    2828from buildbot.buildslave import BuildSlave 
    2929from buildbot import interfaces 
     30from buildbot.process.properties import Properties 
    3031 
    3132######################################## 
    3233 
     
    227228    def detached(self, mind): 
    228229        pass 
    229230 
    230     def perspective_requestBuild(self, buildername, reason, branch, revision, custom_props): 
    231         assert isinstance(custom_props, dict), \ 
    232                "custom_props must be a dict (not %r)" % (custom_props,) 
    233  
    234         # Provide default values for any custom build properties the 
    235         # client did not send. 
    236         for propertyDict in (self.master.customBuildProperties or []): 
    237             custom_props.setdefault(propertyDict['propertyName'], "") 
    238  
     231    def perspective_requestBuild(self, buildername, reason, branch, revision, properties={}): 
    239232        c = interfaces.IControl(self.master) 
    240233        bc = c.getBuilder(buildername) 
    241234        ss = SourceStamp(branch, revision) 
    242         br = BuildRequest(reason, ss, builderName=buildername, custom_props=custom_props) 
     235        properties = Properties() 
     236        properties.update(properties, "remote requestBuild") 
     237        br = BuildRequest(reason, ss, builderName=buildername, properties=properties) 
    243238        bc.requestBuild(br) 
    244239 
    245240    def perspective_pingBuilder(self, buildername): 
     
    347342    projectURL = None 
    348343    buildbotURL = None 
    349344    change_svc = None 
    350     customBuildProperties = None 
     345    properties = Properties() 
    351346 
    352347    def __init__(self, basedir, configFileName="master.cfg"): 
    353348        service.MultiService.__init__(self) 
     
    503498                      "schedulers", "builders", 
    504499                      "slavePortnum", "debugPassword", "manhole", 
    505500                      "status", "projectName", "projectURL", "buildbotURL", 
    506                       "customBuildProperties" 
     501                      "properties" 
    507502                      ) 
    508503        for k in config.keys(): 
    509504            if k not in known_keys: 
     
    531526            projectName = config.get('projectName') 
    532527            projectURL = config.get('projectURL') 
    533528            buildbotURL = config.get('buildbotURL') 
    534             customBuildProperties = config.get('customBuildProperties') 
     529            properties = config.get('properties', {}) 
    535530 
    536531        except KeyError, e: 
    537532            log.msg("config dictionary is missing a required parameter") 
     
    663658                    else: 
    664659                        locks[l.name] = l 
    665660 
     661        if not isinstance(properties, dict): 
     662            raise ValueError("c['properties'] must be a dictionary") 
     663 
    666664        # slavePortnum supposed to be a strports specification 
    667665        if type(slavePortnum) is int: 
    668666            slavePortnum = "tcp:%d" % slavePortnum 
     
    677675        self.projectName = projectName 
    678676        self.projectURL = projectURL 
    679677        self.buildbotURL = buildbotURL 
    680         self.customBuildProperties = customBuildProperties 
     678         
     679        self.properties = Properties() 
     680        self.properties.update(properties, self.configFileName) 
    681681 
    682682        # self.slaves: Disconnect any that were attached and removed from the 
    683683        # list. Update self.checker with the new list of passwords, including 
  • buildbot/process/base.py

    diff -rN -u old-124/buildbot/process/base.py new-124/buildbot/process/base.py
    old new  
    1111from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, EXCEPTION 
    1212from buildbot.status.builder import Results, BuildRequestStatus 
    1313from buildbot.status.progress import BuildProgress 
     14from buildbot.process.properties import Properties 
    1415 
    1516class BuildRequest: 
    1617    """I represent a request to a specific Builder to run a single build. 
     
    3940                  provide this, but for forced builds the user requesting the 
    4041                  build will provide a string. 
    4142 
    42     @type custom_props: dictionary. 
    43     @ivar custom_props: custom user properties. 
     43    @type properties: Properties object 
     44    @ivar properties: properties that should be applied to this build 
    4445 
    4546    @ivar status: the IBuildStatus object which tracks our status 
    4647 
     
    5253    source = None 
    5354    builder = None 
    5455    startCount = 0 # how many times we have tried to start this build 
    55     custom_props = {} 
    5656 
    5757    implements(interfaces.IBuildRequestControl) 
    5858 
    59     def __init__(self, reason, source, builderName=None, scheduler=None, custom_props=None): 
     59    def __init__(self, reason, source, builderName=None, properties=None): 
    6060        # TODO: remove the =None on builderName, it is there so I don't have 
    6161        # to change a lot of tests that create BuildRequest objects 
    6262        assert interfaces.ISourceStamp(source, None) 
    6363        self.reason = reason 
    6464        self.source = source 
    65         self.scheduler = scheduler 
    6665 
    67         if not custom_props: custom_props = {} 
    68         self.custom_props = custom_props 
    69         assert isinstance(self.custom_props, dict), \ 
    70                "custom_props must be a dict (not %r)" % (self.custom_props,) 
     66        self.properties = Properties() 
     67        if properties: 
     68            self.properties.updateFromProperties(properties) 
    7169 
    7270        self.start_watchers = [] 
    7371        self.finish_watchers = [] 
     
    9593        self.finish_watchers.append(d) 
    9694        return d 
    9795 
    98     def customProps(self): 
    99      return self.custom_props 
    100  
    10196    # these are called by the Builder 
    10297 
    10398    def requestSubmitted(self, builder): 
     
    182177        # build a source stamp 
    183178        self.source = requests[0].mergeWith(requests[1:]) 
    184179        self.reason = requests[0].mergeReasons(requests[1:]) 
    185         self.scheduler = requests[0].scheduler 
    186  
    187         # Set custom properties. 
    188         self.custom_properties = requests[0].customProps() 
    189  
    190         #self.abandoned = False 
    191180 
    192181        self.progress = None 
    193182        self.currentStep = None 
     
    207196    def getSourceStamp(self): 
    208197        return self.source 
    209198 
    210     def setProperty(self, propname, value): 
     199    def setProperty(self, propname, value, source): 
    211200        """Set a property on this build. This may only be called after the 
    212201        build has started, so that it has a BuildStatus object where the 
    213202        properties can live.""" 
    214         self.build_status.setProperty(propname, value) 
     203        self.build_status.setProperty(propname, value, source) 
    215204 
    216     def getCustomProperties(self): 
    217         return self.custom_properties 
     205    def getProperties(self): 
     206        return self.build_status.getProperties() 
    218207 
    219208    def getProperty(self, propname): 
    220         return self.build_status.properties[propname] 
     209        return self.build_status.getProperty(propname) 
    221210 
    222211    def allChanges(self): 
    223212        return self.source.changes 
     
    275264    def getSlaveName(self): 
    276265        return self.slavebuilder.slave.slavename 
    277266 
    278     def setupStatus(self, build_status): 
    279         self.build_status = build_status 
    280         self.setProperty("buildername", self.builder.name) 
    281         self.setProperty("buildnumber", self.build_status.number) 
    282         self.setProperty("branch", self.source.branch) 
    283         self.setProperty("revision", self.source.revision) 
    284         if self.scheduler is None: 
    285             self.setProperty("scheduler", "none") 
    286         else: 
    287             self.setProperty("scheduler", self.scheduler.name) 
    288         for key, userProp in self.custom_properties.items(): 
    289             self.setProperty(key, userProp) 
     267    def setupProperties(self): 
     268        props = self.getProperties() 
     269 
     270        # start with global properties from the configuration 
     271        buildmaster = self.builder.botmaster.parent 
     272        props.updateFromProperties(buildmaster.properties) 
     273 
     274        # get any properties from requests (this is the path through 
     275        # which schedulers will send us properties) 
     276        for rq in self.requests: 
     277            props.updateFromProperties(rq.properties) 
     278 
     279        # now set some properties of our own, corresponding to the 
     280        # build itself 
     281        props.setProperty("buildername", self.builder.name, "Build") 
     282        props.setProperty("buildnumber", self.build_status.number, "Build") 
     283        props.setProperty("branch", self.source.branch, "Build") 
     284        props.setProperty("revision", self.source.revision, "Build") 
    290285 
    291286    def setupSlaveBuilder(self, slavebuilder): 
    292287        self.slavebuilder = slavebuilder 
     288 
     289        # navigate our way back to the L{buildbot.buildslave.BuildSlave} 
     290        # object that came from the config, and get its properties 
     291        buildslave_properties = slavebuilder.slave.properties 
     292        self.getProperties().updateFromProperties(buildslave_properties) 
     293 
    293294        self.slavename = slavebuilder.slave.slavename 
    294295        self.build_status.setSlavename(self.slavename) 
    295         self.setProperty("slavename", self.slavename) 
    296296 
    297297    def startBuild(self, build_status, expectations, slavebuilder): 
    298298        """This method sets up the build, then starts it by invoking the 
     
    305305        # the Deferred returned by this method. 
    306306 
    307307        log.msg("%s.startBuild" % self) 
    308         self.setupStatus(build_status) 
     308        self.build_status = build_status 
    309309        # now that we have a build_status, we can set properties 
     310        self.setupProperties() 
    310311        self.setupSlaveBuilder(slavebuilder) 
    311312 
    312313        # convert all locks into their real forms 
  • buildbot/process/builder.py

    diff -rN -u old-124/buildbot/process/builder.py new-124/buildbot/process/builder.py
    old new  
    253253    @type building: list of L{buildbot.process.base.Build} 
    254254    @ivar building: Builds that are actively running 
    255255 
     256    @type slaves: list of L{buildbot.buildslave.BuildSlave} objects 
     257    @ivar slaves: the slaves currently available for building 
    256258    """ 
    257259 
    258260    expectations = None # this is created the first time we get a good build 
     
    445447        """This is invoked by the BuildSlave when the self.slavename bot 
    446448        registers their builder. 
    447449 
    448         @type  slave: L{buildbot.master.BuildSlave} 
     450        @type  slave: L{buildbot.buildslave.BuildSlave} 
    449451        @param slave: the BuildSlave that represents the buildslave as a whole 
    450452        @type  remote: L{twisted.spread.pb.RemoteReference} 
    451453        @param remote: a reference to the L{buildbot.slave.bot.SlaveBuilder} 
  • buildbot/process/buildstep.py

    diff -rN -u old-124/buildbot/process/buildstep.py new-124/buildbot/process/buildstep.py
    old new  
    632632    def getProperty(self, propname): 
    633633        return self.build.getProperty(propname) 
    634634 
    635     def setProperty(self, propname, value): 
    636         self.build.setProperty(propname, value) 
     635    def setProperty(self, propname, value, source): 
     636        self.build.setProperty(propname, value, source) 
    637637 
    638638    def startStep(self, remote): 
    639639        """Begin the step. This returns a Deferred that will fire when the 
     
    10921092        self.step_status.setText(self.getText(cmd, results)) 
    10931093        self.step_status.setText2(self.maybeGetText2(cmd, results)) 
    10941094 
    1095 class _BuildPropertyMapping: 
    1096     def __init__(self, build): 
    1097         self.build = build 
    1098     def __getitem__(self, name): 
    1099         p = self.build.getProperty(name) 
    1100         if p is None: 
    1101             p = "" 
    1102         return p 
    1103  
    1104 class WithProperties(util.ComparableMixin): 
    1105     """This is a marker class, used in ShellCommand's command= argument to 
    1106     indicate that we want to interpolate a build property. 
    1107     """ 
    1108  
    1109     compare_attrs = ('fmtstring', 'args') 
    1110  
    1111     def __init__(self, fmtstring, *args): 
    1112         self.fmtstring = fmtstring 
    1113         self.args = args 
    1114  
    1115     def render(self, build): 
    1116         pmap = _BuildPropertyMapping(build) 
    1117         if self.args: 
    1118             strings = [] 
    1119             for name in self.args: 
    1120                 strings.append(pmap[name]) 
    1121             s = self.fmtstring % tuple(strings) 
    1122         else: 
    1123             s = self.fmtstring % pmap 
    1124         return s 
    1125  
    1126 def render_properties(s, build): 
    1127     """Return a string based on s and build that is suitable for use 
    1128     in a running BuildStep.  If s is a string, return s.  If s is a 
    1129     WithProperties object, return the result of s.render(build). 
    1130     Otherwise, return str(s). 
    1131     """ 
    1132     if isinstance(s, (str, unicode)): 
    1133         return s 
    1134     elif isinstance(s, WithProperties): 
    1135         return s.render(build) 
    1136     else: 
    1137         return str(s) 
     1095# (WithProeprties used to be available in this module) 
     1096from buildbot.process.properties import WithProperties 
  • buildbot/process/properties.py

    diff -rN -u old-124/buildbot/process/properties.py new-124/buildbot/process/properties.py
    old new  
     1from zope.interface import implements 
     2from buildbot import util 
     3from twisted.python import log 
     4from twisted.python.failure import Failure 
     5 
     6class Properties(util.ComparableMixin): 
     7    """ 
     8    I represent a set of properties that can be interpolated into various 
     9    strings in buildsteps. 
     10 
     11    @ivar properties: dictionary mapping property values to tuples  
     12        (value, source), where source is a string identifing the source 
     13        of the property. 
     14 
     15    Objects of this class can be read like a dictionary -- in this case, 
     16    only the property value is returned. 
     17 
     18    As a special case, a property value of None is returned as an empty  
     19    string when used as a mapping. 
     20    """ 
     21 
     22    compare_attrs = ('properties') 
     23 
     24    def __init__(self, **kwargs): 
     25        """ 
     26        @param kwargs: initial property values (for testing) 
     27        """ 
     28        self.properties = {} 
     29        if kwargs: self.update(kwargs, "TEST") 
     30 
     31    def __getitem__(self, name): 
     32        """Just get the value for this property, special-casing None -> ''""" 
     33        rv = self.properties[name][0] 
     34        if rv is None: rv = '' 
     35        return rv 
     36 
     37    def has_key(self, name): 
     38        return self.properties.has_key(name) 
     39 
     40    def getProperty(self, name, default=None): 
     41        """Get the value for the given property, with no None -> '' special case""" 
     42        return self.properties.get(name, (default,))[0] 
     43 
     44    def getPropertySource(self, name): 
     45        return self.properties[name][1] 
     46 
     47    def asList(self): 
     48        """Return the properties as a sorted list of (name, value, source)""" 
     49        l = [ (k, v[0], v[1]) for k,v in self.properties.items() ] 
     50        l.sort() 
     51        return l 
     52 
     53    def __repr__(self): 
     54        return repr(dict([ (k,v[0]) for k,v in self.properties.iteritems() ])) 
     55 
     56    def setProperty(self, name, value, source): 
     57        self.properties[name] = (value, source) 
     58 
     59    def update(self, dict, source): 
     60        """Update this object from a dictionary, with an explicit source specified.""" 
     61        for k, v in dict.items(): 
     62            self.properties[k] = (v, source) 
     63 
     64    def updateFromProperties(self, other): 
     65        """Update this object based on another object; the other object's """ 
     66        self.properties.update(other.properties) 
     67 
     68    def render(self, value): 
     69        """ 
     70        Return a variant of value that has any WithProperties objects 
     71        substituted.  This recurses into Python's compound data types. 
     72        """ 
     73        if isinstance(value, (str, unicode)): 
     74            return value 
     75        elif isinstance(value, WithProperties): 
     76            return value.render(self) 
     77        elif isinstance(value, list): 
     78            return [ self.render(e) for e in value ] 
     79        elif isinstance(value, tuple): 
     80            return tuple([ self.render(e) for e in value ]) 
     81        elif isinstance(value, dict): 
     82            return dict([ (self.render(k), self.render(v)) for k,v in value.iteritems() ]) 
     83        else: 
     84            return value 
     85 
     86class WithProperties(util.ComparableMixin): 
     87    """This is a marker class, used in ShellCommand's command= argument to 
     88    indicate that we want to interpolate a build property. 
     89    """ 
     90 
     91    compare_attrs = ('fmtstring', 'args') 
     92 
     93    def __init__(self, fmtstring, *args): 
     94        self.fmtstring = fmtstring 
     95        self.args = args 
     96 
     97    def render(self, properties): 
     98        if self.args: 
     99            strings = [] 
     100            for name in self.args: 
     101                strings.append(properties[name]) 
     102            s = self.fmtstring % tuple(strings) 
     103        else: 
     104            s = self.fmtstring % properties 
     105        return s 
  • buildbot/scheduler.py

    diff -rN -u old-124/buildbot/scheduler.py new-124/buildbot/scheduler.py
    old new  
    1414from buildbot.status import builder 
    1515from buildbot.sourcestamp import SourceStamp 
    1616from buildbot.changes.maildir import MaildirService 
     17from buildbot.process.properties import Properties 
    1718 
    1819 
    1920class BaseScheduler(service.MultiService, util.ComparableMixin): 
     21    """ 
     22    A Schduler creates BuildSets and submits them to the BuildMaster. 
     23 
     24    @ivar name: name of the scheduler 
     25 
     26    @ivar properties: additional properties specified in this  
     27        scheduler's configuration 
     28    @type properties: Properties object 
     29    """ 
    2030    implements(interfaces.IScheduler) 
    2131 
    22     def __init__(self, name): 
     32    def __init__(self, name, properties={}): 
     33        """ 
     34        @param name: name for this scheduler 
     35 
     36        @param properties: properties to be propagated from this scheduler 
     37        @type properties: dict 
     38        """ 
    2339        service.MultiService.__init__(self) 
    2440        self.name = name 
     41        self.properties = Properties() 
     42        self.properties.update(properties, "Scheduler") 
     43        self.properties.setProperty("scheduler", name, "Scheduler") 
    2544 
    2645    def __repr__(self): 
    2746        # TODO: why can't id() return a positive number? %d is ugly. 
    2847        return "<Scheduler '%s' at %d>" % (self.name, id(self)) 
    2948 
    30     def submit(self, bs): 
     49    def submitBuildSet(self, bs): 
    3150        self.parent.submitBuildSet(bs) 
    3251 
    3352    def addChange(self, change): 
     
    3655class BaseUpstreamScheduler(BaseScheduler): 
    3756    implements(interfaces.IUpstreamScheduler) 
    3857 
    39     def __init__(self, name): 
    40         BaseScheduler.__init__(self, name) 
     58    def __init__(self, name, properties={}): 
     59        BaseScheduler.__init__(self, name, properties) 
    4160        self.successWatchers = [] 
    4261 
    4362    def subscribeToSuccessfulBuilds(self, watcher): 
     
    4564    def unsubscribeToSuccessfulBuilds(self, watcher): 
    4665        self.successWatchers.remove(watcher) 
    4766 
    48     def submit(self, bs): 
     67    def submitBuildSet(self, bs): 
    4968        d = bs.waitUntilFinished() 
    5069        d.addCallback(self.buildSetFinished) 
    51         self.parent.submitBuildSet(bs) 
     70        BaseScheduler.submitBuildSet(self, bs) 
    5271 
    5372    def buildSetFinished(self, bss): 
    5473        if not self.running: 
     
    6988 
    7089    fileIsImportant = None 
    7190    compare_attrs = ('name', 'treeStableTimer', 'builderNames', 'branch', 
    72                      'fileIsImportant') 
     91                     'fileIsImportant', 'properties') 
    7392     
    7493    def __init__(self, name, branch, treeStableTimer, builderNames, 
    75                  fileIsImportant=None): 
     94                 fileIsImportant=None, properties={}): 
    7695        """ 
    7796        @param name: the name of this Scheduler 
    7897        @param branch: The branch name that the Scheduler should pay 
     
    94113                                build is triggered by an important change. 
    95114                                The default value of None means that all 
    96115                                Changes are important. 
     116 
     117        @param properties: properties to apply to all builds started from this  
     118                           scheduler 
    97119        """ 
    98120 
    99         BaseUpstreamScheduler.__init__(self, name) 
     121        BaseUpstreamScheduler.__init__(self, name, properties) 
    100122        self.treeStableTimer = treeStableTimer 
    101123        errmsg = ("The builderNames= argument to Scheduler must be a list " 
    102124                  "of Builder description names (i.e. the 'name' key of the " 
     
    171193        # create a BuildSet, submit it to the BuildMaster 
    172194        bs = buildset.BuildSet(self.builderNames, 
    173195                               SourceStamp(changes=changes), 
    174                                scheduler=self) 
    175         self.submit(bs) 
     196                               properties=self.properties) 
     197        self.submitBuildSet(bs) 
    176198 
    177199    def stopService(self): 
    178200        self.stopTimer() 
     
    188210    fileIsImportant = None 
    189211 
    190212    compare_attrs = ('name', 'branches', 'treeStableTimer', 'builderNames', 
    191                      'fileIsImportant') 
     213                     'fileIsImportant', 'properties') 
    192214 
    193215    def __init__(self, name, branches, treeStableTimer, builderNames, 
    194                  fileIsImportant=None): 
     216                 fileIsImportant=None, properties={}): 
    195217        """ 
    196218        @param name: the name of this Scheduler 
    197219        @param branches: The branch names that the Scheduler should pay 
     
    216238                                build is triggered by an important change. 
    217239                                The default value of None means that all 
    218240                                Changes are important. 
     241 
     242        @param properties: properties to apply to all builds started from this  
     243                           scheduler 
    219244        """ 
    220245 
    221         BaseUpstreamScheduler.__init__(self, name) 
     246        BaseUpstreamScheduler.__init__(self, name, properties) 
    222247        self.treeStableTimer = treeStableTimer 
    223248        for b in builderNames: 
    224249            assert isinstance(b, str) 
     
    272297            self.schedulers[branch] = s 
    273298        s.addChange(change) 
    274299 
    275     def submitBuildSet(self, bs): 
    276         self.parent.submitBuildSet(bs) 
    277  
    278300 
    279301class Dependent(BaseUpstreamScheduler): 
    280302    """This scheduler runs some set of 'downstream' builds when the 
    281303    'upstream' scheduler has completed successfully.""" 
    282304 
    283     compare_attrs = ('name', 'upstream', 'builders') 
     305    compare_attrs = ('name', 'upstream', 'builders', 'properties') 
    284306 
    285     def __init__(self, name, upstream, builderNames): 
     307    def __init__(self, name, upstream, builderNames, properties={}): 
    286308        assert interfaces.IUpstreamScheduler.providedBy(upstream) 
    287         BaseUpstreamScheduler.__init__(self, name) 
     309        BaseUpstreamScheduler.__init__(self, name, properties) 
    288310        self.upstream = upstream 
    289311        self.builderNames = builderNames 
    290312 
     
    305327        return d 
    306328 
    307329    def upstreamBuilt(self, ss): 
    308         bs = buildset.BuildSet(self.builderNames, ss, scheduler=self) 
    309         self.submit(bs) 
     330        bs = buildset.BuildSet(self.builderNames, ss, 
     331                    properties=self.properties) 
     332        self.submitBuildSet(bs) 
    310333 
    311334 
    312335 
     
    319342    # TODO: consider having this watch another (changed-based) scheduler and 
    320343    # merely enforce a minimum time between builds. 
    321344 
    322     compare_attrs = ('name', 'builderNames', 'periodicBuildTimer', 'branch') 
     345    compare_attrs = ('name', 'builderNames', 'periodicBuildTimer', 'branch', 'properties') 
    323346 
    324347    def __init__(self, name, builderNames, periodicBuildTimer, 
    325                  branch=None): 
    326         BaseUpstreamScheduler.__init__(self, name) 
     348                 branch=None, properties={}): 
     349        BaseUpstreamScheduler.__init__(self, name, properties) 
    327350        self.builderNames = builderNames 
    328351        self.periodicBuildTimer = periodicBuildTimer 
    329352        self.branch = branch 
     
    344367    def doPeriodicBuild(self): 
    345368        bs = buildset.BuildSet(self.builderNames, 
    346369                               SourceStamp(branch=self.branch), 
    347                                self.reason, scheduler=self) 
    348         self.submit(bs) 
     370                               self.reason, 
     371                               properties=self.properties) 
     372        self.submitBuildSet(bs) 
    349373 
    350374 
    351375 
     
    389413 
    390414    compare_attrs = ('name', 'builderNames', 
    391415                     'minute', 'hour', 'dayOfMonth', 'month', 
    392                      'dayOfWeek', 'branch') 
     416                     'dayOfWeek', 'branch', 'properties') 
    393417 
    394418    def __init__(self, name, builderNames, minute=0, hour='*', 
    395419                 dayOfMonth='*', month='*', dayOfWeek='*', 
    396                  branch=None): 
     420                 branch=None, properties={}): 
    397421        # Setting minute=0 really makes this an 'Hourly' scheduler. This 
    398422        # seemed like a better default than minute='*', which would result in 
    399423        # a build every 60 seconds. 
    400         BaseUpstreamScheduler.__init__(self, name) 
     424        BaseUpstreamScheduler.__init__(self, name, properties) 
    401425        self.builderNames = builderNames 
    402426        self.minute = minute 
    403427        self.hour = hour 
     
    502526        # And trigger a build 
    503527        bs = buildset.BuildSet(self.builderNames, 
    504528                               SourceStamp(branch=self.branch), 
    505                                self.reason, scheduler=self) 
    506         self.submit(bs) 
     529                               self.reason, 
     530                               properties=self.properties) 
     531        self.submitBuildSet(bs) 
    507532 
    508533    def addChange(self, change): 
    509534        pass 
    510535 
    511536 
    512537 
    513 class TryBase(service.MultiService, util.ComparableMixin): 
    514     implements(interfaces.IScheduler) 
    515  
    516     def __init__(self, name, builderNames): 
    517         service.MultiService.__init__(self) 
    518         self.name = name 
     538class TryBase(BaseScheduler): 
     539    def __init__(self, name, builderNames, properties={}): 
     540        BaseScheduler.__init__(self, name, properties) 
    519541        self.builderNames = builderNames 
    520542 
    521543    def listBuilderNames(self): 
     
    546568        self.error = True 
    547569 
    548570class Try_Jobdir(TryBase): 
    549     compare_attrs = ["name", "builderNames", "jobdir"] 
     571    compare_attrs = ( 'name', 'builderNames', 'jobdir', 'properties' ) 
    550572 
    551     def __init__(self, name, builderNames, jobdir): 
    552         TryBase.__init__(self, name, builderNames) 
     573    def __init__(self, name, builderNames, jobdir, properties={}): 
     574        TryBase.__init__(self, name, builderNames, properties) 
    553575        self.jobdir = jobdir 
    554576        self.watcher = MaildirService() 
    555577        self.watcher.setServiceParent(self) 
     
    624646                return 
    625647 
    626648        reason = "'try' job" 
    627         bs = buildset.BuildSet(builderNames, ss, reason=reason, bsid=bsid, scheduler=self) 
    628         self.parent.submitBuildSet(bs) 
     649        bs = buildset.BuildSet(builderNames, ss, reason=reason,  
     650                    bsid=bsid, properties=self.properties) 
     651        self.submitBuildSet(bs) 
    629652 
    630653class Try_Userpass(TryBase): 
    631     compare_attrs = ["name", "builderNames", "port", "userpass"] 
     654    compare_attrs = ( 'name', 'builderNames', 'port', 'userpass', 'properties' ) 
    632655    implements(portal.IRealm) 
    633656 
    634     def __init__(self, name, builderNames, port, userpass): 
    635         TryBase.__init__(self, name, builderNames) 
     657    def __init__(self, name, builderNames, port, userpass, properties={}): 
     658        TryBase.__init__(self, name, builderNames, properties) 
    636659        if type(port) is int: 
    637660            port = "tcp:%d" % port 
    638661        self.port = port 
     
    657680        p = Try_Userpass_Perspective(self, avatarID) 
    658681        return (pb.IPerspective, p, lambda: None) 
    659682 
    660     def submitBuildSet(self, bs): 
    661         return self.parent.submitBuildSet(bs) 
    662  
    663683class Try_Userpass_Perspective(pbutil.NewCredPerspective): 
    664684    def __init__(self, parent, username): 
    665685        self.parent = parent 
    666686        self.username = username 
    667687 
    668     def perspective_try(self, branch, revision, patch, builderNames, 
    669                         custom_props): 
     688    def perspective_try(self, branch, revision, patch, builderNames, properties={}): 
    670689        log.msg("user %s requesting build on builders %s" % (self.username, 
    671690                                                             builderNames)) 
    672691        for b in builderNames: 
     
    678697        ss = SourceStamp(branch, revision, patch) 
    679698        reason = "'try' job from user %s" % self.username 
    680699 
     700        # roll the specified props in with our inherited props 
     701        combined_props = Properties() 
     702        combined_props.updateFromProperties(self.parent.properties) 
     703        combined_props.update(properties, "try build") 
     704 
    681705        bs = buildset.BuildSet(builderNames,  
    682706                               ss, 
    683707                               reason=reason,  
    684                                scheduler=self, 
    685                                custom_props=custom_props) 
     708                               properties=combined_props) 
    686709 
    687710        self.parent.submitBuildSet(bs) 
    688711 
     
    696719    the builds that I fire have finished. 
    697720    """ 
    698721 
    699     def __init__(self, name, builderNames): 
    700         BaseUpstreamScheduler.__init__(self, name) 
     722    compare_attrs = ('name', 'builderNames', 'properties') 
     723 
     724    def __init__(self, name, builderNames, properties={}): 
     725        BaseUpstreamScheduler.__init__(self, name, properties) 
    701726        self.builderNames = builderNames 
    702727 
    703728    def listBuilderNames(self): 
     
    706731    def getPendingBuildTimes(self): 
    707732        return [] 
    708733 
    709     def trigger(self, ss, custom_props={}): 
     734    def trigger(self, ss, set_props=None): 
    710735        """Trigger this scheduler. Returns a deferred that will fire when the 
    711736        buildset is finished. 
    712737        """ 
    713         bs = buildset.BuildSet(self.builderNames, ss, scheduler=self, custom_props=custom_props) 
     738 
     739        # properties for this buildset are composed of our own properties, 
     740        # potentially overridden by anything from the triggering build 
     741        props = Properties() 
     742        props.updateFromProperties(self.properties) 
     743        if set_props: props.updateFromProperties(set_props) 
     744 
     745        bs = buildset.BuildSet(self.builderNames, ss, properties=props) 
    714746        d = bs.waitUntilFinished() 
    715         self.submit(bs) 
     747        self.submitBuildSet(bs) 
    716748        return d 
  • buildbot/scripts/runner.py

    diff -rN -u old-124/buildbot/scripts/runner.py new-124/buildbot/scripts/runner.py
    old new  
    763763 
    764764        ["builder", "b", None, 
    765765         "Run the trial build on this Builder. Can be used multiple times."], 
    766         ["customproperties", None, None, 
    767          "A set of custom properties made available in the build environment, format:prop=value,propb=valueb..."], 
     766        ["properties", None, None, 
     767         "A set of properties made available in the build environment, format:prop=value,propb=valueb..."], 
    768768        ] 
    769769 
    770770    optFlags = [ 
     
    774774    def __init__(self): 
    775775        super(TryOptions, self).__init__() 
    776776        self['builders'] = [] 
    777         self['custom_props'] = {} 
     777        self['properties'] = {} 
    778778 
    779779    def opt_builder(self, option): 
    780780        self['builders'].append(option) 
    781781 
    782     def opt_customproperties(self, option): 
    783         # We need to split the value of this option into a dictionary of custom 
    784         # properties 
    785         custom_props = {} 
     782    def opt_properties(self, option): 
     783        # We need to split the value of this option into a dictionary of properties 
     784        properties = {} 
    786785        propertylist = option.split(",") 
    787786        for i in range(0,len(propertylist)): 
    788787            print propertylist[i] 
    789788            splitproperty = propertylist[i].split("=") 
    790             custom_props[splitproperty[0]] = splitproperty[1] 
    791         self['custom_props'] = custom_props 
     789            properties[splitproperty[0]] = splitproperty[1] 
     790        self['properties'] = properties 
    792791 
    793792    def opt_patchlevel(self, option): 
    794793        self['patchlevel'] = int(option) 
  • buildbot/scripts/tryclient.py

    diff -rN -u old-124/buildbot/scripts/tryclient.py new-124/buildbot/scripts/tryclient.py
    old new  
    448448                              ss.revision, 
    449449                              ss.patch, 
    450450                              self.builderNames, 
    451                               self.config.get('custom_props', {})) 
     451                              self.config.get('properties', {})) 
    452452        d.addCallback(self._deliverJob_pb2) 
    453453        return d 
    454454    def _deliverJob_pb2(self, status): 
  • buildbot/status/builder.py

    diff -rN -u old-124/buildbot/status/builder.py new-124/buildbot/status/builder.py
    old new  
    55from twisted.persisted import styles 
    66from twisted.internet import reactor, defer 
    77from twisted.protocols import basic 
     8from buildbot.process.properties import Properties 
    89 
    910import os, shutil, sys, re, urllib, itertools 
    1011from cPickle import load, dump 
     
    888889 
    889890class BuildStatus(styles.Versioned): 
    890891    implements(interfaces.IBuildStatus, interfaces.IStatusEvent) 
    891     persistenceVersion = 2 
     892    persistenceVersion = 3 
    892893 
    893894    source = None 
    894895    reason = None 
     
    924925        self.finishedWatchers = [] 
    925926        self.steps = [] 
    926927        self.testResults = {} 
    927         self.properties = {} 
     928        self.properties = Properties() 
    928929 
    929930    # IBuildStatus 
    930931 
     
    937938    def getProperty(self, propname): 
    938939        return self.properties[propname] 
    939940 
     941    def getProperties(self): 
     942        return self.properties 
     943 
    940944    def getNumber(self): 
    941945        return self.number 
    942946 
     
    10741078        self.steps.append(s) 
    10751079        return s 
    10761080 
    1077     def setProperty(self, propname, value): 
    1078         self.properties[propname] = value 
     1081    def setProperty(self, propname, value, source): 
     1082        self.properties.setProperty(propname, value, source) 
    10791083 
    10801084    def addTestResult(self, result): 
    10811085        self.testResults[result.getName()] = result 
     
    12291233    def upgradeToVersion2(self): 
    12301234        self.properties = {} 
    12311235 
     1236    def upgradeToVersion3(self): 
     1237        # in version 3, self.properties became a Properties object 
     1238        propdict = self.properties 
     1239        self.properties = Properties() 
     1240        self.properties.update(propdict) 
     1241 
    12321242    def upgradeLogfiles(self): 
    12331243        # upgrade any LogFiles that need it. This must occur after we've been 
    12341244        # attached to our Builder, and after we know about all LogFiles of 
     
    17891799        return self.botmaster.parent.projectURL 
    17901800    def getBuildbotURL(self): 
    17911801        return self.botmaster.parent.buildbotURL 
    1792     def getCustomBuildProperties(self): 
    1793      return self.botmaster.parent.customBuildProperties 
    17941802 
    17951803    def getURLForThing(self, thing): 
    17961804        prefix = self.getBuildbotURL() 
  • buildbot/status/web/build.py

    diff -rN -u old-124/buildbot/status/web/build.py new-124/buildbot/status/web/build.py
    old new  
    121121                data += " </li>\n" 
    122122            data += "</ol>\n" 
    123123 
     124        data += "<h2>Build Properties:</h2>\n" 
     125        data += "<table><tr><th valign=\"left\">Name</th><th valign=\"left\">Value</th><th valign=\"left\">Source</th></tr>\n" 
     126        for name, value, source in b.getProperties().asList(): 
     127            data += "<tr><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (name, value, source) 
     128        data += "</table>" 
     129             
    124130        data += "<h2>Blamelist:</h2>\n" 
    125131        if list(b.getResponsibleUsers()): 
    126132            data += " <ol>\n" 
  • buildbot/status/web/builder.py

    diff -rN -u old-124/buildbot/status/web/builder.py new-124/buildbot/status/web/builder.py
    old new  
    152152 
    153153        return data 
    154154 
    155     def force(self, req, custom_props={}): 
     155    def force(self, req): 
    156156        """ 
    157157 
    158158        Custom properties can be passed from the web form.  To do 
     
    161161        by inspecting req.args), then pass them to this superclass 
    162162        force method. 
    163163         
    164         @param custom_props: Custom properties to set on build 
    165          
    166164        """ 
    167165        name = req.args.get("username", ["<unknown>"])[0] 
    168166        reason = req.args.get("comments", ["<no reason specified>"])[0] 
     
    196194        # button, use their name instead of None, so they'll be informed of 
    197195        # the results. 
    198196        s = SourceStamp(branch=branch, revision=revision) 
    199         req = BuildRequest(r, s, builderName=self.builder_status.getName(), custom_props=custom_props) 
     197        req = BuildRequest(r, s, builderName=self.builder_status.getName()) 
    200198        try: 
    201199            self.builder_control.requestBuildSoon(req) 
    202200        except interfaces.NoSlaveError: 
  • buildbot/steps/python.py

    diff -rN -u old-124/buildbot/steps/python.py new-124/buildbot/steps/python.py
    old new  
    9696            if counts[m]: 
    9797                self.descriptionDone.append("%s=%d" % (m, counts[m])) 
    9898                self.addCompleteLog(m, "".join(summaries[m])) 
    99             self.setProperty("pyflakes-%s" % m, counts[m]) 
    100         self.setProperty("pyflakes-total", sum(counts.values())) 
     99            self.setProperty("pyflakes-%s" % m, counts[m], "pyflakes") 
     100        self.setProperty("pyflakes-total", sum(counts.values()), "pyflakes") 
    101101 
    102102 
    103103    def evaluateCommand(self, cmd): 
  • buildbot/steps/shell.py

    diff -rN -u old-124/buildbot/steps/shell.py new-124/buildbot/steps/shell.py
    old new  
    22 
    33import re 
    44from twisted.python import log 
    5 from buildbot.process.buildstep import LoggingBuildStep, RemoteShellCommand, \ 
    6      render_properties 
     5from buildbot.process.buildstep import LoggingBuildStep, RemoteShellCommand 
    76from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE 
    87 
    9 # for existing configurations that import WithProperties from here 
    10 from buildbot.process.buildstep import WithProperties 
     8# for existing configurations that import WithProperties from here.  We like 
     9# to move this class around just to keep our readers guessing. 
     10from buildbot.process.properties import WithProperties 
    1111 
    1212class ShellCommand(LoggingBuildStep): 
    1313    """I run a single shell command on the buildslave. I return FAILURE if 
     
    118118        if self.description is not None: 
    119119            return self.description 
    120120 
     121        properties = self.build.getProperties() 
    121122        words = self.command 
    122123        if isinstance(words, (str, unicode)): 
    123124            words = words.split() 
    124125        # render() each word to handle WithProperties objects 
    125         words = [render_properties(word, self.build) for word in words] 
     126        words = properties.render(words) 
    126127        if len(words) < 1: 
    127128            return ["???"] 
    128129        if len(words) == 1: 
     
    131132            return ["'%s" % words[0], "%s'" % words[1]] 
    132133        return ["'%s" % words[0], "%s" % words[1], "...'"] 
    133134 
    134     def _interpolateProperties(self, value): 
    135         """ 
    136         Expand the L{WithProperties} objects in L{value} 
    137         """ 
    138         if isinstance(value, (str, unicode, bool, int, float, type(None))): 
    139             return value 
    140  
    141         if isinstance(value, list): 
    142             return [self._interpolateProperties(val) for val in value] 
    143  
    144         if isinstance(value, tuple): 
    145             return tuple([self._interpolateProperties(val) for val in value]) 
    146  
    147         if isinstance(value, dict): 
    148             new_dict = { } 
    149             for key, val in value.iteritems(): 
    150                 new_key = self._interpolateProperties(key) 
    151                 new_dict[new_key] = self._interpolateProperties(val) 
    152             return new_dict 
    153  
    154         # To make sure we catch anything we forgot 
    155         assert isinstance(value, WithProperties), \ 
    156                "%s (%s) is not a WithProperties" % (value, type(value)) 
    157  
    158         return value.render(self.build) 
    159  
    160     def _interpolateWorkdir(self, workdir): 
    161         return render_properties(workdir, self.build) 
    162  
    163135    def setupEnvironment(self, cmd): 
     136        # XXX is this used? documented? replaced by properties? 
    164137        # merge in anything from Build.slaveEnvironment . Earlier steps 
    165138        # (perhaps ones which compile libraries or sub-projects that need to 
    166139        # be referenced by later steps) can add keys to 
    167140        # self.build.slaveEnvironment to affect later steps. 
     141        properties = self.build.getProperties() 
    168142        slaveEnv = self.build.slaveEnvironment 
    169143        if slaveEnv: 
    170144            if cmd.args['env'] is None: 
    171145                cmd.args['env'] = {} 
    172             cmd.args['env'].update(self._interpolateProperties(slaveEnv)) 
     146            cmd.args['env'].update(properties.render(slaveEnv)) 
    173147            # note that each RemoteShellCommand gets its own copy of the 
    174148            # dictionary, so we shouldn't be affecting anyone but ourselves. 
    175149 
     
    199173        # this block is specific to ShellCommands. subclasses that don't need 
    200174        # to set up an argv array, an environment, or extra logfiles= (like 
    201175        # the Source subclasses) can just skip straight to startCommand() 
    202         command = self._interpolateProperties(self.command) 
    203         assert isinstance(command, (list, tuple, str)) 
     176        properties = self.build.getProperties() 
     177 
    204178        # create the actual RemoteShellCommand instance now 
    205         kwargs = self._interpolateProperties(self.remote_kwargs) 
    206         kwargs['workdir'] = self._interpolateWorkdir(kwargs['workdir']) 
    207         kwargs['command'] = command 
     179        kwargs = properties.render(self.remote_kwargs) 
     180        kwargs['command'] = properties.render(self.command) 
    208181        kwargs['logfiles'] = self.logfiles 
    209182        cmd = RemoteShellCommand(**kwargs) 
    210183        self.setupEnvironment(cmd) 
     
    224197        m = re.search(r'^(\d+)', out) 
    225198        if m: 
    226199            self.kib = int(m.group(1)) 
    227             self.setProperty("tree-size-KiB", self.kib) 
     200            self.setProperty("tree-size-KiB", self.kib, "treesize") 
    228201 
    229202    def evaluateCommand(self, cmd): 
    230203        if cmd.rc != 0: 
     
    298271            old_count = self.getProperty("warnings-count") 
    299272        except KeyError: 
    300273            old_count = 0 
    301         self.setProperty("warnings-count", old_count + self.warnCount) 
     274        self.setProperty("warnings-count", old_count + self.warnCount, "WarningCountingShellCommand") 
    302275 
    303276 
    304277    def evaluateCommand(self, cmd): 
  • buildbot/steps/source.py

    diff -rN -u old-124/buildbot/steps/source.py new-124/buildbot/steps/source.py
    old new  
    187187        got_revision = None 
    188188        if cmd.updates.has_key("got_revision"): 
    189189            got_revision = str(cmd.updates["got_revision"][-1]) 
    190         self.setProperty("got_revision", got_revision) 
     190        self.setProperty("got_revision", got_revision, "Source") 
    191191 
    192192 
    193193 
  • buildbot/steps/transfer.py

    diff -rN -u old-124/buildbot/steps/transfer.py new-124/buildbot/steps/transfer.py
    old new  
    44from twisted.internet import reactor 
    55from twisted.spread import pb 
    66from twisted.python import log 
    7 from buildbot.process.buildstep import RemoteCommand, BuildStep, \ 
    8      render_properties 
     7from buildbot.process.buildstep import RemoteCommand, BuildStep 
    98from buildbot.process.buildstep import SUCCESS, FAILURE 
    109from buildbot.interfaces import BuildSlaveTooOldError 
    1110 
     
    111110 
    112111    def start(self): 
    113112        version = self.slaveVersion("uploadFile") 
     113        properties = self.build.getProperties() 
     114 
    114115        if not version: 
    115116            m = "slave is too old, does not know about uploadFile" 
    116117            raise BuildSlaveTooOldError(m) 
    117118 
    118         source = render_properties(self.slavesrc, self.build) 
    119         masterdest = render_properties(self.masterdest, self.build) 
     119        source = properties.render(self.slavesrc) 
     120        masterdest = properties.render(self.masterdest) 
    120121        # we rely upon the fact that the buildmaster runs chdir'ed into its 
    121122        # basedir to make sure that relative paths in masterdest are expanded 
    122123        # properly. TODO: maybe pass the master's basedir all the way down 
     
    237238        self.mode = mode 
    238239 
    239240    def start(self): 
     241        properties = self.build.getProperties() 
     242 
    240243        version = self.slaveVersion("downloadFile") 
    241244        if not version: 
    242245            m = "slave is too old, does not know about downloadFile" 
     
    244247 
    245248        # we are currently in the buildmaster's basedir, so any non-absolute 
    246249        # paths will be interpreted relative to that 
    247         source = os.path.expanduser(render_properties(self.mastersrc, 
    248                                                       self.build)) 
    249         slavedest = render_properties(self.slavedest, self.build) 
     250        source = os.path.expanduser(properties.render(self.mastersrc)) 
     251        slavedest = properties.render(self.slavedest) 
    250252        log.msg("FileDownload started, from master %r to slave %r" % 
    251253                (source, slavedest)) 
    252254 
  • buildbot/steps/trigger.py

    diff -rN -u old-124/buildbot/steps/trigger.py new-124/buildbot/steps/trigger.py
    old new  
    11from buildbot.process.buildstep import LoggingBuildStep, SUCCESS, FAILURE, EXCEPTION 
    2 from buildbot.steps.shell import WithProperties 
     2from buildbot.process.properties import WithProperties, Properties 
    33from buildbot.scheduler import Triggerable 
    44from twisted.internet import defer 
    55 
     
    1212    flunkOnFailure = True 
    1313 
    1414    def __init__(self, schedulerNames=[], updateSourceStamp=True, 
    15                  waitForFinish=False, **kwargs): 
     15                 waitForFinish=False, set_properties={}, **kwargs): 
    1616        """ 
    1717        Trigger the given schedulers when this step is executed. 
    1818 
     
    3333                              schedulers. If True, I will wait until all of 
    3434                              the triggered schedulers have finished their 
    3535                              builds. 
     36 
     37        @param set_properties: A dictionary of properties to set for any 
     38                               builds resulting from this trigger.  To copy 
     39                               existing properties, use WithProperties.  These 
     40                               properties will override properties set in the 
     41                               Triggered scheduler's constructor. 
     42 
    3643        """ 
    3744        assert schedulerNames, "You must specify a scheduler to trigger" 
    3845        self.schedulerNames = schedulerNames 
    3946        self.updateSourceStamp = updateSourceStamp 
    4047        self.waitForFinish = waitForFinish 
     48        self.set_properties = set_properties 
    4149        self.running = False 
    4250        LoggingBuildStep.__init__(self, **kwargs) 
    4351        self.addFactoryArguments(schedulerNames=schedulerNames, 
     
    5159            self.step_status.setText(["interrupted"]) 
    5260 
    5361    def start(self): 
    54         custom_props = {} 
     62        properties = self.build.getProperties() 
     63 
     64        # make a new properties object from a dict rendered by the old  
     65        # properties object 
     66        props_to_set = Properties() 
     67        props_to_set.update(properties.render(self.set_properties), "Trigger") 
     68 
    5569        self.running = True 
    5670        ss = self.build.getSourceStamp() 
    5771        if self.updateSourceStamp: 
    58             got = None 
    59             try: 
    60                 got = self.build.getProperty('got_revision') 
    61             except KeyError: 
    62                 pass 
     72            got = properties.getProperty('got_revision') 
    6373            if got: 
    6474                ss = ss.getAbsoluteSourceStamp(got) 
    65         custom_props = self.build.getCustomProperties() 
     75 
    6676        # (is there an easier way to find the BuildMaster?) 
    6777        all_schedulers = self.build.builder.botmaster.parent.allSchedulers() 
    6878        all_schedulers = dict([(sch.name, sch) for sch in all_schedulers]) 
     
    7282        # TODO: don't fire any schedulers if we discover an unknown one 
    7383        dl = [] 
    7484        for scheduler in self.schedulerNames: 
    75             if isinstance(scheduler, WithProperties): 
    76                 scheduler = scheduler.render(self.build) 
     85            scheduler = properties.render(scheduler) 
    7786            if all_schedulers.has_key(scheduler): 
    7887                sch = all_schedulers[scheduler] 
    7988                if isinstance(sch, Triggerable): 
    80                     dl.append(sch.trigger(ss, custom_props)) 
     89                    dl.append(sch.trigger(ss, set_props=props_to_set)) 
    8190                    triggered_schedulers.append(scheduler) 
    8291                else: 
    8392                    unknown_schedulers.append(scheduler) 
     
    101110        else: 
    102111            d = defer.succeed([]) 
    103112 
    104         # TODO: review this shadowed 'rc' value: can the callback modify the 
    105         # one that was defined above? 
    106113        def cb(rclist): 
    107             rc = SUCCESS 
     114            rc = SUCCESS # (this rc is not the same variable as that above) 
    108115            for was_cb, buildsetstatus in rclist: 
    109116                # TODO: make this algo more configurable 
    110117                if not was_cb: 
  • buildbot/test/runutils.py

    diff -rN -u old-124/buildbot/test/runutils.py new-124/buildbot/test/runutils.py
    old new  
    1313from buildbot.process.buildstep import BuildStep 
    1414from buildbot.sourcestamp import SourceStamp 
    1515from buildbot.status import builder 
     16from buildbot.process.properties import Properties 
    1617 
    1718 
    1819 
     
    152153        if bs.getResults() != builder.SUCCESS: 
    153154            log.msg("failUnlessBuildSucceeded noticed that the build failed") 
    154155            self.logBuildResults(bs) 
    155         self.failUnless(bs.getResults() == builder.SUCCESS) 
     156        self.failUnlessEqual(bs.getResults(), builder.SUCCESS) 
    156157        return bs # useful for chaining 
    157158 
    158159    def logBuildResults(self, bs): 
     
    275276    from buildbot.slave.registry import commandRegistry 
    276277    return commandRegistry[command] 
    277278 
     279class FakeBuildMaster: 
     280    properties = Properties(masterprop="master") 
     281 
     282class FakeBotMaster: 
     283    parent = FakeBuildMaster() 
     284 
    278285def makeBuildStep(basedir, step_class=BuildStep, **kwargs): 
    279286    bss = setupBuildStepStatus(basedir) 
    280287 
     
    282289    setup = {'name': "builder1", "slavename": "bot1", 
    283290             'builddir': "builddir", 'factory': None} 
    284291    b0 = Builder(setup, bss.getBuild().getBuilder()) 
     292    b0.botmaster = FakeBotMaster() 
    285293    br = BuildRequest("reason", ss) 
    286294    b = Build([br]) 
    287295    b.setBuilder(b0) 
    288296    s = step_class(**kwargs) 
    289297    s.setBuild(b) 
    290298    s.setStepStatus(bss) 
    291     b.setupStatus(bss.getBuild()) 
     299    b.build_status = bss.getBuild() 
     300    b.setupProperties() 
    292301    s.slaveVersion = fake_slaveVersion 
    293302    return s 
    294303 
     
    480489        self.value = value 
    481490 
    482491    def start(self): 
    483         _flags[self.flagname] = self.value 
     492        properties = self.build.getProperties() 
     493        _flags[self.flagname] = properties.render(self.value) 
    484494        self.finished(builder.SUCCESS) 
    485495 
    486496class TestFlagMixin: 
  • buildbot/test/test_properties.py

    diff -rN -u old-124/buildbot/test/test_properties.py new-124/buildbot/test/test_properties.py
    old new  
    66 
    77from buildbot.sourcestamp import SourceStamp 
    88from buildbot.process import base 
    9 from buildbot.steps.shell import ShellCommand, WithProperties 
     9from buildbot.process.properties import WithProperties, Properties 
     10from buildbot.steps.shell import ShellCommand 
    1011from buildbot.status import builder 
    1112from buildbot.slave.commands import rmdirRecursive 
    1213from buildbot.test.runutils import RunMixin 
     
    1415 
    1516class FakeBuild: 
    1617    pass 
     18class FakeBuildMaster: 
     19    properties = Properties(masterprop="master") 
     20class FakeBotMaster: 
     21    parent = FakeBuildMaster() 
    1722class FakeBuilder: 
    1823    statusbag = None 
    1924    name = "fakebuilder" 
     25    botmaster = FakeBotMaster() 
    2026class FakeSlave: 
    2127    slavename = "bot12" 
     28    properties = Properties(slavename="bot12") 
    2229class FakeSlaveBuilder: 
    2330    slave = FakeSlave() 
    2431    def getSlaveCommandVersion(self, command, oldversion=None): 
     
    2633class FakeScheduler: 
    2734    name = "fakescheduler" 
    2835 
    29 class Interpolate(unittest.TestCase): 
     36class TestProperties(unittest.TestCase): 
    3037    def setUp(self): 
    31         self.builder = FakeBuilder() 
    32         self.builder_status = builder.BuilderStatus("fakebuilder") 
    33         self.builder_status.basedir = "test_properties" 
    34         self.builder_status.nextBuildNumber = 5 
    35         rmdirRecursive(self.builder_status.basedir) 
    36         os.mkdir(self.builder_status.basedir) 
    37         self.build_status = self.builder_status.newBuild() 
    38         req = base.BuildRequest("reason", SourceStamp(branch="branch2", 
    39                                                       revision=1234)) 
    40         self.build = base.Build([req]) 
    41         self.build.setBuilder(self.builder) 
    42         self.build.setupStatus(self.build_status) 
    43         self.build.setupSlaveBuilder(FakeSlaveBuilder()) 
     38        self.props = Properties() 
     39 
     40    def testDictBehavior(self): 
     41        self.props.setProperty("do-tests", 1, "scheduler") 
     42        self.props.setProperty("do-install", 2, "scheduler") 
     43 
     44        self.assert_(self.props.has_key('do-tests')) 
     45        self.failUnlessEqual(self.props['do-tests'], 1) 
     46        self.failUnlessEqual(self.props['do-install'], 2) 
     47        self.assertRaises(KeyError, lambda : self.props['do-nothing']) 
     48        self.failUnlessEqual(self.props.getProperty('do-install'), 2) 
     49 
     50    def testEmpty(self): 
     51        # test the special case for Null 
     52        self.props.setProperty("x", None, "hi") 
     53        self.failUnlessEqual(self.props.getProperty('x'), None) 
     54        self.failUnlessEqual(self.props['x'], '') 
     55 
     56    def testUpdate(self): 
     57        self.props.setProperty("x", 24, "old") 
     58        newprops = { 'a' : 1, 'b' : 2 } 
     59        self.props.update(newprops, "new") 
     60 
     61        self.failUnlessEqual(self.props.getProperty('x'), 24) 
     62        self.failUnlessEqual(self.props.getPropertySource('x'), 'old') 
     63        self.failUnlessEqual(self.props.getProperty('a'), 1) 
     64        self.failUnlessEqual(self.props.getPropertySource('a'), 'new') 
     65 
     66    def testUpdateFromProperties(self): 
     67        self.props.setProperty("x", 24, "old") 
     68        newprops = Properties() 
     69        newprops.setProperty('a', 1, "new") 
     70        newprops.setProperty('b', 2, "new") 
     71        self.props.updateFromProperties(newprops) 
     72 
     73        self.failUnlessEqual(self.props.getProperty('x'), 24) 
     74        self.failUnlessEqual(self.props.getPropertySource('x'), 'old') 
     75        self.failUnlessEqual(self.props.getProperty('a'), 1) 
     76        self.failUnlessEqual(self.props.getPropertySource('a'), 'new') 
     77 
     78    # render() is pretty well tested by TestWithProperties 
     79 
     80class TestWithProperties(unittest.TestCase): 
     81    def setUp(self): 
     82        self.props = Properties() 
    4483 
    45     def testWithProperties(self): 
    46         self.build.setProperty("revision", 47) 
    47         self.failUnlessEqual(self.build_status.getProperty("revision"), 47) 
    48         c = ShellCommand(workdir=dir, 
    49                          command=["tar", "czf", 
    50                                   WithProperties("build-%s.tar.gz", 
    51                                                  "revision"), 
    52                                   "source"]) 
    53         c.setBuild(self.build) 
    54         cmd = c._interpolateProperties(c.command) 
    55         self.failUnlessEqual(cmd, 
    56                              ["tar", "czf", "build-47.tar.gz", "source"]) 
    57         self.failUnlessEqual(self.build.getProperty("scheduler"), "none") 
    58  
    59     def testWorkdir(self): 
    60         self.build.setProperty("revision", 47) 
    61         self.failUnlessEqual(self.build_status.getProperty("revision"), 47) 
    62         c = ShellCommand(command=["tar", "czf", "foo.tar.gz", "source"]) 
    63         c.setBuild(self.build) 
    64         workdir = WithProperties("workdir-%d", "revision") 
    65         workdir = c._interpolateWorkdir(workdir) 
    66         self.failUnlessEqual(workdir, "workdir-47") 
    67  
    68     def testWithPropertiesDict(self): 
    69         self.build.setProperty("other", "foo") 
    70         self.build.setProperty("missing", None) 
    71         c = ShellCommand(workdir=dir, 
    72                          command=["tar", "czf", 
    73                                   WithProperties("build-%(other)s.tar.gz"), 
    74                                   "source"]) 
    75         c.setBuild(self.build) 
    76         cmd = c._interpolateProperties(c.command) 
    77         self.failUnlessEqual(cmd, 
    78                              ["tar", "czf", "build-foo.tar.gz", "source"]) 
    79  
    80     def testWithPropertiesEmpty(self): 
    81         self.build.setProperty("empty", None) 
    82         c = ShellCommand(workdir=dir, 
    83                          command=["tar", "czf", 
    84                                   WithProperties("build-%(empty)s.tar.gz"), 
    85                                   "source"]) 
    86         c.setBuild(self.build) 
    87         cmd = c._interpolateProperties(c.command) 
    88         self.failUnlessEqual(cmd, 
    89                              ["tar", "czf", "build-.tar.gz", "source"]) 
    90  
    91     def testSourceStamp(self): 
    92         c = ShellCommand(workdir=dir, 
    93                          command=["touch", 
    94                                   WithProperties("%s-dir", "branch"), 
    95                                   WithProperties("%s-rev", "revision"), 
    96                                   ]) 
    97         c.setBuild(self.build) 
    98         cmd = c._interpolateProperties(c.command) 
    99         self.failUnlessEqual(cmd, 
    100                              ["touch", "branch2-dir", "1234-rev"]) 
    101  
    102     def testSlaveName(self): 
    103         c = ShellCommand(workdir=dir, 
    104                          command=["touch", 
    105                                   WithProperties("%s-slave", "slavename"), 
    106                                   ]) 
    107         c.setBuild(self.build) 
    108         cmd = c._interpolateProperties(c.command) 
    109         self.failUnlessEqual(cmd, 
    110                              ["touch", "bot12-slave"]) 
    111  
    112     def testBuildNumber(self): 
    113         c = ShellCommand(workdir=dir, 
    114                          command=["touch", 
    115                                   WithProperties("build-%d", "buildnumber"), 
    116                                   WithProperties("builder-%s", "buildername"), 
    117                                   ]) 
    118         c.setBuild(self.build) 
    119         cmd = c._interpolateProperties(c.command) 
    120         self.failUnlessEqual(cmd, 
    121                              ["touch", "build-5", "builder-fakebuilder"]) 
     84    def testBasic(self): 
     85        # test basic substitution with WithProperties 
     86        self.props.setProperty("revision", "47", "test") 
     87        command = WithProperties("build-%s.tar.gz", "revision") 
     88        self.failUnlessEqual(self.props.render(command), 
     89                             "build-47.tar.gz") 
     90 
     91    def testDict(self): 
     92        # test dict-style substitution with WithProperties 
     93        self.props.setProperty("other", "foo", "test") 
     94        command = WithProperties("build-%(other)s.tar.gz") 
     95        self.failUnlessEqual(self.props.render(command), 
     96                             "build-foo.tar.gz") 
     97 
     98    def testEmpty(self): 
     99        # None should render as '' 
     100        self.props.setProperty("empty", None, "test") 
     101        command = WithProperties("build-%(empty)s.tar.gz") 
     102        self.failUnlessEqual(self.props.render(command), 
     103                             "build-.tar.gz") 
     104 
     105    def testRecursiveList(self): 
     106        self.props.setProperty("x", 10, "test") 
     107        self.props.setProperty("y", 20, "test") 
     108        command = [ WithProperties("%(x)s %(y)s"), "and", 
     109                    WithProperties("%(y)s %(x)s") ] 
     110        self.failUnlessEqual(self.props.render(command), 
     111                             ["10 20", "and", "20 10"]) 
     112 
     113    def testRecursiveTuple(self): 
     114        self.props.setProperty("x", 10, "test") 
     115        self.props.setProperty("y", 20, "test") 
     116        command = ( WithProperties("%(x)s %(y)s"), "and", 
     117                    WithProperties("%(y)s %(x)s") ) 
     118        self.failUnlessEqual(self.props.render(command), 
     119                             ("10 20", "and", "20 10")) 
     120 
     121    def testRecursiveDict(self): 
     122        self.props.setProperty("x", 10, "test") 
     123        self.props.setProperty("y", 20, "test") 
     124        command = { WithProperties("%(x)s %(y)s") :  
     125                    WithProperties("%(y)s %(x)s") } 
     126        self.failUnlessEqual(self.props.render(command), 
     127                             {"10 20" : "20 10"}) 
    122128 
    123 class SchedulerTest(unittest.TestCase): 
     129class BuildProperties(unittest.TestCase): 
     130    """Test the properties that a build should have.""" 
    124131    def setUp(self): 
    125132        self.builder = FakeBuilder() 
    126133        self.builder_status = builder.BuilderStatus("fakebuilder") 
     
    129136        rmdirRecursive(self.builder_status.basedir) 
    130137        os.mkdir(self.builder_status.basedir) 
    131138        self.build_status = self.builder_status.newBuild() 
    132         req = base.BuildRequest("reason", SourceStamp(branch="branch2", 
    133                                 revision=1234), scheduler=FakeScheduler()) 
     139        req = base.BuildRequest("reason",  
     140                    SourceStamp(branch="branch2", revision="1234"), 
     141                    properties=Properties(scheduler="fakescheduler")) 
    134142        self.build = base.Build([req]) 
     143        self.build.build_status = self.build_status 
    135144        self.build.setBuilder(self.builder) 
    136         self.build.setupStatus(self.build_status) 
     145        self.build.setupProperties() 
    137146        self.build.setupSlaveBuilder(FakeSlaveBuilder()) 
    138147 
    139     def testWithScheduler(self): 
    140         self.failUnlessEqual(self.build.getProperty("scheduler"), 
    141                              "fakescheduler") 
     148    def testProperties(self): 
     149        self.failUnlessEqual(self.build.getProperty("scheduler"), "fakescheduler") 
     150        self.failUnlessEqual(self.build.getProperty("branch"), "branch2") 
     151        self.failUnlessEqual(self.build.getProperty("revision"), "1234") 
     152        self.failUnlessEqual(self.build.getProperty("slavename"), "bot12") 
     153        self.failUnlessEqual(self.build.getProperty("buildnumber"), 5) 
     154        self.failUnlessEqual(self.build.getProperty("buildername"), "fakebuilder") 
     155        self.failUnlessEqual(self.build.getProperty("masterprop"), "master") 
    142156 
    143157run_config = """ 
    144158from buildbot.process import factory 
     
    147161s = factory.s 
    148162 
    149163BuildmasterConfig = c = {} 
    150 c['slaves'] = [BuildSlave('bot1', 'sekrit')] 
     164c['slaves'] = [BuildSlave('bot1', 'sekrit', properties={'slprop':'slprop'})] 
    151165c['schedulers'] = [] 
    152166c['slavePortnum'] = 0 
     167c['properties'] = { 'global' : 'global' } 
    153168 
    154169# Note: when run against twisted-1.3.0, this locks up about 5% of the time. I 
    155170# suspect that a command with no output that finishes quickly triggers a race 
     
    160175f1 = factory.BuildFactory([s(ShellCommand, 
    161176                             flunkOnFailure=True, 
    162177                             command=['touch', 
    163                                       WithProperties('%s-slave', 'slavename'), 
     178                                      WithProperties('%s-%s-%s', 
     179                                        'slavename', 'global', 'slprop'), 
    164180                                      ], 
    165181                             workdir='.', 
    166182                             timeout=10, 
     
    180196        d.addCallback(lambda res: self.requestBuild("full1")) 
    181197        d.addCallback(self.failUnlessBuildSucceeded) 
    182198        def _check_touch(res): 
    183             f = os.path.join("slavebase-bot1", "bd1", "bot1-slave") 
     199            f = os.path.join("slavebase-bot1", "bd1", "bot1-global-slprop") 
    184200            self.failUnless(os.path.exists(f)) 
    185201            return res 
    186202        d.addCallback(_check_touch) 
  • buildbot/test/test_run.py

    diff -rN -u old-124/buildbot/test/test_run.py new-124/buildbot/test/test_run.py
    old new  
    699699        self.failIfFlagSet('triggeree_finished') 
    700700        self.failIfFlagSet('triggerer_finished') 
    701701 
     702class PropertyPropagation(RunMixin, TestFlagMixin, unittest.TestCase): 
     703    def setupTest(self, config, builders, checkFn): 
     704        self.clearFlags() 
     705        m = self.master 
     706        m.loadConfig(config) 
     707        m.readConfig = True 
     708        m.startService() 
     709 
     710        c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff") 
     711        m.change_svc.addChange(c) 
     712 
     713        d = self.connectSlave(builders=builders) 
     714        d.addCallback(self.startTimer, 0.5, checkFn) 
     715        return d 
     716 
     717    def startTimer(self, res, time, next_fn): 
     718        d = defer.Deferred() 
     719        reactor.callLater(time, d.callback, None) 
     720        d.addCallback(next_fn) 
     721        return d 
     722 
     723    config_schprop = config_base + """ 
     724from buildbot.scheduler import Scheduler 
     725from buildbot.steps.dummy import Dummy 
     726from buildbot.test.runutils import SetTestFlagStep 
     727from buildbot.process.properties import WithProperties 
     728c['schedulers'] = [ 
     729    Scheduler('mysched', None, 0.1, ['flagcolor'], properties={'color':'red'}), 
     730] 
     731factory = factory.BuildFactory([ 
     732    s(SetTestFlagStep, flagname='testresult',  
     733      value=WithProperties('color=%(color)s sched=%(scheduler)s')), 
     734    ]) 
     735c['builders'] = [{'name': 'flagcolor', 'slavename': 'bot1', 
     736                  'builddir': 'test', 'factory': factory}, 
     737                ] 
     738""" 
     739 
     740    def testScheduler(self): 
     741        def _check(res): 
     742            self.failUnlessEqual(self.getFlag('testresult'), 
     743                'color=red sched=mysched') 
     744        return self.setupTest(self.config_schprop, ['flagcolor'], _check) 
     745 
     746    config_slaveprop = config_base + """ 
     747from buildbot.scheduler import Scheduler 
     748from buildbot.steps.dummy import Dummy 
     749from buildbot.test.runutils import SetTestFlagStep 
     750from buildbot.process.properties import WithProperties 
     751c['schedulers'] = [ 
     752    Scheduler('mysched', None, 0.1, ['flagcolor']) 
     753] 
     754c['slaves'] = [BuildSlave('bot1', 'sekrit', properties={'color':'orange'})] 
     755factory = factory.BuildFactory([ 
     756    s(SetTestFlagStep, flagname='testresult',  
     757      value=WithProperties('color=%(color)s slavename=%(slavename)s')), 
     758    ]) 
     759c['builders'] = [{'name': 'flagcolor', 'slavename': 'bot1', 
     760                  'builddir': 'test', 'factory': factory}, 
     761                ] 
     762""" 
     763    def testSlave(self): 
     764        def _check(res): 
     765            self.failUnlessEqual(self.getFlag('testresult'), 
     766                'color=orange slavename=bot1') 
     767        return self.setupTest(self.config_slaveprop, ['flagcolor'], _check) 
     768 
     769    config_trigger = config_base + """ 
     770from buildbot.scheduler import Triggerable, Scheduler 
     771from buildbot.steps.trigger import Trigger 
     772from buildbot.steps.dummy import Dummy 
     773from buildbot.test.runutils import SetTestFlagStep 
     774from buildbot.process.properties import WithProperties 
     775c['schedulers'] = [ 
     776    Scheduler('triggerer', None, 0.1, ['triggerer'],  
     777        properties={'color':'mauve', 'pls_trigger':'triggeree'}), 
     778    Triggerable('triggeree', ['triggeree'], properties={'color':'invisible'}) 
     779] 
     780triggerer = factory.BuildFactory([ 
     781    s(SetTestFlagStep, flagname='testresult', value='wrongone'), 
     782    s(Trigger, flunkOnFailure=True,  
     783        schedulerNames=[WithProperties('%(pls_trigger)s')], 
     784        set_properties={'color' : WithProperties('%(color)s')}), 
     785    s(SetTestFlagStep, flagname='testresult', value='triggered'), 
     786    ]) 
     787triggeree = factory.BuildFactory([ 
     788    s(SetTestFlagStep, flagname='testresult',  
     789        value=WithProperties('sched=%(scheduler)s color=%(color)s')), 
     790    ]) 
     791c['builders'] = [{'name': 'triggerer', 'slavename': 'bot1', 
     792                  'builddir': 'triggerer', 'factory': triggerer}, 
     793                 {'name': 'triggeree', 'slavename': 'bot1', 
     794                  'builddir': 'triggeree', 'factory': triggeree}] 
     795""" 
     796    def testTrigger(self): 
     797        def _check(res): 
     798            self.failUnlessEqual(self.getFlag('testresult'), 
     799                'sched=triggeree color=mauve') 
     800        return self.setupTest(self.config_trigger,  
     801                ['triggerer', 'triggeree'], _check) 
     802 
     803 
    702804config_test_flag = config_base + """ 
    703805from buildbot.scheduler import Scheduler 
    704806c['schedulers'] = [Scheduler('quick', None, 0.1, ['dummy'])] 
  • buildbot/test/test_steps.py

    diff -rN -u old-124/buildbot/test/test_steps.py new-124/buildbot/test/test_steps.py
    old new  
    236236        s = makeBuildStep("test_steps.Steps.test_getProperty") 
    237237        bs = s.step_status.getBuild() 
    238238 
    239         s.setProperty("prop1", "value1") 
    240         s.setProperty("prop2", "value2") 
     239        s.setProperty("prop1", "value1", "test") 
     240        s.setProperty("prop2", "value2", "test") 
    241241        self.failUnlessEqual(s.getProperty("prop1"), "value1") 
    242242        self.failUnlessEqual(bs.getProperty("prop1"), "value1") 
    243243        self.failUnlessEqual(s.getProperty("prop2"), "value2") 
    244244        self.failUnlessEqual(bs.getProperty("prop2"), "value2") 
    245         s.setProperty("prop1", "value1a") 
     245        s.setProperty("prop1", "value1a", "test") 
    246246        self.failUnlessEqual(s.getProperty("prop1"), "value1a") 
    247247        self.failUnlessEqual(bs.getProperty("prop1"), "value1a") 
    248248 
     
    601601line 23: warning: we are now on line 23 
    602602ending line 
    603603""" 
    604         step.setProperty("warnings-count", 10) 
     604        step.setProperty("warnings-count", 10, "test") 
    605605        log = step.addLog("stdio") 
    606606        log.addStdout(output) 
    607607        log.finish() 
  • buildbot/test/test_vc.py

    diff -rN -u old-124/buildbot/test/test_vc.py new-124/buildbot/test/test_vc.py
    old new  
    541541        self.shouldExist(self.workdir, "subdir", "subdir.c") 
    542542        if self.metadir: 
    543543            self.shouldExist(self.workdir, self.metadir) 
    544         self.failUnlessEqual(bs.getProperty("revision"), None) 
    545         self.failUnlessEqual(bs.getProperty("branch"), None) 
     544        self.failUnlessEqual(bs.getProperty("revision"), '') 
     545        self.failUnlessEqual(bs.getProperty("branch"), '') 
    546546        self.checkGotRevisionIsLatest(bs) 
    547547 
    548548        self.touch(self.workdir, "newfile") 
     
    565565        self.shouldExist(self.workdir, "subdir", "subdir.c") 
    566566        if self.metadir: 
    567567            self.shouldExist(self.workdir, self.metadir) 
    568         self.failUnlessEqual(bs.getProperty("revision"), self.helper.trunk[0]) 
    569         self.failUnlessEqual(bs.getProperty("branch"), None) 
     568        self.failUnlessEqual(bs.getProperty("revision"), self.helper.trunk[0] or '') 
     569        self.failUnlessEqual(bs.getProperty("branch"), '') 
    570570        self.checkGotRevision(bs, self.helper.trunk[0]) 
    571571        # leave the tree at HEAD 
    572572        return self.doBuild() 
     
    585585                           "version=%d" % self.helper.version) 
    586586        if self.metadir: 
    587587            self.shouldExist(self.workdir, self.metadir) 
    588         self.failUnlessEqual(bs.getProperty("revision"), None) 
     588        self.failUnlessEqual(bs.getProperty("revision"), '') 
    589589        self.checkGotRevisionIsLatest(bs) 
    590590 
    591591        self.touch(self.workdir, "newfile") 
     
    609609        self.shouldContain(self.workdir, "version.c", 
    610610                           "version=%d" % self.helper.version) 
    611611        self.shouldExist(self.workdir, "newfile") 
    612         self.failUnlessEqual(bs.getProperty("revision"), None) 
     612        self.failUnlessEqual(bs.getProperty("revision"), '') 
    613613        self.checkGotRevisionIsLatest(bs) 
    614614 
    615615        # now "update" to an older revision 
     
    623623        self.shouldContain(self.workdir, "version.c", 
    624624                           "version=%d" % (self.helper.version-1)) 
    625625        self.failUnlessEqual(bs.getProperty("revision"), 
    626                              self.helper.trunk[-2]) 
     626                             self.helper.trunk[-2] or '') 
    627627        self.checkGotRevision(bs, self.helper.trunk[-2]) 
    628628 
    629629        # now update to the newer revision 
     
    637637        self.shouldContain(self.workdir, "version.c", 
    638638                           "version=%d" % self.helper.version) 
    639639        self.failUnlessEqual(bs.getProperty("revision"), 
    640                              self.helper.trunk[-1]) 
     640                             self.helper.trunk[-1] or '') 
    641641        self.checkGotRevision(bs, self.helper.trunk[-1]) 
    642642 
    643643 
     
    674674        self.shouldNotExist(self.workdir, "newfile") 
    675675        self.touch(self.workdir, "newfile") 
    676676        self.touch(self.vcdir, "newvcfile") 
    677         self.failUnlessEqual(bs.getProperty("revision"), None) 
     677        self.failUnlessEqual(bs.getProperty("revision"), '') 
    678678        self.checkGotRevisionIsLatest(bs) 
    679679 
    680680        d = self.doBuild() # copy rebuild clobbers new files 
     
    687687        self.shouldNotExist(self.workdir, "newfile") 
    688688        self.shouldExist(self.vcdir, "newvcfile") 
    689689        self.shouldExist(self.workdir, "newvcfile") 
    690         self.failUnlessEqual(bs.getProperty("revision"), None) 
     690        self.failUnlessEqual(bs.getProperty("revision"), '') 
    691691        self.checkGotRevisionIsLatest(bs) 
    692692        self.touch(self.workdir, "newfile") 
    693693 
     
    698698    def _do_vctest_export_1(self, bs): 
    699699        self.shouldNotExist(self.workdir, self.metadir) 
    700700        self.shouldNotExist(self.workdir, "newfile") 
    701         self.failUnlessEqual(bs.getProperty("revision"), None) 
     701        self.failUnlessEqual(bs.getProperty("revision"), '') 
    702702        #self.checkGotRevisionIsLatest(bs) 
    703703        # VC 'export' is not required to have a got_revision 
    704704        self.touch(self.workdir, "newfile") 
     
    709709    def _do_vctest_export_2(self, bs): 
    710710        self.shouldNotExist(self.workdir, self.metadir) 
    711711        self.shouldNotExist(self.workdir, "newfile") 
    712         self.failUnlessEqual(bs.getProperty("revision"), None) 
     712        self.failUnlessEqual(bs.getProperty("revision"), '') 
    713713        #self.checkGotRevisionIsLatest(bs) 
    714714        # VC 'export' is not required to have a got_revision 
    715715 
     
    744744        data = open(subdir_c, "r").read() 
    745745        self.failUnlessIn("Hello patched subdir.\\n", data) 
    746746        self.failUnlessEqual(bs.getProperty("revision"), 
    747                              self.helper.trunk[-1]) 
     747                             self.helper.trunk[-1] or '') 
    748748        self.checkGotRevision(bs, self.helper.trunk[-1]) 
    749749 
    750750        # make sure that a rebuild does not use the leftover patched workdir 
     
    758758                                "subdir", "subdir.c") 
    759759        data = open(subdir_c, "r").read() 
    760760        self.failUnlessIn("Hello subdir.\\n", data) 
    761         self.failUnlessEqual(bs.getProperty("revision"), None) 
     761        self.failUnlessEqual(bs.getProperty("revision"), '') 
    762762        self.checkGotRevisionIsLatest(bs) 
    763763 
    764764        # now make sure we can patch an older revision. We need at least two 
     
    783783        data = open(subdir_c, "r").read() 
    784784        self.failUnlessIn("Hello patched subdir.\\n", data) 
    785785        self.failUnlessEqual(bs.getProperty("revision"), 
    786                              self.helper.trunk[-2]) 
     786                             self.helper.trunk[-2] or '') 
    787787        self.checkGotRevision(bs, self.helper.trunk[-2]) 
    788788 
    789789        # now check that we can patch a branch 
     
    802802        data = open(subdir_c, "r").read() 
    803803        self.failUnlessIn("Hello patched subdir.\\n", data) 
    804804        self.failUnlessEqual(bs.getProperty("revision"), 
    805                              self.helper.branch[-1]) 
    806         self.failUnlessEqual(bs.getProperty("branch"), self.helper.branchname) 
     805                             self.helper.branch[-1] or '') 
     806        self.failUnlessEqual(bs.getProperty("branch"), self.helper.branchname or '') 
    807807        self.checkGotRevision(bs, self.helper.branch[-1]) 
    808808 
    809809 
  • docs/Makefile

    diff -rN -u old-124/docs/Makefile new-124/docs/Makefile
    old new  
    22buildbot.info: buildbot.texinfo 
    33        makeinfo --fill-column=70 $< 
    44 
    5 buildbot.html: buildbot.texinfo images-png 
     5#images-png 
     6buildbot.html: buildbot.texinfo 
    67        makeinfo --no-split --html $< 
    78 
    8 buildbot.ps: buildbot.texinfo images-eps 
     9#images-eps 
     10buildbot.ps: buildbot.texinfo 
    911        texi2dvi $< 
    1012        dvips buildbot.dvi 
    1113        rm buildbot.aux buildbot.cp buildbot.cps buildbot.fn buildbot.ky buildbot.log buildbot.pg buildbot.toc buildbot.tp buildbot.vr 
  • docs/buildbot.texinfo

    diff -rN -u old-124/docs/buildbot.texinfo new-124/docs/buildbot.texinfo
    old new  
    109109* BuildRequest::                 
    110110* Builder::                      
    111111* Users::                        
     112* Build Properties:: 
    112113 
    113114Version Control Systems 
    114115 
     
    130131* Loading the Config File::      
    131132* Testing the Config File::      
    132133* Defining the Project::         
    133 * Listing Change Sources and Schedulers::   
     134* Change Sources and Schedulers::   
    134135* Setting the slaveport::        
    135136* Buildslave Specifiers::        
     137* Defining Global Properties::            
    136138* Defining Builders::            
    137139* Defining Status Targets::      
    138140* Debug options::                
    139141 
    140 Listing Change Sources and Schedulers 
     142Change Sources and Schedulers 
    141143 
    142 * Scheduler Types::              
    143 * Build Dependencies::           
     144* Scheduler Scheduler::              
     145* AnyBranchScheduler::           
     146* Dependent Scheduler::           
     147* Periodic Scheduler::           
     148* Nightly Scheduler::           
     149* Try Schedulers::           
     150* Triggerable Scheduler::           
    144151 
    145152Buildslave Specifiers 
    146153 
     
    180187Build Steps 
    181188 
    182189* Common Parameters::            
     190* Using Build Properties:: 
    183191* Source Checkout::              
    184192* ShellCommand::                 
    185193* Simple ShellCommand Subclasses::   
     
    206214* Compile::                      
    207215* Test::                         
    208216* TreeSize::                     
    209 * Build Properties::             
    210217 
    211218Python BuildSteps 
    212219 
     
    12311238* BuildRequest::                 
    12321239* Builder::                      
    12331240* Users::                        
     1241* Build Properties:: 
    12341242@end menu 
    12351243 
    12361244@node Version Control Systems, Schedulers, Concepts, Concepts 
     
    17561764OS-X-based buildslave. 
    17571765 
    17581766 
    1759 @node Users,  , Builder, Concepts 
     1767@node Users, Build Properties, Builder, Concepts 
    17601768@section Users 
    17611769 
    17621770@cindex Users 
     
    18971905alternative way to deliver low-latency high-interruption messages to the 
    18981906developer (like ``hey, you broke the build''). 
    18991907 
     1908@node Build Properties, , Users, Concepts 
     1909@section Build Properties 
     1910@cindex Properties 
     1911 
     1912Each build has a set of ``Build Properties'', which can be used by its 
     1913BuildStep to modify their actions.  These properties, in the form of 
     1914key-value pairs, provide a general framework for dynamically altering 
     1915the behavior of a build based on its circumstances. 
     1916 
     1917Properties come from a number of places: 
     1918@itemize 
     1919@item global configuration -- 
     1920These properties apply to all builds. 
     1921@item schedulers -- 
     1922A scheduler can specify properties available to all the builds it  
     1923starts. 
     1924@item buildslaves -- 
     1925A buildslave can pass properties on to the builds it performs. 
     1926@item builds -- 
     1927A build automatically sets a number of properties on itself. 
     1928@item steps -- 
     1929Steps of a build can set properties that are available to subsequent 
     1930steps.  In particular, source steps set a number of properties. 
     1931@end itemize 
     1932 
     1933Properties are very flexible, and can be used to implement all manner 
     1934of functionality.  Here are some examples: 
     1935 
     1936Most Source steps record the revision that they checked out in 
     1937the @code{got_revision} property.  A later step could use this 
     1938property to specify the name of a fully-built tarball, dropped in an 
     1939easily-acessible directory for later testing. 
     1940 
     1941Some projects want to perform nightly builds as well as in response 
     1942to committed changes.  Such a project would run two schedulers, 
     1943both pointing to the same set of builders, but could provide an 
     1944@code{is_nightly} property so that steps can distinguish the nightly 
     1945builds, perhaps to run more resource-intensive tests. 
     1946 
     1947Some projects have different build processes on different systems. 
     1948Rather than create a build factory for each slave, the steps can use 
     1949buildslave properties to identify the unique aspects of each slave 
     1950and adapt the build process dynamically. 
    19001951 
    19011952@node Configuration, Getting Source Code Changes, Concepts, Top 
    19021953@chapter Configuration 
     
    19251976* Loading the Config File::      
    19261977* Testing the Config File::      
    19271978* Defining the Project::         
    1928 * Listing Change Sources and Schedulers::   
     1979* Change Sources and Schedulers::   
    19291980* Setting the slaveport::        
    19301981* Buildslave Specifiers::        
     1982* Defining Global Properties::            
    19311983* Defining Builders::            
    19321984* Defining Status Targets::      
    19331985* Debug options::                
     
    20492101 
    20502102@example 
    20512103% buildbot checkconfig master.cfg 
    2052 /usr/lib/python2.4/site-packages/buildbot/master.py:559: DeprecationWarning: c['sources'] is deprecated as of 0.7.6 and will be removed by 0.8.0 . Please use c['change_source'] instead. 
     2104/usr/lib/python2.4/site-packages/buildbot/master.py:559: DeprecationWarning: c['sources'] is 
     2105deprecated as of 0.7.6 and will be removed by 0.8.0 . Please use c['change_source'] instead. 
    20532106  warnings.warn(m, DeprecationWarning) 
    20542107Config file is good! 
    20552108@end example 
     
    20712124@end example 
    20722125 
    20732126 
    2074 @node Defining the Project, Listing Change Sources and Schedulers, Testing the Config File, Configuration 
     2127@node Defining the Project, Change Sources and Schedulers, Testing the Config File, Configuration 
    20752128@section Defining the Project 
    20762129 
    20772130There are a couple of basic settings that you use to tell the buildbot 
     
    21102163more information about this buildbot. 
    21112164 
    21122165 
    2113 @node Listing Change Sources and Schedulers, Setting the slaveport, Defining the Project, Configuration 
    2114 @section Listing Change Sources and Schedulers 
     2166@node Change Sources and Schedulers, Setting the slaveport, Defining the Project, Configuration 
     2167@section Change Sources and Schedulers 
    21152168 
    21162169@bcindex c['sources'] 
    21172170@bcindex c['change_source'] 
     
    21292182from buildbot.changes.pb import PBChangeSource 
    21302183c['change_source'] = PBChangeSource() 
    21312184@end example 
     2185@bcindex c['schedulers'] 
    21322186 
    21332187(note: in buildbot-0.7.5 and earlier, this key was named 
    21342188@code{c['sources']}, and required a list. @code{c['sources']} is 
    21352189deprecated as of buildbot-0.7.6 and is scheduled to be removed in a 
    21362190future release). 
    21372191 
    2138 @bcindex c['schedulers'] 
    2139 @code{c['schedulers']} is a list of Scheduler instances, each of which 
    2140 causes builds to be started on a particular set of Builders. The two 
    2141 basic Scheduler classes you are likely to start with are 
    2142 @code{Scheduler} and @code{Periodic}, but you can write a customized 
    2143 subclass to implement more complicated build scheduling. 
    2144  
    2145 The docstring for @code{buildbot.scheduler.Scheduler} is the best 
    2146 place to see all the options that can be used. Type @code{pydoc 
    2147 buildbot.scheduler.Scheduler} to see it, or look in 
    2148 @file{buildbot/scheduler.py} directly. 
     2192@code{c['schedulers']} is a list of Scheduler instances, each 
     2193of which causes builds to be started on a particular set of 
     2194Builders. The two basic Scheduler classes you are likely to start 
     2195with are @code{Scheduler} and @code{Periodic}, but you can write a 
     2196customized subclass to implement more complicated build scheduling. 
    21492197 
    2150 The basic Scheduler takes four arguments: 
     2198Scheduler arguments 
     2199should always be specified by name (as keyword arguments), to allow 
     2200for future expansion: 
     2201 
     2202@example 
     2203sched = Scheduler(name="quick", builderNames=['lin', 'win']) 
     2204@end example 
     2205 
     2206All schedulers have several arguments in common:   
    21512207 
    21522208@table @code 
    21532209@item name 
    2154 Each Scheduler must have a unique name. This is only used in status 
    2155 displays. 
     2210 
     2211Each Scheduler must have a unique name. This is used in status 
     2212displays, and is also available in the build property @code{scheduler}. 
     2213 
     2214@item builderNames 
     2215 
     2216This is the set of builders which this scheduler should trigger, specified 
     2217as a list of names (strings). 
     2218 
     2219@item properties 
     2220@cindex Properties 
     2221 
     2222This is a dictionary specifying properties that will be transmitted 
     2223to all builds started by this scheduler. 
     2224 
     2225@end table 
     2226 
     2227Here is a brief catalog of the available Scheduler types. All these 
     2228Schedulers are classes in @code{buildbot.scheduler}, and the 
     2229docstrings there are the best source of documentation on the arguments 
     2230taken by each one. 
     2231 
     2232@menu 
     2233* Scheduler Scheduler::              
     2234* AnyBranchScheduler::           
     2235* Dependent Scheduler::           
     2236* Periodic Scheduler::           
     2237* Nightly Scheduler::           
     2238* Try Schedulers::           
     2239* Triggerable Scheduler::           
     2240@end menu 
     2241 
     2242@node Scheduler Scheduler 
     2243@subsection Scheduler Scheduler 
     2244@slindex buildbot.scheduler.Scheduler 
     2245 
     2246This is the original and still most popular Scheduler class. It follows 
     2247exactly one branch, and starts a configurable tree-stable-timer after 
     2248each change on that branch. When the timer expires, it starts a build 
     2249on some set of Builders. The Scheduler accepts a @code{fileIsImportant} 
     2250function which can be used to ignore some Changes if they do not 
     2251affect any ``important'' files. 
     2252 
     2253The arguments to this scheduler are: 
     2254 
     2255@table @code 
     2256@item name 
     2257 
     2258@item builderNames 
     2259 
     2260@item properties 
    21562261 
    21572262@item branch 
    21582263This Scheduler will pay attention to a single branch, ignoring Changes 
    21592264that occur on other branches. Setting @code{branch} equal to the 
    2160 special value of @code{None} means it should only pay attention to the 
    2161 default branch. Note that @code{None} is a keyword, not a string, so 
    2162 you want to use @code{None} and not @code{"None"}. 
     2265special value of @code{None} means it should only pay attention to 
     2266the default branch. Note that @code{None} is a keyword, not a string, 
     2267so you want to use @code{None} and not @code{"None"}. 
    21632268 
    21642269@item treeStableTimer 
    21652270The Scheduler will wait for this many seconds before starting the 
     
    21672272restarted, so really the build will be started after a change and then 
    21682273after this many seconds of inactivity. 
    21692274 
    2170 @item builderNames 
    2171 When the tree-stable-timer finally expires, builds will be started on 
    2172 these Builders. Each Builder gets a unique name: these strings must 
    2173 match. 
    2174  
     2275@item fileIsImportant 
     2276A callable which takes one argument, a Change instance, and returns 
     2277@code{True} if the change is worth building, and @code{False} if 
     2278it is not.  Unimportant Changes are accumulated until the build is 
     2279triggered by an important change.  The default value of None means 
     2280that all Changes are important. 
    21752281@end table 
    21762282 
     2283Example: 
     2284 
    21772285@example 
    21782286from buildbot import scheduler 
    2179 quick = scheduler.Scheduler("quick", None, 60, 
    2180                             ["quick-linux", "quick-netbsd"]) 
    2181 full = scheduler.Scheduler("full", None, 5*60, 
    2182                            ["full-linux", "full-netbsd", "full-OSX"]) 
    2183 nightly = scheduler.Periodic("nightly", ["full-solaris"], 24*60*60) 
    2184 c['schedulers'] = [quick, full, nightly] 
     2287quick = scheduler.Scheduler(name="quick",  
     2288                    branch=None,  
     2289                    treeStableTimer=60, 
     2290                    builderNames=["quick-linux", "quick-netbsd"]) 
     2291full = scheduler.Scheduler(name="full",  
     2292                    branch=None, 
     2293                    treeStableTimer=5*60, 
     2294                    builderNames=["full-linux", "full-netbsd", "full-OSX"]) 
     2295c['schedulers'] = [quick, full] 
    21852296@end example 
    21862297 
    2187 In this example, the two ``quick'' builds are triggered 60 seconds 
     2298In this example, the two ``quick'' builders are triggered 60 seconds 
    21882299after the tree has been changed. The ``full'' builds do not run quite 
    21892300so quickly (they wait 5 minutes), so hopefully if the quick builds 
    21902301fail due to a missing file or really simple typo, the developer can 
    21912302discover and fix the problem before the full builds are started. Both 
    2192 Schedulers only pay attention to the default branch: any changes on 
    2193 other branches are ignored by these Schedulers. Each Scheduler 
     2303Schedulers only pay attention to the default branch: any changes 
     2304on other branches are ignored by these Schedulers. Each Scheduler 
    21942305triggers a different set of Builders, referenced by name. 
    21952306 
    2196 The third Scheduler in this example just runs the full solaris build 
    2197 once per day. (note that this Scheduler only lets you control the time 
    2198 between builds, not the absolute time-of-day of each Build, so this 
    2199 could easily wind up a ``daily'' or ``every afternoon'' scheduler 
    2200 depending upon when it was first activated). 
    2201  
    2202 @menu 
    2203 * Scheduler Types::              
    2204 * Build Dependencies::           
    2205 @end menu 
    2206  
    2207 @node Scheduler Types, Build Dependencies, Listing Change Sources and Schedulers, Listing Change Sources and Schedulers 
    2208 @subsection Scheduler Types 
    2209  
    2210 @slindex buildbot.scheduler.Scheduler 
     2307@node AnyBranchScheduler 
     2308@subsection AnyBranchScheduler 
    22112309@slindex buildbot.scheduler.AnyBranchScheduler 
    2212 @slindex buildbot.scheduler.Periodic 
    2213 @slindex buildbot.scheduler.Nightly 
    22142310 
    2215 Here is a brief catalog of the available Scheduler types. All these 
    2216 Schedulers are classes in @code{buildbot.scheduler}, and the 
    2217 docstrings there are the best source of documentation on the arguments 
    2218 taken by each one. 
    2219  
    2220 @table @code 
    2221 @item Scheduler 
    2222 This is the default Scheduler class. It follows exactly one branch, 
    2223 and starts a configurable tree-stable-timer after each change on that 
    2224 branch. When the timer expires, it starts a build on some set of 
    2225 Builders. The Scheduler accepts a @code{fileIsImportant} function 
    2226 which can be used to ignore some Changes if they do not affect any 
    2227 ``important'' files. 
    2228  
    2229 @item AnyBranchScheduler 
    22302311This scheduler uses a tree-stable-timer like the default one, but 
    22312312follows multiple branches at once. Each branch gets a separate timer. 
    22322313 
    2233 @item Dependent 
    2234 This scheduler watches an ``upstream'' Scheduler. When all the 
    2235 Builders launched by that Scheduler successfully finish, the Dependent 
    2236 scheduler is triggered. The next section (@pxref{Build Dependencies}) 
    2237 describes this scheduler in more detail. 
    2238  
    2239 @item Triggerable 
    2240 This scheduler does nothing until it is triggered by a Trigger 
    2241 step in another build.  This facilitates a more general form of 
    2242 build dependencies, as described in the next section (@pxref{Build 
    2243 Dependencies}). 
     2314The arguments to this scheduler are: 
    22442315 
    2245 @item Periodic 
    2246 This simple scheduler just triggers a build every N seconds. 
     2316@table @code 
     2317@item name 
    22472318 
    2248 @item Nightly 
    2249 This is highly configurable periodic build scheduler, which triggers a 
    2250 build at particular times of day, week, month, or year. The 
    2251 configuration syntax is very similar to the well-known @code{crontab} 
    2252 format, in which you provide values for minute, hour, day, and month 
    2253 (some of which can be wildcards), and a build is triggered whenever 
    2254 the current time matches the given constraints. This can run a build 
    2255 every night, every morning, every weekend, alternate Thursdays, on 
    2256 your boss's birthday, etc. 
     2319@item builderNames 
    22572320 
    2258 @item Try_Jobdir / Try_Userpass 
    2259 This scheduler allows developers to use the @code{buildbot try} 
    2260 command to trigger builds of code they have not yet committed. See 
    2261 @ref{try} for complete details. 
     2321@item properties 
    22622322 
    2263 @end table 
     2323@item branches 
     2324This Scheduler will pay attention to any number of branches, ignoring 
     2325Changes that occur on other branches. Branches are specified just as 
     2326for the @code{Scheduler} class. 
    22642327 
    2265 @node Build Dependencies,  , Scheduler Types, Listing Change Sources and Schedulers 
    2266 @subsection Build Dependencies 
     2328@item treeStableTimer 
     2329The Scheduler will wait for this many seconds before starting the 
     2330build. If new changes are made during this interval, the timer will be 
     2331restarted, so really the build will be started after a change and then 
     2332after this many seconds of inactivity. 
     2333 
     2334@item fileIsImportant 
     2335A callable which takes one argument, a Change instance, and returns 
     2336@code{True} if the change is worth building, and @code{False} if 
     2337it is not.  Unimportant Changes are accumulated until the build is 
     2338triggered by an important change.  The default value of None means 
     2339that all Changes are important. 
     2340@end table 
    22672341 
     2342@node Dependent Scheduler 
     2343@subsection Dependent Scheduler 
    22682344@cindex Dependent 
    22692345@cindex Dependencies 
    22702346@slindex buildbot.scheduler.Dependent 
    2271 @slindex buildbot.scheduler.Triggerable 
    22722347 
    22732348It is common to wind up with one kind of build which should only be 
    22742349performed if the same source code was successfully handled by some 
     
    22842359that is used by some other Builder, you'd want to make sure the 
    22852360consuming Build is run @emph{after} the producing one. 
    22862361 
    2287 You can use @code{Dependencies} to express this relationship to the 
    2288 Buildbot. There is a special kind of Scheduler named 
     2362You can use ``Dependencies'' to express this relationship 
     2363to the Buildbot. There is a special kind of Scheduler named 
    22892364@code{scheduler.Dependent} that will watch an ``upstream'' Scheduler 
    2290 for builds to complete successfully (on all of its Builders). Each 
    2291 time that happens, the same source code (i.e. the same 
    2292 @code{SourceStamp}) will be used to start a new set of builds, on a 
    2293 different set of Builders. This ``downstream'' scheduler doesn't pay 
    2294 attention to Changes at all, it only pays attention to the upstream 
    2295 scheduler. 
     2365for builds to complete successfully (on all of its Builders). Each time 
     2366that happens, the same source code (i.e. the same @code{SourceStamp}) 
     2367will be used to start a new set of builds, on a different set of 
     2368Builders. This ``downstream'' scheduler doesn't pay attention to 
     2369Changes at all. It only pays attention to the upstream scheduler. 
     2370 
     2371If the build fails on any of the Builders in the upstream set, 
     2372the downstream builds will not fire.  Note that, for SourceStamps 
     2373generated by a ChangeSource, the @code{revision} is None, meaning HEAD. 
     2374If any changes are committed between the time the upstream scheduler 
     2375begins its build and the time the dependent scheduler begins its 
     2376build, then those changes will be included in the downstream build. 
     2377See the @pxref{Triggerable Scheduler} for a more flexible dependency 
     2378mechanism that can avoid this problem. 
     2379 
     2380The arguments to this scheduler are: 
     2381 
     2382@table @code 
     2383@item name 
     2384 
     2385@item builderNames 
     2386 
     2387@item properties 
     2388 
     2389@item upstream 
     2390The upstream scheduler to watch.  Note that this is an ``instance'', 
     2391not the name of the scheduler. 
     2392@end table 
    22962393 
    2297 If the SourceStamp fails on any of the Builders in the upstream set, 
    2298 the downstream builds will not fire. 
     2394Example: 
    22992395 
    23002396@example 
    23012397from buildbot import scheduler 
    2302 tests = scheduler.Scheduler("tests", None, 5*60, 
     2398tests = scheduler.Scheduler("just-tests", None, 5*60, 
    23032399                            ["full-linux", "full-netbsd", "full-OSX"]) 
    2304 package = scheduler.Dependent("package", 
    2305                               tests, # upstream scheduler 
     2400package = scheduler.Dependent("build-package", 
     2401                              tests, # upstream scheduler -- no quotes! 
    23062402                              ["make-tarball", "make-deb", "make-rpm"]) 
    23072403c['schedulers'] = [tests, package] 
    23082404@end example 
    23092405 
    2310 Note that @code{Dependent}'s upstream scheduler argument is given as a 
    2311 @code{Scheduler} @emph{instance}, not a name. This makes it impossible 
    2312 to create circular dependencies in the config file. 
    2313  
    2314 A more general way to coordinate builds is by ``triggering'' 
    2315 schedulers from builds. The Triggerable waits to be triggered by a 
    2316 Trigger step (@pxref{Triggering Schedulers}) in another build. That 
    2317 step can optionally wait for the scheduler's builds to complete. This 
     2406@node Periodic Scheduler 
     2407@subsection Periodic Scheduler 
     2408@slindex buildbot.scheduler.Periodic 
     2409 
     2410This simple scheduler just triggers a build every N seconds. 
     2411 
     2412The arguments to this scheduler are: 
     2413 
     2414@table @code 
     2415@item name 
     2416 
     2417@item builderNames 
     2418 
     2419@item properties 
     2420 
     2421@item periodicBuildTimer 
     2422The time, in seconds, after which to start a build. 
     2423@end table 
     2424 
     2425Example: 
     2426 
     2427@example 
     2428from buildbot import scheduler 
     2429nightly = scheduler.Periodic(name="nightly", 
     2430                builderNames=["full-solaris"], 
     2431                periodicBuildTimer=24*60*60) 
     2432c['schedulers'] = [nightly] 
     2433@end example 
     2434 
     2435The Scheduler in this example just runs the full solaris build once 
     2436per day. Note that this Scheduler only lets you control the time 
     2437between builds, not the absolute time-of-day of each Build, so this 
     2438could easily wind up a ``daily'' or ``every afternoon'' scheduler 
     2439depending upon when it was first activated. 
     2440 
     2441@node Nightly Scheduler 
     2442@subsection Nightly Scheduler 
     2443@slindex buildbot.scheduler.Nightly 
     2444 
     2445This is highly configurable periodic build scheduler, which triggers 
     2446a build at particular times of day, week, month, or year. The 
     2447configuration syntax is very similar to the well-known @code{crontab} 
     2448format, in which you provide values for minute, hour, day, and month 
     2449(some of which can be wildcards), and a build is triggered whenever 
     2450the current time matches the given constraints. This can run a build 
     2451every night, every morning, every weekend, alternate Thursdays, 
     2452on your boss's birthday, etc. 
     2453 
     2454Pass some subset of @code{minute}, @code{hour}, @code{dayOfMonth}, 
     2455@code{month}, and @code{dayOfWeek}; each may be a single number or 
     2456a list of valid values. The builds will be triggered whenever the 
     2457current time matches these values. Wildcards are represented by a 
     2458'*' string. All fields default to a wildcard except 'minute', so 
     2459with no fields this defaults to a build every hour, on the hour. 
     2460The full list of parameters is: 
     2461 
     2462@table @code 
     2463@item name 
     2464 
     2465@item builderNames 
     2466 
     2467@item properties 
     2468 
     2469@item branch 
     2470The branch to build, just as for @code{Scheduler}. 
     2471 
     2472@item minute 
     2473The minute of the hour on which to start the build.  This defaults 
     2474to 0, meaning an hourly build. 
     2475 
     2476@item hour 
     2477The hour of the day on which to start the build, in 24-hour notation. 
     2478This defaults to *, meaning every hour. 
     2479 
     2480@item month 
     2481The month in which to start the build, with January = 1.  This defaults 
     2482to *, meaning every month. 
     2483 
     2484@item dayOfWeek 
     2485The day of the week to start a build, with Monday = 0.  This defauls 
     2486to *, meaning every day of the week. 
     2487@end table 
     2488 
     2489For example, the following master.cfg clause will cause a build to be 
     2490started every night at 3:00am: 
     2491 
     2492@example 
     2493s = scheduler.Nightly(name='nightly', 
     2494        builderNames=['builder1', 'builder2'], 
     2495        hour=3,  
     2496        minute=0) 
     2497@end example 
     2498 
     2499This scheduler will perform a build each monday morning at 6:23am and 
     2500again at 8:23am: 
     2501 
     2502@example 
     2503s = scheduler.Nightly(name='BeforeWork', 
     2504         builderNames=['builder1'], 
     2505         dayOfWeek=0, 
     2506         hour=[6,8], 
     2507         minute=23) 
     2508@end example 
     2509 
     2510The following runs a build every two hours, using Python's @code{range} 
     2511function: 
     2512 
     2513@example 
     2514s = Nightly(name='every2hours', 
     2515        builderNames=['builder1'], 
     2516        hour=range(0, 24, 2)) 
     2517@end example 
     2518 
     2519Finally, this example will run only on December 24th: 
     2520 
     2521@example 
     2522s = Nightly(name='SleighPreflightCheck', 
     2523        builderNames=['flying_circuits', 'radar'], 
     2524        month=12, 
     2525        dayOfMonth=24, 
     2526        hour=12, 
     2527        minute=0) 
     2528@end example 
     2529 
     2530@node Try Schedulers 
     2531@subsection Try Schedulers 
     2532@slindex buildbot.scheduler.Try_Jobdir 
     2533@slindex buildbot.scheduler.Try_Userpass 
     2534 
     2535This scheduler allows developers to use the @code{buildbot try} 
     2536command to trigger builds of code they have not yet committed. See 
     2537@ref{try} for complete details. 
     2538 
     2539Two implementations are available: @code{Try_Jobdir} and 
     2540@code{Try_Userpass}.  The former monitors a job directory, specified 
     2541by the @code{jobdir} parameter, while the latter listens for PB  
     2542connections on a specific @code{port}, and authenticates against 
     2543@code{userport}. 
     2544 
     2545@node Triggerable Scheduler 
     2546@subsection Triggerable Scheduler 
     2547@cindex Triggers 
     2548@slindex buildbot.scheduler.Triggerable 
     2549 
     2550The @code{Triggerable} scheduler waits to be triggered by a Trigger 
     2551step (see @ref{Triggering Schedulers}) in another build. That step 
     2552can optionally wait for the scheduler's builds to complete. This 
    23182553provides two advantages over Dependent schedulers. First, the same 
    23192554scheduler can be triggered from multiple builds. Second, the ability 
    23202555to wait for a Triggerable's builds to complete provides a form of 
    2321 "subroutine call", where one or more builds can "call" a scheduler to 
    2322 perform some work for them, perhaps on other buildslaves. 
     2556"subroutine call", where one or more builds can "call" a scheduler 
     2557to perform some work for them, perhaps on other buildslaves. 
     2558 
     2559The parameters are just the basics: 
     2560 
     2561@table @code 
     2562@item name 
     2563@item builderNames 
     2564@item properties 
     2565@end table 
     2566 
     2567This class is only useful in conjunction with the @code{Trigger} step. 
     2568Here is a fully-worked example: 
    23232569 
    23242570@example 
    23252571from buildbot import scheduler 
    23262572from buildbot.steps import trigger 
    23272573 
    2328 checkin = scheduler.Scheduler("checkin", None, 5*60, ["checkin"]) 
    2329 nightly = scheduler.Scheduler("nightly", ... , ["nightly"]) 
    2330  
    2331 mktarball = scheduler.Triggerable("mktarball", 
    2332                                   ["mktarball"]) 
    2333 build = scheduler.Triggerable("build-all-platforms", 
    2334                               ["build-all-platforms"]) 
    2335 test = scheduler.Triggerable("distributed-test", 
    2336                              ["distributed-test"]) 
    2337 package = scheduler.Triggerable("package-all-platforms", 
    2338                                 ["package-all-platforms"]) 
     2574checkin = scheduler.Scheduler(name="checkin", 
     2575            branch=None, 
     2576            treeStableTimer=5*60, 
     2577            builderNames=["checkin"]) 
     2578nightly = scheduler.Nightly(name='nightly', 
     2579            builderNames=['nightly'], 
     2580            hour=3,  
     2581            minute=0) 
     2582 
     2583mktarball = scheduler.Triggerable(name="mktarball", 
     2584                builderNames=["mktarball"]) 
     2585build = scheduler.Triggerable(name="build-all-platforms", 
     2586                builderNames=["build-all-platforms"]) 
     2587test = scheduler.Triggerable(name="distributed-test", 
     2588                builderNames=["distributed-test"]) 
     2589package = scheduler.Triggerable(name="package-all-platforms", 
     2590                builderNames=["package-all-platforms"]) 
    23392591 
    23402592c['schedulers'] = [checkin, nightly, build, test, package] 
    23412593 
     2594# on checkin, make a tarball, build it, and test it 
    23422595checkin_factory = factory.BuildFactory() 
    23432596f.addStep(trigger.Trigger('mktarball', schedulers=['mktarball'], 
    23442597                                       waitForFinish=True) 
     
    23472600f.addStep(trigger.Trigger('test', schedulers=['distributed-test'], 
    23482601                                  waitForFinish=True) 
    23492602 
     2603# and every night, make a tarball, build it, and package it 
    23502604nightly_factory = factory.BuildFactory() 
    23512605f.addStep(trigger.Trigger('mktarball', schedulers=['mktarball'], 
    23522606                                       waitForFinish=True) 
     
    23562610                                     waitForFinish=True) 
    23572611@end example 
    23582612 
    2359 @node Setting the slaveport, Buildslave Specifiers, Listing Change Sources and Schedulers, Configuration 
     2613@node Setting the slaveport, Buildslave Specifiers, Change Sources and Schedulers, Configuration 
    23602614@section Setting the slaveport 
    23612615 
    23622616@bcindex c['slavePortnum'] 
     
    23932647@code{localhost:10000}. 
    23942648 
    23952649 
    2396 @node Buildslave Specifiers, Defining Builders, Setting the slaveport, Configuration 
     2650@node Buildslave Specifiers 
    23972651@section Buildslave Specifiers 
    23982652 
    23992653@bcindex c['slaves'] 
     
    24042658values that need to be provided to the buildslave administrator when 
    24052659they create the buildslave. 
    24062660 
    2407 @example 
    2408 from buildbot.buildslave import BuildSlave 
    2409 c['slaves'] = [BuildSlave('bot-solaris', 'solarispasswd'), 
    2410                BuildSlave('bot-bsd', 'bsdpasswd'), 
    2411               ] 
    2412 @end example 
    2413  
    24142661The slavenames must be unique, of course. The password exists to 
    24152662prevent evildoers from interfering with the buildbot by inserting 
    24162663their own (broken) buildslaves into the system and thus displacing the 
     
    24202667will be rejected when they attempt to connect, and a message 
    24212668describing the problem will be put in the log file (see @ref{Logfiles}). 
    24222669 
    2423 The @code{BuildSlave} constructor can take an optional 
    2424 @code{max_builds} parameter to limit the number of builds that it will 
    2425 execute simultaneously: 
     2670@example 
     2671from buildbot.buildslave import BuildSlave 
     2672c['slaves'] = [BuildSlave('bot-solaris', 'solarispasswd') 
     2673               BuildSlave('bot-bsd', 'bsdpasswd') 
     2674              ] 
     2675@end example 
     2676 
     2677@cindex Properties 
     2678@code{BuildSlave} objects can also be created with an optional 
     2679@code{properties} argument, a dictionary specifying properties that 
     2680will be available to any builds performed on this slave.  For example: 
     2681 
     2682@example 
     2683from buildbot.buildslave import BuildSlave 
     2684c['slaves'] = [BuildSlave('bot-solaris', 'solarispasswd', 
     2685                    properties=@{'os':'solaris'@}), 
     2686              ] 
     2687@end example 
     2688 
     2689The @code{BuildSlave} constructor can also take an optional 
     2690@code{max_builds} parameter to limit the number of builds that it 
     2691will execute simultaneously: 
    24262692 
    24272693@example 
    24282694from buildbot.buildslave import BuildSlave 
     
    24962762              ] 
    24972763@end example 
    24982764 
     2765@node Defining Global Properties 
     2766@section Defining Global Properties 
     2767@bcindex c['properties'] 
     2768@cindex Properties 
     2769 
     2770The @code{'properties'} configuration key defines a dictionary 
     2771of properties that will be available to all builds started by the 
     2772buildmaster: 
    24992773 
    2500 @node Defining Builders, Defining Status Targets, Buildslave Specifiers, Configuration 
     2774@example 
     2775c['properties'] = @{ 
     2776    'Widget-version' : '1.2', 
     2777    'release-stage' : 'alpha' 
     2778@} 
     2779@end example 
     2780 
     2781@node Defining Builders 
    25012782@section Defining Builders 
    25022783 
    25032784@bcindex c['builders'] 
     
    25772858@end table 
    25782859 
    25792860 
    2580 @node Defining Status Targets, Debug options, Defining Builders, Configuration 
     2861@node Defining Status Targets 
    25812862@section Defining Status Targets 
    25822863 
    25832864The Buildmaster has a variety of ways to present build status to 
     
    29693250in a place where the buildmaster can find them, and configure the 
    29703251buildmaster to parse the messages correctly. Once that is in place, 
    29713252the email parser will create Change objects and deliver them to the 
    2972 Schedulers (see @pxref{Scheduler Types}) just like any other 
    2973 ChangeSource. 
     3253Schedulers (see @pxref{Change Sources and Schedulers}) just 
     3254like any other ChangeSource. 
    29743255 
    29753256There are two components to setting up an email-based ChangeSource. 
    29763257The first is to route the email messages to the buildmaster, which is 
     
    37214002* Build Factories::              
    37224003@end menu 
    37234004 
    3724 @node Build Steps, Interlocks, Build Process, Build Process 
     4005@node Build Steps 
    37254006@section Build Steps 
    37264007 
    37274008@code{BuildStep}s are usually specified in the buildmaster's 
     
    37564037 
    37574038@menu 
    37584039* Common Parameters::            
     4040* Using Build Properties:: 
    37594041* Source Checkout::              
    37604042* ShellCommand::                 
    37614043* Simple ShellCommand Subclasses::   
     
    37654047* Writing New BuildSteps::       
    37664048@end menu 
    37674049 
    3768 @node Common Parameters, Source Checkout, Build Steps, Build Steps 
     4050@node Common Parameters 
    37694051@subsection Common Parameters 
    37704052 
    37714053The standard @code{Build} runs a series of @code{BuildStep}s in order, 
     
    38164098 
    38174099@end table 
    38184100 
     4101@node Using Build Properties 
     4102@subsection Using Build Properties 
     4103@cindex Properties 
     4104 
     4105Build properties are a generalized way to provide configuration 
     4106information to build steps; see @ref{Build Properties}. 
     4107 
     4108Some build properties are inherited from external sources -- global 
     4109properties, schedulers, or buildslaves.  Some build properties are 
     4110set when the build starts, such as the SourceStamp information. Other 
     4111properties can be set by BuildSteps as they run, for example the 
     4112various Source steps will set the @code{got_revision} property to the 
     4113source revision that was actually checked out (which can be useful 
     4114when the SourceStamp in use merely requested the ``latest revision'': 
     4115@code{got_revision} will tell you what was actually built). 
     4116 
     4117In custom BuildSteps, you can get and set the build properties with 
     4118the @code{getProperty}/@code{setProperty} methods. Each takes a string 
     4119for the name of the property, and returns or accepts an 
     4120arbitrary@footnote{Build properties are serialized along with the 
     4121build results, so they must be serializable. For this reason, the 
     4122value of any build property should be simple inert data: strings, 
     4123numbers, lists, tuples, and dictionaries. They should not contain 
     4124class instances.} object. For example: 
     4125 
     4126@example 
     4127class MakeTarball(ShellCommand): 
     4128    def start(self): 
     4129        if self.getProperty("os") == "win": 
     4130            self.setCommand([ ... ]) # windows-only command 
     4131        else: 
     4132            self.setCommand([ ... ]) # equivalent for other systems 
     4133        ShellCommand.start(self) 
     4134@end example 
     4135 
     4136@heading WithProperties 
     4137@cindex WithProperties 
     4138 
     4139You can use build properties in ShellCommands by using the 
     4140@code{WithProperties} wrapper when setting the arguments of the 
     4141ShellCommand. This interpolates the named build properties into the 
     4142generated shell command. You can also use a @code{WithProperties} 
     4143as the @code{workdir=} argument: this allows the working directory 
     4144for a command to be varied for each build, depending upon various 
     4145build properties. 
     4146 
     4147@example 
     4148from buildbot.steps.shell import ShellCommand 
     4149from buildbot.process.properties import WithProperties 
     4150 
     4151f.addStep(ShellCommand, 
     4152          command=["tar", "czf", 
     4153                   WithProperties("build-%s.tar.gz", "revision"), 
     4154                   "source"]) 
     4155@end example 
     4156 
     4157If this BuildStep were used in a tree obtained from Subversion, it 
     4158would create a tarball with a name like @file{build-1234.tar.gz}. 
     4159 
     4160The @code{WithProperties} function does @code{printf}-style string 
     4161interpolation, using strings obtained by calling 
     4162@code{build.getProperty(propname)}. Note that for every @code{%s} (or 
     4163@code{%d}, etc), you must have exactly one additional argument to 
     4164indicate which build property you want to insert. 
     4165 
     4166You can also use python dictionary-style string interpolation by using 
     4167the @code{%(propname)s} syntax. In this form, the property name goes 
     4168in the parentheses, and WithProperties takes @emph{no} additional 
     4169arguments: 
     4170 
     4171@example 
     4172f.addStep(ShellCommand, 
     4173          command=["tar", "czf", 
     4174                   WithProperties("build-%(revision)s.tar.gz"), 
     4175                   "source"]) 
     4176@end example 
     4177 
     4178Don't forget the extra ``s'' after the closing parenthesis! This is 
     4179the cause of many confusing errors.  
     4180 
     4181Note that, like python, you can either do positional-argument 
     4182interpolation @emph{or} keyword-argument interpolation, not both. Thus 
     4183you cannot use a string like 
     4184@code{WithProperties("foo-%(revision)s-%s", "branch")}. 
     4185 
     4186Most step parameters accept @code{WithProperties}.  Please file bugs 
     4187for any parameters which do not. 
     4188 
     4189@heading Common Build Properties 
     4190 
     4191The following build properties are set when the build is started, and 
     4192are available to all steps. 
     4193 
     4194@table @code 
     4195@item branch 
     4196 
     4197This comes from the build's SourceStamp, and describes which branch is 
     4198being checked out. This will be @code{None} (which interpolates into 
     4199@code{WithProperties} as an empty string) if the build is on the 
     4200default branch, which is generally the trunk. Otherwise it will be a 
     4201string like ``branches/beta1.4''. The exact syntax depends upon the VC 
     4202system being used. 
     4203 
     4204@item revision 
     4205 
     4206This also comes from the SourceStamp, and is the revision of the 
     4207source code tree that was requested from the VC system. When a build 
     4208is requested of a specific revision (as is generally the case when 
     4209the build is triggered by Changes), this will contain the revision 
     4210specification. The syntax depends upon the VC system in use: for SVN 
     4211it is an integer, for Mercurial it is a short string, for Darcs it 
     4212is a rather large string, etc. 
     4213 
     4214If the ``force build'' button was pressed, the revision will be 
     4215@code{None}, which means to use the most recent revision available. 
     4216This is a ``trunk build''. This will be interpolated as an empty 
     4217string. 
     4218 
     4219@item got_revision 
     4220 
     4221This is set when a Source step checks out the source tree, and 
     4222provides the revision that was actually obtained from the VC system. 
     4223In general this should be the same as @code{revision}, except for 
     4224trunk builds, where @code{got_revision} indicates what revision was 
     4225current when the checkout was performed. This can be used to rebuild 
     4226the same source code later. 
     4227 
     4228Note that for some VC systems (Darcs in particular), the revision is a 
     4229large string containing newlines, and is not suitable for interpolation 
     4230into a filename. 
     4231 
     4232@item buildername 
     4233 
     4234This is a string that indicates which Builder the build was a part of. 
     4235The combination of buildername and buildnumber uniquely identify a 
     4236build. 
     4237 
     4238@item buildnumber 
     4239 
     4240Each build gets a number, scoped to the Builder (so the first build 
     4241performed on any given Builder will have a build number of 0). This 
     4242integer property contains the build's number. 
     4243 
     4244@item slavename 
     4245 
     4246This is a string which identifies which buildslave the build is 
     4247running on. 
     4248 
     4249@item scheduler 
     4250 
     4251If the build was started from a scheduler, then this property will 
     4252contain the name of that scheduler. 
     4253 
     4254@end table 
     4255 
    38194256 
    3820 @node Source Checkout, ShellCommand, Common Parameters, Build Steps 
     4257@node Source Checkout 
    38214258@subsection Source Checkout 
    38224259 
    38234260The first step of any build is typically to acquire the source code 
     
    44754912* Compile::                      
    44764913* Test::                         
    44774914* TreeSize::                     
    4478 * Build Properties::             
    44794915@end menu 
    44804916 
    44814917@node Configure, Compile, Simple ShellCommand Subclasses, Simple ShellCommand Subclasses 
     
    45314967This is meant to handle unit tests. The default command is @code{make 
    45324968test}, and the @code{warnOnFailure} flag is set. 
    45334969 
    4534 @node TreeSize, Build Properties, Test, Simple ShellCommand Subclasses 
     4970@node TreeSize, , Test, Simple ShellCommand Subclasses 
    45354971@subsubsection TreeSize 
    45364972 
    45374973@bsindex buildbot.steps.shell.TreeSize 
     
    45424978property named 'tree-size-KiB' with the same value. 
    45434979 
    45444980 
    4545 @node Build Properties,  , TreeSize, Simple ShellCommand Subclasses 
    4546 @subsubsection Build Properties 
    4547  
    4548 @cindex build properties 
    4549  
    4550 Each build has a set of ``Build Properties'', which can be used by its 
    4551 BuildStep to modify their actions. For example, the SVN revision 
    4552 number of the source code being built is available as a build 
    4553 property, and a ShellCommand step could incorporate this number into a 
    4554 command which create a numbered release tarball. 
    4555  
    4556 Some build properties are set when the build starts, such as the 
    4557 SourceStamp information. Other properties can be set by BuildSteps as 
    4558 they run, for example the various Source steps will set the 
    4559 @code{got_revision} property to the source revision that was actually 
    4560 checked out (which can be useful when the SourceStamp in use merely 
    4561 requested the ``latest revision'': @code{got_revision} will tell you 
    4562 what was actually built). 
    4563  
    4564 @itemize 
    4565 @item buildername 
    4566 Name of this builder 
    4567 @item buildnumber 
    4568 Number of this build (numbers are unique within the builder) 
    4569 @item branch 
    4570 Branch of the source being built, from the SourceStamp 
    4571 @item revision 
    4572 Revision of the source being built, from the SourceStamp, as a string 
    4573 @item scheduler 
    4574 The name of the scheduler that invoked this build 
    4575 @item slavename 
    4576 The name of the buildslave performing this build 
    4577 @end itemize 
    4578  
    4579 In custom BuildSteps, you can get and set the build properties with 
    4580 the @code{getProperty}/@code{setProperty} methods. Each takes a string 
    4581 for the name of the property, and returns or accepts an 
    4582 arbitrary@footnote{Build properties are serialized along with the 
    4583 build results, so they must be serializable. For this reason, the 
    4584 value of any build property should be simple inert data: strings, 
    4585 numbers, lists, tuples, and dictionaries. They should not contain 
    4586 class instances.} object. For example: 
    4587  
    4588 @example 
    4589 class MakeTarball(ShellCommand): 
    4590     def start(self): 
    4591         self.setCommand(["tar", "czf", 
    4592                          "build-%s.tar.gz" % self.getProperty("revision"), 
    4593                          "source"]) 
    4594         ShellCommand.start(self) 
    4595 @end example 
    4596  
    4597 @cindex WithProperties 
    4598  
    4599 You can use build properties in ShellCommands by using the 
    4600 @code{WithProperties} wrapper when setting the arguments of the 
    4601 ShellCommand. This interpolates the named build properties into the 
    4602 generated shell command. You can also use a @code{WithProperties} as 
    4603 the @code{workdir=} argument: this allows the working directory for a 
    4604 command to be varied for each build, depending upon various build 
    4605 properties. 
    4606  
    4607 @example 
    4608 from buildbot.steps.shell import ShellCommand, WithProperties 
    4609  
    4610 f.addStep(ShellCommand, 
    4611           command=["tar", "czf", 
    4612                    WithProperties("build-%s.tar.gz", "revision"), 
    4613                    "source"]) 
    4614 @end example 
    4615  
    4616 If this BuildStep were used in a tree obtained from Subversion, it 
    4617 would create a tarball with a name like @file{build-1234.tar.gz}. 
    4618  
    4619 The @code{WithProperties} function does @code{printf}-style string 
    4620 interpolation, using strings obtained by calling 
    4621 @code{build.getProperty(propname)}. Note that for every @code{%s} (or 
    4622 @code{%d}, etc), you must have exactly one additional argument to 
    4623 indicate which build property you want to insert. 
    4624  
    4625  
    4626 You can also use python dictionary-style string interpolation by using 
    4627 the @code{%(propname)s} syntax. In this form, the property name goes 
    4628 in the parentheses, and WithProperties takes @emph{no} additional 
    4629 arguments: 
    4630  
    4631 @example 
    4632 f.addStep(ShellCommand, 
    4633           command=["tar", "czf", 
    4634                    WithProperties("build-%(revision)s.tar.gz"), 
    4635                    "source"]) 
    4636 @end example 
    4637  
    4638 Don't forget the extra ``s'' after the closing parenthesis! This is 
    4639 the cause of many confusing errors. Also note that you can only use 
    4640 WithProperties in the list form of the command= definition. You cannot 
    4641 currently use it in the (discouraged) @code{command="stuff"} 
    4642 single-string form. However, you can use something like 
    4643 @code{command=["/bin/sh", "-c", "stuff", WithProperties(stuff)]} to 
    4644 use both shell expansion and WithProperties interpolation. 
    4645  
    4646 Note that, like python, you can either do positional-argument 
    4647 interpolation @emph{or} keyword-argument interpolation, not both. Thus 
    4648 you cannot use a string like 
    4649 @code{WithProperties("foo-%(revision)s-%s", "branch")}. 
    4650  
    4651 At the moment, the only way to set build properties is by writing a 
    4652 custom BuildStep. 
    4653  
    4654 @heading Common Build Properties 
    4655  
    4656 The following build properties are set when the build is started, and 
    4657 are available to all steps. 
    4658  
    4659 @table @code 
    4660 @item branch 
    4661  
    4662 This comes from the build's SourceStamp, and describes which branch is 
    4663 being checked out. This will be @code{None} (which interpolates into 
    4664 @code{WithProperties} as an empty string) if the build is on the 
    4665 default branch, which is generally the trunk. Otherwise it will be a 
    4666 string like ``branches/beta1.4''. The exact syntax depends upon the VC 
    4667 system being used. 
    4668  
    4669 @item revision 
    4670  
    4671 This also comes from the SourceStamp, and is the revision of the 
    4672 source code tree that was requested from the VC system. When a build 
    4673 is requested of a specific revision (as is generally the case when the 
    4674 build is triggered by Changes), this will contain the revision 
    4675 specification. The syntax depends upon the VC system in use: for SVN 
    4676 it is an integer, for Mercurial it is a short string, for Darcs it is 
    4677 a rather large string, etc. 
    4678  
    4679 If the ``force build'' button was pressed, the revision will be 
    4680 @code{None}, which means to use the most recent revision available. 
    4681 This is a ``trunk build''. This will be interpolated as an empty 
    4682 string. 
    4683  
    4684 @item got_revision 
    4685  
    4686 This is set when a Source step checks out the source tree, and 
    4687 provides the revision that was actually obtained from the VC system. 
    4688 In general this should be the same as @code{revision}, except for 
    4689 trunk builds, where @code{got_revision} indicates what revision was 
    4690 current when the checkout was performed. This can be used to rebuild 
    4691 the same source code later. 
    4692  
    4693 Note that for some VC systems (Darcs in particular), the revision is a 
    4694 large string containing newlines, and is not suitable for 
    4695 interpolation into a filename. 
    4696  
    4697 @item buildername 
    4698  
    4699 This is a string that indicates which Builder the build was a part of. 
    4700 The combination of buildername and buildnumber uniquely identify a 
    4701 build. 
    4702  
    4703 @item buildnumber 
    4704  
    4705 Each build gets a number, scoped to the Builder (so the first build 
    4706 performed on any given Builder will have a build number of 0). This 
    4707 integer property contains the build's number. 
    4708  
    4709 @item slavename 
    4710  
    4711 This is a string which identifies which buildslave the build is 
    4712 running on. 
    4713  
    4714 @end table 
    4715  
    47164981@node Python BuildSteps, Transferring Files, Simple ShellCommand Subclasses, Build Steps 
    47174982@subsection Python BuildSteps 
    47184983 
     
    48825147@subsection Triggering Schedulers 
    48835148 
    48845149The counterpart to the Triggerable described in section 
    4885 @pxref{Build Dependencies} is the Trigger BuildStep. 
     5150@pxref{Triggerable Scheduler} is the Trigger BuildStep. 
    48865151 
    48875152@example 
    48885153from buildbot.steps.trigger import Trigger 
     
    53025567[GCC 4.1.2 20060928 (prerelease) (Debian 4.1.1-15)] on linux2 
    53035568Type "help", "copyright", "credits" or "license" for more information. 
    53045569>>> import sys 
    5305 >>> print sys.path 
    5306 ['', '/usr/lib/python24.zip', '/usr/lib/python2.4', '/usr/lib/python2.4/plat-linux2', '/usr/lib/python2.4/lib-tk', '/usr/lib/python2.4/lib-dynload', '/usr/local/lib/python2.4/site-packages', '/usr/lib/python2.4/site-packages', '/usr/lib/python2.4/site-packages/Numeric', '/var/lib/python-support/python2.4', '/usr/lib/site-python'] 
    5307 >>>  
     5570>>> import pprint 
     5571>>> pprint.pprint(sys.path) 
     5572['', 
     5573 '/usr/lib/python24.zip', 
     5574 '/usr/lib/python2.4', 
     5575 '/usr/lib/python2.4/plat-linux2', 
     5576 '/usr/lib/python2.4/lib-tk', 
     5577 '/usr/lib/python2.4/lib-dynload', 
     5578 '/usr/local/lib/python2.4/site-packages', 
     5579 '/usr/lib/python2.4/site-packages', 
     5580 '/usr/lib/python2.4/site-packages/Numeric', 
     5581 '/var/lib/python-support/python2.4', 
     5582 '/usr/lib/site-python'] 
    53085583@end example 
    53095584 
    53105585In this case, putting the code into