2 Integration with native distribution package managers.
6 # Copyright (C) 2009, Thomas Leonard
7 # See the README file for details, or visit http://0install.net.
9 from zeroinstall
import _
10 import os
, platform
, re
, subprocess
, sys
11 from logging
import warn
, info
12 from zeroinstall
.injector
import namespaces
, model
, arch
13 from zeroinstall
.support
import basedir
15 _dotted_ints
= '[0-9]+(?:\.[0-9]+)*'
17 # This matches a version number that would be a valid Zero Install version without modification
18 _zeroinstall_regexp
= '(?:%s)(?:-(?:pre|rc|post|)(?:%s))*' % (_dotted_ints
, _dotted_ints
)
20 # This matches the interesting bits of distribution version numbers
21 # (first bit is for Java-style 6b17 syntax)
22 _version_regexp
= '({ints}b)?({zero})(-r{ints})?'.format(zero
= _zeroinstall_regexp
, ints
= _dotted_ints
)
24 # We try to do updates atomically without locking, but we don't worry too much about
25 # duplicate entries or being a little out of sync with the on-disk copy.
27 def __init__(self
, cache_leaf
, source
, format
):
28 """Maintain a cache file (e.g. ~/.cache/0install.net/injector/$name).
29 If the size or mtime of $source has changed, or the cache
30 format version if different, reset the cache first."""
31 self
.cache_leaf
= cache_leaf
34 self
.cache_dir
= basedir
.save_cache_path(namespaces
.config_site
,
35 namespaces
.config_prog
)
36 self
.cached_for
= {} # Attributes of source when cache was created
39 except Exception as ex
:
40 info(_("Failed to load cache (%s). Flushing..."), ex
)
46 info
= os
.stat(self
.source
)
47 mtime
= int(info
.st_mtime
)
49 except Exception as ex
:
50 warn("Failed to stat %s: %s", self
.source
, ex
)
54 tmp
, tmp_name
= tempfile
.mkstemp(dir = self
.cache_dir
)
55 data
= "mtime=%d\nsize=%d\nformat=%d\n\n" % (mtime
, size
, self
.format
)
57 wrote
= os
.write(tmp
, data
)
59 os
.rename(tmp_name
, os
.path
.join(self
.cache_dir
, self
.cache_leaf
))
63 # Populate self.cache from our saved cache file.
64 # Throws an exception if the cache doesn't exist or has the wrong format.
65 def _load_cache(self
):
66 self
.cache
= cache
= {}
67 stream
= open(os
.path
.join(self
.cache_dir
, self
.cache_leaf
))
73 key
, value
= line
.split('=', 1)
74 if key
in ('mtime', 'size', 'format'):
75 self
.cached_for
[key
] = int(value
)
80 key
, value
= line
.split('=', 1)
81 cache
[key
] = value
[:-1]
85 # Check the source file hasn't changed since we created the cache
86 def _check_valid(self
):
87 info
= os
.stat(self
.source
)
88 if self
.cached_for
['mtime'] != int(info
.st_mtime
):
89 raise Exception("Modification time of %s has changed" % self
.source
)
90 if self
.cached_for
['size'] != info
.st_size
:
91 raise Exception("Size of %s has changed" % self
.source
)
92 if self
.cached_for
.get('format', None) != self
.format
:
93 raise Exception("Format of cache has changed")
98 except Exception as ex
:
99 info(_("Cache needs to be refreshed: %s"), ex
)
103 return self
.cache
.get(key
, None)
105 def put(self
, key
, value
):
106 cache_path
= os
.path
.join(self
.cache_dir
, self
.cache_leaf
)
107 self
.cache
[key
] = value
109 stream
= open(cache_path
, 'a')
111 stream
.write('%s=%s\n' % (key
, value
))
114 except Exception as ex
:
115 warn("Failed to write to cache %s: %s=%s: %s", cache_path
, key
, value
, ex
)
117 def try_cleanup_distro_version(version
):
118 """Try to turn a distribution version string into one readable by Zero Install.
119 We do this by stripping off anything we can't parse.
120 @return: the part we understood, or None if we couldn't parse anything
123 version
= version
.split(':')[1] # Skip 'epoch'
124 version
= version
.replace('_', '-')
125 match
= re
.match(_version_regexp
, version
)
127 major
, version
, revision
= match
.groups()
128 if major
is not None:
129 version
= major
[:-1] + '.' + version
133 return '%s-%s' % (version
, revision
[2:])
136 class Distribution(object):
137 """Represents a distribution with which we can integrate.
138 Sub-classes should specialise this to integrate with the package managers of
139 particular distributions. This base class ignores the native package manager.
144 def get_package_info(self
, package
, factory
):
145 """Get information about the given package.
146 Add zero or more implementations using the factory (typically at most two
147 will be added; the currently installed version and the latest available).
148 @param package: package name (e.g. "gimp")
150 @param factory: function for creating new DistributionImplementation objects from IDs
151 @type factory: str -> L{model.DistributionImplementation}
155 def get_score(self
, distribution
):
156 """Indicate how closely the host distribution matches this one.
157 The <package-implementation> with the highest score is passed
158 to L{Distribution.get_package_info}. If several elements get
159 the same score, get_package_info is called for all of them.
160 @param distribution: a distribution name
161 @type distribution: str
162 @return: an integer, or None if there is no match at all
167 def get_feed(self
, master_feed
):
168 """Generate a feed containing information about distribution packages.
169 This should immediately return a feed containing an implementation for the
170 package if it's already installed. Information about versions that could be
171 installed using the distribution's package manager can be added asynchronously
172 later (see L{fetch_candidates}).
173 @param master_feed: feed containing the <package-implementation> elements
174 @type master_feed: L{model.ZeroInstallFeed}
175 @rtype: L{model.ZeroInstallFeed}"""
177 feed
= model
.ZeroInstallFeed(None)
178 feed
.url
= 'distribution:' + master_feed
.url
180 for item
, item_attrs
in master_feed
.get_package_impls(self
):
181 package
= item_attrs
.get('package', None)
183 raise model
.InvalidInterface(_("Missing 'package' attribute on %s") % item
)
185 def factory(id, only_if_missing
= False, installed
= True):
186 assert id.startswith('package:')
187 if id in feed
.implementations
:
190 warn(_("Duplicate ID '%s' for DistributionImplementation"), id)
191 impl
= model
.DistributionImplementation(feed
, id, self
)
192 feed
.implementations
[id] = impl
194 impl
.installed
= installed
195 impl
.metadata
= item_attrs
197 item_main
= item_attrs
.get('main', None)
198 if item_main
and not item_main
.startswith('/'):
199 raise model
.InvalidInterface(_("'main' attribute must be absolute, but '%s' doesn't start with '/'!") %
201 impl
.main
= item_main
202 impl
.upstream_stability
= model
.packaged
206 self
.get_package_info(package
, factory
)
208 if master_feed
.url
== 'http://repo.roscidus.com/python/python' and all(not impl
.installed
for impl
in feed
.implementations
.values()):
209 # Hack: we can support Python on platforms with unsupported package managers
210 # by adding the implementation of Python running us now to the list.
211 python_version
= '.'.join([str(v
) for v
in sys
.version_info
if isinstance(v
, int)])
212 impl_id
= 'package:host:python:' + python_version
213 assert impl_id
not in feed
.implementations
214 impl
= model
.DistributionImplementation(feed
, impl_id
, self
)
215 impl
.installed
= True
216 impl
.version
= model
.parse_version(python_version
)
217 impl
.main
= sys
.executable
218 impl
.upstream_stability
= model
.packaged
219 impl
.machine
= host_machine
# (hopefully)
220 feed
.implementations
[impl_id
] = impl
224 def fetch_candidates(self
, master_feed
):
225 """Collect information about versions we could install using
226 the distribution's package manager. On success, the distribution
227 feed in iface_cache is updated.
228 @return: a L{tasks.Blocker} if the task is in progress, or None if not"""
229 if self
.packagekit
.available
:
230 package_names
= [item
.getAttribute("package") for item
, item_attrs
in master_feed
.get_package_impls(self
)]
231 return self
.packagekit
.fetch_candidates(package_names
)
234 def packagekit(self
):
235 """For use by subclasses.
236 @rtype: L{packagekit.PackageKit}"""
237 if not self
._packagekit
:
238 from zeroinstall
.injector
import packagekit
239 self
._packagekit
= packagekit
.PackageKit()
240 return self
._packagekit
242 class WindowsDistribution(Distribution
):
243 def get_package_info(self
, package
, factory
):
244 def _is_64bit_windows():
248 bits
, linkage
= platform
.architecture()
249 from win32process
import IsWow64Process
250 if p
== 'win64' or (p
== 'win32' and IsWow64Process()): return True
251 elif p
== 'win32': return False
252 else: raise Exception(_("WindowsDistribution may only be used on the Windows platform"))
254 def _read_hklm_reg(key_name
, value_name
):
255 from win32api
import RegOpenKeyEx
, RegQueryValueEx
, RegCloseKey
256 from win32con
import HKEY_LOCAL_MACHINE
, KEY_READ
257 KEY_WOW64_64KEY
= 0x0100
258 KEY_WOW64_32KEY
= 0x0200
259 if _is_64bit_windows():
261 key32
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
, key_name
, 0, KEY_READ | KEY_WOW64_32KEY
)
262 (value32
, _
) = RegQueryValueEx(key32
, value_name
)
267 key64
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
, key_name
, 0, KEY_READ | KEY_WOW64_64KEY
)
268 (value64
, _
) = RegQueryValueEx(key64
, value_name
)
274 key32
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
, key_name
, 0, KEY_READ
)
275 (value32
, _
) = RegQueryValueEx(key32
, value_name
)
280 return (value32
, value64
)
282 if package
== 'openjdk-6-jre':
283 (java32_home
, java64_home
) = _read_hklm_reg(r
"SOFTWARE\JavaSoft\Java Runtime Environment\1.6", "JavaHome")
286 if os
.path
.isfile(java32_home
+ r
"\bin\java.exe"):
287 impl
= factory('package:windows:%s:%s:%s' % (package
, '6', 'i486'))
288 impl
.machine
= 'i486';
289 impl
.version
= model
.parse_version('6')
290 impl
.upstream_stability
= model
.packaged
291 impl
.main
= java32_home
+ r
"\bin\java.exe"
293 if os
.path
.isfile(java64_home
+ r
"\bin\java.exe"):
294 impl
= factory('package:windows:%s:%s:%s' % (package
, '6', 'x86_64'))
295 impl
.machine
= 'x86_64';
296 impl
.version
= model
.parse_version('6')
297 impl
.upstream_stability
= model
.packaged
298 impl
.main
= java64_home
+ r
"\bin\java.exe"
300 if package
== 'openjdk-6-jdk':
301 (java32_home
, java64_home
) = _read_hklm_reg(r
"SOFTWARE\JavaSoft\Java Development Kit\1.6", "JavaHome")
304 if os
.path
.isfile(java32_home
+ r
"\bin\java.exe"):
305 impl
= factory('package:windows:%s:%s:%s' % (package
, '6', 'i486'))
306 impl
.machine
= 'i486';
307 impl
.version
= model
.parse_version('6')
308 impl
.upstream_stability
= model
.packaged
309 impl
.main
= java32_home
+ r
"\bin\java.exe"
311 if os
.path
.isfile(java64_home
+ r
"\bin\java.exe"):
312 impl
= factory('package:windows:%s:%s:%s' % (package
, '6', 'x86_64'))
313 impl
.machine
= 'x86_64';
314 impl
.version
= model
.parse_version('6')
315 impl
.upstream_stability
= model
.packaged
316 impl
.main
= java64_home
+ r
"\bin\java.exe"
318 class CachedDistribution(Distribution
):
319 """For distributions where querying the package database is slow (e.g. requires running
320 an external command), we cache the results.
322 @deprecated: use Cache instead
325 def __init__(self
, db_status_file
):
326 """@param db_status_file: update the cache when the timestamp of this file changes"""
327 self
._status
_details
= os
.stat(db_status_file
)
330 self
.cache_dir
= basedir
.save_cache_path(namespaces
.config_site
,
331 namespaces
.config_prog
)
335 except Exception as ex
:
336 info(_("Failed to load distribution database cache (%s). Regenerating..."), ex
)
338 self
.generate_cache()
340 except Exception as ex
:
341 warn(_("Failed to regenerate distribution database cache: %s"), ex
)
343 def _load_cache(self
):
344 """Load {cache_leaf} cache file into self.versions if it is available and up-to-date.
345 Throws an exception if the cache should be (re)created."""
346 stream
= open(os
.path
.join(self
.cache_dir
, self
.cache_leaf
))
352 name
, value
= line
.split(': ')
353 if name
== 'mtime' and int(value
) != int(self
._status
_details
.st_mtime
):
354 raise Exception(_("Modification time of package database file has changed"))
355 if name
== 'size' and int(value
) != self
._status
_details
.st_size
:
356 raise Exception(_("Size of package database file has changed"))
357 if name
== 'version':
358 cache_version
= int(value
)
360 raise Exception(_('Invalid cache format (bad header)'))
362 if cache_version
is None:
363 raise Exception(_('Old cache format'))
365 versions
= self
.versions
367 package
, version
, zi_arch
= line
[:-1].split('\t')
368 versionarch
= (version
, intern(zi_arch
))
369 if package
not in versions
:
370 versions
[package
] = [versionarch
]
372 versions
[package
].append(versionarch
)
374 def _write_cache(self
, cache
):
375 #cache.sort() # Might be useful later; currently we don't care
377 fd
, tmpname
= tempfile
.mkstemp(prefix
= 'zeroinstall-cache-tmp',
378 dir = self
.cache_dir
)
380 stream
= os
.fdopen(fd
, 'wb')
381 stream
.write('version: 2\n')
382 stream
.write('mtime: %d\n' % int(self
._status
_details
.st_mtime
))
383 stream
.write('size: %d\n' % self
._status
_details
.st_size
)
386 stream
.write(line
+ '\n')
390 os
.path
.join(self
.cache_dir
,
396 # Maps machine type names used in packages to their Zero Install versions
397 _canonical_machine
= {
412 host_machine
= arch
.canonicalize_machine(platform
.uname()[4])
413 def canonical_machine(package_machine
):
414 machine
= _canonical_machine
.get(package_machine
, None)
416 # Safe default if we can't understand the arch
420 class DebianDistribution(Distribution
):
421 """A dpkg-based distribution."""
423 cache_leaf
= 'dpkg-status.cache'
425 def __init__(self
, dpkg_status
, pkgcache
):
426 self
.dpkg_cache
= Cache('dpkg-status.cache', dpkg_status
, 2)
429 def _query_installed_package(self
, package
):
430 null
= os
.open('/dev/null', os
.O_WRONLY
)
431 child
= subprocess
.Popen(["dpkg-query", "-W", "--showformat=${Version}\t${Architecture}\t${Status}\n", "--", package
],
432 stdout
= subprocess
.PIPE
, stderr
= null
)
434 stdout
, stderr
= child
.communicate()
436 for line
in stdout
.split('\n'):
437 if not line
: continue
438 version
, debarch
, status
= line
.split('\t', 2)
439 if not status
.endswith(' installed'): continue
440 clean_version
= try_cleanup_distro_version(version
)
442 return '%s\t%s' % (clean_version
, canonical_machine(debarch
.strip()))
444 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
448 def get_package_info(self
, package
, factory
):
449 # Add any already-installed package...
450 installed_cached_info
= self
._get
_dpkg
_info
(package
)
452 if installed_cached_info
!= '-':
453 installed_version
, machine
= installed_cached_info
.split('\t')
454 impl
= factory('package:deb:%s:%s:%s' % (package
, installed_version
, machine
))
455 impl
.version
= model
.parse_version(installed_version
)
457 impl
.machine
= machine
459 installed_version
= None
461 # Add any uninstalled candidates (note: only one of these two methods will add anything)
464 self
.packagekit
.get_candidates(package
, factory
, 'package:deb')
467 cached
= self
.apt_cache
.get(package
, None)
469 candidate_version
= cached
['version']
470 candidate_arch
= cached
['arch']
471 if candidate_version
and candidate_version
!= installed_version
:
472 impl
= factory('package:deb:%s:%s:%s' % (package
, candidate_version
, candidate_arch
), installed
= False)
473 impl
.version
= model
.parse_version(candidate_version
)
474 if candidate_arch
!= '*':
475 impl
.machine
= candidate_arch
476 def install(handler
):
477 raise model
.SafeException(_("This program depends on '%s', which is a package that is available through your distribution. "
478 "Please install it manually using your distribution's tools and try again. Or, install 'packagekit' and I can "
479 "use that to install it.") % package
)
480 impl
.download_sources
.append(model
.DistributionSource(package
, cached
['size'], install
, needs_confirmation
= False))
482 def get_score(self
, disto_name
):
483 return int(disto_name
== 'Debian')
485 def _get_dpkg_info(self
, package
):
486 installed_cached_info
= self
.dpkg_cache
.get(package
)
487 if installed_cached_info
== None:
488 installed_cached_info
= self
._query
_installed
_package
(package
)
489 self
.dpkg_cache
.put(package
, installed_cached_info
)
491 return installed_cached_info
493 def fetch_candidates(self
, master_feed
):
494 package_names
= [item
.getAttribute("package") for item
, item_attrs
in master_feed
.get_package_impls(self
)]
496 if self
.packagekit
.available
:
497 return self
.packagekit
.fetch_candidates(package_names
)
499 # No PackageKit. Use apt-cache directly.
500 for package
in package_names
:
501 # Check to see whether we could get a newer version using apt-get
503 null
= os
.open('/dev/null', os
.O_WRONLY
)
504 child
= subprocess
.Popen(['apt-cache', 'show', '--no-all-versions', '--', package
], stdout
= subprocess
.PIPE
, stderr
= null
)
507 arch
= version
= size
= None
508 for line
in child
.stdout
:
510 if line
.startswith('Version: '):
512 version
= try_cleanup_distro_version(version
)
513 elif line
.startswith('Architecture: '):
514 arch
= canonical_machine(line
[14:].strip())
515 elif line
.startswith('Size: '):
516 size
= int(line
[6:].strip())
518 cached
= {'version': version
, 'arch': arch
, 'size': size
}
522 except Exception as ex
:
523 warn("'apt-cache show %s' failed: %s", package
, ex
)
525 # (multi-arch support? can there be multiple candidates?)
526 self
.apt_cache
[package
] = cached
528 class RPMDistribution(CachedDistribution
):
529 """An RPM-based distribution."""
531 cache_leaf
= 'rpm-status.cache'
533 def generate_cache(self
):
536 for line
in os
.popen("rpm -qa --qf='%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n'"):
537 package
, version
, rpmarch
= line
.split('\t', 2)
538 if package
== 'gpg-pubkey':
540 zi_arch
= canonical_machine(rpmarch
.strip())
541 clean_version
= try_cleanup_distro_version(version
)
543 cache
.append('%s\t%s\t%s' % (package
, clean_version
, zi_arch
))
545 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
547 self
._write
_cache
(cache
)
549 def get_package_info(self
, package
, factory
):
550 # Add installed versions...
551 versions
= self
.versions
.get(package
, [])
553 for version
, machine
in versions
:
554 impl
= factory('package:rpm:%s:%s:%s' % (package
, version
, machine
))
555 impl
.version
= model
.parse_version(version
)
557 impl
.machine
= machine
559 # Add any uninstalled candidates found by PackageKit
560 self
.packagekit
.get_candidates(package
, factory
, 'package:rpm')
562 def get_score(self
, disto_name
):
563 return int(disto_name
== 'RPM')
565 class SlackDistribution(Distribution
):
566 """A Slack-based distribution."""
568 def __init__(self
, packages_dir
):
569 self
._packages
_dir
= packages_dir
571 def get_package_info(self
, package
, factory
):
572 # Add installed versions...
573 for entry
in os
.listdir(self
._packages
_dir
):
574 name
, version
, arch
, build
= entry
.rsplit('-', 3)
576 zi_arch
= canonical_machine(arch
)
577 clean_version
= try_cleanup_distro_version("%s-%s" % (version
, build
))
578 if not clean_version
:
579 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': name
})
582 impl
= factory('package:slack:%s:%s:%s' % \
583 (package
, clean_version
, zi_arch
))
584 impl
.version
= model
.parse_version(clean_version
)
586 impl
.machine
= zi_arch
588 # Add any uninstalled candidates found by PackageKit
589 self
.packagekit
.get_candidates(package
, factory
, 'package:slack')
591 def get_score(self
, disto_name
):
592 return int(disto_name
== 'Slack')
594 class ArchDistribution(Distribution
):
595 """An Arch Linux distribution."""
597 def __init__(self
, packages_dir
):
598 self
._packages
_dir
= os
.path
.join(packages_dir
, "local")
600 def get_package_info(self
, package
, factory
):
601 # Add installed versions...
602 for entry
in os
.listdir(self
._packages
_dir
):
603 name
, version
, build
= entry
.rsplit('-', 2)
606 for line
in open(os
.path
.join(self
._packages
_dir
, entry
, "desc")):
607 if line
== "%ARCH%\n":
613 zi_arch
= canonical_machine(arch
)
614 clean_version
= try_cleanup_distro_version("%s-%s" % (version
, build
))
615 if not clean_version
:
616 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': name
})
619 impl
= factory('package:arch:%s:%s:%s' % \
620 (package
, clean_version
, zi_arch
))
621 impl
.version
= model
.parse_version(clean_version
)
623 impl
.machine
= zi_arch
625 # Add any uninstalled candidates found by PackageKit
626 self
.packagekit
.get_candidates(package
, factory
, 'package:arch')
628 def get_score(self
, disto_name
):
629 return int(disto_name
== 'Arch')
631 class GentooDistribution(Distribution
):
633 def __init__(self
, pkgdir
):
634 self
._pkgdir
= pkgdir
636 def get_package_info(self
, package
, factory
):
637 # Add installed versions...
638 _version_start_reqexp
= '-[0-9]'
640 if package
.count('/') != 1: return
642 category
, leafname
= package
.split('/')
643 category_dir
= os
.path
.join(self
._pkgdir
, category
)
644 match_prefix
= leafname
+ '-'
646 if not os
.path
.isdir(category_dir
): return
648 for filename
in os
.listdir(category_dir
):
649 if filename
.startswith(match_prefix
) and filename
[len(match_prefix
)].isdigit():
650 name
= open(os
.path
.join(category_dir
, filename
, 'PF')).readline().strip()
652 match
= re
.search(_version_start_reqexp
, name
)
654 warn(_('Cannot parse version from Gentoo package named "%(name)s"'), {'name': name
})
657 version
= try_cleanup_distro_version(name
[match
.start() + 1:])
659 if category
== 'app-emulation' and name
.startswith('emul-'):
660 __
, __
, machine
, __
= name
.split('-', 3)
662 machine
, __
= open(os
.path
.join(category_dir
, filename
, 'CHOST')).readline().split('-', 1)
663 machine
= arch
.canonicalize_machine(machine
)
665 impl
= factory('package:gentoo:%s:%s:%s' % \
666 (package
, version
, machine
))
667 impl
.version
= model
.parse_version(version
)
668 impl
.machine
= machine
670 # Add any uninstalled candidates found by PackageKit
671 self
.packagekit
.get_candidates(package
, factory
, 'package:gentoo')
673 def get_score(self
, disto_name
):
674 return int(disto_name
== 'Gentoo')
676 class PortsDistribution(Distribution
):
678 def __init__(self
, pkgdir
):
679 self
._pkgdir
= pkgdir
681 def get_package_info(self
, package
, factory
):
682 _name_version_regexp
= '^(.+)-([^-]+)$'
684 nameversion
= re
.compile(_name_version_regexp
)
685 for pkgname
in os
.listdir(self
._pkgdir
):
686 pkgdir
= os
.path
.join(self
._pkgdir
, pkgname
)
687 if not os
.path
.isdir(pkgdir
): continue
689 #contents = open(os.path.join(pkgdir, '+CONTENTS')).readline().strip()
691 match
= nameversion
.search(pkgname
)
693 warn(_('Cannot parse version from Ports package named "%(pkgname)s"'), {'pkgname': pkgname
})
696 name
= match
.group(1)
699 version
= try_cleanup_distro_version(match
.group(2))
701 machine
= host_machine
703 impl
= factory('package:ports:%s:%s:%s' % \
704 (package
, version
, machine
))
705 impl
.version
= model
.parse_version(version
)
706 impl
.machine
= machine
708 def get_score(self
, disto_name
):
709 return int(disto_name
== 'Ports')
711 class MacPortsDistribution(CachedDistribution
):
713 cache_leaf
= 'macports-status.cache'
715 def generate_cache(self
):
718 # for line in os.popen("port echo active"):
719 for line
in os
.popen("port -v installed"):
720 if not line
.startswith(" "):
722 if line
.strip().count(" ") > 1:
723 package
, version
, extra
= line
.split(None, 2)
725 package
, version
= line
.split()
727 if not extra
.startswith("(active)"):
729 version
= version
.lstrip('@')
730 version
= re
.sub(r
"\+.*","",version
) # strip variants
732 clean_version
= try_cleanup_distro_version(version
)
734 match
= re
.match(r
" platform='([^' ]*)( \d+)?' archs='([^']*)'", extra
)
736 platform
, major
, archs
= match
.groups()
737 for arch
in archs
.split():
738 zi_arch
= canonical_machine(arch
)
739 cache
.append('%s\t%s\t%s' % (package
, clean_version
, zi_arch
))
741 cache
.append('%s\t%s\t%s' % (package
, clean_version
, zi_arch
))
743 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
745 self
._write
_cache
(cache
)
747 def get_package_info(self
, package
, factory
):
748 # Add installed versions...
749 versions
= self
.versions
.get(package
, [])
751 for version
, machine
in versions
:
752 impl
= factory('package:macports:%s:%s:%s' % (package
, version
, machine
))
753 impl
.version
= model
.parse_version(version
)
755 impl
.machine
= machine
757 def get_score(self
, disto_name
):
758 return int(disto_name
== 'MacPorts')
761 _host_distribution
= None
762 def get_host_distribution():
763 """Get a Distribution suitable for the host operating system.
764 Calling this twice will return the same object.
765 @rtype: L{Distribution}"""
766 global _host_distribution
767 if not _host_distribution
:
768 dpkg_db_status
= '/var/lib/dpkg/status'
769 pkgcache
= '/var/cache/apt/pkgcache.bin'
770 rpm_db_packages
= '/var/lib/rpm/Packages'
771 _slack_db
= '/var/log/packages'
772 _arch_db
= '/var/lib/pacman'
773 _pkg_db
= '/var/db/pkg'
774 _macports_db
= '/opt/local/var/macports/registry/registry.db'
776 if sys
.prefix
== "/sw":
777 dpkg_db_status
= os
.path
.join(sys
.prefix
, dpkg_db_status
)
778 pkgcache
= os
.path
.join(sys
.prefix
, pkgcache
)
779 rpm_db_packages
= os
.path
.join(sys
.prefix
, rpm_db_packages
)
782 _host_distribution
= WindowsDistribution()
783 elif os
.path
.isdir(_pkg_db
):
784 if sys
.platform
.startswith("linux"):
785 _host_distribution
= GentooDistribution(_pkg_db
)
786 elif sys
.platform
.startswith("freebsd"):
787 _host_distribution
= PortsDistribution(_pkg_db
)
788 elif os
.path
.isfile(_macports_db
) \
789 and sys
.prefix
.startswith("/opt/local"):
790 _host_distribution
= MacPortsDistribution(_macports_db
)
791 elif os
.access(dpkg_db_status
, os
.R_OK
):
792 _host_distribution
= DebianDistribution(dpkg_db_status
, pkgcache
)
793 elif os
.path
.isfile(rpm_db_packages
):
794 _host_distribution
= RPMDistribution(rpm_db_packages
)
795 elif os
.path
.isdir(_slack_db
):
796 _host_distribution
= SlackDistribution(_slack_db
)
797 elif os
.path
.isdir(_arch_db
):
798 _host_distribution
= ArchDistribution(_arch_db
)
800 _host_distribution
= Distribution()
802 return _host_distribution