]> git.ipfire.org Git - pakfire.git/blob - pakfire/base.py
Change the way the buildroot is mounted.
[pakfire.git] / pakfire / base.py
1 #!/usr/bin/python
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 ###############################################################################
21
22 import logging
23 import os
24 import random
25 import string
26
27 import builder
28 import config
29 import distro
30 import logger
31 import repository
32 import packages
33 import satsolver
34 import util
35
36 from constants import *
37 from i18n import _
38
39 class Pakfire(object):
40 RELATIONS = (
41 (">=", satsolver.REL_GE,),
42 ("<=", satsolver.REL_LE,),
43 ("=" , satsolver.REL_EQ,),
44 ("<" , satsolver.REL_LT,),
45 (">" , satsolver.REL_GT,),
46 )
47
48 def __init__(self, mode=None, path="/", configs=[],
49 enable_repos=None, disable_repos=None,
50 distro_config=None, **kwargs):
51
52 # Set the mode.
53 assert mode in ("normal", "builder", "server",)
54 self.mode = mode
55
56 # Check if we are operating as the root user.
57 self.check_root_user()
58
59 # The path where we are operating in.
60 self.path = path
61
62 # Configure the instance of Pakfire we just started.
63 if mode == "builder":
64 self.path = os.path.join(BUILD_ROOT, util.random_string())
65
66 elif mode == "normal":
67 # check if we are actually running on an ipfire system.
68 if self.path == "/":
69 self.check_is_ipfire()
70
71 # Read configuration file(s)
72 self.config = config.Config(type=mode)
73 for filename in configs:
74 self.config.read(filename)
75 # Assume, that all other keyword arguments are configuration
76 # parameters.
77 self.config.update(kwargs)
78
79 # Setup the logger
80 logger.setup_logging(self.config)
81 self.config.dump()
82
83 # Get more information about the distribution we are running
84 # or building
85 self.distro = distro.Distribution(self, distro_config)
86 self.pool = satsolver.Pool(self.distro.arch)
87 self.repos = repository.Repositories(self,
88 enable_repos=enable_repos, disable_repos=disable_repos)
89
90 def __del__(self):
91 # Reset logging.
92 logger.setup_logging()
93
94 def create_solver(self):
95 return satsolver.Solver(self, self.pool)
96
97 def create_request(self, builder=False):
98 request = satsolver.Request(self.pool)
99
100 # Add multiinstall information.
101 for solv in PAKFIRE_MULTIINSTALL:
102 request.noobsoletes(solv)
103
104 return request
105
106 def create_relation(self, s):
107 assert s
108
109 if s.startswith("/"):
110 return satsolver.Relation(self.pool, s)
111
112 for pattern, type in self.RELATIONS:
113 if not pattern in s:
114 continue
115
116 name, version = s.split(pattern, 1)
117
118 return satsolver.Relation(self.pool, name, version, type)
119
120 return satsolver.Relation(self.pool, s)
121
122 def destroy(self):
123 if not self.path == "/":
124 util.rm(self.path)
125
126 @property
127 def environ(self):
128 env = {}
129
130 # Get distribution information.
131 env.update(self.distro.environ)
132
133 return env
134
135 @property
136 def supported_arches(self):
137 return self.config.supported_arches
138
139 @property
140 def offline(self):
141 """
142 A shortcut that indicates if the system is running in offline mode.
143 """
144 return self.config.get("offline", False)
145
146 def check_root_user(self):
147 if not os.getuid() == 0 or not os.getgid() == 0:
148 raise Exception, "You must run pakfire as the root user."
149
150 def check_build_mode(self):
151 """
152 Check if we are running in build mode.
153 Otherwise, raise an exception.
154 """
155 if not self.mode == "builder":
156 raise BuildError, "Cannot build when not in build mode."
157
158 def check_host_arch(self, arch):
159 """
160 Check if we can build for arch.
161 """
162 # If no arch was given on the command line we build for our
163 # own arch which should always work.
164 if not arch:
165 return True
166
167 if not self.config.host_supports_arch(arch):
168 raise BuildError, "Cannot build for the target architecture: %s" % arch
169
170 raise BuildError, arch
171
172 def check_is_ipfire(self):
173 return # XXX disabled for now
174
175 ret = os.path.exists("/etc/ipfire-release")
176
177 if not ret:
178 raise NotAnIPFireSystemError, "You can run pakfire only on an IPFire system"
179
180 @property
181 def builder(self):
182 # XXX just backwards compatibility
183 return self.mode == "builder"
184
185 def resolvdep(self, requires):
186 # Create a new request.
187 request = self.create_request()
188 for req in requires:
189 req = self.create_relation(req)
190 request.install(req)
191
192 # Do the solving.
193 solver = self.create_solver()
194 t = solver.solve(request)
195
196 if t:
197 t.dump()
198 else:
199 logging.info(_("Nothing to do"))
200
201 def install(self, requires, interactive=True, logger=None, **kwargs):
202 if not logger:
203 logger = logging.getLogger()
204
205 # Create a new request.
206 request = self.create_request()
207
208 # Expand all groups.
209 for req in requires:
210 if req.startswith("@"):
211 reqs = self.grouplist(req[1:])
212 else:
213 reqs = [req,]
214
215 for req in reqs:
216 if not isinstance(req, packages.BinaryPackage):
217 req = self.create_relation(req)
218
219 request.install(req)
220
221 # Do the solving.
222 solver = self.create_solver()
223 t = solver.solve(request, **kwargs)
224
225 if not t:
226 if not interactive:
227 raise DependencyError
228
229 logging.info(_("Nothing to do"))
230 return
231
232 if interactive:
233 # Ask if the user acknowledges the transaction.
234 if not t.cli_yesno():
235 return
236
237 else:
238 t.dump(logger=logger)
239
240 # Run the transaction.
241 t.run()
242
243 def localinstall(self, files, yes=None):
244 repo_name = repo_desc = "localinstall"
245
246 # Create a new repository that holds all packages we passed on
247 # the commandline.
248 repo = repository.RepositoryDir(self, repo_name, repo_desc,
249 os.path.join(LOCAL_TMP_PATH, "repo_%s" % util.random_string()))
250
251 # Register the repository.
252 self.repos.add_repo(repo)
253
254 try:
255 # Add all packages to the repository index.
256 for file in files:
257 repo.collect_packages(file)
258
259 # Break if no packages were added at all.
260 if not len(repo):
261 logging.critical(_("There are no packages to install."))
262 return
263
264 # Create a new request that installs all solvables from the
265 # repository.
266 request = self.create_request()
267 for solv in [p.solvable for p in repo]:
268 request.install(solv)
269
270 solver = self.create_solver()
271 t = solver.solve(request)
272
273 # If solving was not possible, we exit here.
274 if not t:
275 logging.info(_("Nothing to do"))
276 return
277
278 if yes is None:
279 # Ask the user if this is okay.
280 if not t.cli_yesno():
281 return
282 elif yes:
283 t.dump()
284 else:
285 return
286
287 # If okay, run the transcation.
288 t.run()
289
290 finally:
291 # Remove the temporary copy of the repository we have created earlier.
292 repo.remove()
293 self.repos.rem_repo(repo)
294
295 def update(self, pkgs, check=False):
296 """
297 check indicates, if the method should return after calculation
298 of the transaction.
299 """
300 request = self.create_request()
301
302 # If there are given any packets on the command line, we will
303 # only update them. Otherwise, we update the whole system.
304 if pkgs:
305 update = False
306 for pkg in pkgs:
307 pkg = self.create_relation(pkg)
308 request.update(pkg)
309 else:
310 update = True
311
312 solver = self.create_solver()
313 t = solver.solve(request, update=update)
314
315 if not t:
316 logging.info(_("Nothing to do"))
317
318 # If we are running in check mode, we return a non-zero value to
319 # indicate, that there are no updates.
320 if check:
321 return 1
322 else:
323 return
324
325 # Just exit here, because we won't do the transaction in this mode.
326 if check:
327 t.dump()
328 return
329
330 # Ask the user if the transaction is okay.
331 if not t.cli_yesno():
332 return
333
334 # Run the transaction.
335 t.run()
336
337 def remove(self, pkgs):
338 # Create a new request.
339 request = self.create_request()
340 for pkg in pkgs:
341 pkg = self.create_relation(pkg)
342 request.remove(pkg)
343
344 # Solve the request.
345 solver = self.create_solver()
346 t = solver.solve(request, uninstall=True)
347
348 if not t:
349 logging.info(_("Nothing to do"))
350 return
351
352 # Ask the user if okay.
353 if not t.cli_yesno():
354 return
355
356 # Process the transaction.
357 t.run()
358
359 def info(self, patterns):
360 pkgs = []
361
362 # For all patterns we run a single search which returns us a bunch
363 # of solvables which are transformed into Package objects.
364 for pattern in patterns:
365 if os.path.exists(pattern):
366 pkg = packages.open(self, self.repos.dummy, pattern)
367 if pkg:
368 pkgs.append(pkg)
369
370 else:
371 solvs = self.pool.search(pattern, satsolver.SEARCH_GLOB, "solvable:name")
372
373 for solv in solvs:
374 pkg = packages.SolvPackage(self, solv)
375 if pkg in pkgs:
376 continue
377
378 pkgs.append(pkg)
379
380 return sorted(pkgs)
381
382 def search(self, pattern):
383 # Do the search.
384 pkgs = {}
385 for solv in self.pool.search(pattern, satsolver.SEARCH_STRING|satsolver.SEARCH_FILES):
386 pkg = packages.SolvPackage(self, solv)
387
388 # Check, if a package with the name is already in the resultset
389 # and always replace older ones by more recent ones.
390 if pkgs.has_key(pkg.name):
391 if pkgs[pkg.name] < pkg:
392 pkgs[pkg.name] = pkg
393 else:
394 pkgs[pkg.name] = pkg
395
396 # Return a list of the packages, alphabetically sorted.
397 return sorted(pkgs.values())
398
399 def groupinstall(self, group, **kwargs):
400 self.install("@%s" % group, **kwargs)
401
402 def grouplist(self, group):
403 pkgs = []
404
405 for solv in self.pool.search(group, satsolver.SEARCH_SUBSTRING, "solvable:group"):
406 pkg = packages.SolvPackage(self, solv)
407
408 if group in pkg.groups and not pkg.name in pkgs:
409 pkgs.append(pkg.name)
410
411 return sorted(pkgs)
412
413 @staticmethod
414 def build(pkg, resultdirs=None, shell=False, install_test=True, **kwargs):
415 if not resultdirs:
416 resultdirs = []
417
418 b = builder.BuildEnviron(pkg, **kwargs)
419 p = b.pakfire
420
421 # Always include local repository.
422 resultdirs.append(p.repos.local_build.path)
423
424 try:
425 # Start to prepare the build environment by mounting
426 # the filesystems and extracting files.
427 b.start()
428
429 # Build the package.
430 b.build(install_test=install_test)
431
432 # Copy-out all resultfiles
433 for resultdir in resultdirs:
434 if not resultdir:
435 continue
436
437 b.copy_result(resultdir)
438
439 except BuildError:
440 if shell:
441 b.shell()
442 else:
443 raise
444
445 finally:
446 b.stop()
447
448 def _build(self, pkg, resultdir, nodeps=False, **kwargs):
449 b = builder.Builder(self, pkg, resultdir, **kwargs)
450
451 try:
452 b.build()
453 except Error:
454 raise BuildError, _("Build command has failed.")
455 finally:
456 b.cleanup()
457
458 @staticmethod
459 def shell(pkg, **kwargs):
460 b = builder.BuildEnviron(pkg, **kwargs)
461
462 try:
463 b.start()
464 b.shell()
465 finally:
466 b.stop()
467
468 def dist(self, pkgs, resultdirs=None):
469 if not resultdirs:
470 resultdirs = []
471
472 # Always include local repository
473 resultdirs.append(self.repos.local_build.path)
474
475 for pkg in pkgs:
476 pkg = packages.Makefile(self, pkg)
477
478 pkg.dist(resultdirs)
479
480 def provides(self, patterns):
481 pkgs = []
482 for pattern in patterns:
483 for pkg in self.repos.whatprovides(pattern):
484 if pkg in pkgs:
485 continue
486
487 pkgs.append(pkg)
488
489 return sorted(pkgs)
490
491 def repo_create(self, path, input_paths, type="binary"):
492 assert type in ("binary", "source",)
493
494 repo = repository.RepositoryDir(
495 self,
496 name="new",
497 description="New repository.",
498 path=path,
499 type=type,
500 )
501
502 for input_path in input_paths:
503 repo.collect_packages(input_path)
504
505 repo.save()
506
507 return repo
508
509 def repo_list(self):
510 return [r for r in self.repos]
511
512 def clean_all(self):
513 logging.debug("Cleaning up everything...")
514
515 # Clean up repository caches.
516 self.repos.clean()
517
518 def check(self, downgrade=True, uninstall=True):
519 """
520 Try to fix any errors in the system.
521 """
522 # Detect any errors in the dependency tree.
523 # For that we create an empty request and solver and try to solve
524 # something.
525 request = self.create_request()
526 solver = self.create_solver()
527
528 # XXX the solver does crash if we call it with fix_system=1,
529 # allow_downgrade=1 and uninstall=1. Need to fix this.
530 allow_downgrade = False
531 uninstall = False
532
533 t = solver.solve(request, fix_system=True, allow_downgrade=downgrade,
534 uninstall=uninstall)
535
536 if not t:
537 logging.info(_("Everything is fine."))
538 return
539
540 # Ask the user if okay.
541 if not t.cli_yesno():
542 return
543
544 # Process the transaction.
545 t.run()