- fix for commit b0ddeb909d88e9e9838927624e1de1a57ad6e43b
[opensuse:osc.git] / osc / build.py
1 # Copyright (C) 2006 Novell Inc.  All rights reserved.
2 # This program is free software; it may be used, copied, modified
3 # and distributed under the terms of the GNU General Public Licence,
4 # either version 2, or (at your option) any later version.
5
6
7
8 import os
9 import re
10 import sys
11 import shutil
12 import urlparse
13 from tempfile import NamedTemporaryFile, mkdtemp
14 from osc.fetch import *
15 from osc.core import get_buildinfo, store_read_apiurl, store_read_project, store_read_package, meta_exists, quote_plus, get_buildconfig, is_package_dir
16 from osc.core import get_binarylist, get_binary_file
17 from osc.util import rpmquery, debquery
18 import osc.conf
19 import oscerr
20 import subprocess
21 try:
22     from xml.etree import cElementTree as ET
23 except ImportError:
24     import cElementTree as ET
25
26 from conf import config, cookiejar
27
28 change_personality = {
29             'i686':  'linux32',
30             'i586':  'linux32',
31             'i386':  'linux32',
32             'ppc':   'powerpc32',
33             's390':  's390',
34             'sparc': 'linux32',
35             'sparcv8': 'linux32',
36         }
37
38 can_also_build = {
39              'armv4l': [                                         'armv4l'                                             ],
40              'armv5el':[                                         'armv4l', 'armv5el'                                  ],
41              'armv6el':[                                         'armv4l', 'armv5el', 'armv6el'                       ],
42              'armv6l' :[                                         'armv4l', 'armv5el', 'armv6el'                       ],
43              'armv7el':[                                         'armv4l', 'armv5el', 'armv6el', 'armv7el'            ],
44              'armv7l' :[                                         'armv4l', 'armv5el', 'armv6el', 'armv7el'            ],
45              'armv7hl':[                        'armv7hl'                                                             ],
46              'armv8el':[                                         'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv8el' ],
47              'armv8l' :[                                         'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv8el' ],
48              's390x':  ['s390' ],
49              'ppc64':  [                        'ppc', 'ppc64' ],
50              'sh4':    [                                                                                               'sh4' ],
51              'i386':   [        'i586',         'ppc', 'ppc64',  'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el', 'sh4', 'mips', 'mipsel' ],
52              'i586':   [                'i386', 'ppc', 'ppc64',  'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el', 'sh4', 'mips', 'mipsel' ],
53              'i686':   [        'i586',         'ppc', 'ppc64',  'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el', 'sh4', 'mips', 'mipsel' ],
54              'x86_64': ['i686', 'i586', 'i386', 'ppc', 'ppc64',  'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el', 'sh4', 'mips', 'mipsel' ],
55              'sparc64': ['sparc64v', 'sparcv9v', 'sparcv9', 'sparcv8', 'sparc'],
56              'parisc': ['hppa'],
57              }
58
59 # real arch of this machine
60 hostarch = os.uname()[4]
61 if hostarch == 'i686': # FIXME
62     hostarch = 'i586'
63
64 if hostarch == 'parisc':
65     hostarch = 'hppa'
66
67 class Buildinfo:
68     """represent the contents of a buildinfo file"""
69
70     def __init__(self, filename, apiurl, buildtype = 'spec', localpkgs = []):
71         try:
72             tree = ET.parse(filename)
73         except:
74             print >>sys.stderr, 'could not parse the buildinfo:'
75             print >>sys.stderr, open(filename).read()
76             sys.exit(1)
77
78         root = tree.getroot()
79
80         self.apiurl = apiurl
81
82         if root.find('error') != None:
83             sys.stderr.write('buildinfo is broken... it says:\n')
84             error = root.find('error').text
85             sys.stderr.write(error + '\n')
86             sys.exit(1)
87
88         if not (apiurl.startswith('https://') or apiurl.startswith('http://')):
89             raise urllib2.URLError('invalid protocol for the apiurl: \'%s\'' % apiurl)
90
91         self.buildtype = buildtype
92         self.apiurl = apiurl
93
94         # are we building .rpm or .deb?
95         # XXX: shouldn't we deliver the type via the buildinfo?
96         self.pacsuffix = 'rpm'
97         if self.buildtype == 'dsc':
98             self.pacsuffix = 'deb'
99
100         self.buildarch = root.find('arch').text
101         if root.find('release') != None:
102             self.release = root.find('release').text
103         else:
104             self.release = None
105         self.downloadurl = root.get('downloadurl')
106         self.debuginfo = 0
107         if root.find('debuginfo') != None:
108             try:
109                 self.debuginfo = int(root.find('debuginfo').text)
110             except ValueError:
111                 pass
112
113         self.deps = []
114         self.projects = {}
115         self.keys = []
116         self.prjkeys = []
117         for node in root.findall('bdep'):
118             p = Pac(node, self.buildarch, self.pacsuffix,
119                     apiurl, localpkgs)
120             if p.project:
121                 self.projects[p.project] = 1
122             self.deps.append(p)
123
124         self.vminstall_list = [ dep.name for dep in self.deps if dep.vminstall ]
125         self.cbinstall_list = [ dep.name for dep in self.deps if dep.cbinstall ]
126         self.cbpreinstall_list = [ dep.name for dep in self.deps if dep.cbpreinstall ]
127         self.preinstall_list = [ dep.name for dep in self.deps if dep.preinstall ]
128         self.runscripts_list = [ dep.name for dep in self.deps if dep.runscripts ]
129
130
131     def has_dep(self, name):
132         for i in self.deps:
133             if i.name == name:
134                 return True
135         return False
136
137     def remove_dep(self, name):
138         # we need to iterate over all deps because if this a
139         # kiwi build the same package might appear multiple times
140         for i in self.deps:
141             # only remove those which are needed for the build itself
142             if i.name == name and not i.noinstall:
143                 self.deps.remove(i)
144
145
146 class Pac:
147     """represent a package to be downloaded
148
149     We build a map that's later used to fill our URL templates
150     """
151     def __init__(self, node, buildarch, pacsuffix, apiurl, localpkgs = []):
152
153         self.mp = {}
154         for i in ['name', 'package',
155                   'version', 'release',
156                   'project', 'repository',
157                   'preinstall', 'vminstall', 'noinstall', 'runscripts',
158                   'cbinstall', 'cbpreinstall',
159                  ]:
160             self.mp[i] = node.get(i)
161
162         self.mp['buildarch']  = buildarch
163         self.mp['pacsuffix']  = pacsuffix
164
165         self.mp['arch'] = node.get('arch') or self.mp['buildarch']
166
167         # this is not the ideal place to check if the package is a localdep or not
168         localdep = self.mp['name'] in localpkgs and not self.mp['noinstall']
169         if not localdep and not (node.get('project') and node.get('repository')):
170             raise oscerr.APIError('incomplete information for package %s, may be caused by a broken project configuration.'
171                                   % self.mp['name'] )
172
173         if not localdep:
174             self.mp['extproject'] = node.get('project').replace(':', ':/')
175             self.mp['extrepository'] = node.get('repository').replace(':', ':/')
176         self.mp['repopackage'] = node.get('package') or '_repository'
177         self.mp['repoarch'] = node.get('repoarch') or self.mp['buildarch']
178
179         if pacsuffix == 'deb' and not (self.mp['name'] and self.mp['arch'] and self.mp['version']):
180             raise oscerr.APIError(
181                 "buildinfo for package %s/%s/%s is incomplete"
182                     % (self.mp['name'], self.mp['arch'], self.mp['version']))
183
184         self.mp['apiurl'] = apiurl
185
186         if pacsuffix == 'deb':
187             self.filename = debquery.DebQuery.filename(self.mp['name'], self.mp['version'], self.mp['release'], self.mp['arch'])
188         else:
189             self.filename = rpmquery.RpmQuery.filename(self.mp['name'], self.mp['version'], self.mp['release'], self.mp['arch'])
190
191         self.mp['filename'] = self.filename
192         if self.mp['repopackage'] == '_repository':
193             self.mp['repofilename'] = self.mp['name']
194         else:
195             self.mp['repofilename'] = self.mp['filename']
196
197         # make the content of the dictionary accessible as class attributes
198         self.__dict__.update(self.mp)
199
200
201     def makeurls(self, cachedir, urllist):
202
203         self.urllist = []
204
205         # build up local URL
206         # by using the urlgrabber with local urls, we basically build up a cache.
207         # the cache has no validation, since the package servers don't support etags,
208         # or if-modified-since, so the caching is simply name-based (on the assumption
209         # that the filename is suitable as identifier)
210         self.localdir = '%s/%s/%s/%s' % (cachedir, self.project, self.repository, self.arch)
211         self.fullfilename = os.path.join(self.localdir, self.filename)
212         self.url_local = 'file://%s' % self.fullfilename
213
214         # first, add the local URL
215         self.urllist.append(self.url_local)
216
217         # remote URLs
218         for url in urllist:
219             self.urllist.append(url % self.mp)
220
221     def __str__(self):
222         return self.name
223
224     def __repr__(self):
225         return "%s" % self.name
226
227
228
229 def get_built_files(pacdir, pactype):
230     if pactype == 'rpm':
231         b_built = subprocess.Popen(['find', os.path.join(pacdir, 'RPMS'),
232                                     '-name', '*.rpm'],
233                                    stdout=subprocess.PIPE).stdout.read().strip()
234         s_built = subprocess.Popen(['find', os.path.join(pacdir, 'SRPMS'),
235                                     '-name', '*.rpm'],
236                                    stdout=subprocess.PIPE).stdout.read().strip()
237     elif pactype == 'kiwi':
238         b_built = subprocess.Popen(['find', os.path.join(pacdir, 'KIWI'),
239                                     '-type', 'f'],
240                                    stdout=subprocess.PIPE).stdout.read().strip()
241     else:
242         b_built = subprocess.Popen(['find', os.path.join(pacdir, 'DEBS'),
243                                     '-name', '*.deb'],
244                                    stdout=subprocess.PIPE).stdout.read().strip()
245         s_built = subprocess.Popen(['find', os.path.join(pacdir, 'SOURCES.DEB'),
246                                     '-type', 'f'],
247                                    stdout=subprocess.PIPE).stdout.read().strip()
248     return s_built, b_built
249
250 def get_repo(path):
251     """Walks up path looking for any repodata directories.
252
253     @param path path to a directory
254     @return str path to repository directory containing repodata directory
255     """
256     oldDirectory = None
257     currentDirectory = os.path.abspath(path)
258     repositoryDirectory = None
259
260     # while there are still parent directories
261     while currentDirectory != oldDirectory:
262         children = os.listdir(currentDirectory)
263
264         if "repodata" in children:
265             repositoryDirectory = currentDirectory
266             break
267
268         # ascend
269         oldDirectory = currentDirectory
270         currentDirectory = os.path.abspath(os.path.join(oldDirectory,
271                                                         os.pardir))
272
273     return repositoryDirectory
274
275 def get_prefer_pkgs(dirs, wanted_arch, type):
276     import glob
277     from util import repodata, packagequery, cpio
278     paths = []
279     repositories = []
280
281     suffix = '*.rpm'
282     if type == 'dsc':
283         suffix = '*.deb'
284
285     for dir in dirs:
286         # check for repodata
287         repository = get_repo(dir)
288         if repository is None:
289             paths += glob.glob(os.path.join(os.path.abspath(dir), suffix))
290         else:
291             repositories.append(repository)
292
293     packageQueries = packagequery.PackageQueries(wanted_arch)
294
295     for repository in repositories:
296         repodataPackageQueries = repodata.queries(repository)
297
298         for packageQuery in repodataPackageQueries:
299             packageQueries.add(packageQuery)
300
301     for path in paths:
302         if path.endswith('src.rpm'):
303             continue
304         if path.find('-debuginfo-') > 0:
305             continue
306         packageQuery = packagequery.PackageQuery.query(path)
307         packageQueries.add(packageQuery)
308
309     prefer_pkgs = dict((name, packageQuery.path())
310                        for name, packageQuery in packageQueries.iteritems())
311
312     depfile = create_deps(packageQueries.values())
313     cpio = cpio.CpioWrite()
314     cpio.add('deps', '\n'.join(depfile))
315     return prefer_pkgs, cpio
316
317
318 def create_deps(pkgqs):
319     """
320     creates a list of requires/provides which corresponds to build's internal
321     dependency file format
322     """
323     depfile = []
324     for p in pkgqs:
325         id = '%s.%s-0/0/0: ' % (p.name(), p.arch())
326         depfile.append('R:%s%s' % (id, ' '.join(p.requires())))
327         depfile.append('P:%s%s' % (id, ' '.join(p.provides())))
328     return depfile
329
330
331 trustprompt = """Would you like to ...
332 0 - quit (default)
333 1 - trust packages from '%(project)s' always
334 2 - trust them just this time
335 ? """
336 def check_trusted_projects(apiurl, projects):
337     trusted = config['api_host_options'][apiurl]['trusted_prj']
338     tlen = len(trusted)
339     for prj in projects:
340         if not prj in trusted:
341             print "\nThe build root needs packages from project '%s'." % prj
342             print "Note that malicious packages can compromise the build result or even your system."
343             r = raw_input(trustprompt % { 'project':prj })
344             if r == '1':
345                 print "adding '%s' to ~/.oscrc: ['%s']['trusted_prj']" % (prj,apiurl)
346                 trusted.append(prj)
347             elif r != '2':
348                 print "Well, good good bye then :-)"
349                 raise oscerr.UserAbort()
350
351     if tlen != len(trusted):
352         config['api_host_options'][apiurl]['trusted_prj'] = trusted
353         conf.config_set_option(apiurl, 'trusted_prj', ' '.join(trusted))
354
355 def main(apiurl, opts, argv):
356
357     repo = argv[0]
358     arch = argv[1]
359     build_descr = argv[2]
360     xp = []
361     build_root = None
362     cache_dir  = None
363     build_uid=''
364     vm_type = config['build-type']
365
366     build_descr = os.path.abspath(build_descr)
367     build_type = os.path.splitext(build_descr)[1][1:]
368     if build_type not in ['spec', 'dsc', 'kiwi']:
369         raise oscerr.WrongArgs(
370                 'Unknown build type: \'%s\'. Build description should end in .spec, .dsc or .kiwi.' \
371                         % build_type)
372     if not os.path.isfile(build_descr):
373         raise oscerr.WrongArgs('Error: build description file named \'%s\' does not exist.' % build_descr)
374
375     buildargs = []
376     if not opts.userootforbuild:
377         buildargs.append('--norootforbuild')
378     if opts.clean:
379         buildargs.append('--clean')
380     if opts.noinit:
381         buildargs.append('--noinit')
382     if opts.nochecks:
383         buildargs.append('--no-checks')
384     if not opts.no_changelog:
385         buildargs.append('--changelog')
386     if opts.root:
387         build_root = opts.root
388     if opts.jobs:
389         buildargs.append('--jobs=%s' % opts.jobs)
390     elif config['build-jobs'] > 1:
391         buildargs.append('--jobs=%s' % config['build-jobs'])
392     if opts.icecream or config['icecream'] != '0':
393         if opts.icecream:
394             num = opts.icecream
395         else:
396             num = config['icecream']
397
398         if int(num) > 0:
399             buildargs.append('--icecream=%s' % num)
400             xp.append('icecream')
401             xp.append('gcc-c++')
402     if opts.ccache:
403         buildargs.append('--ccache')
404         xp.append('ccache')
405     if opts.linksources:
406         buildargs.append('--linksources')
407     if opts.baselibs:
408         buildargs.append('--baselibs')
409     if opts.debuginfo:
410         buildargs.append('--debug')
411     if opts._with:
412         for o in opts._with:
413             buildargs.append('--with=%s' % o)
414     if opts.without:
415         for o in opts.without:
416             buildargs.append('--without=%s' % o)
417     if opts.define:
418         for o in opts.define:
419             buildargs.append('--define=%s' % o)
420     if config['build-uid']:
421         build_uid = config['build-uid']
422     if opts.build_uid:
423         build_uid = opts.build_uid
424     if build_uid:
425         buildidre = re.compile('^[0-9]{1,5}:[0-9]{1,5}$')
426         if build_uid == 'caller':
427             buildargs.append('--uid=%s:%s' % (os.getuid(), os.getgid()))
428         elif buildidre.match(build_uid):
429             buildargs.append('--uid=%s' % build_uid)
430         else:
431             print >>sys.stderr, 'Error: build-uid arg must be 2 colon separated numerics: "uid:gid" or "caller"'
432             return 1
433     if opts.vm_type:
434         vm_type = opts.vm_type
435     if opts.alternative_project:
436         prj = opts.alternative_project
437         pac = '_repository'
438     else:
439         prj = store_read_project(os.curdir)
440         if opts.local_package:
441             pac = '_repository'
442         else:
443             pac = store_read_package(os.curdir)
444     if opts.shell:
445         buildargs.append("--shell")
446
447     # make it possible to override configuration of the rc file
448     for var in ['OSC_PACKAGECACHEDIR', 'OSC_SU_WRAPPER', 'OSC_BUILD_ROOT']:
449         val = os.getenv(var)
450         if val:
451             if var.startswith('OSC_'): var = var[4:]
452             var = var.lower().replace('_', '-')
453             if config.has_key(var):
454                 print 'Overriding config value for %s=\'%s\' with \'%s\'' % (var, config[var], val)
455             config[var] = val
456
457     pacname = pac
458     if pacname == '_repository':
459         if not opts.local_package:
460             try:
461                 pacname = store_read_package(os.curdir)
462             except oscerr.NoWorkingCopy:
463                 opts.local_package = True
464         if opts.local_package:
465             pacname = os.path.splitext(build_descr)[0]
466     apihost = urlparse.urlsplit(apiurl)[1]
467     if not build_root:
468         build_root = config['build-root'] % {'repo': repo, 'arch': arch,
469             'project': prj, 'package': pacname, 'apihost': apihost}
470
471     cache_dir = config['packagecachedir'] % {'apihost': apihost}
472
473     extra_pkgs = []
474     if not opts.extra_pkgs:
475         extra_pkgs = config['extra-pkgs']
476     elif opts.extra_pkgs != ['']:
477         extra_pkgs = opts.extra_pkgs
478
479     if xp:
480         extra_pkgs += xp
481
482     prefer_pkgs = {}
483     build_descr_data = open(build_descr).read()
484
485     # XXX: dirty hack but there's no api to provide custom defines
486     if opts.without:
487         s = ''
488         for i in opts.without:
489             s += "%%define _without_%s 1\n" % i
490             s += "%%define _with_%s 0\n" % i
491         build_descr_data = s + build_descr_data
492     if opts._with:
493         s = ''
494         for i in opts._with:
495             s += "%%define _without_%s 0\n" % i
496             s += "%%define _with_%s 1\n" % i
497         build_descr_data = s + build_descr_data
498     if opts.define:
499         s = ''
500         for i in opts.define:
501             s += "%%define %s\n" % i
502         build_descr_data = s + build_descr_data
503
504     if opts.prefer_pkgs:
505         print 'Scanning the following dirs for local packages: %s' % ', '.join(opts.prefer_pkgs)
506         prefer_pkgs, cpio = get_prefer_pkgs(opts.prefer_pkgs, arch, build_type)
507         cpio.add(os.path.basename(build_descr), build_descr_data)
508         build_descr_data = cpio.get()
509
510     # special handling for overlay and rsync-src/dest
511     specialcmdopts = []
512     if opts.rsyncsrc or opts.rsyncdest :
513         if not opts.rsyncsrc or not opts.rsyncdest:
514             raise oscerr.WrongOptions('When using --rsync-{src,dest} both parameters have to be specified.')
515         myrsyncsrc = os.path.abspath(os.path.expanduser(os.path.expandvars(opts.rsyncsrc)))
516         if not os.path.isdir(myrsyncsrc):
517             raise oscerr.WrongOptions('--rsync-src %s is no valid directory!' % opts.rsyncsrc)
518         # can't check destination - its in the target chroot ;) - but we can check for sanity
519         myrsyncdest = os.path.expandvars(opts.rsyncdest)
520         if not os.path.isabs(myrsyncdest):
521             raise oscerr.WrongOptions('--rsync-dest %s is no absolute path (starting with \'/\')!' % opts.rsyncdest)
522         specialcmdopts = ['--rsync-src='+myrsyncsrc, '--rsync-dest='+myrsyncdest]
523     if opts.overlay:
524         myoverlay = os.path.abspath(os.path.expanduser(os.path.expandvars(opts.overlay)))
525         if not os.path.isdir(myoverlay):
526             raise oscerr.WrongOptions('--overlay %s is no valid directory!' % opts.overlay)
527         specialcmdopts += ['--overlay='+myoverlay]
528
529     bi_file = None
530     bc_file = None
531     bi_filename = '_buildinfo-%s-%s.xml' % (repo, arch)
532     bc_filename = '_buildconfig-%s-%s' % (repo, arch)
533     if is_package_dir('.') and os.access(osc.core.store, os.W_OK):
534         bi_filename = os.path.join(os.getcwd(), osc.core.store, bi_filename)
535         bc_filename = os.path.join(os.getcwd(), osc.core.store, bc_filename)
536     elif not os.access('.', os.W_OK):
537         bi_file = NamedTemporaryFile(prefix=bi_filename)
538         bi_filename = bi_file.name
539         bc_file = NamedTemporaryFile(prefix=bc_filename)
540         bc_filename = bc_file.name
541     else:
542         bi_filename = os.path.abspath(bi_filename)
543         bc_filename = os.path.abspath(bc_filename)
544
545     try:
546         if opts.noinit:
547             if not os.path.isfile(bi_filename):
548                 raise oscerr.WrongOptions('--noinit is not possible, no local buildinfo file')
549             print 'Use local \'%s\' file as buildinfo' % bi_filename
550             if not os.path.isfile(bc_filename):
551                 raise oscerr.WrongOptions('--noinit is not possible, no local buildconfig file')
552             print 'Use local \'%s\' file as buildconfig' % bc_filename
553         elif opts.offline:
554             if not os.path.isfile(bi_filename):
555                 raise oscerr.WrongOptions('--offline is not possible, no local buildinfo file')
556             print 'Use local \'%s\' file as buildinfo' % bi_filename
557             if not os.path.isfile(bc_filename):
558                 raise oscerr.WrongOptions('--offline is not possible, no local buildconfig file')
559         else:
560             print 'Getting buildinfo from server and store to %s' % bi_filename
561             bi_text = ''.join(get_buildinfo(apiurl,
562                                             prj,
563                                             pac,
564                                             repo,
565                                             arch,
566                                             specfile=build_descr_data,
567                                             addlist=extra_pkgs))
568             if not bi_file:
569                 bi_file = open(bi_filename, 'w')
570             # maybe we should check for errors before saving the file
571             bi_file.write(bi_text)
572             bi_file.flush()
573             print 'Getting buildconfig from server and store to %s' % bc_filename
574             bc = get_buildconfig(apiurl, prj, repo)
575             if not bc_file:
576                 bc_file = open(bc_filename, 'w')
577             bc_file.write(bc)
578             bc_file.flush()
579     except urllib2.HTTPError, e:
580         if e.code == 404:
581             # check what caused the 404
582             if meta_exists(metatype='prj', path_args=(quote_plus(prj), ),
583                            template_args=None, create_new=False, apiurl=apiurl):
584                 pkg_meta_e = None
585                 try:
586                     # take care, not to run into double trouble.
587                     pkg_meta_e = meta_exists(metatype='pkg', path_args=(quote_plus(prj), 
588                                         quote_plus(pac)), template_args=None, create_new=False, 
589                                         apiurl=apiurl)
590                 except:
591                     pass
592
593                 if pac == '_repository' or pkg_meta_e:
594                     print >>sys.stderr, 'ERROR: Either wrong repo/arch as parameter or a parse error of .spec/.dsc/.kiwi file due to syntax error'
595                 else:
596                     print >>sys.stderr, 'The package \'%s\' does not exists - please ' \
597                                         'rerun with \'--local-package\'' % pac
598             else:
599                 print >>sys.stderr, 'The project \'%s\' does not exists - please ' \
600                                     'rerun with \'--alternative-project <alternative_project>\'' % prj
601             sys.exit(1)
602         else:
603             raise
604
605     bi = Buildinfo(bi_filename, apiurl, build_type, prefer_pkgs.keys())
606
607     if bi.debuginfo and not (opts.disable_debuginfo or '--debug' in buildargs):
608         buildargs.append('--debug')
609
610     if opts.release:
611         bi.release = opts.release
612
613     if bi.release:
614         buildargs.append('--release=%s' % bi.release)
615
616     # real arch of this machine
617     # vs.
618     # arch we are supposed to build for
619     if hostarch != bi.buildarch:
620         if not bi.buildarch in can_also_build.get(hostarch, []):
621             print >>sys.stderr, 'Error: hostarch \'%s\' cannot build \'%s\'.' % (hostarch, bi.buildarch)
622             return 1
623
624     rpmlist_prefers = []
625     if prefer_pkgs:
626         print 'Evaluating preferred packages'
627         for name, path in prefer_pkgs.iteritems():
628             if bi.has_dep(name):
629                 # We remove a preferred package from the buildinfo, so that the
630                 # fetcher doesn't take care about them.
631                 # Instead, we put it in a list which is appended to the rpmlist later.
632                 # At the same time, this will make sure that these packages are
633                 # not verified.
634                 bi.remove_dep(name)
635                 rpmlist_prefers.append((name, path))
636                 print ' - %s (%s)' % (name, path)
637
638     print 'Updating cache of required packages'
639
640     urllist = []
641     if not opts.download_api_only:
642         # transform 'url1, url2, url3' form into a list
643         if 'urllist' in config:
644             if type(config['urllist']) == str:
645                 re_clist = re.compile('[, ]+')
646                 urllist = [ i.strip() for i in re_clist.split(config['urllist'].strip()) ]
647             else:
648                 urllist = config['urllist']
649
650         # OBS 1.5 and before has no downloadurl defined in buildinfo
651         if bi.downloadurl:
652             urllist.append(bi.downloadurl + '/%(extproject)s/%(extrepository)s/%(arch)s/%(filename)s')
653     if opts.disable_cpio_bulk_download:
654         urllist.append( '%(apiurl)s/build/%(project)s/%(repository)s/%(repoarch)s/%(repopackage)s/%(repofilename)s' )
655
656     fetcher = Fetcher(cache_dir,
657                       urllist = urllist,
658                       api_host_options = config['api_host_options'],
659                       offline = opts.noinit or opts.offline,
660                       http_debug = config['http_debug'],
661                       enable_cpio = not opts.disable_cpio_bulk_download,
662                       cookiejar=cookiejar)
663
664     # implicitly trust the project we are building for
665     check_trusted_projects(apiurl, [ i for i in bi.projects.keys() if not i == prj ])
666
667     # now update the package cache
668     fetcher.run(bi)
669
670     old_pkg_dir = None
671     if opts.oldpackages:
672         old_pkg_dir = opts.oldpackages
673         if not old_pkg_dir.startswith('/') and not opts.offline:
674             data = [ prj, pacname, repo, arch]
675             if old_pkg_dir == '_link':
676                 p = osc.core.findpacs(os.curdir)[0]
677                 if not p.islink():
678                     raise oscerr.WrongOptions('package is not a link')
679                 data[0] = p.linkinfo.project
680                 data[1] = p.linkinfo.package
681                 repos = osc.core.get_repositories_of_project(apiurl, data[0])
682                 # hack for links to e.g. Factory
683                 if not data[2] in repos and 'standard' in repos:
684                     data[2] = 'standard'
685             elif old_pkg_dir != '' and old_pkg_dir != '_self':
686                 a = old_pkg_dir.split('/')
687                 for i in range(0, len(a)):
688                     data[i] = a[i]
689
690             destdir = os.path.join(cache_dir, data[0], data[2], data[3])
691             old_pkg_dir = None
692             try:
693                 print "Downloading previous build from %s ..." % '/'.join(data)
694                 binaries = get_binarylist(apiurl, data[0], data[2], data[3], package=data[1], verbose=True)
695             except Exception, e:
696                 print "Error: failed to get binaries: %s" % str(e)
697                 binaries = []
698
699             if binaries:
700                 class mytmpdir:
701                     """ temporary directory that removes itself"""
702                     def __init__(self, *args, **kwargs):
703                         self.name = mkdtemp(*args, **kwargs)
704                     def cleanup(self):
705                         shutil.rmtree(self.name)
706                     def __del__(self):
707                         self.cleanup()
708                     def __exit__(self):
709                         self.cleanup()
710                     def __str__(self):
711                         return self.name
712
713                 old_pkg_dir = mytmpdir(prefix='.build.oldpackages', dir=os.path.abspath(os.curdir))
714                 if not os.path.exists(destdir):
715                     os.makedirs(destdir)
716             for i in binaries:
717                 fname = os.path.join(destdir, i.name)
718                 os.symlink(fname, os.path.join(str(old_pkg_dir), i.name))
719                 if os.path.exists(fname):
720                     st = os.stat(fname)
721                     if st.st_mtime == i.mtime and st.st_size == i.size:
722                         continue
723                 get_binary_file(apiurl,
724                                 data[0],
725                                 data[2], data[3],
726                                 i.name,
727                                 package = data[1],
728                                 target_filename = fname,
729                                 target_mtime = i.mtime,
730                                 progress_meter = True)
731
732         if old_pkg_dir != None:
733             buildargs.append('--oldpackages=%s' % old_pkg_dir)
734
735     # Make packages from buildinfo available as repos for kiwi
736     if build_type == 'kiwi':
737         if not os.path.exists('repos'):
738             os.mkdir('repos')
739         else:
740             shutil.rmtree('repos')
741             os.mkdir('repos')
742         for i in bi.deps:
743             # project
744             pdir = str(i.extproject).replace(':/', ':')
745             # repo
746             rdir = str(i.extrepository).replace(':/', ':')
747             # arch
748             adir = i.repoarch
749             # project/repo
750             prdir = "repos/"+pdir+"/"+rdir
751             # project/repo/arch
752             pradir = prdir+"/"+adir
753             # source fullfilename
754             sffn = i.fullfilename
755             print "Using package: "+sffn
756             # target fullfilename
757             tffn = pradir+"/"+sffn.split("/")[-1]
758             if not os.path.exists(os.path.join(pradir)):
759                 os.makedirs(os.path.join(pradir))
760             if not os.path.exists(tffn):
761                 if opts.linksources:
762                     os.link(sffn, tffn)
763                 else:
764                     os.symlink(sffn, tffn)
765
766     if bi.pacsuffix == 'rpm':
767         if opts.no_verify:
768             print 'Skipping verification of package signatures'
769         else:
770             print 'Verifying integrity of cached packages'
771             verify_pacs(bi)
772
773     elif bi.pacsuffix == 'deb':
774         if vm_type == "xen" or vm_type == "kvm" or vm_type == "lxc":
775             print 'Skipping verification of package signatures due to secure VM build'
776         elif opts.no_verify or opts.noinit:
777             print 'Skipping verification of package signatures'
778         else:
779             print 'WARNING: deb packages get not verified, they can compromise your system !'
780     else:
781         print 'WARNING: unknown packages get not verified, they can compromise your system !'
782
783     print 'Writing build configuration'
784
785     rpmlist = [ '%s %s\n' % (i.name, i.fullfilename) for i in bi.deps if not i.noinstall ]
786     rpmlist += [ '%s %s\n' % (i[0], i[1]) for i in rpmlist_prefers ]
787
788     rpmlist.append('preinstall: ' + ' '.join(bi.preinstall_list) + '\n')
789     rpmlist.append('vminstall: ' + ' '.join(bi.vminstall_list) + '\n')
790     rpmlist.append('cbinstall: ' + ' '.join(bi.cbinstall_list) + '\n')
791     rpmlist.append('cbpreinstall: ' + ' '.join(bi.cbpreinstall_list) + '\n')
792     rpmlist.append('runscripts: ' + ' '.join(bi.runscripts_list) + '\n')
793
794     rpmlist_file = NamedTemporaryFile(prefix='rpmlist.')
795     rpmlist_filename = rpmlist_file.name
796     rpmlist_file.writelines(rpmlist)
797     rpmlist_file.flush()
798
799     subst = { 'repo': repo, 'arch': arch, 'project' : prj, 'package' : pacname }
800     vm_options = []
801     # XXX check if build-device present
802     my_build_device = ''
803     if config['build-device']:
804         my_build_device = config['build-device'] % subst
805     else:
806         # obs worker uses /root here but that collides with the
807         # /root directory if the build root was used without vm
808         # before
809         my_build_device = build_root + '/img'
810
811     need_root = True
812     if vm_type:
813         if config['build-swap']:
814             my_build_swap = config['build-swap'] % subst
815         else:
816             my_build_swap = build_root + '/swap'
817
818         vm_options = [ '--vm-type=%s'%vm_type ]
819         if vm_type != 'lxc':
820             vm_options += [ '--vm-disk=' + my_build_device ]
821             vm_options += [ '--vm-swap=' + my_build_swap ]
822             vm_options += [ '--logfile=%s/.build.log' % build_root ]
823             if vm_type == 'kvm':
824                 if os.access(build_root, os.W_OK) and os.access('/dev/kvm', os.W_OK):
825                     # so let's hope there's also an fstab entry
826                     need_root = False
827             build_root += '/.mount'
828
829         if config['build-memory']:
830             vm_options += [ '--memory=' + config['build-memory'] ]
831         if config['build-vmdisk-rootsize']:
832             vm_options += [ '--vmdisk-rootsize=' + config['build-vmdisk-rootsize'] ]
833         if config['build-vmdisk-swapsize']:
834             vm_options += [ '--vmdisk-swapsize=' + config['build-vmdisk-swapsize'] ]
835
836     if opts.preload:
837         print "Preload done for selected repo/arch."
838         sys.exit(0)
839
840     print 'Running build'
841     cmd = [ config['build-cmd'], '--root='+build_root,
842                     '--rpmlist='+rpmlist_filename,
843                     '--dist='+bc_filename,
844                     '--arch='+bi.buildarch ]
845     cmd += specialcmdopts + vm_options + buildargs
846     cmd += [ build_descr ]
847
848     if need_root:
849         sucmd = config['su-wrapper'].split()
850         if sucmd[0] == 'su':
851             if sucmd[-1] == '-c':
852                 sucmd.pop()
853             cmd = sucmd + ['-s', cmd[0], 'root', '--' ] + cmd[1:]
854         else:
855             cmd = sucmd + cmd
856
857     # change personality, if needed
858     if hostarch != bi.buildarch and bi.buildarch in change_personality:
859         cmd = [ change_personality[bi.buildarch] ] + cmd;
860
861     try:
862         rc = subprocess.call(cmd)
863         if rc:
864             print
865             print 'The buildroot was:', build_root
866             sys.exit(rc)
867     except KeyboardInterrupt, i:
868         print "keyboard interrupt, killing build ..."
869         subprocess.call(cmd + ["--kill"])
870         raise i
871
872     pacdir = os.path.join(build_root, '.build.packages')
873     if os.path.islink(pacdir):
874         pacdir = os.readlink(pacdir)
875         pacdir = os.path.join(build_root, pacdir)
876
877     if os.path.exists(pacdir):
878         (s_built, b_built) = get_built_files(pacdir, bi.pacsuffix)
879
880         print
881         if s_built: print s_built
882         print
883         print b_built
884
885         if opts.keep_pkgs:
886             for i in b_built.splitlines() + s_built.splitlines():
887                 shutil.copy2(i, os.path.join(opts.keep_pkgs, os.path.basename(i)))
888
889     if bi_file:
890         bi_file.close()
891     if bc_file:
892         bc_file.close()
893     rpmlist_file.close()
894
895 # vim: sw=4 et