summaryrefslogtreecommitdiff
path: root/modulesetparser.py
blob: 233eb8d3a50abc4bf6d1c73f716d49dfd38da297 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
#!/usr/bin/env python
#
# this reads a jhbuild moduleset file called 'xorg.modules' and generates a buildbot configuration
#

import glob
import os
import random

from collections import defaultdict
from lxml import etree
from buildbot.changes.gitpoller import GitPoller
from buildbot.changes.svnpoller import SVNPoller,split_file_branches
from buildbot.schedulers.basic import SingleBranchScheduler
from buildbot.schedulers.forcesched import ForceScheduler
from buildbot.schedulers.triggerable import Triggerable
from buildbot.changes.filter import ChangeFilter
from buildbot.process.factory import BuildFactory
from buildbot.process.properties import Interpolate
from buildbot.steps.source.git import Git
from buildbot.steps.source.svn import SVN
from buildbot.steps.shell import Configure,Compile,ShellCommand
from buildbot.steps.trigger import Trigger
from buildbot.steps.transfer import FileDownload
from buildbot.config import BuilderConfig
from buildbot.status.builder import SUCCESS,SKIPPED
from buildbot.plugins import util

tree = None
repos = {}
default_repo = None
rdep = defaultdict(list)
modules = {}

# XXX: needs to be arch-dependent
# skip building stuff which isn't appropriate for Windows target
skip = [ 'applewmproto',
         'libAppleWM',
         'xf86dgaproto',
         'xf86driproto',
         'xf86vidmodeproto',
         'dri2proto',
         'dri3proto',
         'libXv',
         'libXvMC',
         'libXxf86dga',
         'libXxf86dga',
         'libXxf86vm',
         'libxshmfence',
         'libxkbui',
         'libevdev',
         'libpciaccess',
         'mesa-drm',
         'mtdev',
         'app-xdriinfo',
         'app-xgamma',
         'app-xfs',
         'app-xvidtune',
         'app-xvinfo' ]

# skip building all drivers apart from xf86-video-dummy and xf86-video-nested
skip += [
         'glamor',
         'xf86-input-acecad',
         'xf86-input-aiptek',
         'xf86-input-evdev',
         'xf86-input-joystick',
         'xf86-input-keyboard',
         'xf86-input-libinput',
         'xf86-input-mouse',
         'xf86-input-synaptics',
         'xf86-input-vmmouse',
         'xf86-input-void',
         'xf86-video-apm',
         'xf86-video-ark',
         'xf86-video-ast',
         'xf86-video-ati',
         'xf86-video-chips',
         'xf86-video-cirrus',
         'xf86-video-fbdev',
         'xf86-video-geode',
         'xf86-video-glint',
         'xf86-video-i128',
         'xf86-video-i740',
         'xf86-video-intel',
         'xf86-video-mach64',
         'xf86-video-mga',
         'xf86-video-modesetting',
         'xf86-video-neomagic',
         'xf86-video-nouveau',
         'xf86-video-nv',
         'xf86-video-openchrome',
         'xf86-video-r128',
         'xf86-video-rendition',
         'xf86-video-s3',
         'xf86-video-s3virge',
         'xf86-video-savage',
         'xf86-video-siliconmotion',
         'xf86-video-sis',
         'xf86-video-sisusb',
         'xf86-video-suncg14',
         'xf86-video-suncg3',
         'xf86-video-suncg6',
         'xf86-video-sunffb',
         'xf86-video-sunleo',
         'xf86-video-suntcx',
         'xf86-video-tdfx',
         'xf86-video-tga',
         'xf86-video-trident',
         'xf86-video-tseng',
         'xf86-video-v4l',
         'xf86-video-vesa',
         'xf86-video-vmware',
         'xf86-video-wsfb',
         'xf86-video-voodoo',
         'xf86-video-xgixp' ]

def parse():
  global tree
  global default_repo

  # already parsed
  if tree:
    return

  # parse the XML moduleset
  tree = etree.parse('xorg.modules')

  # process any 'include' elements
  for r in tree.iter('include'):
    href = r.get('href')
    r[:] = etree.parse(href).getroot()

  # process 'repository' elements
  for r in tree.iter('repository'):
    t = r.get('type')
    n = r.get('name')
    d = r.get('default')
    h = r.get('href')
#    print("%s %s %s %s" % (t, n ,d , h))

    # stash this node in a dict indexed by name
    repos[r.get('name')] = r

    if r.get('default') == 'yes':
      if default_repo:
        print("error: multiple default repository elements")
      else:
        default_repo = r

  # process module elements
  for m in tree.iter('autotools', 'meson', 'metamodule'):
    #print("%s %s" % (m.get('id'), m))

    # ignore if on skip list
    if m.get('id') in skip:
      continue

    # stash module
    modules[m.get('id')] = m

    # store depedencies
    for d in m.iter('dependencies'):
      for p in m.iter('dep'):
        rdep[p.get('package')].append(m.get('id'))

  return

def get_repo(b):
#    print("%s - %s" % (b.get('module'), b.get('checkoutdir')))
  r = b.get('repo')
  if not r:
    repo = default_repo
  else:
    repo = repos[r]

  return repo

def is_incremental(name):
  return name in [ 'mesa-mesa', 'xserver', 'xserver-meson' ]

def ChangeSourceList():
  parse()

  result = []
  interval = 6000 # 100 minutes

  # process modules of interest
  for i in modules:
    m = modules[i]
    b = m.find('branch')
    if b is not None:
      # if a specific commit is specified, we don't need to poll
      if b.get('tag', None):
         continue

      #print("%s %s %s" % (m.get('id'), m, b))
      repo = get_repo(b)

      if repo.get('type') == 'git':
        result.append(GitPoller(repourl=repo.get('href') + b.get('module'),
                                branches=['master'],
                                workdir='gitpoller-work/' + m.get('id'),
                                pollInterval=random.randint(int(interval*0.9), int(interval*1.10))))
        # XXX: anongit.f.o doesn't like it if we poll 100 git repos in rapid succession (go figure!)
        # really we want to stagger the poll times, but buildbot doesn't have that feature, so instead
        # add some randomness to the timers, so all the pollers aren't synchronized.
      elif repo.get('type') == 'svn':
        result.append(SVNPoller(repourl=repo.get('href') + repo.get('trunk-template'),
                                pollInterval=interval,
                                split_file=split_file_branches))
      else:
        print("error: repo type %s unhandled" % repo.get('type'))

  return result

def SchedulerList():
  parse()

  result = []

  for i in modules:
    m = modules[i]
    b = m.find('branch')
    if b is not None:
      repo = get_repo(b)

      # high-activity repositories get a tree-stable timer
      if m.get('id') in [ 'mesa-mesa' ]:
        timer = 300 # 5 minutes
#        print("%s %d" % (m.get('id'), timer))
      else:
        timer = None

      # a scheduler which builds the repo when changes occur in that repo
      if repo.get('type') == 'git':
        repo_url = repo.get('href') + b.get('module')
        filter = ChangeFilter(repository=repo_url, branch='master')
#        print("filter on repo %s" % repo_url)
        result.append(SingleBranchScheduler(
                              name=m.get('id'),
                              change_filter=filter,
                              treeStableTimer=timer,
                              builderNames=[m.get('id')]))
      elif repo.get('type') == 'svn':
        repo_url = repo.get('href') + repo.get('trunk-template')
        filter = ChangeFilter(repository=repo_url, branch='trunk')
      else:
        print("error: repo type %s unhandled" % repo.get('type'))

      # a scheduler to force building the module
      codebase = util.CodebaseParameter(codebase='',
                                        project=util.FixedParameter(name='project', default=''),
                                        repository=repo_url,
                                        branch='master',
                                        revision='')

      custom_properties = []
      if is_incremental(m.get('id')):
        custom_properties += [util.BooleanParameter(name="configure",
                                                    label="run configuration step",
                                                    default=False)]

      result.append(ForceScheduler(name='force-' + m.get('id'),
                                 codebases=[codebase],
                                 builderNames=[m.get('id')],
                                 properties=custom_properties))

    # a scheduler to trigger building the module when a dependency is rebuilt
#    result.append(Triggerable(name='triggerable-' + m.get('id'),
#                              builderNames=[m.get('id')]))

  return result

def BuilderList(workers):
  parse()

  result = []

  for i in modules:
    m = modules[i]
    factory = BuildFactory()

    # determine tags for the builder
    name = m.get('id')
    if name.startswith('app-'):
      tags = ['app']
    elif name.startswith('data-'):
      tags = ['data']
    elif name.startswith('font-'):
      tags = ['font']
    elif name.endswith('proto'):
      tags = ['proto']
    elif name.startswith('lib'):
      tags = ['lib']
    elif name.startswith('mesa-'):
      tags = ['mesa']
    elif name.startswith('xcb'):
      tags = ['xcb']
    else:
      tags = ['misc']

    incremental = is_incremental(name)

    # log environment if the logEnviron property is present ?
#    logEnviron = Interpolate('%(prop:logEnviron:-False)s')
    logEnviron = False

    b = m.find('branch')
    if b is not None:
      repo = get_repo(b)

      # construct a build factory which knows how to build this repo

      # prefix: the location on the buildworker where we stash the results of other builders
      # XXX: this is very wrong, as it means a builder is not self contained
      prefix = '%(prop:builddir)s/../install'

      # construct env for build steps (maybe this should be sourced from a file or script?)
      # XXX: need to run test on buildworker to determine if ccache is available, and if so, use it
      env = {}
      env['CC'] = 'ccache gcc'
      env['CXX'] = 'ccache g++'
      env['CC_FOR_BUILD'] = env['CC']
      env['ACLOCAL'] = Interpolate('aclocal -I ' + os.path.join(prefix, 'share', 'aclocal'))
      #env['PKG_CONFIG_DEBUG_SPEW'] = '1'
      env['PKG_CONFIG_PATH'] = Interpolate(os.path.join(prefix, 'lib', 'pkgconfig') + ':' + os.path.join(prefix, 'share', 'pkgconfig') + ':${PKG_CONFIG_PATH}')
      env['INSTALL']  = '/usr/local/bin/install-check'
      env['CFLAGS'] = '-g -O0 -fdiagnostics-show-option'
      env['CXXFLAGS'] = '-g -O0 -fdiagnostics-show-option'
      env['AUTOMAKE'] = 'automake --force-missing --copy'
      #env['V'] = '1'
      env['MAKEFLAGS'] = '-j4' # should depend on 'nproc' run on buildworker?
      env['PATH'] = Interpolate(os.path.join(prefix, 'bin') + ':${PATH}')
      env['PREFIX'] = Interpolate(prefix)
      env['PYTHONUNBUFFERED'] = '1'  # make meson output appear while it's running

      if name == 'libpixman':
        # pixman tests run slower when OpenMP is allowed to use multiple threads. I'm assuming this is a toolchain bug...
        os.environ['OMP_NUM_THREADS'] = '1'

      # construct the configuration command
      # some modules require additional configuration options
      confcmd = ['./configure', '--config-cache', '--enable-silent-rules', Interpolate('--prefix='+prefix)]

      autogenargs = {
                      'xserver'          : '--enable-xvfb --enable-xnest --enable-dmx --enable-kdrive --enable-xephyr --enable-xfake --disable-xfbdev --enable-xorg --enable-xwin --enable-xf86bigfont --enable-debug --disable-glamor --disable-xf86-input-inputtest',
                      'mesa-demos'       : '--with-glut='+prefix,
                      'xf86-video-dummy' : '--disable-dga', # config check needs fixing to default to auto, not yes
                      'xts'              : '--disable-shared',
                      'fontconfig'       : '--with-default-fonts=' + os.path.join(prefix, 'share', 'fonts'),
                      'xserver-meson'    : '-Dudev=false -Dudev_kms=false -Dpciaccess=false -Dint10=false -Dsystemd_logind=false -Ddmx=true -Dxephyr=true -Dglamor=false -Ddefault_font_path=catalogue:/etc/X11/fontpath.d,built-ins -Dxkb_default_rules=base -Dxf86-input-inputtest=false',
                      'mesa-mesa'  : '-Degl=false -Dbuild-tests=true',
                    }

      extra = []
      if name in autogenargs:
         extra = autogenargs[name].split()
         extra = list(map(Interpolate, extra))
         confcmd.extend(extra)

      # fontrootdir doesn't default to $prefix/share/fonts/ (bug?), so set it explicitly for font pacakages
      if 'font' in tags:
         confcmd.append(Interpolate('--with-fontrootdir=' + os.path.join(prefix, 'share', 'fonts')))

      if repo.get('type') == 'git':
        repo_url = repo.get('href') + b.get('module')
        if incremental:
           factory.addStep(Git(repourl=repo_url, logEnviron=logEnviron, submodules=True, mode='incremental'))
        else:
           factory.addStep(Git(repourl=repo_url, logEnviron=logEnviron, submodules=True, mode='full', method='fresh'))
      elif repo.get('type') == 'svn':
        repo_url = repo.get('href') + repo.get('trunk-template')
        if incremental:
           factory.addStep(SVN(repourl=repo_url, logEnviron=logEnviron, mode='incremental'))
        else:
           factory.addStep(SVN(repourl=repo_url, logEnviron=logEnviron, mode='full', method='fresh'))
      else:
        print("error: repo type %s unhandled" % repo.get('type'))

      # some repos need patches (which are not yet upstreamable) applied
      patches = sorted(glob.glob('patches/' + name + '/*.patch'))
      for fn in patches:
        p = os.path.basename(fn)
        factory.addStep(FileDownload(mastersrc=fn, workerdest=p, hideStepIf=lambda r,s: r == SUCCESS))
        factory.addStep(ShellCommand(command=['sh', '-c', 'patch -p1 <' + p], name='patch', description='patching', descriptionDone='patch', logEnviron=logEnviron, haltOnFailure=False, flunkOnFailure=False))
        factory.addStep(ShellCommand(command=['sh', '-c', 'rm ' + p], name='tidy', description='tidying', descriptionDone='tidy', hideStepIf=lambda r,s: r == SUCCESS, haltOnFailure=False, flunkOnFailure=False))

      # incremental builds only configure if property 'configure' is present and True
      def force_configure(step):
        return step.getProperty("configure", False)

      if m.tag == 'autotools':

        if (not incremental) and (m.get('skip-autogen') != 'true'):
          factory.addStep(Configure(command=['sh', '-c', 'NOCONFIGURE=1 ./autogen.sh'], name='autoreconf', description='autoreconfing', descriptionDone='autoreconf', logEnviron=logEnviron, env=env))
          factory.addStep(Configure(command=confcmd, logEnviron=logEnviron, env=env))
        elif incremental:
          factory.addStep(Configure(command=['sh', '-c', 'NOCONFIGURE=1 ./autogen.sh'], name='autoreconf', description='autoreconfing', descriptionDone='autoreconf', logEnviron=logEnviron, env=env, doStepIf=force_configure, hideStepIf=lambda results, s: results==SKIPPED))
          factory.addStep(Configure(command=confcmd, logEnviron=logEnviron, env=env, logfiles={'log' : 'config.log'}, doStepIf=force_configure, hideStepIf=lambda results, s: results==SKIPPED))

        factory.addStep(Compile(logEnviron=logEnviron, env=env))

        if not (m.get('check-target') == 'false'):
          factory.addStep(Compile(command=['make', 'check'], name='test', description='testing', descriptionDone='test', logEnviron=logEnviron, env=env, timeout=3600))

        factory.addStep(Compile(command=['make', 'install'], name='install', description='installing', descriptionDone='install', logEnviron=logEnviron, env=env))

      elif m.tag == 'meson':

        if not incremental:
            factory.addStep(Configure(command=['meson', '--prefix', Interpolate(prefix), 'build'] + extra, name='meson', description='meson', descriptionDone='meson', logEnviron=logEnviron, env=env))
        else:
            factory.addStep(Configure(command=['rm', '-rf', 'build'], name='wipe', description='wipe', descriptionDone='wipe', logEnviron=logEnviron, env=env, doStepIf=force_configure, hideStepIf=lambda results, s: results==SKIPPED))
            factory.addStep(Configure(command=['meson', '--prefix', Interpolate(prefix), 'build'] + extra, name='meson', description='meson', descriptionDone='meson', logEnviron=logEnviron, env=env, doStepIf=force_configure, hideStepIf=lambda results, s: results==SKIPPED))

        factory.addStep(Compile(command=['ninja', '-C', 'build'], logEnviron=logEnviron, env=env))
        factory.addStep(Compile(command=['meson', 'test', '-C', 'build', '--timeout-multiplier=4'], name='test', description='testing', descriptionDone='test', logEnviron=logEnviron, env=env, timeout=3600))
        factory.addStep(Compile(command=['ninja', '-C', 'build', 'install'], name='install', description='installing', descriptionDone='install', logEnviron=logEnviron, env=env))

    # add build step to trigger dependent modules (if any)
#    print("%s rdeps are %s" % (e.get('id'), rdep[e.get('id')]))
#    if len(rdep[name]):
#      factory.addStep(Trigger(schedulerNames=['triggerable-' + i for i in rdep[name] ],
#                              alwaysUseLatest=True))

      result.append(BuilderConfig(name=name,
                                builddir='builder-work/' + name,
                                workernames=workers,
                                factory=factory,
                                tags=tags))

  return result

if __name__ == "__main__":
    print("%d changesources " % len(ChangeSourceList()))
    print("%d schedulers " % len(SchedulerList()))
    print("%d builders" % len(BuilderList('')))