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