]>
git.ipfire.org Git - pakfire.git/blob - pakfire/builder.py
21 from constants
import *
23 from errors
import BuildError
, BuildRootLocked
, Error
26 class Builder(object):
27 # The version of the kernel this machine is running.
28 kernel_version
= os
.uname()[2]
30 def __init__(self
, pakfire
, pkg
, build_id
=None, **settings
):
31 self
.pakfire
= pakfire
35 "enable_loop_devices" : True,
36 "enable_ccache" : True,
37 "enable_icecream" : False,
39 self
.settings
.update(settings
)
41 self
.buildroot
= "/buildroot"
47 # Save the build time.
48 self
.build_time
= int(time
.time())
50 # Save the build id and generate one if no build id was provided.
52 build_id
= uuid
.uuid4()
54 self
.build_id
= build_id
59 "build_date" : time
.strftime("%a, %d %b %Y %H:%M:%S +0000", time
.gmtime(self
.build_time
)),
60 "build_host" : socket
.gethostname(),
61 "build_id" : self
.build_id
,
62 "build_time" : self
.build_time
,
67 return self
.pakfire
.path
70 filename
= os
.path
.join(self
.path
, ".lock")
73 self
._lock
= open(filename
, "a+")
78 fcntl
.lockf(self
._lock
.fileno(), fcntl
.LOCK_EX | fcntl
.LOCK_NB
)
80 raise BuildRootLocked
, "Buildroot is locked"
89 def copyin(self
, file_out
, file_in
):
90 if file_in
.startswith("/"):
93 file_in
= self
.chrootPath(file_in
)
95 #if not os.path.exists(file_out):
98 dir_in
= os
.path
.dirname(file_in
)
99 if not os
.path
.exists(dir_in
):
102 logging
.debug("%s --> %s" % (file_out
, file_in
))
104 shutil
.copy2(file_out
, file_in
)
106 def copyout(self
, file_in
, file_out
):
107 if file_in
.startswith("/"):
108 file_in
= file_in
[1:]
110 file_in
= self
.chrootPath(file_in
)
112 #if not os.path.exists(file_in):
115 dir_out
= os
.path
.dirname(file_out
)
116 if not os
.path
.exists(dir_out
):
119 logging
.debug("%s --> %s" % (file_in
, file_out
))
121 shutil
.copy2(file_in
, file_out
)
123 def copy_result(self
, resultdir
):
124 dir_in
= self
.chrootPath("result")
126 for dir, subdirs
, files
in os
.walk(dir_in
):
127 basename
= os
.path
.basename(dir)
128 dir = dir[len(self
.chrootPath()):]
130 file_in
= os
.path
.join(dir, file)
132 file_out
= os
.path
.join(
138 self
.copyout(file_in
, file_out
)
140 def extract(self
, requires
=None, build_deps
=True):
142 Gets a dependency set and extracts all packages
148 # Add neccessary build dependencies.
149 requires
+= BUILD_PACKAGES
151 # If we have ccache enabled, we need to extract it
152 # to the build chroot.
153 if self
.settings
.get("enable_ccache"):
154 requires
.append("ccache")
156 # If we have icecream enabled, we need to extract it
157 # to the build chroot.
158 if self
.settings
.get("enable_icecream"):
159 requires
.append("icecream")
161 # Get build dependencies from source package.
162 if isinstance(self
.pkg
, packages
.SourcePackage
):
163 for req
in self
.pkg
.requires
:
166 # Install all packages.
167 self
.install(requires
)
169 # Copy the makefile and load source tarballs.
170 if isinstance(self
.pkg
, packages
.Makefile
):
171 self
.pkg
.extract(self
)
173 elif isinstance(self
.pkg
, packages
.SourcePackage
):
174 self
.pkg
.extract(_("Extracting: %s (source)") % self
.pkg
.name
,
175 prefix
=os
.path
.join(self
.path
, "build"))
177 # If we have a makefile, we can only get the build dependencies
178 # after we have extracted all the rest.
179 if build_deps
and isinstance(self
.pkg
, packages
.Makefile
):
180 requires
= self
.make_requires()
184 self
.install(requires
)
186 def install(self
, requires
):
188 Install everything that is required in requires.
190 # If we got nothing to do, we quit immediately.
194 ds
= depsolve
.DependencySet(self
.pakfire
)
196 if isinstance(r
, packages
.BinaryPackage
):
203 ts
= transaction
.Transaction(self
.pakfire
, ds
)
206 def install_test(self
):
209 # Connect packages to the FS repository.
210 r
= repository
.FileSystemRepository(self
.pakfire
)
212 for dir, subdirs
, files
in os
.walk(self
.chrootPath("result")):
214 file = os
.path
.join(dir, file)
216 if not file.endswith(PACKAGE_EXTENSION
):
219 p
= packages
.BinaryPackage(self
.pakfire
, r
, file)
226 # XXX for now, return the root logger
227 return logging
.getLogger()
229 def chrootPath(self
, *args
):
230 # Remove all leading slashes
233 if arg
.startswith("/"):
238 ret
= os
.path
.join(self
.path
, *args
)
239 ret
= ret
.replace("//", "/")
241 assert ret
.startswith(self
.path
)
247 if not os
.path
.exists(self
.path
):
248 os
.makedirs(self
.path
)
250 # Create important directories.
265 # Create cache dir if ccache is enabled.
266 if self
.settings
.get("enable_ccache"):
267 dirs
.append("var/cache/ccache")
269 if not os
.path
.exists(CCACHE_CACHE_DIR
):
270 os
.makedirs(CCACHE_CACHE_DIR
)
273 dir = self
.chrootPath(dir)
274 if not os
.path
.exists(dir):
277 # Create neccessary files like /etc/fstab and /etc/mtab.
284 file = self
.chrootPath(file)
285 dir = os
.path
.dirname(file)
286 if not os
.path
.exists(dir):
294 def _prepare_dev(self
):
295 prevMask
= os
.umask(0000)
298 ("dev/null", stat
.S_IFCHR |
0666, os
.makedev(1, 3)),
299 ("dev/full", stat
.S_IFCHR |
0666, os
.makedev(1, 7)),
300 ("dev/zero", stat
.S_IFCHR |
0666, os
.makedev(1, 5)),
301 ("dev/random", stat
.S_IFCHR |
0666, os
.makedev(1, 8)),
302 ("dev/urandom", stat
.S_IFCHR |
0444, os
.makedev(1, 9)),
303 ("dev/tty", stat
.S_IFCHR |
0666, os
.makedev(5, 0)),
304 ("dev/console", stat
.S_IFCHR |
0600, os
.makedev(5, 1)),
307 # If we need loop devices (which are optional) we create them here.
308 if self
.settings
["enable_loop_devices"]:
309 for i
in range(0, 7):
310 nodes
.append(("dev/loop%d" % i
, stat
.S_IFBLK |
0660, os
.makedev(7, i
)))
312 # Create all the nodes.
314 self
._create
_node
(*node
)
316 os
.symlink("/proc/self/fd/0", self
.chrootPath("dev", "stdin"))
317 os
.symlink("/proc/self/fd/1", self
.chrootPath("dev", "stdout"))
318 os
.symlink("/proc/self/fd/2", self
.chrootPath("dev", "stderr"))
319 os
.symlink("/proc/self/fd", self
.chrootPath("dev", "fd"))
321 # make device node for el4 and el5
322 if self
.kernel_version
< "2.6.19":
323 self
._make
_node
("dev/ptmx", stat
.S_IFCHR |
0666, os
.makedev(5, 2))
325 os
.symlink("/dev/pts/ptmx", self
.chrootPath("dev", "ptmx"))
329 def _prepare_dns(self
):
331 Add DNS resolution facility to chroot environment by copying
332 /etc/resolv.conf and /etc/hosts.
334 for i
in ("/etc/resolv.conf", "/etc/hosts"):
337 def _create_node(self
, filename
, mode
, device
):
338 logging
.debug("Create node: %s (%s)" % (filename
, mode
))
340 filename
= self
.chrootPath(filename
)
342 # Create parent directory if it is missing.
343 dirname
= os
.path
.dirname(filename
)
344 if not os
.path
.exists(dirname
):
347 os
.mknod(filename
, mode
, device
)
350 logging
.debug("Destroying environment %s" % self
.path
)
352 if os
.path
.exists(self
.path
):
356 logging
.debug("Cleaning environemnt.")
358 # Run make clean and let it cleanup its stuff.
361 # Remove the build directory and buildroot.
362 dirs
= ("build", self
.buildroot
, "result")
365 d
= self
.chrootPath(d
)
366 if not os
.path
.exists(d
):
372 # Clear make_info cache.
373 if hasattr(self
, "_make_info"):
377 self
.log
.debug("Mounting environment")
378 for cmd
, mountpoint
in self
.mountpoints
:
379 cmd
= "%s %s" % (cmd
, self
.chrootPath(mountpoint
))
380 util
.do(cmd
, shell
=True)
382 def _umountall(self
):
383 self
.log
.debug("Umounting environment")
384 for cmd
, mountpoint
in self
.mountpoints
:
385 cmd
= "umount -n %s" % self
.chrootPath(mountpoint
)
386 util
.do(cmd
, raiseExc
=0, shell
=True)
389 def mountpoints(self
):
391 ("mount -n -t proc pakfire_chroot_proc", "proc"),
392 ("mount -n -t sysfs pakfire_chroot_sysfs", "sys"),
395 mountopt
= "gid=%d,mode=0620,ptmxmode=0666" % grp
.getgrnam("tty").gr_gid
396 if self
.kernel_version
>= "2.6.29":
397 mountopt
+= ",newinstance"
400 ("mount -n -t devpts -o %s pakfire_chroot_devpts" % mountopt
, "dev/pts"),
401 ("mount -n -t tmpfs pakfire_chroot_shmfs", "dev/shm"),
404 if self
.settings
.get("enable_ccache"):
405 ret
.append(("mount -n --bind %s" % CCACHE_CACHE_DIR
, "var/cache/ccache"))
410 def calc_parallelism():
412 Calculate how many processes to run
415 We take the log10(number of processors) * factor
417 num
= os
.sysconf("SC_NPROCESSORS_CONF")
421 return int(round(math
.log10(num
) * 26))
426 # Add HOME manually, because it is occasionally not set
427 # and some builds get in trouble then.
429 "TERM" : os
.environ
.get("TERM", "dumb"),
432 "BUILDROOT" : self
.buildroot
,
433 "PARALLELISMFLAGS" : "-j%s" % self
.calc_parallelism(),
436 # Inherit environment from distro
437 env
.update(self
.pakfire
.distro
.environ
)
439 # Icecream environment settings
440 if self
.settings
.get("enable_icecream", None):
441 # Set the toolchain path
442 if self
.settings
.get("icecream_toolchain", None):
443 env
["ICECC_VERSION"] = self
.settings
.get("icecream_toolchain")
445 # Set preferred host if configured.
446 if self
.settings
.get("icecream_preferred_host", None):
447 env
["ICECC_PREFERRED_HOST"] = \
448 self
.settings
.get("icecream_preferred_host")
450 # XXX what do we need else?
454 def do(self
, command
, shell
=True, personality
=None, *args
, **kwargs
):
457 # Environment variables
460 if kwargs
.has_key("env"):
461 env
.update(kwargs
.pop("env"))
463 logging
.debug("Environment:")
464 for k
, v
in sorted(env
.items()):
465 logging
.debug(" %s=%s" % (k
, v
))
467 # Update personality it none was set
469 personality
= self
.pakfire
.distro
.personality
471 # Make every shell to a login shell because we set a lot of
472 # environment things there.
474 command
= ["bash", "--login", "-c", command
]
478 if not kwargs
.has_key("chrootPath"):
479 kwargs
["chrootPath"] = self
.chrootPath()
483 personality
=personality
,
496 def make(self
, *args
, **kwargs
):
497 if isinstance(self
.pkg
, packages
.Makefile
):
498 filename
= os
.path
.basename(self
.pkg
.filename
)
499 elif isinstance(self
.pkg
, packages
.SourcePackage
):
500 filename
= "%s.%s" % (self
.pkg
.name
, MAKEFILE_EXTENSION
)
502 return self
.do("make -f /build/%s %s" % (filename
, " ".join(args
)),
507 if not hasattr(self
, "_make_info"):
510 output
= self
.make("buildinfo", returnOutput
=True)
512 for line
in output
.splitlines():
517 m
= re
.match(r
"^(\w+)=(.*)$", line
)
521 info
[m
.group(1)] = m
.group(2).strip("\"")
523 self
._make
_info
= info
525 return self
._make
_info
529 if hasattr(self
, "_packages"):
530 return self
._packages
533 output
= self
.make("packageinfo", returnOutput
=True)
536 for line
in output
.splitlines():
541 m
= re
.match(r
"^(\w+)=(.*)$", line
)
546 pkg
[k
] = v
.strip("\"")
548 # Create a dummy repository to link the virtual packages to
549 repo
= repository
.DummyRepository(self
.pakfire
)
553 pkg
= packages
.VirtualPackage(self
.pakfire
, pkg
) # XXX had to remove repo here?!
554 self
._packages
.append(pkg
)
556 return self
._packages
558 def make_requires(self
):
559 return self
.make_info
.get("PKG_BUILD_DEPS", "").split()
561 def make_sources(self
):
562 return self
.make_info
.get("PKG_FILES", "").split()
564 def create_icecream_toolchain(self
):
565 if not self
.settings
.get("enable_icecream", None):
568 out
= self
.do("icecc --build-native", returnOutput
=True)
570 for line
in out
.splitlines():
571 m
= re
.match(r
"^creating ([a-z0-9]+\.tar\.gz)", line
)
573 self
.settings
["icecream_toolchain"] = "/%s" % m
.group(1)
576 self
.create_icecream_toolchain()
582 raise BuildError
, "The build command failed."
584 for pkg
in reversed(self
.packages
):
585 packager
= packages
.BinaryPackager(self
.pakfire
, pkg
, self
)
591 def shell(self
, args
=[]):
592 if not util
.cli_is_interactive():
593 logging
.warning("Cannot run shell on non-interactive console.")
596 # Install all packages that are needed to run a shell.
597 self
.install(SHELL_PACKAGES
)
599 # XXX need to set CFLAGS here
600 command
= "/usr/sbin/chroot %s /usr/bin/chroot-shell %s" % \
601 (self
.chrootPath(), " ".join(args
))
603 # Add personality if we require one
604 if self
.pakfire
.distro
.personality
:
605 command
= "%s %s" % (self
.pakfire
.distro
.personality
, command
)
607 for key
, val
in self
.environ
.items():
608 command
= "%s=\"%s\" " % (key
, val
) + command
610 # Empty the environment
611 command
= "env -i - %s" % command
613 logging
.debug("Shell command: %s" % command
)
618 shell
= os
.system(command
)
619 return os
.WEXITSTATUS(shell
)