]>
git.ipfire.org Git - pakfire.git/blob - pakfire/builder.py
22 from constants
import *
24 from errors
import BuildError
, BuildRootLocked
, Error
27 BUILD_LOG_HEADER
= """
29 | _ \ __ _| | __/ _(_)_ __ ___ | |__ _ _(_) | __| | ___ _ __
30 | |_) / _` | |/ / |_| | '__/ _ \ | '_ \| | | | | |/ _` |/ _ \ '__|
31 | __/ (_| | <| _| | | | __/ | |_) | |_| | | | (_| | __/ |
32 |_| \__,_|_|\_\_| |_|_| \___| |_.__/ \__,_|_|_|\__,_|\___|_|
40 class Builder(object):
41 # The version of the kernel this machine is running.
42 kernel_version
= os
.uname()[2]
44 def __init__(self
, pkg
=None, distro_config
=None, build_id
=None, logfile
=None,
45 builder_mode
="release", **pakfire_args
):
47 assert builder_mode
in ("development", "release",)
48 self
.mode
= builder_mode
50 # Disable the build repository in release mode.
51 if self
.mode
== "release":
52 if pakfire_args
.has_key("disable_repos") and pakfire_args
["disable_repos"]:
53 pakfire_args
["disable_repos"] += ["build",]
55 pakfire_args
["disable_repos"] = ["build",]
57 # Save the build id and generate one if no build id was provided.
59 build_id
= "%s" % uuid
.uuid4()
61 self
.build_id
= build_id
65 self
.log
= logging
.getLogger(self
.build_id
)
66 # Propage everything to the root logger that we will see something
68 self
.log
.propagate
= 1
69 self
.log
.setLevel(logging
.INFO
)
71 # Add the given logfile to the logger.
72 h
= logging
.FileHandler(logfile
)
73 self
.log
.addHandler(h
)
75 # Format the log output for the file.
76 f
= logger
.BuildFormatter()
79 # If no logile was given, we use the root logger.
80 self
.log
= logging
.getLogger()
83 "host" : socket
.gethostname(),
84 "time" : time
.strftime("%a, %d %b %Y %H:%M:%S +0000", time
.gmtime()),
85 "version" : "Pakfire %s" % PAKFIRE_VERSION
,
87 for line
in BUILD_LOG_HEADER
.splitlines():
88 self
.log
.info(line
% logdata
)
90 # Create pakfire instance.
91 if pakfire_args
.has_key("mode"):
92 del pakfire_args
["mode"]
93 self
.pakfire
= base
.Pakfire(mode
="builder", distro_config
=distro_config
, **pakfire_args
)
94 self
.distro
= self
.pakfire
.distro
95 self
.path
= self
.pakfire
.path
100 # XXX need to make this configureable
102 "enable_loop_devices" : True,
103 "enable_ccache" : True,
104 "enable_icecream" : False,
106 #self.settings.update(settings)
108 self
.buildroot
= "/buildroot"
114 # Save the build time.
115 self
.build_time
= int(time
.time())
118 return getattr(self
, "_pkg", None)
120 def set_pkg(self
, pkg
):
125 self
._pkg
= packages
.open(self
.pakfire
, None, pkg
)
127 # Log the package information.
128 if not isinstance(self
._pkg
, packages
.Makefile
):
129 self
.log
.info("Package information:")
130 for line
in self
._pkg
.dump(long=True).splitlines():
131 self
.log
.info(" %s" % line
)
136 pkg
= property(get_pkg
, set_pkg
)
141 Inherit architecture from distribution configuration.
143 return self
.distro
.arch
148 "build_date" : time
.strftime("%a, %d %b %Y %H:%M:%S +0000", time
.gmtime(self
.build_time
)),
149 "build_host" : socket
.gethostname(),
150 "build_id" : self
.build_id
,
151 "build_time" : self
.build_time
,
155 filename
= os
.path
.join(self
.path
, ".lock")
158 self
._lock
= open(filename
, "a+")
163 fcntl
.lockf(self
._lock
.fileno(), fcntl
.LOCK_EX | fcntl
.LOCK_NB
)
165 raise BuildRootLocked
, "Buildroot is locked"
174 def copyin(self
, file_out
, file_in
):
175 if file_in
.startswith("/"):
176 file_in
= file_in
[1:]
178 file_in
= self
.chrootPath(file_in
)
180 #if not os.path.exists(file_out):
183 dir_in
= os
.path
.dirname(file_in
)
184 if not os
.path
.exists(dir_in
):
187 logging
.debug("%s --> %s" % (file_out
, file_in
))
189 shutil
.copy2(file_out
, file_in
)
191 def copyout(self
, file_in
, file_out
):
192 if file_in
.startswith("/"):
193 file_in
= file_in
[1:]
195 file_in
= self
.chrootPath(file_in
)
197 #if not os.path.exists(file_in):
200 dir_out
= os
.path
.dirname(file_out
)
201 if not os
.path
.exists(dir_out
):
204 logging
.debug("%s --> %s" % (file_in
, file_out
))
206 shutil
.copy2(file_in
, file_out
)
208 def copy_result(self
, resultdir
):
209 dir_in
= self
.chrootPath("result")
211 for dir, subdirs
, files
in os
.walk(dir_in
):
212 basename
= os
.path
.basename(dir)
213 dir = dir[len(self
.chrootPath()):]
215 file_in
= os
.path
.join(dir, file)
217 file_out
= os
.path
.join(
223 self
.copyout(file_in
, file_out
)
225 def extract(self
, requires
=None, build_deps
=True):
227 Gets a dependency set and extracts all packages
233 # Add neccessary build dependencies.
234 requires
+= BUILD_PACKAGES
236 # If we have ccache enabled, we need to extract it
237 # to the build chroot.
238 if self
.settings
.get("enable_ccache"):
239 requires
.append("ccache")
241 # If we have icecream enabled, we need to extract it
242 # to the build chroot.
243 if self
.settings
.get("enable_icecream"):
244 requires
.append("icecream")
246 # Get build dependencies from source package.
247 if isinstance(self
.pkg
, packages
.SourcePackage
):
248 for req
in self
.pkg
.requires
:
251 # Install all packages.
252 self
.install(requires
)
254 # Copy the makefile and load source tarballs.
255 if isinstance(self
.pkg
, packages
.Makefile
):
256 self
.pkg
.extract(self
)
258 elif isinstance(self
.pkg
, packages
.SourcePackage
):
259 self
.pkg
.extract(_("Extracting: %s (source)") % self
.pkg
.name
,
260 prefix
=os
.path
.join(self
.path
, "build"))
262 # If we have a makefile, we can only get the build dependencies
263 # after we have extracted all the rest.
264 if build_deps
and isinstance(self
.pkg
, packages
.Makefile
):
265 requires
= self
.make_requires()
269 self
.install(requires
)
271 def install(self
, requires
):
273 Install everything that is required in requires.
275 # If we got nothing to do, we quit immediately.
279 # Create a request and fill it with what we need.
280 request
= self
.pakfire
.create_request()
283 if isinstance(req
, packages
.BinaryPackage
):
284 req
= req
.friendly_name
286 if "<" in req
or ">" in req
or "=" in req
or req
.startswith("/"):
287 req
= self
.pakfire
.create_relation(req
)
291 # Create a new solver instance.
292 solver
= self
.pakfire
.create_solver()
295 transaction
= solver
.solve(request
, allow_downgrade
=True)
297 # XXX check for errors
299 raise DependencyError
, "Could not resolve dependencies"
301 # Show the user what is going to be done.
302 transaction
.dump(logger
=self
.log
)
304 # Run the transaction.
307 def install_test(self
):
308 # XXX currently disabled
312 for dir, subdirs
, files
in os
.walk(self
.chrootPath("result")):
314 pkgs
.append(os
.path
.join(dir, file))
316 self
.pakfire
.localinstall(pkgs
)
318 def chrootPath(self
, *args
):
319 # Remove all leading slashes
322 if arg
.startswith("/"):
327 ret
= os
.path
.join(self
.path
, *args
)
328 ret
= ret
.replace("//", "/")
330 assert ret
.startswith(self
.path
)
335 prepared_tag
= ".prepared"
337 if os
.path
.exists(self
.chrootPath(prepared_tag
)):
341 if not os
.path
.exists(self
.path
):
342 os
.makedirs(self
.path
)
344 # Create important directories.
359 # Create cache dir if ccache is enabled.
360 if self
.settings
.get("enable_ccache"):
361 dirs
.append("var/cache/ccache")
363 if not os
.path
.exists(CCACHE_CACHE_DIR
):
364 os
.makedirs(CCACHE_CACHE_DIR
)
367 dir = self
.chrootPath(dir)
368 if not os
.path
.exists(dir):
371 # Create neccessary files like /etc/fstab and /etc/mtab.
379 file = self
.chrootPath(file)
380 dir = os
.path
.dirname(file)
381 if not os
.path
.exists(dir):
389 def _prepare_dev(self
):
390 prevMask
= os
.umask(0000)
393 ("dev/null", stat
.S_IFCHR |
0666, os
.makedev(1, 3)),
394 ("dev/full", stat
.S_IFCHR |
0666, os
.makedev(1, 7)),
395 ("dev/zero", stat
.S_IFCHR |
0666, os
.makedev(1, 5)),
396 ("dev/random", stat
.S_IFCHR |
0666, os
.makedev(1, 8)),
397 ("dev/urandom", stat
.S_IFCHR |
0444, os
.makedev(1, 9)),
398 ("dev/tty", stat
.S_IFCHR |
0666, os
.makedev(5, 0)),
399 ("dev/console", stat
.S_IFCHR |
0600, os
.makedev(5, 1)),
402 # If we need loop devices (which are optional) we create them here.
403 if self
.settings
["enable_loop_devices"]:
404 for i
in range(0, 7):
405 nodes
.append(("dev/loop%d" % i
, stat
.S_IFBLK |
0660, os
.makedev(7, i
)))
407 # Create all the nodes.
409 self
._create
_node
(*node
)
411 os
.symlink("/proc/self/fd/0", self
.chrootPath("dev", "stdin"))
412 os
.symlink("/proc/self/fd/1", self
.chrootPath("dev", "stdout"))
413 os
.symlink("/proc/self/fd/2", self
.chrootPath("dev", "stderr"))
414 os
.symlink("/proc/self/fd", self
.chrootPath("dev", "fd"))
416 # make device node for el4 and el5
417 if self
.kernel_version
< "2.6.19":
418 self
._make
_node
("dev/ptmx", stat
.S_IFCHR |
0666, os
.makedev(5, 2))
420 os
.symlink("/dev/pts/ptmx", self
.chrootPath("dev", "ptmx"))
424 def _prepare_dns(self
):
426 Add DNS resolution facility to chroot environment by copying
427 /etc/resolv.conf and /etc/hosts.
429 for i
in ("/etc/resolv.conf", "/etc/hosts"):
432 def _create_node(self
, filename
, mode
, device
):
433 logging
.debug("Create node: %s (%s)" % (filename
, mode
))
435 filename
= self
.chrootPath(filename
)
437 # Create parent directory if it is missing.
438 dirname
= os
.path
.dirname(filename
)
439 if not os
.path
.exists(dirname
):
442 os
.mknod(filename
, mode
, device
)
445 logging
.debug("Destroying environment %s" % self
.path
)
447 if os
.path
.exists(self
.path
):
451 logging
.debug("Cleaning environemnt.")
453 # Run make clean and let it cleanup its stuff.
456 # Remove the build directory and buildroot.
457 dirs
= ("build", self
.buildroot
, "result")
460 d
= self
.chrootPath(d
)
461 if not os
.path
.exists(d
):
467 # Clear make_info cache.
468 if hasattr(self
, "_make_info"):
472 self
.log
.debug("Mounting environment")
473 for cmd
, mountpoint
in self
.mountpoints
:
474 cmd
= "%s %s" % (cmd
, self
.chrootPath(mountpoint
))
475 chroot
.do(cmd
, shell
=True)
477 def _umountall(self
):
478 self
.log
.debug("Umounting environment")
479 for cmd
, mountpoint
in self
.mountpoints
:
480 cmd
= "umount -n %s" % self
.chrootPath(mountpoint
)
481 chroot
.do(cmd
, raiseExc
=0, shell
=True)
484 def mountpoints(self
):
486 ("mount -n -t proc pakfire_chroot_proc", "proc"),
487 ("mount -n -t sysfs pakfire_chroot_sysfs", "sys"),
490 mountopt
= "gid=%d,mode=0620,ptmxmode=0666" % grp
.getgrnam("tty").gr_gid
491 if self
.kernel_version
>= "2.6.29":
492 mountopt
+= ",newinstance"
495 ("mount -n -t devpts -o %s pakfire_chroot_devpts" % mountopt
, "dev/pts"),
496 ("mount -n -t tmpfs pakfire_chroot_shmfs", "dev/shm"),
499 if self
.settings
.get("enable_ccache"):
500 ret
.append(("mount -n --bind %s" % CCACHE_CACHE_DIR
, "var/cache/ccache"))
505 def calc_parallelism():
507 Calculate how many processes to run
510 We take the log10(number of processors) * factor
512 num
= os
.sysconf("SC_NPROCESSORS_CONF")
516 return int(round(math
.log10(num
) * 26))
521 # Add HOME manually, because it is occasionally not set
522 # and some builds get in trouble then.
524 "TERM" : os
.environ
.get("TERM", "dumb"),
527 "BUILDROOT" : self
.buildroot
,
528 "PARALLELISMFLAGS" : "-j%s" % self
.calc_parallelism(),
531 # Inherit environment from distro
532 env
.update(self
.pakfire
.distro
.environ
)
534 # Icecream environment settings
535 if self
.settings
.get("enable_icecream", None):
536 # Set the toolchain path
537 if self
.settings
.get("icecream_toolchain", None):
538 env
["ICECC_VERSION"] = self
.settings
.get("icecream_toolchain")
540 # Set preferred host if configured.
541 if self
.settings
.get("icecream_preferred_host", None):
542 env
["ICECC_PREFERRED_HOST"] = \
543 self
.settings
.get("icecream_preferred_host")
545 # XXX what do we need else?
549 def do(self
, command
, shell
=True, personality
=None, logger
=None, *args
, **kwargs
):
552 # Environment variables
555 if kwargs
.has_key("env"):
556 env
.update(kwargs
.pop("env"))
558 logging
.debug("Environment:")
559 for k
, v
in sorted(env
.items()):
560 logging
.debug(" %s=%s" % (k
, v
))
562 # Update personality it none was set
564 personality
= self
.distro
.personality
566 # Make every shell to a login shell because we set a lot of
567 # environment things there.
569 command
= ["bash", "--login", "-c", command
]
573 if not kwargs
.has_key("chrootPath"):
574 kwargs
["chrootPath"] = self
.chrootPath()
578 personality
=personality
,
591 def make(self
, *args
, **kwargs
):
592 if isinstance(self
.pkg
, packages
.Makefile
):
593 filename
= os
.path
.basename(self
.pkg
.filename
)
594 elif isinstance(self
.pkg
, packages
.SourcePackage
):
595 filename
= "%s.%s" % (self
.pkg
.name
, MAKEFILE_EXTENSION
)
597 return self
.do("make -f /build/%s %s" % (filename
, " ".join(args
)),
602 if not hasattr(self
, "_make_info"):
605 output
= self
.make("buildinfo", returnOutput
=True)
607 for line
in output
.splitlines():
612 m
= re
.match(r
"^(\w+)=(.*)$", line
)
616 info
[m
.group(1)] = m
.group(2).strip("\"")
618 self
._make
_info
= info
620 return self
._make
_info
624 if hasattr(self
, "_packages"):
625 return self
._packages
628 output
= self
.make("packageinfo", returnOutput
=True)
631 for line
in output
.splitlines():
636 m
= re
.match(r
"^(\w+)=(.*)$", line
)
641 pkg
[k
] = v
.strip("\"")
645 pkg
= packages
.VirtualPackage(self
.pakfire
, pkg
)
646 self
._packages
.append(pkg
)
648 return self
._packages
650 def make_requires(self
):
651 return self
.make_info
.get("PKG_BUILD_DEPS", "").split()
653 def make_sources(self
):
654 return self
.make_info
.get("PKG_FILES", "").split()
656 def create_icecream_toolchain(self
):
657 if not self
.settings
.get("enable_icecream", None):
660 out
= self
.do("icecc --build-native", returnOutput
=True)
662 for line
in out
.splitlines():
663 m
= re
.match(r
"^creating ([a-z0-9]+\.tar\.gz)", line
)
665 self
.settings
["icecream_toolchain"] = "/%s" % m
.group(1)
668 self
.create_icecream_toolchain()
671 self
.make("build", logger
=self
.log
)
674 raise BuildError
, "The build command failed."
676 for pkg
in reversed(self
.packages
):
677 packager
= packages
.BinaryPackager(self
.pakfire
, pkg
, self
)
683 def shell(self
, args
=[]):
684 if not util
.cli_is_interactive():
685 logging
.warning("Cannot run shell on non-interactive console.")
688 # Install all packages that are needed to run a shell.
689 self
.install(SHELL_PACKAGES
)
691 # XXX need to set CFLAGS here
692 command
= "/usr/sbin/chroot %s /usr/bin/chroot-shell %s" % \
693 (self
.chrootPath(), " ".join(args
))
695 # Add personality if we require one
696 if self
.pakfire
.distro
.personality
:
697 command
= "%s %s" % (self
.pakfire
.distro
.personality
, command
)
699 for key
, val
in self
.environ
.items():
700 command
= "%s=\"%s\" " % (key
, val
) + command
702 # Empty the environment
703 command
= "env -i - %s" % command
705 logging
.debug("Shell command: %s" % command
)
710 shell
= os
.system(command
)
711 return os
.WEXITSTATUS(shell
)