~adiroiban/+junk/deps

« back to all changes in this revision

Viewing changes to buildbot-0.8.5/contrib/svn_buildbot.py

  • Committer: Adi Roiban
  • Date: 2012-01-17 06:17:46 UTC
  • Revision ID: adi@roiban.ro-20120117061746-b1gwbsd97ukx9lv0
Upgrade to buildbot 0.8.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
# this requires python >=2.3 for the 'sets' module.
 
4
 
 
5
# The sets.py from python-2.3 appears to work fine under python2.2 . To
 
6
# install this script on a host with only python2.2, copy
 
7
# /usr/lib/python2.3/sets.py from a newer python into somewhere on your
 
8
# PYTHONPATH, then edit the #! line above to invoke python2.2
 
9
 
 
10
# python2.1 is right out
 
11
 
 
12
# If you run this program as part of your SVN post-commit hooks, it will
 
13
# deliver Change notices to a buildmaster that is running a PBChangeSource
 
14
# instance.
 
15
 
 
16
# edit your svn-repository/hooks/post-commit file, and add lines that look
 
17
# like this:
 
18
 
 
19
'''
 
20
# set up PYTHONPATH to contain Twisted/buildbot perhaps, if not already
 
21
# installed site-wide
 
22
. ~/.environment
 
23
 
 
24
/path/to/svn_buildbot.py --repository "$REPOS" --revision "$REV" \
 
25
--bbserver localhost --bbport 9989
 
26
'''
 
27
 
 
28
import commands
 
29
import sys
 
30
import os
 
31
import re
 
32
import sets
 
33
 
 
34
# We have hackish "-d" handling here rather than in the Options
 
35
# subclass below because a common error will be to not have twisted in
 
36
# PYTHONPATH; we want to be able to print that error to the log if
 
37
# debug mode is on, so we set it up before the imports.
 
38
 
 
39
DEBUG = None
 
40
 
 
41
if '-d' in sys.argv:
 
42
    i = sys.argv.index('-d')
 
43
    DEBUG = sys.argv[i+1]
 
44
    del sys.argv[i]
 
45
    del sys.argv[i]
 
46
 
 
47
if DEBUG:
 
48
    f = open(DEBUG, 'a')
 
49
    sys.stderr = f
 
50
    sys.stdout = f
 
51
 
 
52
 
 
53
from twisted.internet import defer, reactor
 
54
from twisted.python import usage
 
55
from twisted.spread import pb
 
56
from twisted.cred import credentials
 
57
 
 
58
 
 
59
class Options(usage.Options):
 
60
    optParameters = [
 
61
        ['repository', 'r', None,
 
62
         "The repository that was changed."],
 
63
        ['slave-repo', 'c', None, "In case the repository differs for the slaves."],
 
64
        ['revision', 'v', None,
 
65
         "The revision that we want to examine (default: latest)"],
 
66
        ['bbserver', 's', 'localhost',
 
67
         "The hostname of the server that buildbot is running on"],
 
68
        ['bbport', 'p', 8007,
 
69
         "The port that buildbot is listening on"],
 
70
        ['include', 'f', None,
 
71
         '''\
 
72
Search the list of changed files for this regular expression, and if there is
 
73
at least one match notify buildbot; otherwise buildbot will not do a build.
 
74
You may provide more than one -f argument to try multiple
 
75
patterns.  If no filter is given, buildbot will always be notified.'''],
 
76
        ['filter', 'f', None, "Same as --include.  (Deprecated)"],
 
77
        ['exclude', 'F', None,
 
78
         '''\
 
79
The inverse of --filter.  Changed files matching this expression will never
 
80
be considered for a build.
 
81
You may provide more than one -F argument to try multiple
 
82
patterns.  Excludes override includes, that is, patterns that match both an
 
83
include and an exclude will be excluded.'''],
 
84
        ['encoding', 'e', "utf8",
 
85
         "The encoding of the strings from subversion (default: utf8)" ],
 
86
        ['project', 'P', None, "The project for the source."]
 
87
        ]
 
88
    optFlags = [
 
89
        ['dryrun', 'n', "Do not actually send changes"],
 
90
        ]
 
91
 
 
92
    def __init__(self):
 
93
        usage.Options.__init__(self)
 
94
        self._includes = []
 
95
        self._excludes = []
 
96
        self['includes'] = None
 
97
        self['excludes'] = None
 
98
 
 
99
    def opt_include(self, arg):
 
100
        self._includes.append('.*%s.*' % (arg, ))
 
101
 
 
102
    opt_filter = opt_include
 
103
 
 
104
    def opt_exclude(self, arg):
 
105
        self._excludes.append('.*%s.*' % (arg, ))
 
106
 
 
107
    def postOptions(self):
 
108
        if self['repository'] is None:
 
109
            raise usage.error("You must pass --repository")
 
110
        if self._includes:
 
111
            self['includes'] = '(%s)' % ('|'.join(self._includes), )
 
112
        if self._excludes:
 
113
            self['excludes'] = '(%s)' % ('|'.join(self._excludes), )
 
114
 
 
115
 
 
116
def split_file_dummy(changed_file):
 
117
    """Split the repository-relative filename into a tuple of (branchname,
 
118
    branch_relative_filename). If you have no branches, this should just
 
119
    return (None, changed_file).
 
120
    """
 
121
    return (None, changed_file)
 
122
 
 
123
 
 
124
# this version handles repository layouts that look like:
 
125
#  trunk/files..                  -> trunk
 
126
#  branches/branch1/files..       -> branches/branch1
 
127
#  branches/branch2/files..       -> branches/branch2
 
128
#
 
129
 
 
130
 
 
131
def split_file_branches(changed_file):
 
132
    pieces = changed_file.split(os.sep)
 
133
    if pieces[0] == 'branches':
 
134
        return (os.path.join(*pieces[:2]),
 
135
                os.path.join(*pieces[2:]))
 
136
    if pieces[0] == 'trunk':
 
137
        return (pieces[0], os.path.join(*pieces[1:]))
 
138
    ## there are other sibilings of 'trunk' and 'branches'. Pretend they are
 
139
    ## all just funny-named branches, and let the Schedulers ignore them.
 
140
    #return (pieces[0], os.path.join(*pieces[1:]))
 
141
 
 
142
    raise RuntimeError("cannot determine branch for '%s'" % changed_file)
 
143
 
 
144
 
 
145
split_file = split_file_dummy
 
146
 
 
147
 
 
148
class ChangeSender:
 
149
 
 
150
    def getChanges(self, opts):
 
151
        """Generate and stash a list of Change dictionaries, ready to be sent
 
152
        to the buildmaster's PBChangeSource."""
 
153
 
 
154
        # first we extract information about the files that were changed
 
155
        repo = opts['repository']
 
156
        slave_repo = opts['slave-repo'] or repo
 
157
        print "Repo:", repo
 
158
        rev_arg = ''
 
159
        if opts['revision']:
 
160
            rev_arg = '-r %s' % (opts['revision'], )
 
161
        changed = commands.getoutput('svnlook changed %s "%s"' % (
 
162
            rev_arg, repo)).split('\n')
 
163
        # the first 4 columns can contain status information
 
164
        changed = [x[4:] for x in changed]
 
165
 
 
166
        message = commands.getoutput('svnlook log %s "%s"' % (rev_arg, repo))
 
167
        who = commands.getoutput('svnlook author %s "%s"' % (rev_arg, repo))
 
168
        revision = opts.get('revision')
 
169
        if revision is not None:
 
170
            revision = str(int(revision))
 
171
 
 
172
        # see if we even need to notify buildbot by looking at filters first
 
173
        changestring = '\n'.join(changed)
 
174
        fltpat = opts['includes']
 
175
        if fltpat:
 
176
            included = sets.Set(re.findall(fltpat, changestring))
 
177
        else:
 
178
            included = sets.Set(changed)
 
179
 
 
180
        expat = opts['excludes']
 
181
        if expat:
 
182
            excluded = sets.Set(re.findall(expat, changestring))
 
183
        else:
 
184
            excluded = sets.Set([])
 
185
        if len(included.difference(excluded)) == 0:
 
186
            print changestring
 
187
            print """\
 
188
    Buildbot was not interested, no changes matched any of these filters:\n %s
 
189
    or all the changes matched these exclusions:\n %s\
 
190
    """ % (fltpat, expat)
 
191
            sys.exit(0)
 
192
 
 
193
        # now see which branches are involved
 
194
        files_per_branch = {}
 
195
        for f in changed:
 
196
            branch, filename = split_file(f)
 
197
            if branch in files_per_branch.keys():
 
198
                files_per_branch[branch].append(filename)
 
199
            else:
 
200
                files_per_branch[branch] = [filename]
 
201
 
 
202
        # now create the Change dictionaries
 
203
        changes = []
 
204
        encoding = opts['encoding']
 
205
        for branch in files_per_branch.keys():
 
206
            d = {'who': unicode(who, encoding=encoding),
 
207
                 'repository': unicode(slave_repo, encoding=encoding),
 
208
                 'comments': unicode(message, encoding=encoding),
 
209
                 'revision': revision,
 
210
                 'project' : unicode(opts['project'] or "", encoding=encoding),
 
211
                 'src' : 'svn',
 
212
                 }
 
213
            if branch:
 
214
                d['branch'] = unicode(branch, encoding=encoding)
 
215
            else:
 
216
                d['branch'] = branch
 
217
 
 
218
            files = []
 
219
            for file in files_per_branch[branch]:
 
220
                files.append(unicode(file, encoding=encoding))
 
221
            d['files'] = files
 
222
 
 
223
            changes.append(d)
 
224
 
 
225
        return changes
 
226
 
 
227
    def sendChanges(self, opts, changes):
 
228
        pbcf = pb.PBClientFactory()
 
229
        reactor.connectTCP(opts['bbserver'], int(opts['bbport']), pbcf)
 
230
        d = pbcf.login(credentials.UsernamePassword('change', 'changepw'))
 
231
        d.addCallback(self.sendAllChanges, changes)
 
232
        return d
 
233
 
 
234
    def sendAllChanges(self, remote, changes):
 
235
        dl = [remote.callRemote('addChange', change)
 
236
              for change in changes]
 
237
        return defer.DeferredList(dl)
 
238
 
 
239
    def run(self):
 
240
        opts = Options()
 
241
        try:
 
242
            opts.parseOptions()
 
243
        except usage.error, ue:
 
244
            print opts
 
245
            print "%s: %s" % (sys.argv[0], ue)
 
246
            sys.exit()
 
247
 
 
248
        changes = self.getChanges(opts)
 
249
        if opts['dryrun']:
 
250
            for i, c in enumerate(changes):
 
251
                print "CHANGE #%d" % (i+1)
 
252
                keys = c.keys()
 
253
                keys.sort()
 
254
                for k in keys:
 
255
                    print "[%10s]: %s" % (k, c[k])
 
256
            print "*NOT* sending any changes"
 
257
            return
 
258
 
 
259
        d = self.sendChanges(opts, changes)
 
260
 
 
261
        def quit(*why):
 
262
            print "quitting! because", why
 
263
            reactor.stop()
 
264
 
 
265
        def failed(f):
 
266
            print "FAILURE"
 
267
            print f
 
268
            reactor.stop()
 
269
 
 
270
        d.addCallback(quit, "SUCCESS")
 
271
        d.addErrback(failed)
 
272
        reactor.callLater(60, quit, "TIMEOUT")
 
273
        reactor.run()
 
274
 
 
275
 
 
276
if __name__ == '__main__':
 
277
    s = ChangeSender()
 
278
    s.run()