2 Integration with native distribution package managers.
6 # Copyright (C) 2007, Thomas Leonard
7 # See the README file for details, or visit http://0install.net.
10 from logging
import warn
, info
11 from zeroinstall
.injector
import namespaces
, basedir
, model
13 dotted_ints
= '[0-9]+(\.[0-9]+)*'
14 version_regexp
= '(%s)(-(pre|rc|post|)%s)*' % (dotted_ints
, dotted_ints
)
16 def try_cleanup_distro_version(version
):
17 """Try to turn a distribution version string into one readable by Zero Install.
18 We do this by stripping off anything we can't parse.
19 @return: the part we understood, or None if we couldn't parse anything
21 match
= re
.match(version_regexp
, version
)
26 class Distribution(object):
27 """Represents a distribution with which we can integrate.
28 Sub-classes should specialise this to integrate with the package managers of
29 particular distributions. This base class ignores the native package manager.
33 def get_package_info(self
, package
, factory
):
34 """Get information about the given package.
35 Add zero or more implementations using the factory (typically at most two
36 will be added; the currently installed version and the latest available).
37 @param package: package name (e.g. "gimp")
39 @param factory: function for creating new DistributionImplementation objects from IDs
40 @type factory: str -> L{model.DistributionImplementation}
44 class DebianDistribution(Distribution
):
45 def __init__(self
, db_dir
):
47 dpkg_status
= db_dir
+ '/status'
48 self
.status_details
= os
.stat(self
.db_dir
+ '/status')
51 self
.cache_dir
= basedir
.save_cache_path(namespaces
.config_site
, namespaces
.config_prog
)
56 info("Failed to load dpkg cache (%s). Regenerating...", ex
)
61 warn("Failed to regenerate dpkg cache: %s", ex
)
64 stream
= file(self
.cache_dir
+ '/dpkg-status.cache')
69 name
, value
= line
.split(': ')
70 if name
== 'mtime' and int(value
) != int(self
.status_details
.st_mtime
):
71 raise Exception("Modification time of dpkg status file has changed")
72 if name
== 'size' and int(value
) != self
.status_details
.st_size
:
73 raise Exception("Size of dpkg status file has changed")
75 raise Exception('Invalid cache format (bad header)')
77 versions
= self
.versions
79 package
, version
= line
[:-1].split('\t')
80 versions
[package
] = version
82 def generate_cache(self
):
85 for line
in os
.popen("dpkg-query -W"):
86 package
, version
= line
.split('\t', 1)
88 # Debian's 'epoch' system
89 version
= version
.split(':', 1)[1]
90 clean_version
= try_cleanup_distro_version(version
)
92 cache
.append('%s\t%s' % (package
, clean_version
))
94 warn("Can't parse distribution version '%s' for package '%s'", version
, package
)
96 cache
.sort() # Might be useful later; currently we don't care
99 fd
, tmpname
= tempfile
.mkstemp(prefix
= 'dpkg-cache-tmp', dir = self
.cache_dir
)
101 stream
= os
.fdopen(fd
, 'wb')
102 stream
.write('mtime: %d\n' % int(self
.status_details
.st_mtime
))
103 stream
.write('size: %d\n' % self
.status_details
.st_size
)
106 stream
.write(line
+ '\n')
109 os
.rename(tmpname
, self
.cache_dir
+ '/dpkg-status.cache')
114 def get_package_info(self
, package
, factory
):
116 version
= self
.versions
[package
]
120 impl
= factory('package:deb:%s:%s' % (package
, version
))
121 impl
.version
= model
.parse_version(version
)
123 class RPMDistribution(Distribution
):
124 cache_leaf
= 'rpm-status.cache'
126 def __init__(self
, db_dir
):
128 pkg_status
= os
.path
.join(db_dir
, 'Packages')
129 self
.status_details
= os
.stat(pkg_status
)
132 self
.cache_dir
=basedir
.save_cache_path(namespaces
.config_site
,
133 namespaces
.config_prog
)
137 except Exception, ex
:
138 info("Failed to load cache (%s). Regenerating...",
141 self
.generate_cache()
143 except Exception, ex
:
144 warn("Failed to regenerate cache: %s", ex
)
146 def load_cache(self
):
147 stream
= file(os
.path
.join(self
.cache_dir
, self
.cache_leaf
))
152 name
, value
= line
.split(': ')
153 if name
== 'mtime' and (int(value
) !=
154 int(self
.status_details
.st_mtime
)):
155 raise Exception("Modification time of rpm status file has changed")
156 if name
== 'size' and (int(value
) !=
157 self
.status_details
.st_size
):
158 raise Exception("Size of rpm status file has changed")
160 raise Exception('Invalid cache format (bad header)')
162 versions
= self
.versions
164 package
, version
= line
[:-1].split('\t')
165 versions
[package
] = version
167 def __parse_rpm_name(self
, line
):
168 """Some samples we have to cope with (from SuSE 10.2):
169 mp3blaster-3.2.0-0.pm0
171 gpg-pubkey-1abd1afb-450ef738
174 gnome-backgrounds-2.16.1-14
175 gnome-icon-theme-2.16.0.1-12
176 opensuse-quickstart_en-10.2-9
177 susehelp_en-2006.06.20-25
178 yast2-schema-2.14.2-3"""
180 parts
=line
.strip().split('-')
182 return parts
[0], try_cleanup_distro_version(parts
[1])
187 package
='-'.join(parts
[:-2])
191 return package
, try_cleanup_distro_version(version
+'-'+mod
)
193 def generate_cache(self
):
196 for line
in os
.popen("rpm -qa"):
197 package
, version
= self
.__parse
_rpm
_name
(line
)
198 if package
and version
:
199 cache
.append('%s\t%s' % (package
, version
))
201 cache
.sort() # Might be useful later; currently we don't care
204 fd
, tmpname
= tempfile
.mkstemp(prefix
= 'rpm-cache-tmp',
205 dir = self
.cache_dir
)
207 stream
= os
.fdopen(fd
, 'wb')
208 stream
.write('mtime: %d\n' % int(self
.status_details
.st_mtime
))
209 stream
.write('size: %d\n' % self
.status_details
.st_size
)
212 stream
.write(line
+ '\n')
216 os
.path
.join(self
.cache_dir
,
222 def get_package_info(self
, package
, factory
):
224 version
= self
.versions
[package
]
228 impl
= factory('package:rpm:%s:%s' % (package
, version
))
229 impl
.version
= model
.parse_version(version
)
231 _host_distribution
= None
232 def get_host_distribution():
233 global _host_distribution
234 if not _host_distribution
:
235 _dpkg_db_dir
= '/var/lib/dpkg'
236 _rpm_db_dir
= '/var/lib/rpm'
238 if os
.access(_dpkg_db_dir
, os
.R_OK | os
.X_OK
):
239 _host_distribution
= DebianDistribution(_dpkg_db_dir
)
240 elif os
.path
.isdir(_rpm_db_dir
):
241 _host_distribution
= RPMDistribution(_rpm_db_dir
)
243 _host_distribution
= Distribution()
245 return _host_distribution