]>
Commit | Line | Data |
---|---|---|
47a4cb89 | 1 | #!/usr/bin/python |
b792d887 MT |
2 | ############################################################################### |
3 | # # | |
4 | # Pakfire - The IPFire package management system # | |
5 | # Copyright (C) 2011 Pakfire development team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
47a4cb89 MT |
21 | |
22 | import fcntl | |
23 | import grp | |
24 | import logging | |
d59bde4c | 25 | import math |
47a4cb89 MT |
26 | import os |
27 | import re | |
28 | import shutil | |
fc4d4177 | 29 | import socket |
47a4cb89 MT |
30 | import stat |
31 | import time | |
f02283bb | 32 | import uuid |
47a4cb89 | 33 | |
7c8f2953 | 34 | import base |
93bd0aa4 | 35 | import chroot |
9eac53ba | 36 | import logger |
47a4cb89 | 37 | import packages |
e0636b31 | 38 | import packages.packager |
fa6d335b | 39 | import repository |
47a4cb89 MT |
40 | import util |
41 | ||
42 | from constants import * | |
4496b160 | 43 | from i18n import _ |
e9c20259 | 44 | from errors import BuildError, BuildRootLocked, Error |
47a4cb89 MT |
45 | |
46 | ||
9eac53ba MT |
47 | BUILD_LOG_HEADER = """ |
48 | ____ _ __ _ _ _ _ _ | |
49 | | _ \ __ _| | __/ _(_)_ __ ___ | |__ _ _(_) | __| | ___ _ __ | |
50 | | |_) / _` | |/ / |_| | '__/ _ \ | '_ \| | | | | |/ _` |/ _ \ '__| | |
51 | | __/ (_| | <| _| | | | __/ | |_) | |_| | | | (_| | __/ | | |
52 | |_| \__,_|_|\_\_| |_|_| \___| |_.__/ \__,_|_|_|\__,_|\___|_| | |
53 | ||
54 | Time : %(time)s | |
55 | Host : %(host)s | |
56 | Version : %(version)s | |
57 | ||
58 | """ | |
59 | ||
c07a3ca7 | 60 | class BuildEnviron(object): |
47a4cb89 MT |
61 | # The version of the kernel this machine is running. |
62 | kernel_version = os.uname()[2] | |
63 | ||
9eac53ba | 64 | def __init__(self, pkg=None, distro_config=None, build_id=None, logfile=None, |
f22069bb MT |
65 | builder_mode="release", **pakfire_args): |
66 | # Set mode. | |
67 | assert builder_mode in ("development", "release",) | |
68 | self.mode = builder_mode | |
69 | ||
70 | # Disable the build repository in release mode. | |
71 | if self.mode == "release": | |
72 | if pakfire_args.has_key("disable_repos") and pakfire_args["disable_repos"]: | |
73 | pakfire_args["disable_repos"] += ["build",] | |
74 | else: | |
75 | pakfire_args["disable_repos"] = ["build",] | |
76 | ||
9eac53ba MT |
77 | # Save the build id and generate one if no build id was provided. |
78 | if not build_id: | |
79 | build_id = "%s" % uuid.uuid4() | |
80 | ||
81 | self.build_id = build_id | |
82 | ||
83 | # Setup the logging. | |
84 | if logfile: | |
85 | self.log = logging.getLogger(self.build_id) | |
86 | # Propage everything to the root logger that we will see something | |
87 | # on the terminal. | |
88 | self.log.propagate = 1 | |
89 | self.log.setLevel(logging.INFO) | |
90 | ||
91 | # Add the given logfile to the logger. | |
92 | h = logging.FileHandler(logfile) | |
93 | self.log.addHandler(h) | |
94 | ||
95 | # Format the log output for the file. | |
96 | f = logger.BuildFormatter() | |
97 | h.setFormatter(f) | |
98 | else: | |
99 | # If no logile was given, we use the root logger. | |
100 | self.log = logging.getLogger() | |
101 | ||
8330691a MT |
102 | # Log information about pakfire and some more information, when we |
103 | # are running in release mode. | |
104 | if self.mode == "release": | |
105 | logdata = { | |
106 | "host" : socket.gethostname(), | |
107 | "time" : time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()), | |
108 | "version" : "Pakfire %s" % PAKFIRE_VERSION, | |
109 | } | |
110 | ||
111 | for line in BUILD_LOG_HEADER.splitlines(): | |
112 | self.log.info(line % logdata) | |
9eac53ba | 113 | |
7c8f2953 | 114 | # Create pakfire instance. |
1ee5d54a MT |
115 | if pakfire_args.has_key("mode"): |
116 | del pakfire_args["mode"] | |
eb34496a | 117 | self.pakfire = base.Pakfire(mode="builder", distro_config=distro_config, **pakfire_args) |
7c8f2953 MT |
118 | self.distro = self.pakfire.distro |
119 | self.path = self.pakfire.path | |
120 | ||
c07a3ca7 MT |
121 | # Log the package information. |
122 | self.pkg = packages.Makefile(self.pakfire, pkg) | |
123 | self.log.info(_("Package information:")) | |
124 | for line in self.pkg.dump(long=True).splitlines(): | |
125 | self.log.info(" %s" % line) | |
126 | self.log.info("") | |
7c8f2953 MT |
127 | |
128 | # XXX need to make this configureable | |
47a4cb89 MT |
129 | self.settings = { |
130 | "enable_loop_devices" : True, | |
33f4679b | 131 | "enable_ccache" : True, |
8c716255 | 132 | "enable_icecream" : False, |
47a4cb89 | 133 | } |
7c8f2953 | 134 | #self.settings.update(settings) |
47a4cb89 MT |
135 | |
136 | self.buildroot = "/buildroot" | |
137 | ||
138 | # Lock the buildroot | |
139 | self._lock = None | |
140 | self.lock() | |
141 | ||
fc4d4177 MT |
142 | # Save the build time. |
143 | self.build_time = int(time.time()) | |
f02283bb | 144 | |
8930b228 MT |
145 | def start(self): |
146 | # Mount the directories. | |
147 | self._mountall() | |
148 | ||
149 | # Create all devnodes and other dirs we need. | |
150 | self.prepare() | |
151 | ||
152 | # Extract all needed packages. | |
153 | self.extract() | |
154 | ||
155 | def stop(self): | |
156 | # Kill all still running processes. | |
157 | util.orphans_kill(self.path) | |
158 | ||
159 | # Close pakfire instance. | |
160 | del self.pakfire | |
161 | ||
162 | # Umount the build environment. | |
163 | self._umountall() | |
164 | ||
165 | # Remove all files. | |
166 | self.destroy() | |
167 | ||
7c8f2953 MT |
168 | @property |
169 | def arch(self): | |
170 | """ | |
171 | Inherit architecture from distribution configuration. | |
172 | """ | |
173 | return self.distro.arch | |
174 | ||
fc4d4177 MT |
175 | @property |
176 | def info(self): | |
177 | return { | |
178 | "build_date" : time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(self.build_time)), | |
179 | "build_host" : socket.gethostname(), | |
180 | "build_id" : self.build_id, | |
181 | "build_time" : self.build_time, | |
182 | } | |
183 | ||
47a4cb89 MT |
184 | def lock(self): |
185 | filename = os.path.join(self.path, ".lock") | |
186 | ||
187 | try: | |
188 | self._lock = open(filename, "a+") | |
189 | except IOError, e: | |
190 | return 0 | |
191 | ||
192 | try: | |
193 | fcntl.lockf(self._lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) | |
194 | except IOError, e: | |
195 | raise BuildRootLocked, "Buildroot is locked" | |
196 | ||
197 | return 1 | |
198 | ||
199 | def unlock(self): | |
200 | if self._lock: | |
201 | self._lock.close() | |
202 | self._lock = None | |
203 | ||
204 | def copyin(self, file_out, file_in): | |
205 | if file_in.startswith("/"): | |
206 | file_in = file_in[1:] | |
207 | ||
208 | file_in = self.chrootPath(file_in) | |
209 | ||
210 | #if not os.path.exists(file_out): | |
211 | # return | |
212 | ||
213 | dir_in = os.path.dirname(file_in) | |
214 | if not os.path.exists(dir_in): | |
215 | os.makedirs(dir_in) | |
216 | ||
217 | logging.debug("%s --> %s" % (file_out, file_in)) | |
218 | ||
219 | shutil.copy2(file_out, file_in) | |
220 | ||
221 | def copyout(self, file_in, file_out): | |
222 | if file_in.startswith("/"): | |
223 | file_in = file_in[1:] | |
224 | ||
225 | file_in = self.chrootPath(file_in) | |
226 | ||
227 | #if not os.path.exists(file_in): | |
228 | # return | |
229 | ||
230 | dir_out = os.path.dirname(file_out) | |
231 | if not os.path.exists(dir_out): | |
232 | os.makedirs(dir_out) | |
233 | ||
234 | logging.debug("%s --> %s" % (file_in, file_out)) | |
235 | ||
236 | shutil.copy2(file_in, file_out) | |
237 | ||
238 | def copy_result(self, resultdir): | |
239 | dir_in = self.chrootPath("result") | |
240 | ||
241 | for dir, subdirs, files in os.walk(dir_in): | |
242 | basename = os.path.basename(dir) | |
243 | dir = dir[len(self.chrootPath()):] | |
244 | for file in files: | |
245 | file_in = os.path.join(dir, file) | |
246 | ||
247 | file_out = os.path.join( | |
248 | resultdir, | |
249 | basename, | |
250 | file, | |
251 | ) | |
252 | ||
253 | self.copyout(file_in, file_out) | |
254 | ||
9c2ad426 | 255 | def extract(self, requires=None, build_deps=True): |
47a4cb89 MT |
256 | """ |
257 | Gets a dependency set and extracts all packages | |
258 | to the environment. | |
259 | """ | |
9c2ad426 MT |
260 | if not requires: |
261 | requires = [] | |
5be98997 | 262 | |
9c2ad426 MT |
263 | # Add neccessary build dependencies. |
264 | requires += BUILD_PACKAGES | |
3d960a21 | 265 | |
33f4679b MT |
266 | # If we have ccache enabled, we need to extract it |
267 | # to the build chroot. | |
268 | if self.settings.get("enable_ccache"): | |
9c2ad426 | 269 | requires.append("ccache") |
33f4679b | 270 | |
5be98997 MT |
271 | # If we have icecream enabled, we need to extract it |
272 | # to the build chroot. | |
273 | if self.settings.get("enable_icecream"): | |
9c2ad426 | 274 | requires.append("icecream") |
47a4cb89 MT |
275 | |
276 | # Get build dependencies from source package. | |
c07a3ca7 MT |
277 | for req in self.pkg.requires: |
278 | requires.append(req) | |
47a4cb89 | 279 | |
9c2ad426 MT |
280 | # Install all packages. |
281 | self.install(requires) | |
47a4cb89 MT |
282 | |
283 | # Copy the makefile and load source tarballs. | |
c07a3ca7 MT |
284 | self.pkg.extract(_("Extracting"), |
285 | prefix=os.path.join(self.path, "build")) | |
47a4cb89 | 286 | |
9c2ad426 MT |
287 | def install(self, requires): |
288 | """ | |
289 | Install everything that is required in requires. | |
290 | """ | |
c9ec78ca MT |
291 | # If we got nothing to do, we quit immediately. |
292 | if not requires: | |
293 | return | |
294 | ||
c07a3ca7 MT |
295 | self.pakfire.install(requires, interactive=False, |
296 | allow_downgrade=True, logger=self.log) | |
47a4cb89 | 297 | |
c9ec78ca MT |
298 | def install_test(self): |
299 | pkgs = [] | |
c9ec78ca MT |
300 | for dir, subdirs, files in os.walk(self.chrootPath("result")): |
301 | for file in files: | |
b88090e9 | 302 | pkgs.append(os.path.join(dir, file)) |
c9ec78ca | 303 | |
08d60af8 | 304 | self.pakfire.localinstall(pkgs, yes=True) |
c9ec78ca | 305 | |
47a4cb89 MT |
306 | def chrootPath(self, *args): |
307 | # Remove all leading slashes | |
308 | _args = [] | |
309 | for arg in args: | |
310 | if arg.startswith("/"): | |
311 | arg = arg[1:] | |
312 | _args.append(arg) | |
313 | args = _args | |
314 | ||
315 | ret = os.path.join(self.path, *args) | |
316 | ret = ret.replace("//", "/") | |
317 | ||
318 | assert ret.startswith(self.path) | |
319 | ||
320 | return ret | |
321 | ||
322 | def prepare(self): | |
677ff42a MT |
323 | prepared_tag = ".prepared" |
324 | ||
325 | if os.path.exists(self.chrootPath(prepared_tag)): | |
326 | return | |
327 | ||
47a4cb89 MT |
328 | # Create directory. |
329 | if not os.path.exists(self.path): | |
330 | os.makedirs(self.path) | |
331 | ||
332 | # Create important directories. | |
333 | dirs = [ | |
334 | "build", | |
335 | self.buildroot, | |
336 | "dev", | |
337 | "dev/pts", | |
338 | "dev/shm", | |
339 | "etc", | |
340 | "proc", | |
341 | "result", | |
342 | "sys", | |
343 | "tmp", | |
344 | "usr/src", | |
345 | ] | |
33f4679b MT |
346 | |
347 | # Create cache dir if ccache is enabled. | |
348 | if self.settings.get("enable_ccache"): | |
349 | dirs.append("var/cache/ccache") | |
350 | ||
351 | if not os.path.exists(CCACHE_CACHE_DIR): | |
352 | os.makedirs(CCACHE_CACHE_DIR) | |
353 | ||
47a4cb89 MT |
354 | for dir in dirs: |
355 | dir = self.chrootPath(dir) | |
356 | if not os.path.exists(dir): | |
357 | os.makedirs(dir) | |
358 | ||
6378690e MT |
359 | # Create neccessary files like /etc/fstab and /etc/mtab. |
360 | files = ( | |
361 | "etc/fstab", | |
677ff42a MT |
362 | "etc/mtab", |
363 | prepared_tag, | |
6378690e MT |
364 | ) |
365 | ||
366 | for file in files: | |
367 | file = self.chrootPath(file) | |
368 | dir = os.path.dirname(file) | |
369 | if not os.path.exists(dir): | |
370 | os.makedirs(dir) | |
371 | f = open(file, "w") | |
372 | f.close() | |
373 | ||
47a4cb89 | 374 | self._prepare_dev() |
47a4cb89 MT |
375 | self._prepare_dns() |
376 | ||
377 | def _prepare_dev(self): | |
378 | prevMask = os.umask(0000) | |
379 | ||
380 | nodes = [ | |
381 | ("dev/null", stat.S_IFCHR | 0666, os.makedev(1, 3)), | |
382 | ("dev/full", stat.S_IFCHR | 0666, os.makedev(1, 7)), | |
383 | ("dev/zero", stat.S_IFCHR | 0666, os.makedev(1, 5)), | |
384 | ("dev/random", stat.S_IFCHR | 0666, os.makedev(1, 8)), | |
385 | ("dev/urandom", stat.S_IFCHR | 0444, os.makedev(1, 9)), | |
386 | ("dev/tty", stat.S_IFCHR | 0666, os.makedev(5, 0)), | |
387 | ("dev/console", stat.S_IFCHR | 0600, os.makedev(5, 1)), | |
388 | ] | |
389 | ||
390 | # If we need loop devices (which are optional) we create them here. | |
391 | if self.settings["enable_loop_devices"]: | |
392 | for i in range(0, 7): | |
393 | nodes.append(("dev/loop%d" % i, stat.S_IFBLK | 0660, os.makedev(7, i))) | |
394 | ||
395 | # Create all the nodes. | |
396 | for node in nodes: | |
397 | self._create_node(*node) | |
398 | ||
399 | os.symlink("/proc/self/fd/0", self.chrootPath("dev", "stdin")) | |
400 | os.symlink("/proc/self/fd/1", self.chrootPath("dev", "stdout")) | |
401 | os.symlink("/proc/self/fd/2", self.chrootPath("dev", "stderr")) | |
402 | os.symlink("/proc/self/fd", self.chrootPath("dev", "fd")) | |
403 | ||
404 | # make device node for el4 and el5 | |
405 | if self.kernel_version < "2.6.19": | |
406 | self._make_node("dev/ptmx", stat.S_IFCHR | 0666, os.makedev(5, 2)) | |
407 | else: | |
408 | os.symlink("/dev/pts/ptmx", self.chrootPath("dev", "ptmx")) | |
409 | ||
410 | os.umask(prevMask) | |
411 | ||
47a4cb89 | 412 | def _prepare_dns(self): |
18973967 MT |
413 | """ |
414 | Add DNS resolution facility to chroot environment by copying | |
415 | /etc/resolv.conf and /etc/hosts. | |
416 | """ | |
417 | for i in ("/etc/resolv.conf", "/etc/hosts"): | |
418 | self.copyin(i, i) | |
47a4cb89 MT |
419 | |
420 | def _create_node(self, filename, mode, device): | |
421 | logging.debug("Create node: %s (%s)" % (filename, mode)) | |
422 | ||
423 | filename = self.chrootPath(filename) | |
424 | ||
425 | # Create parent directory if it is missing. | |
426 | dirname = os.path.dirname(filename) | |
427 | if not os.path.exists(dirname): | |
428 | os.makedirs(dirname) | |
429 | ||
430 | os.mknod(filename, mode, device) | |
431 | ||
526c3e7f | 432 | def destroy(self): |
e412b8dc | 433 | logging.debug("Destroying environment %s" % self.path) |
47a4cb89 MT |
434 | |
435 | if os.path.exists(self.path): | |
436 | util.rm(self.path) | |
437 | ||
e412b8dc MT |
438 | def cleanup(self): |
439 | logging.debug("Cleaning environemnt.") | |
440 | ||
e412b8dc MT |
441 | # Remove the build directory and buildroot. |
442 | dirs = ("build", self.buildroot, "result") | |
443 | ||
444 | for d in dirs: | |
445 | d = self.chrootPath(d) | |
446 | if not os.path.exists(d): | |
447 | continue | |
448 | ||
449 | util.rm(d) | |
450 | os.makedirs(d) | |
451 | ||
47a4cb89 MT |
452 | def _mountall(self): |
453 | self.log.debug("Mounting environment") | |
8930b228 MT |
454 | for src, dest, fs, options in self.mountpoints: |
455 | mountpoint = self.chrootPath(dest) | |
456 | if options: | |
457 | options = "-o %s" % options | |
458 | ||
459 | # Eventually create mountpoint directory | |
460 | if not os.path.exists(mountpoint): | |
461 | os.makedirs(mountpoint) | |
462 | ||
463 | cmd = "mount -n -t %s %s %s %s" % \ | |
464 | (fs, options, src, mountpoint) | |
93bd0aa4 | 465 | chroot.do(cmd, shell=True) |
47a4cb89 MT |
466 | |
467 | def _umountall(self): | |
468 | self.log.debug("Umounting environment") | |
8930b228 MT |
469 | |
470 | mountpoints = [] | |
471 | for src, dest, fs, options in reversed(self.mountpoints): | |
472 | if not dest in mountpoints: | |
473 | mountpoints.append(dest) | |
474 | ||
475 | for dest in mountpoints: | |
476 | mountpoint = self.chrootPath(dest) | |
477 | ||
478 | chroot.do("umount -n %s" % mountpoint, raiseExc=0, shell=True) | |
47a4cb89 MT |
479 | |
480 | @property | |
481 | def mountpoints(self): | |
8930b228 MT |
482 | mountpoints = [] |
483 | ||
484 | # Make root as a tmpfs. | |
485 | #mountpoints += [ | |
486 | # ("pakfire_root", "/", "tmpfs", "defaults"), | |
487 | #] | |
488 | ||
489 | mountpoints += [ | |
490 | # src, dest, fs, options | |
491 | ("pakfire_proc", "/proc", "proc", "nosuid,noexec,nodev"), | |
492 | ("/proc/sys", "/proc/sys", "bind", "bind"), | |
493 | ("/proc/sys", "/proc/sys", "bind", "bind,ro,remount"), | |
494 | ("/sys", "/sys", "bind", "bind"), | |
495 | ("/sys", "/sys", "bind", "bind,ro,remount"), | |
496 | ("pakfire_tmpfs", "/dev", "tmpfs", "mode=755,nosuid"), | |
497 | ("/dev/pts", "/dev/pts", "bind", "bind"), | |
498 | ("pakfire_tmpfs", "/run", "tmpfs", "mode=755,nosuid,nodev"), | |
47a4cb89 MT |
499 | ] |
500 | ||
8930b228 MT |
501 | # If selinux is enabled. |
502 | if os.path.exists("/sys/fs/selinux"): | |
503 | mountpoints += [ | |
504 | ("/sys/fs/selinux", "/sys/fs/selinux", "bind", "bind"), | |
505 | ("/sys/fs/selinux", "/sys/fs/selinux", "bind", "bind,ro,remount"), | |
506 | ] | |
47a4cb89 | 507 | |
8930b228 | 508 | # If ccache support is requested, we bind mount the cache. |
33f4679b | 509 | if self.settings.get("enable_ccache"): |
8930b228 MT |
510 | mountpoints += [ |
511 | (CCACHE_CACHE_DIR, "/var/cache/ccache", "bind", "bind"), | |
512 | ] | |
33f4679b | 513 | |
8930b228 | 514 | return mountpoints |
47a4cb89 MT |
515 | |
516 | @property | |
517 | def environ(self): | |
518 | env = { | |
0cf10c2d MT |
519 | # Add HOME manually, because it is occasionally not set |
520 | # and some builds get in trouble then. | |
521 | "HOME" : "/root", | |
89ebac67 MT |
522 | "TERM" : os.environ.get("TERM", "dumb"), |
523 | "PS1" : "\u:\w\$ ", | |
0cf10c2d | 524 | |
47a4cb89 | 525 | "BUILDROOT" : self.buildroot, |
c07a3ca7 | 526 | "PARALLELISMFLAGS" : "-j%s" % util.calc_parallelism(), |
47a4cb89 MT |
527 | } |
528 | ||
529 | # Inherit environment from distro | |
530 | env.update(self.pakfire.distro.environ) | |
531 | ||
5be98997 | 532 | # Icecream environment settings |
c07a3ca7 | 533 | if self.settings.get("enable_icecream", False): |
5be98997 MT |
534 | # Set the toolchain path |
535 | if self.settings.get("icecream_toolchain", None): | |
536 | env["ICECC_VERSION"] = self.settings.get("icecream_toolchain") | |
537 | ||
538 | # Set preferred host if configured. | |
539 | if self.settings.get("icecream_preferred_host", None): | |
540 | env["ICECC_PREFERRED_HOST"] = \ | |
541 | self.settings.get("icecream_preferred_host") | |
542 | ||
47a4cb89 MT |
543 | # XXX what do we need else? |
544 | ||
545 | return env | |
546 | ||
9eac53ba | 547 | def do(self, command, shell=True, personality=None, logger=None, *args, **kwargs): |
47a4cb89 | 548 | ret = None |
47a4cb89 | 549 | |
8930b228 MT |
550 | # Environment variables |
551 | env = self.environ | |
47a4cb89 | 552 | |
8930b228 MT |
553 | if kwargs.has_key("env"): |
554 | env.update(kwargs.pop("env")) | |
15398910 | 555 | |
8930b228 MT |
556 | logging.debug("Environment:") |
557 | for k, v in sorted(env.items()): | |
558 | logging.debug(" %s=%s" % (k, v)) | |
e360ea59 | 559 | |
8930b228 MT |
560 | # Update personality it none was set |
561 | if not personality: | |
562 | personality = self.distro.personality | |
5be98997 | 563 | |
8930b228 MT |
564 | # Make every shell to a login shell because we set a lot of |
565 | # environment things there. | |
566 | if shell: | |
567 | command = ["bash", "--login", "-c", command] | |
47a4cb89 | 568 | |
8930b228 MT |
569 | if not kwargs.has_key("chrootPath"): |
570 | kwargs["chrootPath"] = self.chrootPath() | |
47a4cb89 | 571 | |
8930b228 MT |
572 | ret = chroot.do( |
573 | command, | |
574 | personality=personality, | |
575 | shell=False, | |
576 | env=env, | |
577 | logger=logger, | |
578 | *args, | |
579 | **kwargs | |
580 | ) | |
47a4cb89 MT |
581 | |
582 | return ret | |
583 | ||
75bb74a7 | 584 | def build(self, install_test=True): |
c157d1e2 MT |
585 | assert self.pkg |
586 | ||
c07a3ca7 MT |
587 | pkgfile = os.path.join("/build", os.path.basename(self.pkg.filename)) |
588 | resultdir = self.chrootPath("/result") | |
589 | ||
590 | # Create the build command, that is executed in the chroot. | |
9b875540 | 591 | build_command = ["/usr/lib/pakfire/builder", "--offline", "build", pkgfile, |
75bb74a7 | 592 | "--nodeps", "--resultdir=/result",] |
c07a3ca7 MT |
593 | |
594 | try: | |
595 | self.do(" ".join(build_command), logger=self.log) | |
596 | ||
597 | except Error: | |
598 | raise BuildError, _("The build command failed. See logfile for details.") | |
599 | ||
75bb74a7 MT |
600 | # Perform install test. |
601 | if install_test: | |
602 | self.install_test() | |
603 | ||
c07a3ca7 MT |
604 | # Copy the final packages and stuff. |
605 | # XXX TODO resultdir | |
47a4cb89 MT |
606 | |
607 | def shell(self, args=[]): | |
e9c20259 MT |
608 | if not util.cli_is_interactive(): |
609 | logging.warning("Cannot run shell on non-interactive console.") | |
610 | return | |
611 | ||
9c2ad426 MT |
612 | # Install all packages that are needed to run a shell. |
613 | self.install(SHELL_PACKAGES) | |
614 | ||
47a4cb89 | 615 | # XXX need to set CFLAGS here |
a7596ccf | 616 | command = "/usr/sbin/chroot %s /usr/bin/chroot-shell %s" % \ |
47a4cb89 MT |
617 | (self.chrootPath(), " ".join(args)) |
618 | ||
e360ea59 MT |
619 | # Add personality if we require one |
620 | if self.pakfire.distro.personality: | |
a7596ccf MT |
621 | command = "%s %s" % (self.pakfire.distro.personality, command) |
622 | ||
623 | for key, val in self.environ.items(): | |
624 | command = "%s=\"%s\" " % (key, val) + command | |
e360ea59 | 625 | |
47a4cb89 | 626 | # Empty the environment |
a7596ccf | 627 | command = "env -i - %s" % command |
47a4cb89 MT |
628 | |
629 | logging.debug("Shell command: %s" % command) | |
630 | ||
8930b228 MT |
631 | shell = os.system(command) |
632 | return os.WEXITSTATUS(shell) | |
c07a3ca7 | 633 | |
56f5e5ff | 634 | |
ff9299d0 | 635 | class Builder(object): |
c07a3ca7 MT |
636 | def __init__(self, pakfire, filename, resultdir, **kwargs): |
637 | self.pakfire = pakfire | |
638 | ||
639 | self.filename = filename | |
640 | ||
641 | self.resultdir = resultdir | |
642 | ||
643 | # Open package file. | |
644 | self.pkg = packages.Makefile(self.pakfire, self.filename) | |
645 | ||
646 | #self.buildroot = "/tmp/pakfire_buildroot/%s" % util.random_string(20) | |
647 | self.buildroot = "/buildroot" | |
648 | ||
649 | self._environ = { | |
650 | "BUILDROOT" : self.buildroot, | |
651 | "LANG" : "C", | |
652 | } | |
653 | ||
654 | @property | |
655 | def distro(self): | |
656 | return self.pakfire.distro | |
657 | ||
658 | @property | |
659 | def environ(self): | |
660 | environ = os.environ | |
3c45a6af MT |
661 | |
662 | # Get all definitions from the package. | |
663 | environ.update(self.pkg.exports) | |
664 | ||
665 | # Overwrite some definitions by default values. | |
c07a3ca7 MT |
666 | environ.update(self._environ) |
667 | ||
668 | return environ | |
669 | ||
670 | def do(self, command, shell=True, personality=None, cwd=None, *args, **kwargs): | |
671 | # Environment variables | |
672 | logging.debug("Environment:") | |
673 | for k, v in sorted(self.environ.items()): | |
674 | logging.debug(" %s=%s" % (k, v)) | |
675 | ||
676 | # Update personality it none was set | |
677 | if not personality: | |
678 | personality = self.distro.personality | |
679 | ||
680 | if not cwd: | |
681 | cwd = "/%s" % LOCAL_TMP_PATH | |
682 | ||
683 | # Make every shell to a login shell because we set a lot of | |
684 | # environment things there. | |
685 | if shell: | |
686 | command = ["bash", "--login", "-c", command] | |
687 | ||
688 | return chroot.do( | |
689 | command, | |
690 | personality=personality, | |
691 | shell=False, | |
692 | env=self.environ, | |
693 | logger=logging.getLogger(), | |
694 | cwd=cwd, | |
695 | *args, | |
696 | **kwargs | |
697 | ) | |
698 | ||
699 | def create_icecream_toolchain(self): | |
700 | try: | |
9c6179fb | 701 | out = self.do("icecc --build-native 2>/dev/null", returnOutput=True) |
c07a3ca7 MT |
702 | except Error: |
703 | return | |
704 | ||
705 | for line in out.splitlines(): | |
706 | m = re.match(r"^creating ([a-z0-9]+\.tar\.gz)", line) | |
707 | if m: | |
708 | self._environ["icecream_toolchain"] = "/%s" % m.group(1) | |
709 | ||
710 | def create_buildscript(self, stage): | |
711 | file = "/tmp/build_%s" % util.random_string() | |
712 | ||
713 | # Get buildscript from the package. | |
714 | script = self.pkg.get_buildscript(stage) | |
715 | ||
716 | # Write script to an empty file. | |
717 | f = open(file, "w") | |
718 | f.write("#!/bin/sh\n\n") | |
719 | f.write("set -e\n") | |
720 | f.write("set -x\n") | |
721 | f.write("\n%s\n" % script) | |
722 | f.write("exit 0\n") | |
723 | f.close() | |
724 | os.chmod(file, 700) | |
725 | ||
726 | return file | |
727 | ||
728 | def build(self): | |
729 | # Create buildroot. | |
730 | if not os.path.exists(self.buildroot): | |
731 | os.makedirs(self.buildroot) | |
732 | ||
733 | # Build icecream toolchain if icecream is installed. | |
734 | self.create_icecream_toolchain() | |
735 | ||
736 | for stage in ("prepare", "build", "test", "install"): | |
737 | self.build_stage(stage) | |
738 | ||
739 | # Package the result. | |
740 | # Make all these little package from the build environment. | |
741 | logging.info(_("Creating packages:")) | |
56f5e5ff | 742 | pkgs = [] |
c07a3ca7 | 743 | for pkg in reversed(self.pkg.packages): |
5dda54e4 MT |
744 | packager = packages.packager.BinaryPackager(self.pakfire, pkg, |
745 | self, self.buildroot) | |
56f5e5ff MT |
746 | pkg = packager.run(self.resultdir) |
747 | pkgs.append(pkg) | |
748 | logging.info("") | |
749 | ||
750 | for pkg in sorted(pkgs): | |
751 | for line in pkg.dump(long=True).splitlines(): | |
752 | logging.info(line) | |
753 | logging.info("") | |
c07a3ca7 MT |
754 | logging.info("") |
755 | ||
756 | def build_stage(self, stage): | |
757 | # Get the buildscript for this stage. | |
758 | buildscript = self.create_buildscript(stage) | |
759 | ||
760 | # Execute the buildscript of this stage. | |
761 | logging.info(_("Running stage %s:") % stage) | |
c07a3ca7 | 762 | |
75bb74a7 MT |
763 | try: |
764 | self.do(buildscript, shell=False) | |
765 | ||
766 | finally: | |
767 | # Remove the buildscript. | |
768 | if os.path.exists(buildscript): | |
769 | os.unlink(buildscript) | |
c07a3ca7 MT |
770 | |
771 | def cleanup(self): | |
772 | if os.path.exists(self.buildroot): | |
773 | util.rm(self.buildroot) |