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
, portable_rename
, intern
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 matching group is for Java-style 6b17 syntax, or "major")
22 _version_regexp
= '(?:[a-z])?({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
= tempfile
.NamedTemporaryFile(mode
= 'wt', dir = self
.cache_dir
, delete
= False)
55 tmp
.write("mtime=%d\nsize=%d\nformat=%d\n\n" % (mtime
, size
, self
.format
))
57 portable_rename(tmp
.name
, os
.path
.join(self
.cache_dir
, self
.cache_leaf
))
61 # Populate self.cache from our saved cache file.
62 # Throws an exception if the cache doesn't exist or has the wrong format.
63 def _load_cache(self
):
64 self
.cache
= cache
= {}
65 with
open(os
.path
.join(self
.cache_dir
, self
.cache_leaf
)) as stream
:
70 key
, value
= line
.split('=', 1)
71 if key
in ('mtime', 'size', 'format'):
72 self
.cached_for
[key
] = int(value
)
77 key
, value
= line
.split('=', 1)
78 cache
[key
] = value
[:-1]
80 # Check the source file hasn't changed since we created the cache
81 def _check_valid(self
):
82 info
= os
.stat(self
.source
)
83 if self
.cached_for
['mtime'] != int(info
.st_mtime
):
84 raise Exception("Modification time of %s has changed" % self
.source
)
85 if self
.cached_for
['size'] != info
.st_size
:
86 raise Exception("Size of %s has changed" % self
.source
)
87 if self
.cached_for
.get('format', None) != self
.format
:
88 raise Exception("Format of cache has changed")
93 except Exception as ex
:
94 info(_("Cache needs to be refreshed: %s"), ex
)
98 return self
.cache
.get(key
, None)
100 def put(self
, key
, value
):
101 cache_path
= os
.path
.join(self
.cache_dir
, self
.cache_leaf
)
102 self
.cache
[key
] = value
104 with
open(cache_path
, 'a') as stream
:
105 stream
.write('%s=%s\n' % (key
, value
))
106 except Exception as ex
:
107 warn("Failed to write to cache %s: %s=%s: %s", cache_path
, key
, value
, ex
)
109 def try_cleanup_distro_version(version
):
110 """Try to turn a distribution version string into one readable by Zero Install.
111 We do this by stripping off anything we can't parse.
112 @return: the part we understood, or None if we couldn't parse anything
115 version
= version
.split(':')[1] # Skip 'epoch'
116 version
= version
.replace('_', '-')
118 version
, suffix
= version
.split('~', 1)
119 suffix
= '-pre' + try_cleanup_distro_version(suffix
)
122 match
= re
.match(_version_regexp
, version
)
124 major
, version
, revision
= match
.groups()
125 if major
is not None:
126 version
= major
[:-1] + '.' + version
127 if revision
is not None:
128 version
= '%s-%s' % (version
, revision
[2:])
129 return version
+ suffix
132 class Distribution(object):
133 """Represents a distribution with which we can integrate.
134 Sub-classes should specialise this to integrate with the package managers of
135 particular distributions. This base class ignores the native package manager.
140 def get_package_info(self
, package
, factory
):
141 """Get information about the given package.
142 Add zero or more implementations using the factory (typically at most two
143 will be added; the currently installed version and the latest available).
144 @param package: package name (e.g. "gimp")
146 @param factory: function for creating new DistributionImplementation objects from IDs
147 @type factory: str -> L{model.DistributionImplementation}
151 def get_score(self
, distribution
):
152 """Indicate how closely the host distribution matches this one.
153 The <package-implementation> with the highest score is passed
154 to L{Distribution.get_package_info}. If several elements get
155 the same score, get_package_info is called for all of them.
156 @param distribution: a distribution name
157 @type distribution: str
158 @return: an integer, or -1 if there is no match at all
163 def get_feed(self
, master_feed
):
164 """Generate a feed containing information about distribution packages.
165 This should immediately return a feed containing an implementation for the
166 package if it's already installed. Information about versions that could be
167 installed using the distribution's package manager can be added asynchronously
168 later (see L{fetch_candidates}).
169 @param master_feed: feed containing the <package-implementation> elements
170 @type master_feed: L{model.ZeroInstallFeed}
171 @rtype: L{model.ZeroInstallFeed}"""
173 feed
= model
.ZeroInstallFeed(None)
174 feed
.url
= 'distribution:' + master_feed
.url
176 for item
, item_attrs
in master_feed
.get_package_impls(self
):
177 package
= item_attrs
.get('package', None)
179 raise model
.InvalidInterface(_("Missing 'package' attribute on %s") % item
)
181 def factory(id, only_if_missing
= False, installed
= True):
182 assert id.startswith('package:')
183 if id in feed
.implementations
:
186 warn(_("Duplicate ID '%s' for DistributionImplementation"), id)
187 impl
= model
.DistributionImplementation(feed
, id, self
, item
)
188 feed
.implementations
[id] = impl
190 impl
.installed
= installed
191 impl
.metadata
= item_attrs
193 if 'run' not in impl
.commands
:
194 item_main
= item_attrs
.get('main', None)
196 if item_main
.startswith('/'):
197 impl
.main
= item_main
199 raise model
.InvalidInterface(_("'main' attribute must be absolute, but '%s' doesn't start with '/'!") %
201 impl
.upstream_stability
= model
.packaged
205 self
.get_package_info(package
, factory
)
207 if master_feed
.url
== 'http://repo.roscidus.com/python/python' and all(not impl
.installed
for impl
in feed
.implementations
.values()):
208 # Hack: we can support Python on platforms with unsupported package managers
209 # by adding the implementation of Python running us now to the list.
210 python_version
= '.'.join([str(v
) for v
in sys
.version_info
if isinstance(v
, int)])
211 impl_id
= 'package:host:python:' + python_version
212 assert impl_id
not in feed
.implementations
213 impl
= model
.DistributionImplementation(feed
, impl_id
, self
)
214 impl
.installed
= True
215 impl
.version
= model
.parse_version(python_version
)
216 impl
.main
= sys
.executable
217 impl
.upstream_stability
= model
.packaged
218 impl
.machine
= host_machine
# (hopefully)
219 feed
.implementations
[impl_id
] = impl
223 def fetch_candidates(self
, master_feed
):
224 """Collect information about versions we could install using
225 the distribution's package manager. On success, the distribution
226 feed in iface_cache is updated.
227 @return: a L{tasks.Blocker} if the task is in progress, or None if not"""
228 if self
.packagekit
.available
:
229 package_names
= [item
.getAttribute("package") for item
, item_attrs
in master_feed
.get_package_impls(self
)]
230 return self
.packagekit
.fetch_candidates(package_names
)
233 def packagekit(self
):
234 """For use by subclasses.
235 @rtype: L{packagekit.PackageKit}"""
236 if not self
._packagekit
:
237 from zeroinstall
.injector
import packagekit
238 self
._packagekit
= packagekit
.PackageKit()
239 return self
._packagekit
241 class WindowsDistribution(Distribution
):
242 def get_package_info(self
, package
, factory
):
243 def _is_64bit_windows():
245 from win32process
import IsWow64Process
246 if p
== 'win64' or (p
== 'win32' and IsWow64Process()): return True
247 elif p
== 'win32': return False
248 else: raise Exception(_("WindowsDistribution may only be used on the Windows platform"))
250 def _read_hklm_reg(key_name
, value_name
):
251 from win32api
import RegOpenKeyEx
, RegQueryValueEx
, RegCloseKey
252 from win32con
import HKEY_LOCAL_MACHINE
, KEY_READ
253 KEY_WOW64_64KEY
= 0x0100
254 KEY_WOW64_32KEY
= 0x0200
255 if _is_64bit_windows():
257 key32
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
, key_name
, 0, KEY_READ | KEY_WOW64_32KEY
)
258 (value32
, _
) = RegQueryValueEx(key32
, value_name
)
263 key64
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
, key_name
, 0, KEY_READ | KEY_WOW64_64KEY
)
264 (value64
, _
) = RegQueryValueEx(key64
, value_name
)
270 key32
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
, key_name
, 0, KEY_READ
)
271 (value32
, _
) = RegQueryValueEx(key32
, value_name
)
276 return (value32
, value64
)
278 def find_java(part
, win_version
, zero_version
):
279 reg_path
= r
"SOFTWARE\JavaSoft\{part}\{win_version}".format(part
= part
, win_version
= win_version
)
280 (java32_home
, java64_home
) = _read_hklm_reg(reg_path
, "JavaHome")
282 for (home
, arch
) in [(java32_home
, 'i486'),
283 (java64_home
, 'x86_64')]:
284 if os
.path
.isfile(home
+ r
"\bin\java.exe"):
285 impl
= factory('package:windows:%s:%s:%s' % (package
, zero_version
, arch
))
287 impl
.version
= model
.parse_version(zero_version
)
288 impl
.upstream_stability
= model
.packaged
289 impl
.main
= home
+ r
"\bin\java.exe"
291 if package
== 'openjdk-6-jre':
292 find_java("Java Runtime Environment", "1.6", '6')
293 elif package
== 'openjdk-6-jdk':
294 find_java("Java Development Kit", "1.6", '6')
295 elif package
== 'openjdk-7-jre':
296 find_java("Java Runtime Environment", "1.7", '7')
297 elif package
== 'openjdk-7-jdk':
298 find_java("Java Development Kit", "1.7", '7')
300 def get_score(self
, disto_name
):
301 return int(disto_name
== 'Windows')
303 class DarwinDistribution(Distribution
):
304 def get_package_info(self
, package
, factory
):
305 def java_home(version
, arch
):
306 null
= os
.open(os
.devnull
, os
.O_WRONLY
)
307 child
= subprocess
.Popen(["/usr/libexec/java_home", "--failfast", "--version", version
, "--arch", arch
],
308 stdout
= subprocess
.PIPE
, stderr
= null
, universal_newlines
= True)
309 home
= child
.stdout
.read().strip()
314 def find_java(part
, jvm_version
, zero_version
):
315 for arch
in ['i386', 'x86_64']:
316 home
= java_home(jvm_version
, arch
)
317 if os
.path
.isfile(home
+ "/bin/java"):
318 impl
= factory('package:darwin:%s:%s:%s' % (package
, zero_version
, arch
))
320 impl
.version
= model
.parse_version(zero_version
)
321 impl
.upstream_stability
= model
.packaged
322 impl
.main
= home
+ "/bin/java"
324 if package
== 'openjdk-6-jre':
325 find_java("Java Runtime Environment", "1.6", '6')
326 elif package
== 'openjdk-6-jdk':
327 find_java("Java Development Kit", "1.6", '6')
328 elif package
== 'openjdk-7-jre':
329 find_java("Java Runtime Environment", "1.7", '7')
330 elif package
== 'openjdk-7-jdk':
331 find_java("Java Development Kit", "1.7", '7')
333 def get_score(self
, disto_name
):
334 return int(disto_name
== 'Darwin')
336 class CachedDistribution(Distribution
):
337 """For distributions where querying the package database is slow (e.g. requires running
338 an external command), we cache the results.
340 @deprecated: use Cache instead
343 def __init__(self
, db_status_file
):
344 """@param db_status_file: update the cache when the timestamp of this file changes"""
345 self
._status
_details
= os
.stat(db_status_file
)
348 self
.cache_dir
= basedir
.save_cache_path(namespaces
.config_site
,
349 namespaces
.config_prog
)
353 except Exception as ex
:
354 info(_("Failed to load distribution database cache (%s). Regenerating..."), ex
)
356 self
.generate_cache()
358 except Exception as ex
:
359 warn(_("Failed to regenerate distribution database cache: %s"), ex
)
361 def _load_cache(self
):
362 """Load {cache_leaf} cache file into self.versions if it is available and up-to-date.
363 Throws an exception if the cache should be (re)created."""
364 with
open(os
.path
.join(self
.cache_dir
, self
.cache_leaf
), 'rt') as stream
:
369 name
, value
= line
.split(': ')
370 if name
== 'mtime' and int(value
) != int(self
._status
_details
.st_mtime
):
371 raise Exception(_("Modification time of package database file has changed"))
372 if name
== 'size' and int(value
) != self
._status
_details
.st_size
:
373 raise Exception(_("Size of package database file has changed"))
374 if name
== 'version':
375 cache_version
= int(value
)
377 raise Exception(_('Invalid cache format (bad header)'))
379 if cache_version
is None:
380 raise Exception(_('Old cache format'))
382 versions
= self
.versions
384 package
, version
, zi_arch
= line
[:-1].split('\t')
385 versionarch
= (version
, intern(zi_arch
))
386 if package
not in versions
:
387 versions
[package
] = [versionarch
]
389 versions
[package
].append(versionarch
)
391 def _write_cache(self
, cache
):
392 #cache.sort() # Might be useful later; currently we don't care
394 fd
, tmpname
= tempfile
.mkstemp(prefix
= 'zeroinstall-cache-tmp',
395 dir = self
.cache_dir
)
397 stream
= os
.fdopen(fd
, 'wt')
398 stream
.write('version: 2\n')
399 stream
.write('mtime: %d\n' % int(self
._status
_details
.st_mtime
))
400 stream
.write('size: %d\n' % self
._status
_details
.st_size
)
403 stream
.write(line
+ '\n')
406 portable_rename(tmpname
,
407 os
.path
.join(self
.cache_dir
,
413 # Maps machine type names used in packages to their Zero Install versions
414 _canonical_machine
= {
429 host_machine
= arch
.canonicalize_machine(platform
.uname()[4])
430 def canonical_machine(package_machine
):
431 machine
= _canonical_machine
.get(package_machine
, None)
433 # Safe default if we can't understand the arch
437 class DebianDistribution(Distribution
):
438 """A dpkg-based distribution."""
440 cache_leaf
= 'dpkg-status.cache'
442 def __init__(self
, dpkg_status
):
443 self
.dpkg_cache
= Cache('dpkg-status.cache', dpkg_status
, 2)
446 def _query_installed_package(self
, package
):
447 null
= os
.open(os
.devnull
, os
.O_WRONLY
)
448 child
= subprocess
.Popen(["dpkg-query", "-W", "--showformat=${Version}\t${Architecture}\t${Status}\n", "--", package
],
449 stdout
= subprocess
.PIPE
, stderr
= null
,
450 universal_newlines
= True) # Needed for Python 3
452 stdout
, stderr
= child
.communicate()
454 for line
in stdout
.split('\n'):
455 if not line
: continue
456 version
, debarch
, status
= line
.split('\t', 2)
457 if not status
.endswith(' installed'): continue
458 clean_version
= try_cleanup_distro_version(version
)
459 if debarch
.find("-") != -1:
460 debarch
= debarch
.split("-")[-1]
462 return '%s\t%s' % (clean_version
, canonical_machine(debarch
.strip()))
464 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
468 def get_package_info(self
, package
, factory
):
469 # Add any already-installed package...
470 installed_cached_info
= self
._get
_dpkg
_info
(package
)
472 if installed_cached_info
!= '-':
473 installed_version
, machine
= installed_cached_info
.split('\t')
474 impl
= factory('package:deb:%s:%s:%s' % (package
, installed_version
, machine
))
475 impl
.version
= model
.parse_version(installed_version
)
477 impl
.machine
= machine
479 installed_version
= None
481 # Add any uninstalled candidates (note: only one of these two methods will add anything)
484 self
.packagekit
.get_candidates(package
, factory
, 'package:deb')
487 cached
= self
.apt_cache
.get(package
, None)
489 candidate_version
= cached
['version']
490 candidate_arch
= cached
['arch']
491 if candidate_version
and candidate_version
!= installed_version
:
492 impl
= factory('package:deb:%s:%s:%s' % (package
, candidate_version
, candidate_arch
), installed
= False)
493 impl
.version
= model
.parse_version(candidate_version
)
494 if candidate_arch
!= '*':
495 impl
.machine
= candidate_arch
496 def install(handler
):
497 raise model
.SafeException(_("This program depends on '%s', which is a package that is available through your distribution. "
498 "Please install it manually using your distribution's tools and try again. Or, install 'packagekit' and I can "
499 "use that to install it.") % package
)
500 impl
.download_sources
.append(model
.DistributionSource(package
, cached
['size'], install
, needs_confirmation
= False))
502 def get_score(self
, disto_name
):
503 return int(disto_name
== 'Debian')
505 def _get_dpkg_info(self
, package
):
506 installed_cached_info
= self
.dpkg_cache
.get(package
)
507 if installed_cached_info
== None:
508 installed_cached_info
= self
._query
_installed
_package
(package
)
509 self
.dpkg_cache
.put(package
, installed_cached_info
)
511 return installed_cached_info
513 def fetch_candidates(self
, master_feed
):
514 package_names
= [item
.getAttribute("package") for item
, item_attrs
in master_feed
.get_package_impls(self
)]
516 if self
.packagekit
.available
:
517 return self
.packagekit
.fetch_candidates(package_names
)
519 # No PackageKit. Use apt-cache directly.
520 for package
in package_names
:
521 # Check to see whether we could get a newer version using apt-get
523 null
= os
.open(os
.devnull
, os
.O_WRONLY
)
524 child
= subprocess
.Popen(['apt-cache', 'show', '--no-all-versions', '--', package
], stdout
= subprocess
.PIPE
, stderr
= null
, universal_newlines
= True)
527 arch
= version
= size
= None
528 for line
in child
.stdout
:
530 if line
.startswith('Version: '):
532 version
= try_cleanup_distro_version(version
)
533 elif line
.startswith('Architecture: '):
534 arch
= canonical_machine(line
[14:].strip())
535 elif line
.startswith('Size: '):
536 size
= int(line
[6:].strip())
538 cached
= {'version': version
, 'arch': arch
, 'size': size
}
543 except Exception as ex
:
544 warn("'apt-cache show %s' failed: %s", package
, ex
)
546 # (multi-arch support? can there be multiple candidates?)
547 self
.apt_cache
[package
] = cached
549 class RPMDistribution(CachedDistribution
):
550 """An RPM-based distribution."""
552 cache_leaf
= 'rpm-status.cache'
554 def generate_cache(self
):
557 child
= subprocess
.Popen(["rpm", "-qa", "--qf=%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n"],
558 stdout
= subprocess
.PIPE
, universal_newlines
= True)
559 for line
in child
.stdout
:
560 package
, version
, rpmarch
= line
.split('\t', 2)
561 if package
== 'gpg-pubkey':
563 zi_arch
= canonical_machine(rpmarch
.strip())
564 clean_version
= try_cleanup_distro_version(version
)
566 cache
.append('%s\t%s\t%s' % (package
, clean_version
, zi_arch
))
568 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
570 self
._write
_cache
(cache
)
574 def get_package_info(self
, package
, factory
):
575 # Add installed versions...
576 versions
= self
.versions
.get(package
, [])
578 for version
, machine
in versions
:
579 impl
= factory('package:rpm:%s:%s:%s' % (package
, version
, machine
))
580 impl
.version
= model
.parse_version(version
)
582 impl
.machine
= machine
584 # Add any uninstalled candidates found by PackageKit
585 self
.packagekit
.get_candidates(package
, factory
, 'package:rpm')
587 def get_score(self
, disto_name
):
588 return int(disto_name
== 'RPM')
590 class SlackDistribution(Distribution
):
591 """A Slack-based distribution."""
593 def __init__(self
, packages_dir
):
594 self
._packages
_dir
= packages_dir
596 def get_package_info(self
, package
, factory
):
597 # Add installed versions...
598 for entry
in os
.listdir(self
._packages
_dir
):
599 name
, version
, arch
, build
= entry
.rsplit('-', 3)
601 zi_arch
= canonical_machine(arch
)
602 clean_version
= try_cleanup_distro_version("%s-%s" % (version
, build
))
603 if not clean_version
:
604 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': name
})
607 impl
= factory('package:slack:%s:%s:%s' % \
608 (package
, clean_version
, zi_arch
))
609 impl
.version
= model
.parse_version(clean_version
)
611 impl
.machine
= zi_arch
613 # Add any uninstalled candidates found by PackageKit
614 self
.packagekit
.get_candidates(package
, factory
, 'package:slack')
616 def get_score(self
, disto_name
):
617 return int(disto_name
== 'Slack')
619 class ArchDistribution(Distribution
):
620 """An Arch Linux distribution."""
622 def __init__(self
, packages_dir
):
623 self
._packages
_dir
= os
.path
.join(packages_dir
, "local")
625 def get_package_info(self
, package
, factory
):
626 # Add installed versions...
627 for entry
in os
.listdir(self
._packages
_dir
):
628 name
, version
, build
= entry
.rsplit('-', 2)
631 with
open(os
.path
.join(self
._packages
_dir
, entry
, "desc"), 'rt') as stream
:
633 if line
== "%ARCH%\n":
639 zi_arch
= canonical_machine(arch
)
640 clean_version
= try_cleanup_distro_version("%s-%s" % (version
, build
))
641 if not clean_version
:
642 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': name
})
645 impl
= factory('package:arch:%s:%s:%s' % \
646 (package
, clean_version
, zi_arch
))
647 impl
.version
= model
.parse_version(clean_version
)
649 impl
.machine
= zi_arch
651 # Add any uninstalled candidates found by PackageKit
652 self
.packagekit
.get_candidates(package
, factory
, 'package:arch')
654 def get_score(self
, disto_name
):
655 return int(disto_name
== 'Arch')
657 class GentooDistribution(Distribution
):
659 def __init__(self
, pkgdir
):
660 self
._pkgdir
= pkgdir
662 def get_package_info(self
, package
, factory
):
663 # Add installed versions...
664 _version_start_reqexp
= '-[0-9]'
666 if package
.count('/') != 1: return
668 category
, leafname
= package
.split('/')
669 category_dir
= os
.path
.join(self
._pkgdir
, category
)
670 match_prefix
= leafname
+ '-'
672 if not os
.path
.isdir(category_dir
): return
674 for filename
in os
.listdir(category_dir
):
675 if filename
.startswith(match_prefix
) and filename
[len(match_prefix
)].isdigit():
676 with
open(os
.path
.join(category_dir
, filename
, 'PF'), 'rt') as stream
:
677 name
= stream
.readline().strip()
679 match
= re
.search(_version_start_reqexp
, name
)
681 warn(_('Cannot parse version from Gentoo package named "%(name)s"'), {'name': name
})
684 version
= try_cleanup_distro_version(name
[match
.start() + 1:])
686 if category
== 'app-emulation' and name
.startswith('emul-'):
687 __
, __
, machine
, __
= name
.split('-', 3)
689 with
open(os
.path
.join(category_dir
, filename
, 'CHOST'), 'rt') as stream
:
690 machine
, __
= stream
.readline().split('-', 1)
691 machine
= arch
.canonicalize_machine(machine
)
693 impl
= factory('package:gentoo:%s:%s:%s' % \
694 (package
, version
, machine
))
695 impl
.version
= model
.parse_version(version
)
696 impl
.machine
= machine
698 # Add any uninstalled candidates found by PackageKit
699 self
.packagekit
.get_candidates(package
, factory
, 'package:gentoo')
701 def get_score(self
, disto_name
):
702 return int(disto_name
== 'Gentoo')
704 class PortsDistribution(Distribution
):
706 def __init__(self
, pkgdir
):
707 self
._pkgdir
= pkgdir
709 def get_package_info(self
, package
, factory
):
710 _name_version_regexp
= '^(.+)-([^-]+)$'
712 nameversion
= re
.compile(_name_version_regexp
)
713 for pkgname
in os
.listdir(self
._pkgdir
):
714 pkgdir
= os
.path
.join(self
._pkgdir
, pkgname
)
715 if not os
.path
.isdir(pkgdir
): continue
717 #contents = open(os.path.join(pkgdir, '+CONTENTS')).readline().strip()
719 match
= nameversion
.search(pkgname
)
721 warn(_('Cannot parse version from Ports package named "%(pkgname)s"'), {'pkgname': pkgname
})
724 name
= match
.group(1)
727 version
= try_cleanup_distro_version(match
.group(2))
729 machine
= host_machine
731 impl
= factory('package:ports:%s:%s:%s' % \
732 (package
, version
, machine
))
733 impl
.version
= model
.parse_version(version
)
734 impl
.machine
= machine
736 def get_score(self
, disto_name
):
737 return int(disto_name
== 'Ports')
739 class MacPortsDistribution(CachedDistribution
):
741 cache_leaf
= 'macports-status.cache'
743 def generate_cache(self
):
746 child
= subprocess
.Popen(["port", "-v", "installed"],
747 stdout
= subprocess
.PIPE
, universal_newlines
= True)
748 for line
in child
.stdout
:
749 if not line
.startswith(" "):
751 if line
.strip().count(" ") > 1:
752 package
, version
, extra
= line
.split(None, 2)
754 package
, version
= line
.split()
756 if not extra
.startswith("(active)"):
758 version
= version
.lstrip('@')
759 version
= re
.sub(r
"\+.*", "", version
) # strip variants
761 clean_version
= try_cleanup_distro_version(version
)
763 match
= re
.match(r
" platform='([^' ]*)( \d+)?' archs='([^']*)'", extra
)
765 platform
, major
, archs
= match
.groups()
766 for arch
in archs
.split():
767 zi_arch
= canonical_machine(arch
)
768 cache
.append('%s\t%s\t%s' % (package
, clean_version
, zi_arch
))
770 cache
.append('%s\t%s\t%s' % (package
, clean_version
, zi_arch
))
772 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
773 self
._write
_cache
(cache
)
777 def get_package_info(self
, package
, factory
):
778 # Add installed versions...
779 versions
= self
.versions
.get(package
, [])
781 for version
, machine
in versions
:
782 impl
= factory('package:macports:%s:%s:%s' % (package
, version
, machine
))
783 impl
.version
= model
.parse_version(version
)
785 impl
.machine
= machine
787 def get_score(self
, disto_name
):
788 return int(disto_name
== 'MacPorts')
790 class CygwinDistribution(CachedDistribution
):
791 """A Cygwin-based distribution."""
793 cache_leaf
= 'cygcheck-status.cache'
795 def generate_cache(self
):
798 zi_arch
= canonical_machine(arch
)
799 for line
in os
.popen("cygcheck -c -d"):
800 if line
== "Cygwin Package Information\r\n":
804 package
, version
= line
.split()
805 if package
== "Package" and version
== "Version":
807 clean_version
= try_cleanup_distro_version(version
)
809 cache
.append('%s\t%s\t%s' % (package
, clean_version
, zi_arch
))
811 warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
813 self
._write
_cache
(cache
)
815 def get_package_info(self
, package
, factory
):
816 # Add installed versions...
817 versions
= self
.versions
.get(package
, [])
819 for version
, machine
in versions
:
820 impl
= factory('package:cygwin:%s:%s:%s' % (package
, version
, machine
))
821 impl
.version
= model
.parse_version(version
)
823 impl
.machine
= machine
825 def get_score(self
, disto_name
):
826 return int(disto_name
== 'Cygwin')
829 _host_distribution
= None
830 def get_host_distribution():
831 """Get a Distribution suitable for the host operating system.
832 Calling this twice will return the same object.
833 @rtype: L{Distribution}"""
834 global _host_distribution
835 if not _host_distribution
:
836 dpkg_db_status
= '/var/lib/dpkg/status'
837 rpm_db_packages
= '/var/lib/rpm/Packages'
838 _slack_db
= '/var/log/packages'
839 _arch_db
= '/var/lib/pacman'
840 _pkg_db
= '/var/db/pkg'
841 _macports_db
= '/opt/local/var/macports/registry/registry.db'
842 _cygwin_log
= '/var/log/setup.log'
844 if sys
.prefix
== "/sw":
845 dpkg_db_status
= os
.path
.join(sys
.prefix
, dpkg_db_status
)
846 rpm_db_packages
= os
.path
.join(sys
.prefix
, rpm_db_packages
)
849 _host_distribution
= WindowsDistribution()
850 elif os
.path
.isdir(_pkg_db
):
851 if sys
.platform
.startswith("linux"):
852 _host_distribution
= GentooDistribution(_pkg_db
)
853 elif sys
.platform
.startswith("freebsd"):
854 _host_distribution
= PortsDistribution(_pkg_db
)
855 elif os
.path
.isfile(_macports_db
) \
856 and sys
.prefix
.startswith("/opt/local"):
857 _host_distribution
= MacPortsDistribution(_macports_db
)
858 elif os
.path
.isfile(_cygwin_log
) and sys
.platform
== "cygwin":
859 _host_distribution
= CygwinDistribution(_cygwin_log
)
860 elif os
.access(dpkg_db_status
, os
.R_OK
) \
861 and os
.path
.getsize(dpkg_db_status
) > 0:
862 _host_distribution
= DebianDistribution(dpkg_db_status
)
863 elif os
.path
.isfile(rpm_db_packages
):
864 _host_distribution
= RPMDistribution(rpm_db_packages
)
865 elif os
.path
.isdir(_slack_db
):
866 _host_distribution
= SlackDistribution(_slack_db
)
867 elif os
.path
.isdir(_arch_db
):
868 _host_distribution
= ArchDistribution(_arch_db
)
869 elif sys
.platform
== "darwin":
870 _host_distribution
= DarwinDistribution()
872 _host_distribution
= Distribution()
874 return _host_distribution