]> git.pld-linux.org Git - projects/pld-builder.new.git/blob - PLD_Builder/rpm_builder.py
- use --nodeps when installing src.rpm due to rpm5 requiring deps even
[projects/pld-builder.new.git] / PLD_Builder / rpm_builder.py
1 # vi: encoding=utf-8 ts=8 sts=4 sw=4 et
2
3 import sys
4 import os
5 import atexit
6 import time
7 import datetime
8 import string
9 import urllib
10 import urllib2
11
12 from config import config, init_conf
13 from bqueue import B_Queue
14 import lock
15 import util
16 import loop
17 import path
18 import status
19 import log
20 import chroot
21 import ftp
22 import buildlogs
23 import notify
24 import build
25 import report
26 import install
27
28 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
29 import socket
30
31 socket.myorigsocket=socket.socket
32
33 def mysocket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
34     s=socket.myorigsocket(family, type, proto)
35     s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
36     return s
37
38 socket.socket=mysocket
39 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
40
41 # this code is duplicated in srpm_builder, but we
42 # might want to handle some cases differently here
43 def pick_request(q):
44     def mycmp(r1, r2):
45         if r1.kind != 'group' or r2.kind != 'group':
46             raise Exception, "non-group requests"
47         pri_diff = cmp(r1.priority, r2.priority)
48         if pri_diff == 0:
49             return cmp(r1.time, r2.time)
50         else:
51             return pri_diff
52     q.requests.sort(mycmp)
53     ret = q.requests[0]
54     return ret
55
56 def check_skip_build(r, b):
57     src_url = config.control_url + "/srpms/" + r.id + "/skipme"
58     good  = False
59     b.log_line("checking if we should skip the build")
60     while not good:
61         try:
62             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
63             req = urllib2.Request(url=src_url, headers=headers)
64             f = urllib2.urlopen(req)
65             good = True
66         except urllib2.HTTPError, error:
67             return False
68         except urllib2.URLError, error:
69             # see errno.h
70             try:
71                 errno = error.errno
72             except AttributeError:
73                 # python 2.4
74                 errno = error.reason[0]
75
76             if errno in [-3, 60, 61, 110, 111]:
77                 b.log_line("unable to connect... trying again")
78                 continue
79             else:
80                 return False
81         f.close()
82         return True
83     return False
84
85 def fetch_src(r, b):
86     src_url = config.control_url + "/srpms/" + r.id + "/" + urllib.quote(b.src_rpm)
87     b.log_line("fetching %s" % src_url)
88     start = time.time()
89     good = False
90     while not good:
91         try:
92             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
93             req = urllib2.Request(url=src_url, headers=headers)
94             f = urllib2.urlopen(req)
95             good = True
96         except urllib2.HTTPError, error:
97             # fail in a way where cron job will retry
98             msg = "unable to fetch url %s, http code: %d" % (src_url, error.code)
99             b.log_line(msg)
100             queue_time = time.time() - r.time
101             # 6 hours
102             if error.code != 404 or (queue_time >= 0 and queue_time < (6 * 60 * 60)):
103                 raise IOError, msg
104             else:
105                 msg = "in queue for more than 6 hours, download failing"
106                 b.log_line(msg)
107                 return False
108         except urllib2.URLError, error:
109             # see errno.h
110             try:
111                 errno = error.errno
112             except AttributeError:
113                 # python 2.4
114                 errno = error.reason[0]
115
116             if errno in [-3, 60, 61, 110, 111]:
117                 b.log_line("unable to connect to %s... trying again" % (src_url))
118                 continue
119             else:
120                 raise
121
122     o = chroot.popen("cat > %s" % b.src_rpm, mode = "w")
123
124     try:
125         bytes = util.sendfile(f, o)
126     except IOError, e:
127         b.log_line("error: unable to write to `%s': %s" % (b.src_rpm, e))
128         raise
129
130     f.close()
131     o.close()
132     t = time.time() - start
133     if t == 0:
134         b.log_line("fetched %d bytes" % bytes)
135     else:
136         b.log_line("fetched %d bytes, %.1f K/s" % (bytes, bytes / 1024.0 / t))
137
138 def prepare_env():
139     chroot.run("""
140         test ! -f /proc/uptime && mount /proc 2>/dev/null
141         test ! -c /dev/full && rm -f /dev/full && mknod -m 666 /dev/full c 1 7
142         test ! -c /dev/null && rm -f /dev/null && mknod -m 666 /dev/null c 1 3
143         test ! -c /dev/random && rm -f /dev/random && mknod -m 644 /dev/random c 1 8
144         test ! -c /dev/urandom && rm -f /dev/urandom && mknod -m 644 /dev/urandom c 1 9
145         test ! -c /dev/zero && rm -f /dev/zero && mknod -m 666 /dev/zero c 1 5
146
147         # need entry for "/" in mtab, for diskspace() to work in rpm
148         [ -z $(awk '$2 == "/" {print $1; exit}' /etc/mtab) ] && mount -f -t rootfs rootfs /
149
150         # make neccessary files readable for builder user
151         # TODO: see if they really aren't readable for builder
152         for db in Packages Name Basenames Providename Pubkeys; do
153             db=/var/lib/rpm/$db
154             test -f $db && chmod a+r $db
155         done
156
157         # try to limit network access for builder account
158         /bin/setfacl -m u:builder:--- /etc/resolv.conf
159     """, 'root')
160
161 def build_rpm(r, b):
162     if len(b.spec) <= 5:
163         # should not really get here
164         b.log_line("error: No .spec not given of malformed: '%s'" % b.spec)
165         res = "FAIL_INTERNAL"
166         return res
167
168     packagename = b.spec[:-5]
169     status.push("building %s (%s)" % (b.spec, packagename))
170     b.log_line("request from: %s" % r.requester)
171
172     if check_skip_build(r, b):
173         b.log_line("build skipped due to src builder request")
174         res = "SKIP_REQUESTED"
175         return res
176
177     b.log_line("started at: %s" % time.asctime())
178     fetch_src(r, b)
179     b.log_line("installing srpm: %s" % b.src_rpm)
180     res = chroot.run("""
181         # b.id %(bid)s
182         set -ex;
183         install -d rpm/packages/%(package)s rpm/BUILD/%(package)s;
184         rpm -Uhv --nodeps %(rpmdefs)s %(src_rpm)s;
185         rm -f %(src_rpm)s;
186     """ % {
187         'bid' : b.b_id,
188         'package' : packagename,
189         'rpmdefs' : b.rpmbuild_opts(),
190         'src_rpm' : b.src_rpm
191     }, logfile = b.logfile)
192     b.files = []
193
194     tmpdir = "/tmp/B.%s.%s" % (packagename, b.b_id[0:6])
195     if res:
196         b.log_line("error: installing src rpm failed")
197         res = "FAIL_SRPM_INSTALL"
198     else:
199         prepare_env()
200         chroot.run("install -m 700 -d %s" % tmpdir)
201
202         b.default_target(config.arch)
203         # check for build arch before filling BR
204         cmd = "set -ex; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
205             "rpmbuild -bp --short-circuit --nodeps %(rpmdefs)s --define 'prep exit 0' rpm/packages/%(package)s/%(spec)s" % {
206             'tmpdir': tmpdir,
207             'nice' : config.nice,
208             'rpmdefs' : b.rpmbuild_opts(),
209             'package' : packagename,
210             'spec': b.spec,
211         }
212         res = chroot.run(cmd, logfile = b.logfile)
213         if res:
214             res = "UNSUPP"
215             b.log_line("error: build arch check (%s) failed" % cmd)
216
217         if not res:
218             if ("no-install-br" not in r.flags) and not install.uninstall_self_conflict(b):
219                 res = "FAIL_DEPS_UNINSTALL"
220             if ("no-install-br" not in r.flags) and not install.install_br(r, b):
221                 res = "FAIL_DEPS_INSTALL"
222             if not res:
223                 max_jobs = max(min(int(os.sysconf('SC_NPROCESSORS_ONLN') + 1), config.max_jobs), 1)
224                 if r.max_jobs > 0:
225                     max_jobs = max(min(config.max_jobs, r.max_jobs), 1)
226                 cmd = "set -ex; : build-id: %(r_id)s; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
227                     "rpmbuild -bb --define '_smp_mflags -j%(max_jobs)d' %(rpmdefs)s rpm/packages/%(package)s/%(spec)s" % {
228                     'r_id' : r.id,
229                     'tmpdir': tmpdir,
230                     'nice' : config.nice,
231                     'rpmdefs' : b.rpmbuild_opts(),
232                     'package' : packagename,
233                     'max_jobs' : max_jobs,
234                     'spec': b.spec,
235                 }
236                 b.log_line("building RPM using: %s" % cmd)
237                 begin_time = time.time()
238                 res = chroot.run(cmd, logfile = b.logfile)
239                 end_time = time.time()
240                 b.log_line("ended at: %s, done in %s" % (time.asctime(), datetime.timedelta(0, end_time - begin_time)))
241                 if res:
242                     res = "FAIL"
243                 files = util.collect_files(b.logfile)
244                 if len(files) > 0:
245                     r.chroot_files.extend(files)
246                 else:
247                     b.log_line("error: No files produced.")
248                     last_section = util.find_last_section(b.logfile)
249                     if last_section == None:
250                         res = "FAIL"
251                     else:
252                         res = "FAIL_%s" % last_section.upper()
253                 b.files = files
254
255     chroot.run("""
256         set -ex;
257         rpmbuild %(rpmdefs)s --nodeps --nobuild --clean --rmspec --rmsource rpm/packages/%(package)s/%(spec)s
258         rm -rf %(tmpdir)s;
259         chmod -R u+rwX rpm/BUILD/%(package)s;
260         rm -rf rpm/BUILD/%(package)s;
261     """ %
262         {'tmpdir' : tmpdir, 'spec': b.spec, 'package' : packagename, 'rpmdefs' : b.rpmbuild_opts()}, logfile = b.logfile)
263
264     def ll(l):
265         util.append_to(b.logfile, l)
266
267     if b.files != []:
268         rpm_cache_dir = config.rpm_cache_dir
269         if "test-build" not in r.flags:
270             # NOTE: copying to cache dir doesn't mean that build failed, so ignore result
271             b.log_line("copy rpm files to cache_dir: %s" % rpm_cache_dir)
272             chroot.run(
273                     "cp -f %s %s && poldek --mo=nodiff --mkidxz -s %s/" % \
274                         (string.join(b.files), rpm_cache_dir, rpm_cache_dir),
275                      logfile = b.logfile, user = "root"
276             )
277         else:
278             ll("test-build: not copying to " + rpm_cache_dir)
279         ll("Begin-PLD-Builder-Info")
280         if "upgrade" in r.flags:
281             b.upgraded = install.upgrade_from_batch(r, b)
282         else:
283             ll("not upgrading")
284         ll("End-PLD-Builder-Info")
285
286     for f in b.files:
287         local = r.tmp_dir + os.path.basename(f)
288         chroot.cp(f, outfile = local, rm = True)
289         ftp.add(local)
290
291     def uploadinfo(b):
292         c="file:SRPMS:%s\n" % b.src_rpm
293         for f in b.files:
294             c=c + "file:ARCH:%s\n" % os.path.basename(f)
295         c=c + "END\n"
296         return c
297
298     if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
299         fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
300         f = open(fname, "w")
301         f.write(uploadinfo(b))
302         f.close()
303         ftp.add(fname, "uploadinfo")
304
305     status.pop()
306
307     return res
308
309 def handle_request(r):
310     ftp.init(r)
311     buildlogs.init(r)
312     build.build_all(r, build_rpm)
313     report.send_report(r, is_src = False)
314     ftp.flush()
315     notify.send(r)
316
317 def check_load():
318     do_exit = 0
319     try:
320         f = open("/proc/loadavg")
321         if float(string.split(f.readline())[2]) > config.max_load:
322             do_exit = 1
323     except:
324         pass
325     if do_exit:
326         sys.exit(0)
327
328 def main_for(builder):
329     msg = ""
330
331     init_conf(builder)
332
333     q = B_Queue(path.queue_file + "-" + config.builder)
334     q.lock(0)
335     q.read()
336     if q.requests == []:
337         q.unlock()
338         return
339     req = pick_request(q)
340     q.unlock()
341
342     # high priority tasks have priority < 0, normal tasks >= 0
343     if req.priority >= 0:
344
345         # allow only one build in given builder at once
346         if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
347             return
348         # don't kill server
349         check_load()
350         # not more then job_slots builds at once
351         locked = 0
352         for slot in range(config.job_slots):
353             if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
354                 locked = 1
355                 break
356         if not locked:
357             return
358
359         # record fact that we got lock for this builder, load balancer
360         # will use it for fair-queuing
361         l = lock.lock("got-lock")
362         f = open(path.got_lock_file, "a")
363         f.write(config.builder + "\n")
364         f.close()
365         l.close()
366     else:
367         msg = "HIGH PRIORITY: "
368
369     msg += "handling request %s (%d) for %s from %s, priority %s" \
370             % (req.id, req.no, config.builder, req.requester, req.priority)
371     log.notice(msg)
372     status.push(msg)
373     handle_request(req)
374     status.pop()
375
376     def otherreqs(r):
377         if r.no==req.no:
378             return False
379         else:
380             return True
381
382     q = B_Queue(path.queue_file + "-" + config.builder)
383     q.lock(0)
384     q.read()
385     previouslen=len(q.requests)
386     q.requests=filter(otherreqs, q.requests)
387     if len(q.requests)<previouslen:
388         q.write()
389     q.unlock()
390
391 def main():
392     if len(sys.argv) < 2:
393         raise Exception, "fatal: need to have builder name as first arg"
394     return main_for(sys.argv[1])
395
396 if __name__ == '__main__':
397     loop.run_loop(main)
This page took 0.061282 seconds and 3 git commands to generate.