1# ***** BEGIN LICENSE BLOCK ***** 2# Version: MPL 1.1/GPL 2.0/LGPL 2.1 3# 4# The contents of this file are subject to the Mozilla Public License Version 5# 1.1 (the "License"); you may not use this file except in compliance with 6# the License. You may obtain a copy of the License at 7# http://www.mozilla.org/MPL/ 8# 9# Software distributed under the License is distributed on an "AS IS" basis, 10# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11# for the specific language governing rights and limitations under the 12# License. 13# 14# The Original Code is Mozilla-specific Buildbot steps. 15# 16# The Initial Developer of the Original Code is 17# Mozilla Foundation. 18# Portions created by the Initial Developer are Copyright (C) 2009 19# the Initial Developer. All Rights Reserved. 20# 21# Contributor(s): 22# Brian Warner <warner@lothar.com> 23# 24# Alternatively, the contents of this file may be used under the terms of 25# either the GNU General Public License Version 2 or later (the "GPL"), or 26# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27# in which case the provisions of the GPL or the LGPL are applicable instead 28# of those above. If you wish to allow use of your version of this file only 29# under the terms of either the GPL or the LGPL, and not to allow others to 30# use your version of this file under the terms of the MPL, indicate your 31# decision by deleting the provisions above and replace them with the notice 32# and other provisions required by the GPL or the LGPL. If you do not delete 33# the provisions above, a recipient may use your version of this file under 34# the terms of any one of the MPL, the GPL or the LGPL. 35# 36# ***** END LICENSE BLOCK ***** 37 38importtime 39fromtwisted.internetimportdefer 40fromtwisted.pythonimportlog 41frombuildbot.sourcestampimportSourceStamp 42frombuildbot.schedulersimportbase 43

67"""Instead of watching for Changes, this Scheduler can just start a build 68 at fixed intervals. The C{periodicBuildTimer} parameter sets the number 69 of seconds to wait between such periodic builds. The first build will be 70 run immediately.""" 71 72# TODO: consider having this watch another (changed-based) scheduler and 73# merely enforce a minimum time between builds. 74compare_attrs=('name','builderNames','periodicBuildTimer','branch', 75'properties') 76

120"""Imitate 'cron' scheduling. This can be used to schedule a nightly121 build, or one which runs are certain times of the day, week, or month.122123 Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek; each124 may be a single number or a list of valid values. The builds will be125 triggered whenever the current time matches these values. Wildcards are126 represented by a '*' string. All fields default to a wildcard except127 'minute', so with no fields this defaults to a build every hour, on the128 hour.129130 For example, the following master.cfg clause will cause a build to be131 started every night at 3:00am::132133 s = Nightly(name='nightly', builderNames=['builder1', 'builder2'],134 hour=3, minute=0)135 c['schedules'].append(s)136137 This scheduler will perform a build each monday morning at 6:23am and138 again at 8:23am::139140 s = Nightly(name='BeforeWork', builderNames=['builder1'],141 dayOfWeek=0, hour=[6,8], minute=23)142143 The following runs a build every two hours::144145 s = Nightly(name='every2hours', builderNames=['builder1'],146 hour=range(0, 24, 2))147148 And this one will run only on December 24th::149150 s = Nightly(name='SleighPreflightCheck',151 builderNames=['flying_circuits', 'radar'],152 month=12, dayOfMonth=24, hour=12, minute=0)153154 For dayOfWeek and dayOfMonth, builds are triggered if the date matches155 either of them. All time values are compared against the tuple returned156 by time.localtime(), so month and dayOfMonth numbers start at 1, not157 zero. dayOfWeek=0 is Monday, dayOfWeek=6 is Sunday.158159 When onlyIfChanged is True, the build is triggered only if changes have160 arrived on the given branch since the last build was performed. As a161 further restriction, if fileIsImportant= is provided (a one-argument162 callable which takes a Change object and returns a bool), then the build163 will be triggered only if at least one of those changes qualifies as164 'important'. The following example will run a build at 3am, but only when165 a source code file (.c/.h) has been changed:166167 def isSourceFile(change):168 for fn in change.files:169 if fn.endswith('.c') or fn.endswith('.h'):170 return True171 return False172 s = Nightly(name='nightly-when-changed', builderNames=['builder1'],173 hour=3, minute=0,174 onlyIfChanged=True, fileIsImportant=isSourceFile)175176 onlyIfChanged defaults to False, which means a build will be performed177 even if nothing has changed.178 """179180compare_attrs=('name','builderNames',181'minute','hour','dayOfMonth','month',182'dayOfWeek','branch','onlyIfChanged',183'fileIsImportant','properties')184

189# Setting minute=0 really makes this an 'Hourly' scheduler. This190# seemed like a better default than minute='*', which would result in191# a build every 60 seconds.192base.BaseScheduler.__init__(self,name,builderNames,properties)193self.minute=minute194self.hour=hour195self.dayOfMonth=dayOfMonth196self.month=month197self.dayOfWeek=dayOfWeek198self.branch=branch199self.onlyIfChanged=onlyIfChanged200self.delayedRun=None201self.nextRunTime=None202self.reason=("The Nightly scheduler named '%s' triggered this build"203%name)204self.fileIsImportant=None205iffileIsImportant:206assertcallable(fileIsImportant)207self.fileIsImportant=fileIsImportant208self._start_time=time.time()209210# this scheduler does not support filtering, but ClassifierMixin needs a211# filter anyway212self.make_filter()

227d=defer.succeed(None)228db=self.parent.db229ifself.onlyIfChanged:230# call classify_changes, so that we can keep last_processed231# up to date, in case we are configured with onlyIfChanged.232d.addCallback(lambdaign:db.runInteraction(self.classify_changes))233d.addCallback(lambdaign:db.runInteraction(self._check_timer))234returnd

237now=time.time()238s=self.get_state(t)239last_build=s["last_build"]240iflast_buildisNone:241next=self._calculateNextRunTimeFrom(self._start_time)242else:243next=self._calculateNextRunTimeFrom(last_build)244245# not ready to fire yet246ifnext>=now:247returnnext+1.0248249self._maybe_start_build(t)250self.update_last_build(t,now)251252# reschedule for the next timer253returnself._check_timer(t)

293294ifnotcheck(self.minute,timetuple[4]):295#print 'bad minute', timetuple[4], self.minute296returnFalse297298ifnotcheck(self.hour,timetuple[3]):299#print 'bad hour', timetuple[3], self.hour300returnFalse301302ifnotcheck(self.month,timetuple[1]):303#print 'bad month', timetuple[1], self.month304returnFalse305306ifself.dayOfMonth!='*'andself.dayOfWeek!='*':307# They specified both day(s) of month AND day(s) of week.308# This means that we only have to match one of the two. If309# neither one matches, this time is not the right time.310ifnot(check(self.dayOfMonth,timetuple[2])or311check(self.dayOfWeek,timetuple[6])):312#print 'bad day'313returnFalse314else:315ifnotcheck(self.dayOfMonth,timetuple[2]):316#print 'bad day of month'317returnFalse318319ifnotcheck(self.dayOfWeek,timetuple[6]):320#print 'bad day of week'321returnFalse322323returnTrue

326dateTime=time.localtime(now)327328# Remove seconds by advancing to at least the next minute329dateTime=self._addTime(dateTime,60-dateTime[5])330331# Now we just keep adding minutes until we find something that matches332333# It not an efficient algorithm, but it'll *work* for now334yearLimit=dateTime[0]+2335whilenotself._isRunTime(dateTime):336dateTime=self._addTime(dateTime,60)337#print 'Trying', time.asctime(dateTime)338assertdateTime[0]<yearLimit,'Something is wrong with this code'339returntime.mktime(dateTime)