]> git.ipfire.org Git - pakfire.git/blame - pakfire/builder.py
Add personality support to build chroot.
[pakfire.git] / pakfire / builder.py
CommitLineData
47a4cb89
MT
1#!/usr/bin/python
2
3import fcntl
4import grp
5import logging
6import os
7import re
8import shutil
9import stat
10import time
11
12import depsolve
13import packages
14import transaction
15import util
16
17from constants import *
18from errors import BuildRootLocked
19
20
21class Builder(object):
22 # The version of the kernel this machine is running.
23 kernel_version = os.uname()[2]
24
25 def __init__(self, pakfire, pkg):
26 self.pakfire = pakfire
27 self.pkg = pkg
28
29 self.settings = {
30 "enable_loop_devices" : True,
31 }
32
33 self.buildroot = "/buildroot"
34
35 # Lock the buildroot
36 self._lock = None
37 self.lock()
38
39 # Initialize the environment
40 self.prepare()
41
42 @property
43 def path(self):
44 return self.pakfire.path
45
46 def lock(self):
47 filename = os.path.join(self.path, ".lock")
48
49 try:
50 self._lock = open(filename, "a+")
51 except IOError, e:
52 return 0
53
54 try:
55 fcntl.lockf(self._lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
56 except IOError, e:
57 raise BuildRootLocked, "Buildroot is locked"
58
59 return 1
60
61 def unlock(self):
62 if self._lock:
63 self._lock.close()
64 self._lock = None
65
66 def copyin(self, file_out, file_in):
67 if file_in.startswith("/"):
68 file_in = file_in[1:]
69
70 file_in = self.chrootPath(file_in)
71
72 #if not os.path.exists(file_out):
73 # return
74
75 dir_in = os.path.dirname(file_in)
76 if not os.path.exists(dir_in):
77 os.makedirs(dir_in)
78
79 logging.debug("%s --> %s" % (file_out, file_in))
80
81 shutil.copy2(file_out, file_in)
82
83 def copyout(self, file_in, file_out):
84 if file_in.startswith("/"):
85 file_in = file_in[1:]
86
87 file_in = self.chrootPath(file_in)
88
89 #if not os.path.exists(file_in):
90 # return
91
92 dir_out = os.path.dirname(file_out)
93 if not os.path.exists(dir_out):
94 os.makedirs(dir_out)
95
96 logging.debug("%s --> %s" % (file_in, file_out))
97
98 shutil.copy2(file_in, file_out)
99
100 def copy_result(self, resultdir):
101 dir_in = self.chrootPath("result")
102
103 for dir, subdirs, files in os.walk(dir_in):
104 basename = os.path.basename(dir)
105 dir = dir[len(self.chrootPath()):]
106 for file in files:
107 file_in = os.path.join(dir, file)
108
109 file_out = os.path.join(
110 resultdir,
111 basename,
112 file,
113 )
114
115 self.copyout(file_in, file_out)
116
117 def extract(self, requires=[], build_deps=True):
118 """
119 Gets a dependency set and extracts all packages
120 to the environment.
121 """
122 ds = depsolve.DependencySet(self.pakfire)
123 for p in BUILD_PACKAGES + requires:
124 ds.add_requires(p)
125 ds.resolve()
126
127 # Get build dependencies from source package.
128 if isinstance(self.pkg, packages.SourcePackage):
129 for req in self.pkg.requires:
130 ds.add_requires(req)
131
132 ts = transaction.TransactionSet(self.pakfire, ds)
133 ts.dump()
134 ts.run()
135
136 # Copy the makefile and load source tarballs.
137 if isinstance(self.pkg, packages.Makefile):
138 self.pkg.extract(self)
139
140 # If we have a makefile, we can only get the build dependencies
141 # after we have extracted all the rest.
142 if build_deps and isinstance(self.pkg, packages.Makefile):
143 requires = self.make_requires()
144 if not requires:
145 return
146
147 ds = depsolve.DependencySet(self.pakfire)
148 for r in requires:
149 ds.add_requires(r)
150 ds.resolve()
151
152 ts = transaction.TransactionSet(self.pakfire, ds)
153 ts.dump()
154 ts.run()
155
156 @property
157 def log(self):
158 # XXX for now, return the root logger
159 return logging.getLogger()
160
161 def chrootPath(self, *args):
162 # Remove all leading slashes
163 _args = []
164 for arg in args:
165 if arg.startswith("/"):
166 arg = arg[1:]
167 _args.append(arg)
168 args = _args
169
170 ret = os.path.join(self.path, *args)
171 ret = ret.replace("//", "/")
172
173 assert ret.startswith(self.path)
174
175 return ret
176
177 def prepare(self):
178 # Create directory.
179 if not os.path.exists(self.path):
180 os.makedirs(self.path)
181
182 # Create important directories.
183 dirs = [
184 "build",
185 self.buildroot,
186 "dev",
187 "dev/pts",
188 "dev/shm",
189 "etc",
190 "proc",
191 "result",
192 "sys",
193 "tmp",
194 "usr/src",
195 ]
196 for dir in dirs:
197 dir = self.chrootPath(dir)
198 if not os.path.exists(dir):
199 os.makedirs(dir)
200
201 self._prepare_dev()
202 self._prepare_users()
203 self._prepare_dns()
204
205 def _prepare_dev(self):
206 prevMask = os.umask(0000)
207
208 nodes = [
209 ("dev/null", stat.S_IFCHR | 0666, os.makedev(1, 3)),
210 ("dev/full", stat.S_IFCHR | 0666, os.makedev(1, 7)),
211 ("dev/zero", stat.S_IFCHR | 0666, os.makedev(1, 5)),
212 ("dev/random", stat.S_IFCHR | 0666, os.makedev(1, 8)),
213 ("dev/urandom", stat.S_IFCHR | 0444, os.makedev(1, 9)),
214 ("dev/tty", stat.S_IFCHR | 0666, os.makedev(5, 0)),
215 ("dev/console", stat.S_IFCHR | 0600, os.makedev(5, 1)),
216 ]
217
218 # If we need loop devices (which are optional) we create them here.
219 if self.settings["enable_loop_devices"]:
220 for i in range(0, 7):
221 nodes.append(("dev/loop%d" % i, stat.S_IFBLK | 0660, os.makedev(7, i)))
222
223 # Create all the nodes.
224 for node in nodes:
225 self._create_node(*node)
226
227 os.symlink("/proc/self/fd/0", self.chrootPath("dev", "stdin"))
228 os.symlink("/proc/self/fd/1", self.chrootPath("dev", "stdout"))
229 os.symlink("/proc/self/fd/2", self.chrootPath("dev", "stderr"))
230 os.symlink("/proc/self/fd", self.chrootPath("dev", "fd"))
231
232 # make device node for el4 and el5
233 if self.kernel_version < "2.6.19":
234 self._make_node("dev/ptmx", stat.S_IFCHR | 0666, os.makedev(5, 2))
235 else:
236 os.symlink("/dev/pts/ptmx", self.chrootPath("dev", "ptmx"))
237
238 os.umask(prevMask)
239
240 def _prepare_users(self):
241 f = open(self.chrootPath("etc", "passwd"), "w")
242 f.write("root:x:0:0:root:/root:/bin/bash\n")
243 f.write("nobody:x:99:99:Nobody:/:/sbin/nologin\n")
244 f.close()
245
246 f = open(self.chrootPath("etc", "group"), "w")
247 f.write("root:x:0:root\n")
248 f.write("nobody:x:99:\n")
249 f.close()
250
251 def _prepare_dns(self):
18973967
MT
252 """
253 Add DNS resolution facility to chroot environment by copying
254 /etc/resolv.conf and /etc/hosts.
255 """
256 for i in ("/etc/resolv.conf", "/etc/hosts"):
257 self.copyin(i, i)
47a4cb89
MT
258
259 def _create_node(self, filename, mode, device):
260 logging.debug("Create node: %s (%s)" % (filename, mode))
261
262 filename = self.chrootPath(filename)
263
264 # Create parent directory if it is missing.
265 dirname = os.path.dirname(filename)
266 if not os.path.exists(dirname):
267 os.makedirs(dirname)
268
269 os.mknod(filename, mode, device)
270
271 def cleanup(self):
272 logging.debug("Cleanup environment %s" % self.path)
273
274 if os.path.exists(self.path):
275 util.rm(self.path)
276
277 def _mountall(self):
278 self.log.debug("Mounting environment")
279 for cmd, mountpoint in self.mountpoints:
280 cmd = "%s %s" % (cmd, self.chrootPath(mountpoint))
281 util.do(cmd, shell=True)
282
283 def _umountall(self):
284 self.log.debug("Umounting environment")
285 for cmd, mountpoint in self.mountpoints:
286 cmd = "umount -n %s" % self.chrootPath(mountpoint)
287 util.do(cmd, raiseExc=0, shell=True)
288
289 @property
290 def mountpoints(self):
291 ret = [
292 ("mount -n -t proc pakfire_chroot_proc", "proc"),
293 ("mount -n -t sysfs pakfire_chroot_sysfs", "sys"),
294 ]
295
296 mountopt = "gid=%d,mode=0620,ptmxmode=0666" % grp.getgrnam("tty").gr_gid
297 if self.kernel_version >= "2.6.29":
298 mountopt += ",newinstance"
299
300 ret.extend([
301 ("mount -n -t devpts -o %s pakfire_chroot_devpts" % mountopt, "dev/pts"),
302 ("mount -n -t tmpfs pakfire_chroot_shmfs", "dev/shm"),
303 ])
304
305 return ret
306
307 @property
308 def environ(self):
309 env = {
310 "BUILDROOT" : self.buildroot,
47a4cb89
MT
311 }
312
313 # Inherit environment from distro
314 env.update(self.pakfire.distro.environ)
315
316 # XXX what do we need else?
317
318 return env
319
320 def do(self, command, shell=True, personality=None, *args, **kwargs):
321 ret = None
322 try:
323 # Environment variables
324 env = self.environ
325
326 if kwargs.has_key("env"):
327 env.update(kwargs.pop("env"))
328
e360ea59
MT
329 # Update personality it none was set
330 if not personality:
331 personality = self.pakfire.distro.personality
332
47a4cb89
MT
333 self._mountall()
334
335 if not kwargs.has_key("chrootPath"):
336 kwargs["chrootPath"] = self.chrootPath()
337
338 ret = util.do(
339 command,
340 personality=personality,
341 shell=shell,
342 env=env,
343 logger=self.log,
344 *args,
345 **kwargs
346 )
347
348 finally:
349 self._umountall()
350
351 return ret
352
353 def make(self, *args, **kwargs):
63473754
MT
354 command = ["bash", "--login", "-c",]
355 command.append("make -f /build/%s %s" % \
356 (os.path.basename(self.pkg.filename), " ".join(args)))
47a4cb89 357
63473754 358 return self.do(command, shell=False, **kwargs)
47a4cb89
MT
359
360 @property
361 def make_info(self):
362 if not hasattr(self, "_make_info"):
363 info = {}
364
365 output = self.make("buildinfo", returnOutput=True)
366
367 for line in output.splitlines():
368 # XXX temporarily
369 if not line:
370 break
371
372 m = re.match(r"^(\w+)=(.*)$", line)
373 if not m:
374 continue
375
376 info[m.group(1)] = m.group(2).strip("\"")
377
378 self._make_info = info
379
380 return self._make_info
381
382 @property
383 def packages(self):
384 if hasattr(self, "_packages"):
385 return self._packages
386
387 pkgs = []
388 output = self.make("packageinfo", returnOutput=True)
389
390 pkg = {}
391 for line in output.splitlines():
392 if not line:
393 pkgs.append(pkg)
394 pkg = {}
395
396 m = re.match(r"^(\w+)=(.*)$", line)
397 if not m:
398 continue
399
400 k, v = m.groups()
401 pkg[k] = v.strip("\"")
402
403 self._packages = []
404 for pkg in pkgs:
405 pkg = packages.VirtualPackage(pkg)
406 self._packages.append(pkg)
407
408 return self._packages
409
410 def make_requires(self):
411 return self.make_info.get("PKG_BUILD_DEPS", "").split()
412
413 def make_sources(self):
414 return self.make_info.get("PKG_FILES", "").split()
415
416 def build(self):
417 self.make("build")
418
419 for pkg in reversed(self.packages):
420 packager = packages.Packager(self.pakfire, pkg, self)
421 packager()
422
423 def dist(self):
424 self.pkg.dist(self)
425
426 def shell(self, args=[]):
427 # XXX need to add linux32 or linux64 to the command line
428 # XXX need to set CFLAGS here
429 command = "chroot %s /usr/bin/chroot-shell %s" % \
430 (self.chrootPath(), " ".join(args))
431
432 for key, val in self.environ.items():
433 command = "%s=\"%s\" " % (key, val) + command
434
e360ea59
MT
435 # Add personality if we require one
436 if self.pakfire.distro.personality:
437 command = "%s %s" % (self.pakfire.disto.personality, command)
438
47a4cb89 439 # Empty the environment
19979f20 440 #command = "env -i - %s" % command
47a4cb89
MT
441
442 logging.debug("Shell command: %s" % command)
443
444 try:
445 self._mountall()
446
447 shell = os.system(command)
448 return os.WEXITSTATUS(shell)
449
450 finally:
451 self._umountall()