- bump version (0.7)
[opensuse:osc.git] / osc / build.py
1 #!/usr/bin/python
2
3 # Copyright (C) 2006 Peter Poeml.  All rights reserved.
4 # This program is free software; it may be used, copied, modified
5 # and distributed under the terms of the GNU General Public Licence,
6 # either version 2, or (at your option) any later version.
7
8
9
10 import os
11 import sys
12 import ConfigParser
13 import cElementTree as ET
14 from tempfile import NamedTemporaryFile
15 from osc.fetch import *
16
17
18 DEFAULTS = { 'packagecachedir': '/var/tmp/osbuild-packagecache',
19              'su-wrapper': 'su -c',
20              'build-cmd': '/usr/bin/build',
21              'build-root': '/var/tmp/build-root',
22
23              # default list of download URLs, which will be tried in order
24              'urllist': [
25                 # the normal repo server, redirecting to mirrors
26                 'http://software.opensuse.org/download/%(project)s/%(repository)s/%(arch)s/%(filename)s',
27                 # direct access to "full" tree
28                 'http://api.opensuse.org/rpm/%(project)s/%(repository)s/_repository/%(buildarch)s/%(name)s',
29               ],
30 }
31
32
33
34 text_config_incomplete = """
35
36 Your configuration is not complete.
37 Make sure that you have a [general] section in %%s:
38 (You can copy&paste it. Some commented defaults are shown.)
39
40 [general]
41
42 # Downloaded packages are cached here. Must be writable by you.
43 #packagecachedir: %(packagecachedir)s
44
45 # Wrapper to call build as root (sudo, su -, ...)
46 #su-wrapper: %(su-wrapper)s
47
48 # rootdir to setup the chroot environment
49 #build-root: %(build-root)s
50
51
52 Note:
53 Configuration can be overridden by envvars, e.g. 
54 OSC_SU_WRAPPER overrides the setting of su-wrapper.
55 """ % DEFAULTS
56
57 change_personality = {
58             'i686': 'linux32',
59             'i586': 'linux32',
60             'ppc': 'powerpc32',
61             's390': 's390',
62         }
63
64 can_also_build = { 
65              'x86_64': ['i686', 'i586'],
66              'i686': ['i586'],
67              'ppc64': ['ppc'],
68              's390x': ['s390'],
69             }
70
71 hostarch = os.uname()[4]
72 if hostarch == 'i686': # FIXME
73     hostarch = 'i586'
74
75
76 class Buildinfo:
77     """represent the contents of a buildinfo file"""
78
79     def __init__(self, filename):
80
81         tree = ET.parse(filename)
82         root = tree.getroot()
83
84         if root.find('error') != None:
85             sys.stderr.write('buildinfo is borken... it says:\n')
86             error = root.find('error').text
87             sys.stderr.write(error + '\n')
88             sys.exit(1)
89
90         # are we building  .rpm or .deb?
91         # need the right suffix for downloading
92         # if a package named debhelper is in the dependencies, it must be .deb
93         self.pacsuffix = 'rpm'
94         for node in root.findall('dep'):
95             if node.text == 'debhelper':
96                 self.pacsuffix = 'deb'
97                 break
98
99         self.buildarch = root.find('arch').text
100
101         self.deps = []
102         for node in root.findall('bdep'):
103             p = Pac(node.get('name'),
104                     node.get('version'),
105                     node.get('release'),
106                     node.get('project'),
107                     node.get('repository'),
108                     node.get('arch'),
109                     self.buildarch,       # buildarch is used only for the URL to access the full tree...
110                     self.pacsuffix)
111             self.deps.append(p)
112
113         self.pdeps = []
114         for node in root.findall('pdep'):
115             self.pdeps.append(node.text)
116
117
118
119 class Pac:
120     """represent a package to be downloaded"""
121     def __init__(self, name, version, release, project, repository, arch, buildarch, pacsuffix):
122
123         self.name = name
124         self.version = version
125         self.release = release
126         self.arch = arch
127         self.project = project
128         self.repository = repository
129         self.buildarch = buildarch
130         self.pacsuffix = pacsuffix
131
132         # build a map to fill our the URL templates
133         self.mp = {}
134         self.mp['name'] = self.name
135         self.mp['version'] = self.version
136         self.mp['release'] = self.release
137         self.mp['arch'] = self.arch
138         self.mp['project'] = self.project
139         self.mp['repository'] = self.repository
140         self.mp['buildarch'] = self.buildarch
141         self.mp['pacsuffix'] = self.pacsuffix
142
143         self.filename = '%(name)s-%(version)s-%(release)s.%(arch)s.%(pacsuffix)s' % self.mp
144
145         self.mp['filename'] = self.filename
146
147
148     def makeurls(self, cachedir, urllist):
149
150         self.urllist = []
151
152         # build up local URL
153         # by using the urlgrabber with local urls, we basically build up a cache.
154         # the cache has no validation, since the package servers don't support etags,
155         # or if-modified-since, so the caching is simply name-based (on the assumption
156         # that the filename is suitable as identifier)
157         self.localdir = '%s/%s/%s/%s' % (cachedir, self.project, self.repository, self.arch)
158         self.fullfilename=os.path.join(self.localdir, self.filename)
159         self.url_local = 'file://%s/' % self.fullfilename
160
161         # first, add the local URL 
162         self.urllist.append(self.url_local)
163
164         # remote URLs
165         for url in urllist:
166             self.urllist.append(url % self.mp)
167
168
169     def __str__(self):
170         return self.name
171
172
173
174
175 def get_build_conf():
176     auth_dict = { } # to hold multiple usernames and passwords
177
178     conffile = os.path.expanduser('~/.oscrc')
179     if not os.path.exists(conffile):
180         print >>sys.stderr, 'Error:'
181         print >>sys.stderr, 'You need to create ~/.oscrc.'
182         print >>sys.stderr, 'Running the osc command will do this for you.'
183         sys.exit(1)
184
185     config = ConfigParser.ConfigParser(DEFAULTS)
186     config.read(conffile)
187
188
189     if not config.has_section('general'):
190         # FIXME: it might be sufficient to just assume defaults?
191         print >>sys.stderr, text_config_incomplete % conffile
192         sys.exit(1)
193
194
195     for host in [ x for x in config.sections() if x != 'general' ]:
196         auth_dict[host] = dict(config.items(host))
197
198
199     config = dict(config.items('general'))
200
201     # make it possible to override configuration of the rc file
202     for var in ['OSC_PACKAGECACHEDIR', 'BUILD_ROOT']:
203         val = os.getenv(var)
204         if val:
205             if var.startswith('OSC_'): var = var[4:]
206             var = var.lower().replace('_', '-')
207             config[var] = val
208
209     return config, auth_dict
210
211
212 def get_built_files(pacdir, pactype):
213     if pactype == 'rpm':
214         b_built = os.popen('find %s -name *.rpm' \
215                     % os.path.join(pacdir, 'RPMS')).read().strip()
216         s_built = os.popen('find %s -name *.rpm' \
217                     % os.path.join(pacdir, 'SRPMS')).read().strip()
218     else:
219         b_built = os.popen('find %s -name *.deb' \
220                     % os.path.join(pacdir, 'DEBS')).read().strip()
221         s_built = None
222     return s_built, b_built
223
224
225 def main(argv):
226
227     global config
228     config, auth = get_build_conf()
229
230     if len(argv) < 2:
231         print 'you have to choose a repo to build on'
232         print 'possible repositories are:'
233
234         (i, o) = os.popen4(['osc', 'repos'])
235         i.close()
236
237         for line in o.readlines():
238             if line.split()[1] == hostarch or line.split()[1] in can_also_build[hostarch]:
239                 print line.strip()
240         sys.exit(1)
241         
242     repo = argv[1]
243     arch = argv[2]
244     spec = argv[3]
245     buildargs = []
246     buildargs += argv[4:]
247
248     if not os.path.exists(spec):
249         print >>sys.stderr, 'Error: specfile \'%s\' does not exist.' % spec
250         sys.exit(1)
251
252
253     print 'Getting buildinfo from server'
254     bi_file = NamedTemporaryFile(suffix='.xml', prefix='buildinfo.', dir = '/tmp')
255     os.system('osc buildinfo %s %s > %s' % (repo, arch, bi_file.name))
256     bi = Buildinfo(bi_file.name)
257
258
259     print 'Updating cache of required packages'
260     fetcher = Fetcher(cachedir = config['packagecachedir'], 
261                       urllist = config['urllist'],
262                       auth_dict = auth)
263     # now update the package cache
264     fetcher.run(bi)
265
266
267     if bi.pacsuffix == 'rpm':
268         """don't know how to verify .deb packages. They are verified on install
269         anyway, I assume... verifying package now saves time though, since we don't
270         even try to set up the buildroot if it wouldn't work."""
271
272         print 'Verifying integrity of cached packages'
273         verify_pacs([ i.fullfilename for i in bi.deps ])
274
275
276     print 'Writing build configuration'
277
278     buildconf = [ '%s %s\n' % (i.name, i.fullfilename) for i in bi.deps ]
279
280     buildconf.append('preinstall: ' + ' '.join(bi.pdeps) + '\n')
281
282     rpmlist = NamedTemporaryFile(prefix='rpmlist.', dir = '/tmp')
283     rpmlist.writelines(buildconf)
284     rpmlist.flush()
285     os.fsync(rpmlist)
286
287
288
289     print 'Getting buildconfig from server'
290     bc_file = NamedTemporaryFile(prefix='buildconfig.', dir = '/tmp')
291     os.system('osc buildconfig %s %s > %s' % (repo, arch, bc_file.name))
292
293
294     print 'Running build'
295
296     buildargs = ' '.join(buildargs)
297
298     cmd = '%s --root=%s --rpmlist=%s --dist=%s %s %s' \
299                  % (config['build-cmd'],
300                     config['build-root'], 
301                     rpmlist.name, 
302                     bc_file.name, 
303                     spec, 
304                     buildargs)
305
306     if config['su-wrapper'].startswith('su '):
307         tmpl = '%s \'%s\''
308     else:
309         tmpl = '%s %s'
310     cmd = tmpl % (config['su-wrapper'], cmd)
311         
312     if hostarch != bi.buildarch:
313         cmd = change_personality[bi.buildarch] + ' ' + cmd
314
315     print cmd
316     os.system(cmd)
317
318     pacdirlink = os.readlink(os.path.join(config['build-root'], '.build.packages'))
319     pacdir = os.path.join(config['build-root'] + pacdirlink)
320
321     if os.path.exists(pacdir):
322         (s_built, b_built) = get_built_files(pacdir, bi.pacsuffix)
323
324         print
325         #print 'built source packages:'
326         if s_built: print s_built
327         #print 'built binary packages:'
328         print b_built
329
330