]> git.ipfire.org Git - pakfire.git/blame - pakfire/base.py
macros: Move make_*_targets into build section.
[pakfire.git] / pakfire / base.py
CommitLineData
47a4cb89 1#!/usr/bin/python
b792d887
MT
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###############################################################################
47a4cb89 21
7c8f2953
MT
22import logging
23import os
24import random
25import string
26
18edfe75 27import builder
53bb7960 28import config
7c8f2953
MT
29import distro
30import logger
53bb7960 31import repository
ae20b05f 32import packages
c605d735 33import satsolver
7c8f2953
MT
34import util
35
7c8f2953 36from constants import *
7c8f2953
MT
37from i18n import _
38
7c8f2953 39class Pakfire(object):
018127aa
MT
40 RELATIONS = (
41 (">=", satsolver.REL_GE,),
42 ("<=", satsolver.REL_LE,),
43 ("=" , satsolver.REL_EQ,),
44 ("<" , satsolver.REL_LT,),
45 (">" , satsolver.REL_GT,),
46 )
47
eb34496a 48 def __init__(self, mode=None, path="/", configs=[],
715d7009 49 enable_repos=None, disable_repos=None,
6a509182 50 distro_config=None, **kwargs):
6557ff4c
MT
51
52 # Set the mode.
6a509182 53 assert mode in ("normal", "builder", "server",)
6557ff4c
MT
54 self.mode = mode
55
7c8f2953
MT
56 # Check if we are operating as the root user.
57 self.check_root_user()
58
59 # The path where we are operating in.
6557ff4c
MT
60 self.path = path
61
62 # Configure the instance of Pakfire we just started.
63 if mode == "builder":
7c8f2953 64 self.path = os.path.join(BUILD_ROOT, util.random_string())
7c8f2953 65
6557ff4c 66 elif mode == "normal":
7b1e25fd 67 # check if we are actually running on an ipfire system.
0891edd2
MT
68 if self.path == "/":
69 self.check_is_ipfire()
7c8f2953
MT
70
71 # Read configuration file(s)
6557ff4c 72 self.config = config.Config(type=mode)
7c8f2953
MT
73 for filename in configs:
74 self.config.read(filename)
6a509182
MT
75 # Assume, that all other keyword arguments are configuration
76 # parameters.
77 self.config.update(kwargs)
7c8f2953
MT
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
53bb7960 85 self.distro = distro.Distribution(self, distro_config)
c605d735 86 self.pool = satsolver.Pool(self.distro.arch)
ae20b05f
MT
87 self.repos = repository.Repositories(self,
88 enable_repos=enable_repos, disable_repos=disable_repos)
7c8f2953 89
60845a36
MT
90 def __del__(self):
91 # Reset logging.
92 logger.setup_logging()
93
c605d735
MT
94 def create_solver(self):
95 return satsolver.Solver(self, self.pool)
96
063606f6
MT
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
7c8f2953 105
018127aa
MT
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
7c8f2953
MT
122 def destroy(self):
123 if not self.path == "/":
124 util.rm(self.path)
125
c07a3ca7
MT
126 @property
127 def environ(self):
128 env = {}
129
130 # Get distribution information.
131 env.update(self.distro.environ)
132
133 return env
134
7c8f2953
MT
135 @property
136 def supported_arches(self):
3ad4bb5a 137 return self.config.supported_arches
7c8f2953 138
6a509182
MT
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
7c8f2953
MT
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 """
6557ff4c 155 if not self.mode == "builder":
7c8f2953
MT
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 """
7c8f2953
MT
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
3ad4bb5a 167 if not self.config.host_supports_arch(arch):
7c8f2953
MT
168 raise BuildError, "Cannot build for the target architecture: %s" % arch
169
170 raise BuildError, arch
18edfe75 171
7b1e25fd 172 def check_is_ipfire(self):
c07a3ca7
MT
173 return # XXX disabled for now
174
7b1e25fd
MT
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
6557ff4c
MT
180 @property
181 def builder(self):
182 # XXX just backwards compatibility
183 return self.mode == "builder"
184
b25a3d84
MT
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
c07a3ca7
MT
201 def install(self, requires, interactive=True, logger=None, **kwargs):
202 if not logger:
203 logger = logging.getLogger()
204
ae20b05f 205 # Create a new request.
84680c15 206 request = self.create_request()
c07a3ca7
MT
207
208 # Expand all groups.
18edfe75 209 for req in requires:
c07a3ca7
MT
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)
18edfe75 220
ae20b05f 221 # Do the solving.
84680c15 222 solver = self.create_solver()
c07a3ca7 223 t = solver.solve(request, **kwargs)
18edfe75 224
ae20b05f 225 if not t:
c07a3ca7
MT
226 if not interactive:
227 raise DependencyError
228
c9a51ed9 229 logging.info(_("Nothing to do"))
18edfe75
MT
230 return
231
c07a3ca7
MT
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)
c0fd807c
MT
239
240 # Run the transaction.
ae20b05f 241 t.run()
18edfe75 242
08d60af8 243 def localinstall(self, files, yes=None):
de7fba8f 244 repo_name = repo_desc = "localinstall"
e0b99370
MT
245
246 # Create a new repository that holds all packages we passed on
247 # the commandline.
022c792a
MT
248 repo = repository.RepositoryDir(self, repo_name, repo_desc,
249 os.path.join(LOCAL_TMP_PATH, "repo_%s" % util.random_string()))
e0b99370 250
022c792a
MT
251 # Register the repository.
252 self.repos.add_repo(repo)
e0b99370 253
022c792a
MT
254 try:
255 # Add all packages to the repository index.
256 for file in files:
257 repo.collect_packages(file)
e0b99370 258
022c792a
MT
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
e0b99370 263
022c792a
MT
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)
e0b99370 269
022c792a
MT
270 solver = self.create_solver()
271 t = solver.solve(request)
e0b99370 272
022c792a
MT
273 # If solving was not possible, we exit here.
274 if not t:
c9a51ed9 275 logging.info(_("Nothing to do"))
022c792a 276 return
c0fd807c 277
08d60af8
MT
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:
022c792a
MT
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()
e0b99370 293
e38914be
MT
294 def update(self, pkgs, check=False):
295 """
296 check indicates, if the method should return after calculation
297 of the transaction.
298 """
84680c15 299 request = self.create_request()
0c1a80b8 300
6c395339
MT
301 # If there are given any packets on the command line, we will
302 # only update them. Otherwise, we update the whole system.
303 if pkgs:
304 update = False
305 for pkg in pkgs:
b34c426d 306 pkg = self.create_relation(pkg)
6c395339
MT
307 request.update(pkg)
308 else:
309 update = True
0c1a80b8 310
84680c15 311 solver = self.create_solver()
6c395339 312 t = solver.solve(request, update=update)
0c1a80b8
MT
313
314 if not t:
c9a51ed9 315 logging.info(_("Nothing to do"))
e38914be
MT
316
317 # If we are running in check mode, we return a non-zero value to
318 # indicate, that there are no updates.
319 if check:
320 return 1
321 else:
322 return
323
324 # Just exit here, because we won't do the transaction in this mode.
325 if check:
326 t.dump()
0c1a80b8
MT
327 return
328
c0fd807c
MT
329 # Ask the user if the transaction is okay.
330 if not t.cli_yesno():
331 return
0c1a80b8 332
c0fd807c 333 # Run the transaction.
0c1a80b8 334 t.run()
18edfe75 335
a39fd08b
MT
336 def remove(self, pkgs):
337 # Create a new request.
84680c15 338 request = self.create_request()
a39fd08b 339 for pkg in pkgs:
b34c426d 340 pkg = self.create_relation(pkg)
a39fd08b
MT
341 request.remove(pkg)
342
343 # Solve the request.
84680c15 344 solver = self.create_solver()
6eb8f338 345 t = solver.solve(request, uninstall=True)
a39fd08b
MT
346
347 if not t:
c9a51ed9 348 logging.info(_("Nothing to do"))
a39fd08b
MT
349 return
350
c0fd807c
MT
351 # Ask the user if okay.
352 if not t.cli_yesno():
353 return
354
355 # Process the transaction.
a39fd08b
MT
356 t.run()
357
c1e7f927 358 def info(self, patterns):
18edfe75
MT
359 pkgs = []
360
1f27e8fe
MT
361 # For all patterns we run a single search which returns us a bunch
362 # of solvables which are transformed into Package objects.
18edfe75 363 for pattern in patterns:
c07a3ca7
MT
364 if os.path.exists(pattern):
365 pkg = packages.open(self, self.repos.dummy, pattern)
366 if pkg:
367 pkgs.append(pkg)
1f27e8fe 368
c07a3ca7
MT
369 else:
370 solvs = self.pool.search(pattern, satsolver.SEARCH_GLOB, "solvable:name")
e1972777 371
c07a3ca7
MT
372 for solv in solvs:
373 pkg = packages.SolvPackage(self, solv)
374 if pkg in pkgs:
375 continue
376
377 pkgs.append(pkg)
18edfe75 378
e1972777 379 return sorted(pkgs)
18edfe75
MT
380
381 def search(self, pattern):
382 # Do the search.
c2e67297 383 pkgs = {}
884cd2fb 384 for solv in self.pool.search(pattern, satsolver.SEARCH_STRING|satsolver.SEARCH_FILES):
c2e67297 385 pkg = packages.SolvPackage(self, solv)
18edfe75 386
c2e67297
MT
387 # Check, if a package with the name is already in the resultset
388 # and always replace older ones by more recent ones.
389 if pkgs.has_key(pkg.name):
390 if pkgs[pkg.name] < pkg:
391 pkgs[pkg.name] = pkg
392 else:
393 pkgs[pkg.name] = pkg
394
395 # Return a list of the packages, alphabetically sorted.
396 return sorted(pkgs.values())
18edfe75 397
c07a3ca7
MT
398 def groupinstall(self, group, **kwargs):
399 self.install("@%s" % group, **kwargs)
18edfe75
MT
400
401 def grouplist(self, group):
fec9a917
MT
402 pkgs = []
403
404 for solv in self.pool.search(group, satsolver.SEARCH_SUBSTRING, "solvable:group"):
405 pkg = packages.SolvPackage(self, solv)
18edfe75 406
fec9a917
MT
407 if group in pkg.groups and not pkg.name in pkgs:
408 pkgs.append(pkg.name)
18edfe75 409
fec9a917 410 return sorted(pkgs)
18edfe75
MT
411
412 @staticmethod
dacaa18b 413 def build(pkg, resultdirs=None, shell=False, **kwargs):
18edfe75
MT
414 if not resultdirs:
415 resultdirs = []
416
417 b = builder.Builder(pkg, **kwargs)
418 p = b.pakfire
419
420 # Always include local repository.
421 resultdirs.append(p.repos.local_build.path)
422
423 try:
424 b.prepare()
425 b.extract()
426 b.build()
427 b.install_test()
428
429 # Copy-out all resultfiles
430 for resultdir in resultdirs:
431 if not resultdir:
432 continue
433
434 b.copy_result(resultdir)
435
436 except BuildError:
dacaa18b
MT
437 if shell:
438 b.shell()
439 else:
440 raise
18edfe75
MT
441
442 finally:
443 b.destroy()
444
c07a3ca7
MT
445 def _build(self, pkg, resultdir, nodeps=False, **kwargs):
446 print kwargs
447
448 b = builder.Builder2(self, pkg, resultdir, **kwargs)
449
450 try:
451 b.build()
452 except Error:
453 raise BuildError, _("Build command has failed.")
454 finally:
455 b.cleanup()
456
18edfe75
MT
457 @staticmethod
458 def shell(pkg, **kwargs):
459 b = builder.Builder(pkg, **kwargs)
460
461 try:
462 b.prepare()
463 b.extract()
464 b.shell()
465 finally:
466 b.destroy()
467
c07a3ca7 468 def dist(self, pkgs, resultdirs=None):
18edfe75
MT
469 if not resultdirs:
470 resultdirs = []
471
472 # Always include local repository
c07a3ca7 473 resultdirs.append(self.repos.local_build.path)
18edfe75 474
c07a3ca7
MT
475 for pkg in pkgs:
476 pkg = packages.Makefile(self, pkg)
50f96897 477
c07a3ca7 478 pkg.dist(resultdirs)
18edfe75
MT
479
480 def provides(self, patterns):
481 pkgs = []
482 for pattern in patterns:
e1972777
MT
483 for pkg in self.repos.whatprovides(pattern):
484 if pkg in pkgs:
485 continue
18edfe75 486
e1972777 487 pkgs.append(pkg)
18edfe75 488
e1972777 489 return sorted(pkgs)
18edfe75 490
8276111d
MT
491 def repo_create(self, path, input_paths, type="binary"):
492 assert type in ("binary", "source",)
493
c605d735 494 repo = repository.RepositoryDir(
18edfe75
MT
495 self,
496 name="new",
497 description="New repository.",
498 path=path,
8276111d 499 type=type,
18edfe75
MT
500 )
501
502 for input_path in input_paths:
c605d735 503 repo.collect_packages(input_path)
18edfe75
MT
504
505 repo.save()
506
8276111d
MT
507 return repo
508
18edfe75 509 def repo_list(self):
c605d735 510 return [r for r in self.repos]
31267a64
MT
511
512 def clean_all(self):
513 logging.debug("Cleaning up everything...")
514
515 # Clean up repository caches.
516 self.repos.clean()
35d89fd7
MT
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()