# (c) Copyright 2012. CodeWeavers, Inc.
"""Provides efficient access to the cxdiag diagnostics."""
import os.path
import time
import re
import cxconfig
import cxlog
import cxutils
import distversion
# for localization
from cxutils import cxgettext as _
import bottlequery
import cxproduct
class CXDiag(object):
"""Stores the cxdiag warnings and properties for the given library path
setting.
Once a CXDiag object is created it is never updated. So users should not
keep references to them but instead systematically call cxdiag.get().
The warnings are stored in the warnings dictionary in the form of
(level, title, description) tuples, already localized.
The properties are stored in the properties dictionary.
"""
MISSING_RE = re.compile('^Missing 32bit (?P<library>.+) library$')
def __init__(self, libpath):
# Set up the library path so cxdiag takes into account the builtin
# libraries. Note that it is configurable per bottle.
self.libpath = libpath
self.warnings = {}
self.properties = {}
if not distversion.IS_MACOSX:
env = os.environ.copy()
if libpath:
# Re-run cxdiag with the new library path.
ldpath_envname = "LD_LIBRARY_PATH"
if ldpath_envname not in env:
env[ldpath_envname] = libpath
else:
env[ldpath_envname] = ':'.join((libpath, env[ldpath_envname]))
cxlog.log("running cxdiag with %s=%s" % (ldpath_envname, libpath))
cxdiag = os.path.join(cxutils.CX_ROOT, "bin", "cxdiag")
retcode, out, _err = cxutils.run((cxdiag, ), env=env,
stdout=cxutils.GRAB)
if retcode == 0:
config = cxconfig.Raw()
config.read_string(out)
for name, section in config.iteritems():
name = name.lower()
if name == 'properties':
for prop, value in section.iteritems():
self.properties[prop.lower()] = value
else:
match = self.MISSING_RE.match(section['Title'])
if match:
# Reformat the message so it can be translated
title = _('Missing 32bit %s library') % match.group('library')
else:
title = cxutils.cxgettext(section['Title'])
self.warnings[name] = (section['Level'], title, cxutils.cxgettext(section['Description']))
elif not os.access(cxdiag, os.R_OK | os.X_OK):
# Consider that cxdiag has been disabled
cxlog.log("cxdiag is missing / not executable")
elif retcode == -1:
cxlog.warn("could not run cxdiag")
self.warnings['missinglibc'] = \
('Require',
_('Missing 32bit C library'),
_('Without this library Windows applications cannot run.'))
else:
cxlog.warn("cxdiag failed (%d)" % retcode)
self.warnings['brokencxdiag'] = \
('Require',
_('Cxdiag failed'),
_('An error (%d) prevented cxdiag from running successfully. The same error is likely to cause some Windows applications to crash.') % retcode)
self.timestamp = time.time()
#####
#
# The cxdiag cache
#
#####
_CACHE = {}
def get(bottlename):
"""Return a CXDiag object with the results for the specified bottle.
The results are cached so we don't run cxdiag a dozen times if we have
half a dozen bottles. However they are also expired after a while so we
detect configuration changes such as the addition or removal of packages.
"""
# The cxdiag result depends on the configuration settings, specifically
# on the LibPath setting.
if bottlename:
config = bottlequery.get_config(bottlename)
else:
config = cxproduct.get_config()
libpath = config['Wine'].get('LibPath', None)
if libpath is not None:
libpath = bottlequery.expand_unix_string(bottlename, libpath)
else:
libpath = os.path.join(cxutils.CX_ROOT, "lib")
cxdiag = _CACHE.get(libpath, None)
if cxdiag and time.time()-cxdiag.timestamp > 10:
# Remove old results in case the user added / removed packages in the
# meantime
cxlog.log("cxdiag: pruning obsolete results")
cxdiag = None
if cxdiag is None:
cxdiag = _CACHE[libpath] = CXDiag(libpath)
return cxdiag