1 """
2 Integration with native distribution package managers.
3 @since: 0.28
4 """
5
6
7
8
9 from zeroinstall import _, logger, gobject
10 import os, platform, re, subprocess, sys
11 from zeroinstall.injector import namespaces, model, arch, qdom
12 from zeroinstall.support import basedir, portable_rename, intern
13
14 _dotted_ints = '[0-9]+(?:\.[0-9]+)*'
15
16
17 _zeroinstall_regexp = '(?:%s)(?:-(?:pre|rc|post|)(?:%s))*' % (_dotted_ints, _dotted_ints)
18
19
20
21 _version_regexp = '(?:[a-z])?({ints}\.?[bu])?({zero})(-r{ints})?'.format(zero = _zeroinstall_regexp, ints = _dotted_ints)
22
23 _PYTHON_URI = 'http://repo.roscidus.com/python/python'
24
25
26
27 -class Cache(object):
28 - def __init__(self, cache_leaf, source, format):
29 """Maintain a cache file (e.g. ~/.cache/0install.net/injector/$name).
30 If the size or mtime of $source has changed, or the cache
31 format version if different, reset the cache first.
32 @type cache_leaf: str
33 @type source: str
34 @type format: int"""
35 self.cache_leaf = cache_leaf
36 self.source = source
37 self.format = format
38 self.cache_dir = basedir.save_cache_path(namespaces.config_site,
39 namespaces.config_prog)
40 self.cached_for = {}
41 try:
42 self._load_cache()
43 except Exception as ex:
44 logger.info(_("Failed to load cache (%s). Flushing..."), ex)
45 self.flush()
46
48
49 try:
50 info = os.stat(self.source)
51 mtime = int(info.st_mtime)
52 size = info.st_size
53 except Exception as ex:
54 logger.warning("Failed to stat %s: %s", self.source, ex)
55 mtime = size = 0
56 self.cache = {}
57 import tempfile
58 tmp = tempfile.NamedTemporaryFile(mode = 'wt', dir = self.cache_dir, delete = False)
59 tmp.write("mtime=%d\nsize=%d\nformat=%d\n\n" % (mtime, size, self.format))
60 tmp.close()
61 portable_rename(tmp.name, os.path.join(self.cache_dir, self.cache_leaf))
62
63 self._load_cache()
64
65
66
68 self.cache = cache = {}
69 with open(os.path.join(self.cache_dir, self.cache_leaf)) as stream:
70 for line in stream:
71 line = line.strip()
72 if not line:
73 break
74 key, value = line.split('=', 1)
75 if key in ('mtime', 'size', 'format'):
76 self.cached_for[key] = int(value)
77
78 self._check_valid()
79
80 for line in stream:
81 key, value = line.split('=', 1)
82 cache[key] = value[:-1]
83
84
86 info = os.stat(self.source)
87 if self.cached_for['mtime'] != int(info.st_mtime):
88 raise Exception("Modification time of %s has changed" % self.source)
89 if self.cached_for['size'] != info.st_size:
90 raise Exception("Size of %s has changed" % self.source)
91 if self.cached_for.get('format', None) != self.format:
92 raise Exception("Format of cache has changed")
93
95 """@type key: str
96 @rtype: str"""
97 try:
98 self._check_valid()
99 except Exception as ex:
100 logger.info(_("Cache needs to be refreshed: %s"), ex)
101 self.flush()
102 return None
103 else:
104 return self.cache.get(key, None)
105
106 - def put(self, key, value):
107 """@type key: str
108 @type value: str"""
109 cache_path = os.path.join(self.cache_dir, self.cache_leaf)
110 self.cache[key] = value
111 try:
112 with open(cache_path, 'a') as stream:
113 stream.write('%s=%s\n' % (key, value))
114 except Exception as ex:
115 logger.warning("Failed to write to cache %s: %s=%s: %s", cache_path, key, value, ex)
116
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 @type version: str
121 @return: the part we understood, or None if we couldn't parse anything
122 @rtype: str"""
123 if ':' in version:
124 version = version.split(':')[1]
125 version = version.replace('_', '-')
126 if '~' in version:
127 version, suffix = version.split('~', 1)
128 if suffix.startswith('pre'):
129 suffix = suffix[3:]
130 suffix = '-pre' + (try_cleanup_distro_version(suffix) or '')
131 else:
132 suffix = ''
133 match = re.match(_version_regexp, version)
134 if match:
135 major, version, revision = match.groups()
136 if major is not None:
137 version = major[:-1].rstrip('.') + '.' + version
138 if revision is not None:
139 version = '%s-%s' % (version, revision[2:])
140 return version + suffix
141 return None
142
144 """Represents a distribution with which we can integrate.
145 Sub-classes should specialise this to integrate with the package managers of
146 particular distributions. This base class ignores the native package manager.
147 @since: 0.28
148 @ivar name: the default value for Implementation.distro_name for our implementations
149 @type name: str
150 @ivar system_paths: list of paths to search for binaries (we MUST NOT find 0install launchers, so only include directories where system packages install binaries - e.g. /usr/bin but not /usr/local/bin)
151 @type system_paths: [str]
152 """
153
154 name = "fallback"
155
156 _packagekit = None
157
158 system_paths = ['/usr/bin', '/bin', '/usr/sbin', '/sbin']
159
161 """Get information about the given package.
162 Add zero or more implementations using the factory (typically at most two
163 will be added; the currently installed version and the latest available).
164 @param package: package name (e.g. "gimp")
165 @type package: str
166 @param factory: function for creating new DistributionImplementation objects from IDs
167 @type factory: str -> L{model.DistributionImplementation}"""
168 return
169
171 """Indicate how closely the host distribution matches this one.
172 The <package-implementation> with the highest score is passed
173 to L{Distribution.get_package_info}. If several elements get
174 the same score, get_package_info is called for all of them.
175 @param distribution: a distribution name
176 @type distribution: str
177 @return: an integer, or -1 if there is no match at all
178 @rtype: int"""
179 return 0
180
182 """Generate a feed containing information about distribution packages.
183 This should immediately return a feed containing an implementation for the
184 package if it's already installed. Information about versions that could be
185 installed using the distribution's package manager can be added asynchronously
186 later (see L{fetch_candidates}).
187 @param master_feed: feed containing the <package-implementation> elements
188 @type master_feed: L{model.ZeroInstallFeed}
189 @rtype: L{model.ZeroInstallFeed}"""
190
191 feed = model.ZeroInstallFeed(None)
192 feed.url = 'distribution:' + master_feed.url
193
194 for item, item_attrs, depends in master_feed.get_package_impls(self):
195 package = item_attrs.get('package', None)
196 if package is None:
197 raise model.InvalidInterface(_("Missing 'package' attribute on %s") % item)
198
199 new_impls = []
200
201 def factory(id, only_if_missing = False, installed = True):
202 assert id.startswith('package:')
203 if id in feed.implementations:
204 if only_if_missing:
205 return None
206 logger.warning(_("Duplicate ID '%s' for DistributionImplementation"), id)
207 impl = model.DistributionImplementation(feed, id, self, item)
208 feed.implementations[id] = impl
209 new_impls.append(impl)
210
211 impl.installed = installed
212 impl.metadata = item_attrs
213 impl.requires = depends
214
215 if 'run' not in impl.commands:
216 item_main = item_attrs.get('main', None)
217 if item_main:
218 impl.main = item_main
219 impl.upstream_stability = model.packaged
220
221 return impl
222
223 self.get_package_info(package, factory)
224
225 for impl in new_impls:
226 self.fixup(package, impl)
227 if impl.installed:
228 self.installed_fixup(impl)
229
230 if master_feed.url == _PYTHON_URI and os.name != "nt":
231
232
233 python_version = '.'.join([str(v) for v in sys.version_info if isinstance(v, int)])
234 impl_id = 'package:host:python:' + python_version
235 assert impl_id not in feed.implementations
236 impl = model.DistributionImplementation(feed, impl_id, self, distro_name = 'host')
237 impl.installed = True
238 impl.version = model.parse_version(python_version)
239 impl.main = sys.executable or '/usr/bin/python'
240 impl.upstream_stability = model.packaged
241 impl.machine = host_machine
242 feed.implementations[impl_id] = impl
243 elif master_feed.url == 'http://repo.roscidus.com/python/python-gobject' and os.name != "nt" and gobject:
244
245 impl_id = 'package:host:python-gobject:' + '.'.join(str(x) for x in gobject.pygobject_version)
246 assert impl_id not in feed.implementations
247 impl = model.DistributionImplementation(feed, impl_id, self, distro_name = 'host')
248 impl.installed = True
249 impl.version = [list(gobject.pygobject_version)]
250 impl.upstream_stability = model.packaged
251 impl.machine = host_machine
252
253
254 restriction_element = qdom.Element(namespaces.XMLNS_IFACE, 'restricts', {'interface': _PYTHON_URI, 'distribution': 'host'})
255 impl.requires.append(model.process_depends(restriction_element, None))
256
257 feed.implementations[impl_id] = impl
258
259 return feed
260
262 """Collect information about versions we could install using
263 the distribution's package manager. On success, the distribution
264 feed in iface_cache is updated.
265 @return: a L{tasks.Blocker} if the task is in progress, or None if not"""
266 if self.packagekit.available:
267 package_names = [item.getAttribute("package") for item, item_attrs, depends in master_feed.get_package_impls(self)]
268 return self.packagekit.fetch_candidates(package_names)
269
270 @property
278
279 - def fixup(self, package, impl):
280 """Some packages require special handling (e.g. Java). This is called for each
281 package that was added by L{get_package_info} after it returns. The default
282 method does nothing.
283 @param package: the name of the package
284 @param impl: the constructed implementation"""
285 pass
286
288 """Called when an installed package is added (after L{fixup}), or when installation
289 completes. This is useful to fix up the main value.
290 The default implementation checks that main exists, and searches L{Distribution.system_paths} for
291 it if not.
292 @type impl: L{DistributionImplementation}
293 @since: 1.11"""
294
295 run = impl.commands.get('run', None)
296 if not run: return
297
298 path = run.path
299
300 if not path: return
301
302 if os.path.isabs(path) and os.path.exists(path):
303 return
304
305 basename = os.path.basename(path)
306 if os.name == "nt" and not basename.endswith('.exe'):
307 basename += '.exe'
308
309 for d in self.system_paths:
310 path = os.path.join(d, basename)
311 if os.path.isfile(path):
312 logger.info("Found %s by searching system paths", path)
313 run.qdom.attrs["path"] = path
314 return
315 else:
316 logger.info("Binary '%s' not found in any system path (checked %s)", basename, self.system_paths)
317
319 """@type distro_name: str
320 @rtype: int"""
321 return int(distro_name == self.name)
322
324 name = 'Windows'
325
326 system_paths = []
327
329 def _is_64bit_windows():
330 p = sys.platform
331 from win32process import IsWow64Process
332 if p == 'win64' or (p == 'win32' and IsWow64Process()): return True
333 elif p == 'win32': return False
334 else: raise Exception(_("WindowsDistribution may only be used on the Windows platform"))
335
336 def _read_hklm_reg(key_name, value_name):
337 from win32api import RegOpenKeyEx, RegQueryValueEx, RegCloseKey
338 from win32con import HKEY_LOCAL_MACHINE, KEY_READ
339 KEY_WOW64_64KEY = 0x0100
340 KEY_WOW64_32KEY = 0x0200
341 if _is_64bit_windows():
342 try:
343 key32 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_name, 0, KEY_READ | KEY_WOW64_32KEY)
344 (value32, _) = RegQueryValueEx(key32, value_name)
345 RegCloseKey(key32)
346 except:
347 value32 = ''
348 try:
349 key64 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_name, 0, KEY_READ | KEY_WOW64_64KEY)
350 (value64, _) = RegQueryValueEx(key64, value_name)
351 RegCloseKey(key64)
352 except:
353 value64 = ''
354 else:
355 try:
356 key32 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_name, 0, KEY_READ)
357 (value32, _) = RegQueryValueEx(key32, value_name)
358 RegCloseKey(key32)
359 except:
360 value32 = ''
361 value64 = ''
362 return (value32, value64)
363
364 def find_java(part, win_version, zero_version):
365 reg_path = r"SOFTWARE\JavaSoft\{part}\{win_version}".format(part = part, win_version = win_version)
366 (java32_home, java64_home) = _read_hklm_reg(reg_path, "JavaHome")
367
368 for (home, arch) in [(java32_home, 'i486'), (java64_home, 'x86_64')]:
369 if os.path.isfile(home + r"\bin\java.exe"):
370 impl = factory('package:windows:%s:%s:%s' % (package, zero_version, arch))
371 impl.machine = arch
372 impl.version = model.parse_version(zero_version)
373 impl.upstream_stability = model.packaged
374 impl.main = home + r"\bin\java.exe"
375
376 def find_netfx(win_version, zero_version):
377 reg_path = r"SOFTWARE\Microsoft\NET Framework Setup\NDP\{win_version}".format(win_version = win_version)
378 (netfx32_install, netfx64_install) = _read_hklm_reg(reg_path, "Install")
379
380 for (install, arch) in [(netfx32_install, 'i486'), (netfx64_install, 'x86_64')]:
381 impl = factory('package:windows:%s:%s:%s' % (package, zero_version, arch))
382 impl.installed = (install == 1)
383 impl.machine = arch
384 impl.version = model.parse_version(zero_version)
385 impl.upstream_stability = model.packaged
386 impl.main = ""
387
388 def find_netfx_release(win_version, release_version, zero_version):
389 reg_path = r"SOFTWARE\Microsoft\NET Framework Setup\NDP\{win_version}".format(win_version = win_version)
390 (netfx32_install, netfx64_install) = _read_hklm_reg(reg_path, "Install")
391 (netfx32_release, netfx64_release) = _read_hklm_reg(reg_path, "Release")
392
393 for (install, release, arch) in [(netfx32_install, netfx32_release, 'i486'), (netfx64_install, netfx64_release, 'x86_64')]:
394 impl = factory('package:windows:%s:%s:%s' % (package, zero_version, arch))
395 impl.installed = (install == 1 and release != '' and release >= release_version)
396 impl.machine = arch
397 impl.version = model.parse_version(zero_version)
398 impl.upstream_stability = model.packaged
399 impl.main = ""
400
401 if package == 'openjdk-6-jre':
402 find_java("Java Runtime Environment", "1.6", '6')
403 elif package == 'openjdk-6-jdk':
404 find_java("Java Development Kit", "1.6", '6')
405 elif package == 'openjdk-7-jre':
406 find_java("Java Runtime Environment", "1.7", '7')
407 elif package == 'openjdk-7-jdk':
408 find_java("Java Development Kit", "1.7", '7')
409 elif package == 'netfx':
410 find_netfx("v2.0.50727", '2.0')
411 find_netfx("v3.0", '3.0')
412 find_netfx("v3.5", '3.5')
413 find_netfx("v4\\Full", '4.0')
414 find_netfx_release("v4\\Full", 378389, '4.5')
415 find_netfx("v5", '5.0')
416 elif package == 'netfx-client':
417 find_netfx("v4\\Client", '4.0')
418 find_netfx_release("v4\\Client", 378389, '4.5')
419
421 """@since: 1.11"""
422
423 name = 'Darwin'
424
426 """@type package: str"""
427 def java_home(version, arch):
428 null = os.open(os.devnull, os.O_WRONLY)
429 child = subprocess.Popen(["/usr/libexec/java_home", "--failfast", "--version", version, "--arch", arch],
430 stdout = subprocess.PIPE, stderr = null, universal_newlines = True)
431 home = child.stdout.read().strip()
432 child.stdout.close()
433 child.wait()
434 return home
435
436 def find_java(part, jvm_version, zero_version):
437 for arch in ['i386', 'x86_64']:
438 home = java_home(jvm_version, arch)
439 if os.path.isfile(home + "/bin/java"):
440 impl = factory('package:darwin:%s:%s:%s' % (package, zero_version, arch))
441 impl.machine = arch
442 impl.version = model.parse_version(zero_version)
443 impl.upstream_stability = model.packaged
444 impl.main = home + "/bin/java"
445
446 if package == 'openjdk-6-jre':
447 find_java("Java Runtime Environment", "1.6", '6')
448 elif package == 'openjdk-6-jdk':
449 find_java("Java Development Kit", "1.6", '6')
450 elif package == 'openjdk-7-jre':
451 find_java("Java Runtime Environment", "1.7", '7')
452 elif package == 'openjdk-7-jdk':
453 find_java("Java Development Kit", "1.7", '7')
454
455 def get_output(args):
456 child = subprocess.Popen(args, stdout = subprocess.PIPE, universal_newlines = True)
457 return child.communicate()[0]
458
459 def get_version(program):
460 stdout = get_output([program, "--version"])
461 return stdout.strip().split('\n')[0].split()[-1]
462
463 def find_program(file):
464 if os.path.isfile(file) and os.access(file, os.X_OK):
465 program_version = try_cleanup_distro_version(get_version(file))
466 impl = factory('package:darwin:%s:%s' % (package, program_version), True)
467 if impl:
468 impl.installed = True
469 impl.version = model.parse_version(program_version)
470 impl.upstream_stability = model.packaged
471 impl.machine = host_machine
472 impl.main = file
473
474 if package == 'gnupg':
475 find_program("/usr/local/bin/gpg")
476 elif package == 'gnupg2':
477 find_program("/usr/local/bin/gpg2")
478
480 """For distributions where querying the package database is slow (e.g. requires running
481 an external command), we cache the results.
482 @since: 0.39
483 @deprecated: use Cache instead
484 """
485
487 """@param db_status_file: update the cache when the timestamp of this file changes
488 @type db_status_file: str"""
489 self._status_details = os.stat(db_status_file)
490
491 self.versions = {}
492 self.cache_dir = basedir.save_cache_path(namespaces.config_site,
493 namespaces.config_prog)
494
495 try:
496 self._load_cache()
497 except Exception as ex:
498 logger.info(_("Failed to load distribution database cache (%s). Regenerating..."), ex)
499 try:
500 self.generate_cache()
501 self._load_cache()
502 except Exception as ex:
503 logger.warning(_("Failed to regenerate distribution database cache: %s"), ex)
504
506 """Load {cache_leaf} cache file into self.versions if it is available and up-to-date.
507 Throws an exception if the cache should be (re)created."""
508 with open(os.path.join(self.cache_dir, self.cache_leaf), 'rt') as stream:
509 cache_version = None
510 for line in stream:
511 if line == '\n':
512 break
513 name, value = line.split(': ')
514 if name == 'mtime' and int(value) != int(self._status_details.st_mtime):
515 raise Exception(_("Modification time of package database file has changed"))
516 if name == 'size' and int(value) != self._status_details.st_size:
517 raise Exception(_("Size of package database file has changed"))
518 if name == 'version':
519 cache_version = int(value)
520 else:
521 raise Exception(_('Invalid cache format (bad header)'))
522
523 if cache_version is None:
524 raise Exception(_('Old cache format'))
525
526 versions = self.versions
527 for line in stream:
528 package, version, zi_arch = line[:-1].split('\t')
529 versionarch = (version, intern(zi_arch))
530 if package not in versions:
531 versions[package] = [versionarch]
532 else:
533 versions[package].append(versionarch)
534
536
537 """@type cache: [str]"""
538 import tempfile
539 fd, tmpname = tempfile.mkstemp(prefix = 'zeroinstall-cache-tmp',
540 dir = self.cache_dir)
541 try:
542 stream = os.fdopen(fd, 'wt')
543 stream.write('version: 2\n')
544 stream.write('mtime: %d\n' % int(self._status_details.st_mtime))
545 stream.write('size: %d\n' % self._status_details.st_size)
546 stream.write('\n')
547 for line in cache:
548 stream.write(line + '\n')
549 stream.close()
550
551 portable_rename(tmpname,
552 os.path.join(self.cache_dir,
553 self.cache_leaf))
554 except:
555 os.unlink(tmpname)
556 raise
557
558
559
560 _canonical_machine = {
561 'all' : '*',
562 'any' : '*',
563 'noarch' : '*',
564 '(none)' : '*',
565 'x86_64': 'x86_64',
566 'amd64': 'x86_64',
567 'i386': 'i386',
568 'i486': 'i486',
569 'i586': 'i586',
570 'i686': 'i686',
571 'ppc64': 'ppc64',
572 'ppc': 'ppc',
573 }
574
575 host_machine = arch.canonicalize_machine(platform.uname()[4])
577 """@type package_machine: str
578 @rtype: str"""
579 machine = _canonical_machine.get(package_machine.lower(), None)
580 if machine is None:
581
582 return host_machine.lower()
583 return machine
584
586 """A dpkg-based distribution."""
587
588 name = 'Debian'
589
590 cache_leaf = 'dpkg-status.cache'
591
593 """@type dpkg_status: str"""
594 self.dpkg_cache = Cache('dpkg-status.cache', dpkg_status, 2)
595 self.apt_cache = {}
596
598 """@type package: str
599 @rtype: str"""
600 null = os.open(os.devnull, os.O_WRONLY)
601 child = subprocess.Popen(["dpkg-query", "-W", "--showformat=${Version}\t${Architecture}\t${Status}\n", "--", package],
602 stdout = subprocess.PIPE, stderr = null,
603 universal_newlines = True)
604 os.close(null)
605 stdout, stderr = child.communicate()
606 child.wait()
607 for line in stdout.split('\n'):
608 if not line: continue
609 version, debarch, status = line.split('\t', 2)
610 if not status.endswith(' installed'): continue
611 clean_version = try_cleanup_distro_version(version)
612 if debarch.find("-") != -1:
613 debarch = debarch.split("-")[-1]
614 if clean_version:
615 return '%s\t%s' % (clean_version, canonical_machine(debarch.strip()))
616 else:
617 logger.warning(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': package})
618
619 return '-'
620
622
623 """@type package: str"""
624 installed_cached_info = self._get_dpkg_info(package)
625
626 if installed_cached_info != '-':
627 installed_version, machine = installed_cached_info.split('\t')
628 impl = factory('package:deb:%s:%s:%s' % (package, installed_version, machine))
629 impl.version = model.parse_version(installed_version)
630 if machine != '*':
631 impl.machine = machine
632 else:
633 installed_version = None
634
635
636
637
638 self.packagekit.get_candidates(package, factory, 'package:deb')
639
640
641 cached = self.apt_cache.get(package, None)
642 if cached:
643 candidate_version = cached['version']
644 candidate_arch = cached['arch']
645 if candidate_version and candidate_version != installed_version:
646 impl = factory('package:deb:%s:%s:%s' % (package, candidate_version, candidate_arch), installed = False)
647 impl.version = model.parse_version(candidate_version)
648 if candidate_arch != '*':
649 impl.machine = candidate_arch
650 def install(handler):
651 raise model.SafeException(_("This program depends on '%s', which is a package that is available through your distribution. "
652 "Please install it manually using your distribution's tools and try again. Or, install 'packagekit' and I can "
653 "use that to install it.") % package)
654 impl.download_sources.append(model.DistributionSource(package, cached['size'], install, needs_confirmation = False))
655
656 - def fixup(self, package, impl):
657 """@type package: str
658 @type impl: L{zeroinstall.injector.model.DistributionImplementation}"""
659 if impl.id.startswith('package:deb:openjdk-6-jre:') or \
660 impl.id.startswith('package:deb:openjdk-7-jre:'):
661
662
663 impl.version = model.parse_version(impl.get_version().replace('-pre', '.'))
664
666 """@type impl: L{zeroinstall.injector.model.DistributionImplementation}"""
667
668 if impl.id.startswith('package:deb:openjdk-6-jre:'):
669 java_version = '6-openjdk'
670 elif impl.id.startswith('package:deb:openjdk-7-jre:'):
671 java_version = '7-openjdk'
672 else:
673 return Distribution.installed_fixup(self, impl)
674
675 if impl.machine == 'x86_64':
676 java_arch = 'amd64'
677 else:
678 java_arch = impl.machine
679
680 java_bin = '/usr/lib/jvm/java-%s-%s/jre/bin/java' % (java_version, java_arch)
681 if not os.path.exists(java_bin):
682
683 java_bin = '/usr/lib/jvm/java-%s/jre/bin/java' % java_version
684 if not os.path.exists(java_bin):
685 logger.info("Java binary not found (%s)", java_bin)
686 if impl.main is None:
687 java_bin = '/usr/bin/java'
688 else:
689 return
690
691 impl.commands["run"] = model.Command(qdom.Element(namespaces.XMLNS_IFACE, 'command',
692 {'path': java_bin, 'name': 'run'}), None)
693
695 """@type package: str
696 @rtype: str"""
697 installed_cached_info = self.dpkg_cache.get(package)
698 if installed_cached_info == None:
699 installed_cached_info = self._query_installed_package(package)
700 self.dpkg_cache.put(package, installed_cached_info)
701
702 return installed_cached_info
703
705 """@type master_feed: L{zeroinstall.injector.model.ZeroInstallFeed}
706 @rtype: [L{zeroinstall.support.tasks.Blocker}]"""
707 package_names = [item.getAttribute("package") for item, item_attrs, depends in master_feed.get_package_impls(self)]
708
709 if self.packagekit.available:
710 return self.packagekit.fetch_candidates(package_names)
711
712
713 for package in package_names:
714
715 try:
716 null = os.open(os.devnull, os.O_WRONLY)
717 child = subprocess.Popen(['apt-cache', 'show', '--no-all-versions', '--', package], stdout = subprocess.PIPE, stderr = null, universal_newlines = True)
718 os.close(null)
719
720 arch = version = size = None
721 for line in child.stdout:
722 line = line.strip()
723 if line.startswith('Version: '):
724 version = line[9:]
725 version = try_cleanup_distro_version(version)
726 elif line.startswith('Architecture: '):
727 arch = canonical_machine(line[14:].strip())
728 elif line.startswith('Size: '):
729 size = int(line[6:].strip())
730 if version and arch:
731 cached = {'version': version, 'arch': arch, 'size': size}
732 else:
733 cached = None
734 child.stdout.close()
735 child.wait()
736 except Exception as ex:
737 logger.warning("'apt-cache show %s' failed: %s", package, ex)
738 cached = None
739
740 self.apt_cache[package] = cached
741
743 """An RPM-based distribution."""
744
745 name = 'RPM'
746
747 cache_leaf = 'rpm-status.cache'
748
750 cache = []
751
752 child = subprocess.Popen(["rpm", "-qa", "--qf=%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n"],
753 stdout = subprocess.PIPE, universal_newlines = True)
754 for line in child.stdout:
755 package, version, rpmarch = line.split('\t', 2)
756 if package == 'gpg-pubkey':
757 continue
758 zi_arch = canonical_machine(rpmarch.strip())
759 clean_version = try_cleanup_distro_version(version)
760 if clean_version:
761 cache.append('%s\t%s\t%s' % (package, clean_version, zi_arch))
762 else:
763 logger.warning(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': package})
764
765 self._write_cache(cache)
766 child.stdout.close()
767 child.wait()
768
782
784
785 """@type impl: L{zeroinstall.injector.model.DistributionImplementation}"""
786 impl_id = impl.id.replace('_', '.')
787
788
789
790 if impl_id.startswith('package:rpm:java-1.6.0-openjdk:'):
791 java_version = '1.6.0-openjdk'
792 elif impl_id.startswith('package:rpm:java-1.7.0-openjdk:'):
793 java_version = '1.7.0-openjdk'
794 else:
795 return Distribution.installed_fixup(self, impl)
796
797
798
799 java_bin = '/usr/lib/jvm/jre-%s.%s/bin/java' % (java_version, impl.machine)
800 if not os.path.exists(java_bin):
801
802 java_bin = '/usr/lib/jvm/jre-%s/bin/java' % java_version
803 if not os.path.exists(java_bin):
804 logger.info("Java binary not found (%s)", java_bin)
805 if impl.main is None:
806 java_bin = '/usr/bin/java'
807 else:
808 return
809
810 impl.commands["run"] = model.Command(qdom.Element(namespaces.XMLNS_IFACE, 'command',
811 {'path': java_bin, 'name': 'run'}), None)
812
813 - def fixup(self, package, impl):
814
815 """@type package: str
816 @type impl: L{zeroinstall.injector.model.DistributionImplementation}"""
817 package = package.replace('_', '.')
818
819 if package in ('java-1.6.0-openjdk', 'java-1.7.0-openjdk',
820 'java-1.6.0-openjdk-devel', 'java-1.7.0-openjdk-devel'):
821 if impl.version[0][0] == 1:
822
823 del impl.version[0][0]
824
826 """A Slack-based distribution."""
827
828 name = 'Slack'
829
831 """@type packages_dir: str"""
832 self._packages_dir = packages_dir
833
835
836 """@type package: str"""
837 for entry in os.listdir(self._packages_dir):
838 name, version, arch, build = entry.rsplit('-', 3)
839 if name == package:
840 zi_arch = canonical_machine(arch)
841 clean_version = try_cleanup_distro_version("%s-%s" % (version, build))
842 if not clean_version:
843 logger.warning(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': name})
844 continue
845
846 impl = factory('package:slack:%s:%s:%s' % \
847 (package, clean_version, zi_arch))
848 impl.version = model.parse_version(clean_version)
849 if zi_arch != '*':
850 impl.machine = zi_arch
851
852
853 self.packagekit.get_candidates(package, factory, 'package:slack')
854
856 """An Arch Linux distribution."""
857
858 name = 'Arch'
859
861 """@type packages_dir: str"""
862 self._packages_dir = os.path.join(packages_dir, "local")
863
865
866 """@type package: str"""
867 for entry in os.listdir(self._packages_dir):
868 name, version, build = entry.rsplit('-', 2)
869 if name == package:
870 gotarch = False
871 with open(os.path.join(self._packages_dir, entry, "desc"), 'rt') as stream:
872 for line in stream:
873 if line == "%ARCH%\n":
874 gotarch = True
875 continue
876 if gotarch:
877 arch = line.strip()
878 break
879 zi_arch = canonical_machine(arch)
880 clean_version = try_cleanup_distro_version("%s-%s" % (version, build))
881 if not clean_version:
882 logger.warning(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': name})
883 continue
884
885 impl = factory('package:arch:%s:%s:%s' % \
886 (package, clean_version, zi_arch))
887 impl.version = model.parse_version(clean_version)
888 if zi_arch != '*':
889 impl.machine = zi_arch
890
891
892 self.packagekit.get_candidates(package, factory, 'package:arch')
893
895 name = 'Gentoo'
896
898 """@type pkgdir: str"""
899 self._pkgdir = pkgdir
900
902
903 """@type package: str"""
904 _version_start_reqexp = '-[0-9]'
905
906 if package.count('/') != 1: return
907
908 category, leafname = package.split('/')
909 category_dir = os.path.join(self._pkgdir, category)
910 match_prefix = leafname + '-'
911
912 if not os.path.isdir(category_dir): return
913
914 for filename in os.listdir(category_dir):
915 if filename.startswith(match_prefix) and filename[len(match_prefix)].isdigit():
916 with open(os.path.join(category_dir, filename, 'PF'), 'rt') as stream:
917 name = stream.readline().strip()
918
919 match = re.search(_version_start_reqexp, name)
920 if match is None:
921 logger.warning(_('Cannot parse version from Gentoo package named "%(name)s"'), {'name': name})
922 continue
923 else:
924 version = try_cleanup_distro_version(name[match.start() + 1:])
925
926 if category == 'app-emulation' and name.startswith('emul-'):
927 __, __, machine, __ = name.split('-', 3)
928 else:
929 with open(os.path.join(category_dir, filename, 'CHOST'), 'rt') as stream:
930 machine, __ = stream.readline().split('-', 1)
931 machine = arch.canonicalize_machine(machine)
932
933 impl = factory('package:gentoo:%s:%s:%s' % \
934 (package, version, machine))
935 impl.version = model.parse_version(version)
936 impl.machine = machine
937
938
939 self.packagekit.get_candidates(package, factory, 'package:gentoo')
940
942 name = 'Ports'
943
944 system_paths = ['/usr/local/bin']
945
947 """@type pkgdir: str"""
948 self._pkgdir = pkgdir
949
951 """@type package: str"""
952 _name_version_regexp = '^(.+)-([^-]+)$'
953
954 nameversion = re.compile(_name_version_regexp)
955 for pkgname in os.listdir(self._pkgdir):
956 pkgdir = os.path.join(self._pkgdir, pkgname)
957 if not os.path.isdir(pkgdir): continue
958
959
960
961 match = nameversion.search(pkgname)
962 if match is None:
963 logger.warning(_('Cannot parse version from Ports package named "%(pkgname)s"'), {'pkgname': pkgname})
964 continue
965 else:
966 name = match.group(1)
967 if name != package:
968 continue
969 version = try_cleanup_distro_version(match.group(2))
970
971 machine = host_machine
972
973 impl = factory('package:ports:%s:%s:%s' % \
974 (package, version, machine))
975 impl.version = model.parse_version(version)
976 impl.machine = machine
977
979 system_paths = ['/opt/local/bin']
980
981 name = 'MacPorts'
982
987
988 cache_leaf = 'macports-status.cache'
989
991 cache = []
992
993 child = subprocess.Popen(["port", "-v", "installed"],
994 stdout = subprocess.PIPE, universal_newlines = True)
995 for line in child.stdout:
996 if not line.startswith(" "):
997 continue
998 if line.strip().count(" ") > 1:
999 package, version, extra = line.split(None, 2)
1000 else:
1001 package, version = line.split()
1002 extra = ""
1003 if not extra.startswith("(active)"):
1004 continue
1005 version = version.lstrip('@')
1006 version = re.sub(r"\+.*", "", version)
1007 zi_arch = '*'
1008 clean_version = try_cleanup_distro_version(version)
1009 if clean_version:
1010 match = re.match(r" platform='([^' ]*)( \d+)?' archs='([^']*)'", extra)
1011 if match:
1012 platform, major, archs = match.groups()
1013 for arch in archs.split():
1014 zi_arch = canonical_machine(arch)
1015 cache.append('%s\t%s\t%s' % (package, clean_version, zi_arch))
1016 else:
1017 cache.append('%s\t%s\t%s' % (package, clean_version, zi_arch))
1018 else:
1019 logger.warning(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': package})
1020 self._write_cache(cache)
1021 child.stdout.close()
1022 child.wait()
1023
1036
1038
1039
1040
1041
1042 return int(distro_name in ('Darwin', 'MacPorts'))
1043
1045 """A Cygwin-based distribution."""
1046
1047 name = 'Cygwin'
1048
1049 cache_leaf = 'cygcheck-status.cache'
1050
1052 cache = []
1053
1054 zi_arch = canonical_machine(arch)
1055 for line in os.popen("cygcheck -c -d"):
1056 if line == "Cygwin Package Information\r\n":
1057 continue
1058 if line == "\n":
1059 continue
1060 package, version = line.split()
1061 if package == "Package" and version == "Version":
1062 continue
1063 clean_version = try_cleanup_distro_version(version)
1064 if clean_version:
1065 cache.append('%s\t%s\t%s' % (package, clean_version, zi_arch))
1066 else:
1067 logger.warning(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': package})
1068
1069 self._write_cache(cache)
1070
1080
1081
1082 _host_distribution = None
1127