home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / bin / pycentral < prev    next >
Encoding:
Text File  |  2007-04-10  |  52.8 KB  |  1,395 lines

  1. #! /usr/bin/python
  2.  
  3. import fnmatch, glob, os, re, sys, time
  4. from optparse import OptionParser
  5.  
  6. sys.path[0:0] = ['/usr/share/pycentral-data', '/usr/share/python']
  7. import pyversions
  8.  
  9. try:
  10.     SetType = set
  11. except NameError:
  12.     import sets
  13.     SetType = sets.Set
  14.     set = sets.Set
  15.  
  16. program = os.path.basename(sys.argv[0])
  17.  
  18. shared_base = '/usr/share/pycentral/'
  19. req_pycentral_version = '0.5.8'
  20.  
  21. def samefs(path1, path2):
  22.     if not (os.path.exists(path1) and os.path.exists(path2)):
  23.         return False
  24.     while path1 != os.path.dirname(path1):
  25.         if os.path.ismount(path1):
  26.             break
  27.         path1 = os.path.dirname(path1)
  28.     while path2 != os.path.dirname(path2):
  29.         if os.path.ismount(path2):
  30.             break
  31.         path2 = os.path.dirname(path2)
  32.     return path1 == path2
  33.  
  34. def version2depends(vinfo):
  35.     if isinstance(vinfo, set):
  36.         vinfo = list(vinfo)
  37.     if isinstance(vinfo, list):
  38.         vinfo = vinfo[:]
  39.         vinfo.sort()
  40.         nv = [int(s) for s in vinfo[-1].split('.')]
  41.         deps = 'python (>= %s), python (<< %d.%d)' % (vinfo[0], nv[0], nv[1]+1)
  42.     elif vinfo in ('all', 'current'):
  43.         supported = [d[6:] for d in pyversions.supported_versions()
  44.                      if re.match(r'python\d\.\d', d)]
  45.         supported.sort()
  46.         deps = 'python (>= %s)' % supported[0]
  47.     elif vinfo == 'current_ext':
  48.         cv = pyversions.default_version(version_only=True)
  49.         nv = [int(s) for s in cv.split('.')]
  50.         deps = 'python (>= %s), python (<< %d.%d)' % (cv, nv[0], nv[1]+1)
  51.     else:
  52.         raise ValueError, 'unknown version info %s' % vinfo
  53.     return deps + ', python-central (>= %s)' % req_pycentral_version
  54.         
  55.  
  56. def read_dpkg_status():
  57.         """Read the dpkg status file, return a list of packages depending
  58.         on python-central and having a Python-Version information field."""
  59.         packages = []
  60.         rx = re.compile(r'\bpython-central\b')
  61.         pkgname = version = None
  62.         depends = ''
  63.         for line in file('/var/lib/dpkg/status'):
  64.             if line.startswith('Package:'):
  65.                 if version != None:
  66.                     if 'python-support' in depends:
  67.                         continue
  68.                     if rx.search(depends):
  69.                         packages.append((pkgname, version))
  70.                 version = None
  71.                 pkgname = line.split(':', 1)[1].strip()
  72.             elif line.startswith('Python-Version:'):
  73.                 version = line.split(':', 1)[1].strip()
  74.             elif line.startswith('Depends:'):
  75.                 depends = line.split(':', 1)[1].strip()
  76.         if version != None:
  77.             if rx.search(depends):
  78.                 packages.append((pkgname, version))
  79.         return packages
  80.  
  81.  
  82. class PyCentralError(Exception):
  83.     """Python Central Exception"""
  84.     pass
  85.  
  86. class PyCentralVersionMissingError(PyCentralError):
  87.     """Python Central Version Missing Exception"""
  88.     pass
  89.  
  90. class PythonRuntime:
  91.     def __init__(self, name, version, interp, prefix):
  92.         self.name = name
  93.         self.version = version
  94.         if name.startswith('python'):
  95.             self.short_name = name[6:]
  96.         else:
  97.             self.short_name = name
  98.         self.interp = interp
  99.         if prefix.endswith('/'):
  100.             self.prefix = prefix
  101.         else:
  102.             self.prefix = prefix + '/'
  103.  
  104.     def byte_compile_dirs(self, dirs, bc_option, exclude=None):
  105.         """call compileall.py -x <exclude regexp> <dirs> according
  106.         to bc_options"""
  107.         
  108.         logging.debug('\tbyte-compile directories')
  109.         errors = False
  110.         cmd = [self.interp, self.prefix + '/compileall.py', '-q']
  111.         if exclude:
  112.             cmd.extend(['-x', exclude])
  113.         cmd.extend(dirs)
  114.         for opt in ('standard', 'optimize'):
  115.             if not opt in bc_option:
  116.                 continue
  117.             if opt == 'optimize':
  118.                 cmd[1:1] = ['-O']
  119.             rv = os.spawnv(os.P_WAIT, self.interp, cmd[1:])
  120.             if rv:
  121.                 raise PyCentralError
  122.  
  123.     def byte_compile(self, files, bc_option, exclude=None, ignore_errors=False):
  124.         errors = False
  125.         if exclude:
  126.             rx = re.compile(exclude)
  127.             files2 = []
  128.             for fn in files:
  129.                 mo = rx.search(fn)
  130.                 if mo:
  131.                     continue
  132.                 files2.append(fn)
  133.         else:
  134.             files2 = files
  135.         if not files2:
  136.             logging.info('\tno files to byte-compile')
  137.             return
  138.         logging.debug('\tbyte-compile files (%d/%d) %s' \
  139.                       % (len(files), len(files2), self.name))
  140.         debug_files = files2[:min(2, len(files2))]
  141.         if len(files2) > 2:
  142.             debug_files.append('...')
  143.         logging.debug('\t    %s' % debug_files)
  144.         cmd = [self.interp, '/usr/bin/py_compilefiles', '-q']
  145.         if ignore_errors:
  146.             cmd.append('-i')
  147.         cmd.append('-')
  148.         for opt in ('standard', 'optimize'):
  149.             if not opt in bc_option:
  150.                 continue
  151.             if opt == 'optimize':
  152.                 cmd[1:1] = ['-O']
  153.             try:
  154.                 import subprocess
  155.                 p = subprocess.Popen(cmd, bufsize=1,
  156.                                      shell=False, stdin=subprocess.PIPE)
  157.                 fd = p.stdin
  158.             except ImportError:
  159.                 p = None
  160.                 fd = os.popen(' '.join(cmd), 'w')
  161.             for fn in files2:
  162.                 fd.write(fn + '\n')
  163.             rv = fd.close()
  164.             if p:
  165.                 p.wait()
  166.                 errors = p.returncode != 0
  167.             else:
  168.                 errors = rv != None
  169.             if errors:
  170.                 raise PyCentralError, 'error byte-compiling files (%d)' % len(files2)
  171.  
  172.     def remove_byte_code(self, files):
  173.         errors = False
  174.         logging.debug('\tremove byte-code files (%d)' % (len(files)))
  175.         for ext in ('c', 'o'):
  176.             for fn in files:
  177.                 fnc = fn + ext
  178.                 if os.path.exists(fnc):
  179.                     try:
  180.                         os.unlink(fnc)
  181.                     except OSError, e:
  182.                         print "Sorry", e
  183.                         errors = True
  184.         if errors:
  185.             raise PyCentralError, 'error removing the byte-code files'
  186.  
  187.  
  188. installed_runtimes = None
  189. default_runtime = None
  190.  
  191. def get_installed_runtimes(with_unsupported=True):
  192.     global installed_runtimes
  193.     global default_runtime
  194.  
  195.     if not installed_runtimes:
  196.         import glob
  197.         installed_runtimes = []
  198.         default_version = pyversions.default_version(version_only=True)
  199.         supported = pyversions.supported_versions()
  200.         old = pyversions.old_versions()
  201.         if with_unsupported:
  202.             unsupported = pyversions.unsupported_versions()
  203.         else:
  204.             unsupported = []
  205.         for interp in glob.glob('/usr/bin/python[0-9].[0-9]'):
  206.             if old and os.path.basename(interp) in old:
  207.                 print "INFO: using old version '%s'" % interp
  208.             elif unsupported and os.path.basename(interp) in unsupported:
  209.                 print "INFO: using unsupported version '%s'" % interp
  210.             if not (os.path.basename(interp) in supported
  211.                     or (old and os.path.basename(interp) in old)
  212.                     or (unsupported and os.path.basename(interp) in unsupported)):
  213.                 continue
  214.             version = interp[-3:]
  215.             rt = PythonRuntime('python' + version,
  216.                                version,
  217.                                '/usr/bin/python' + version,
  218.                                '/usr/lib/python' + version)
  219.             installed_runtimes.append(rt)
  220.             if version == default_version:
  221.                 default_runtime = rt
  222.     return installed_runtimes
  223.  
  224. def get_default_runtime():
  225.     get_installed_runtimes()
  226.     return default_runtime
  227.  
  228. def get_runtime_for_version(version):
  229.     if version == 'current':
  230.         return get_default_runtime()
  231.     for rt in get_installed_runtimes():
  232.         if rt.version == version:
  233.             return rt
  234.     return None
  235.     
  236. debian_config = None
  237. def get_debian_config():
  238.     global debian_config
  239.     if debian_config is not None:
  240.         return debian_config
  241.  
  242.     from ConfigParser import SafeConfigParser
  243.     config = SafeConfigParser()
  244.     fn = '/etc/python/debian_config'
  245.     if os.path.exists(fn):
  246.         try:
  247.             config.readfp(open(fn))
  248.         except Error:
  249.             logging.error("error reading config file `%s'" % fn)
  250.             sys.exit(1)
  251.     # checks
  252.     if not config.has_option('DEFAULT', 'byte-compile'):
  253.         config.set('DEFAULT', 'byte-compile', 'standard')
  254.     bc_option = config.get('DEFAULT', 'byte-compile')
  255.     bc_values = set([v.strip() for v in bc_option.split(',')])
  256.     bc_unknown = bc_values - set(['standard', 'optimize'])
  257.     if bc_unknown:
  258.         sys.stderr.write("%s: `%s': unknown values `%s'"
  259.                          " in `byte-compile option'\n"
  260.                          % (program, fn, ', '.join(list(bc_unknown))))
  261.         sys.exit(1)
  262.     config.set('DEFAULT', 'byte-compile', ', '.join(bc_values))
  263.     if config.has_option('DEFAULT', 'overwrite-local'):
  264.         val = config.get('DEFAULT', 'overwrite-local').strip().lower()
  265.         overwrite_local = val in ('yes', '1', 'true')
  266.     else:
  267.         overwrite_local = False
  268.     config.set('DEFAULT', 'overwrite-local', overwrite_local and '1' or '0')
  269.     debian_config = config
  270.     return debian_config
  271.  
  272. class DebPackage:
  273.     def __init__(self, kind, name,
  274.                  oldstyle=False, default_runtime=None,
  275.                  pkgdir=None, parse_versions=True):
  276.         self.kind = kind
  277.         self.name = name
  278.         self.version_field = None
  279.         self.oldstyle = oldstyle
  280.         self.parse_versions = parse_versions
  281.         self.default_runtime = default_runtime
  282.         self.shared_prefix = shared_base + name + '/'
  283.         self.pkgdir = pkgdir
  284.         self.has_shared_extension = {}
  285.         self.has_private_extension = False
  286.         self.has_shared_module = {}
  287.         self.has_private_module = False
  288.         if pkgdir:
  289.             self.read_control()
  290.         else:
  291.             self.read_pyfiles()
  292.             #self.print_info()
  293.  
  294.     def read_pyfiles(self):
  295.         self.shared_files = []
  296.         self.pylib_files = {}
  297.         self.private_files = []
  298.         self.omitted_files = []
  299.         self.pysupport_files = []
  300.         if self.pkgdir:
  301.             lines = []
  302.             for root, dirs, files in os.walk(self.pkgdir):
  303.                 if root.endswith('DEBIAN'):
  304.                     continue
  305.                 if root != self.pkgdir:
  306.                     d = root[len(self.pkgdir):]
  307.                     lines.append(d)
  308.                 for name in files:
  309.                     lines.append(os.path.join(d, name))
  310.         else:
  311.             #lines = [s[:-1] for s in file('/var/lib/dpkg/info/%s.list' % self.name).readlines()]
  312.             cmd = ['/usr/bin/dpkg-query', '-L', self.name]
  313.             try:
  314.                 import subprocess
  315.                 p = subprocess.Popen(cmd, bufsize=1,
  316.                                      shell=False, stdout=subprocess.PIPE)
  317.                 fd = p.stdout
  318.             except ImportError:
  319.                 fd = os.popen(' '.join(cmd))
  320.             lines = [s[:-1] for s in fd.readlines()]
  321.  
  322.         for line in lines:
  323.             fn = line
  324.             if fn.startswith(self.shared_prefix):
  325.                 # keep _all_ files and directories
  326.                 self.shared_files.append(fn)
  327.                 if fn.endswith('.py'):
  328.                     self.has_shared_module['all'] = True
  329.                 continue
  330.             elif fn.startswith('/usr/share/python-support') \
  331.                      or fn.startswith('/usr/lib/python-support'):
  332.                 self.pysupport_files.append(fn)
  333.                 continue
  334.             elif not fn.endswith('.py') and not fn.endswith('.so'):
  335.                 continue
  336.             elif fn.startswith('/etc/') or fn.startswith('/usr/share/doc/'):
  337.                 # omit files in /etc and /usr/share/doc
  338.                 self.omitted_files.append(fn)
  339.                 continue
  340.             elif re.search(r'/s?bin/', fn):
  341.                 # omit files located in directories
  342.                 self.omitted_files.append(fn)
  343.                 continue
  344.             elif fn.startswith('/usr/lib/site-python/'):
  345.                 version = pyversions.default_version(version_only=True)
  346.                 self.pylib_files.setdefault(version, []).append(fn)
  347.                 continue
  348.             elif re.match(r'/usr/lib/python\d\.\d/', fn):
  349.                 version = fn[15:18]
  350.                 if fn.endswith('.so'):
  351.                     self.has_shared_extension[version] = True
  352.                 if fn.endswith('.py'):
  353.                     self.has_shared_module[version] = True
  354.                     self.pylib_files.setdefault(version, []).append(fn)
  355.                 continue
  356.             else:
  357.                 self.private_files.append(fn)
  358.                 if fn.endswith('.py'):
  359.                     self.has_private_module = True
  360.  
  361.     def print_info(self, fd=sys.stdout):
  362.         fd.write('Package: %s\n' % self.name)
  363.         fd.write('    shared files  :%4d\n' % len(self.shared_files))
  364.         fd.write('    private files :%4d\n' % len(self.private_files))
  365.         for ver, files in self.pylib_files.items():
  366.             fd.write('    pylib%s files:%4d\n' % (ver, len(files)))
  367.  
  368.     def read_control(self):
  369.         """read the debian/control file, extract the XS-Python-Version
  370.         field; check that XB-Python-Version exists for the package."""
  371.         if not os.path.exists('debian/control'):
  372.             raise PyCentralError, "debian/control not found"
  373.         self.version_field = None
  374.         self.sversion_field = None
  375.         try:
  376.             section = None
  377.             for line in file('debian/control'):
  378.                 line = line.strip()
  379.                 if line == '':
  380.                     section = None
  381.                 elif line.startswith('Source:'):
  382.                     section = 'Source'
  383.                 elif line.startswith('Package: ' + self.name):
  384.                     section = self.name
  385.                 elif line.startswith('XS-Python-Version:'):
  386.                     if section != 'Source':
  387.                         raise PyCentralError, \
  388.                               'attribute XS-Python-Version not in Source section'
  389.                     self.sversion_field = line.split(':', 1)[1].strip()
  390.                 elif line.startswith('XB-Python-Version:'):
  391.                     if section == self.name:
  392.                         self.version_field = line.split(':', 1)[1].strip()
  393.         except:
  394.             pass
  395.         if self.version_field == None:
  396.             raise PyCentralVersionMissingError, \
  397.                   'missing XB-Python-Version attribute in package %s' % self.name
  398.         if self.sversion_field == None:
  399.             raise PyCentralError, 'missing XS-Python-Version attribute'
  400.         if self.parse_versions:
  401.             self.sversion_info = parse_versions(self.sversion_field)
  402.         else:
  403.             self.sversion_info = 'all' # dummy
  404.         self.has_private_extension = self.sversion_info == 'current_ext'
  405.  
  406.     def move_files(self):
  407.         """move files from the installation directory to the pycentral location"""
  408.         import shutil
  409.  
  410.         dsttop = self.pkgdir + shared_base + self.name
  411.         try:
  412.             os.makedirs(dsttop)
  413.         except OSError:
  414.             pass
  415.         pversions = pyversions.supported_versions() \
  416.                     + pyversions.unsupported_versions() + pyversions.old_versions()
  417.         pversions = list(set(pversions))
  418.         for pversion in pversions:
  419.             srctop = os.path.join(self.pkgdir, 'usr/lib', pversion)
  420.             for root, dirs, files in os.walk(srctop):
  421.                 if root == srctop:
  422.                     d = '.'
  423.                 else:
  424.                     d = root[len(srctop)+1:]
  425.                 for name in dirs:
  426.                     src = os.path.join(root, name)
  427.                     dst = os.path.join(dsttop, d, name)
  428.                     try:
  429.                         os.mkdir(dst)
  430.                         shutil.copymode(src, dst)
  431.                     except OSError:
  432.                         pass
  433.                 for name in files:
  434.                     src = os.path.join(root, name)
  435.                     dst = os.path.join(dsttop, d, name)
  436.                     if re.search(r'\.so(\.\d+)*?$', name):
  437.                         continue
  438.                     if name.endswith('.pyc') or name.endswith('.pyo'):
  439.                         os.unlink(src)
  440.                         continue
  441.                     # TODO: if dst already exists, make sure, src == dst
  442.                     os.rename(src, dst)
  443.             # remove empty dirs in /usr/lib/pythonX.Y
  444.             for root, dirs, files in os.walk(self.pkgdir + '/usr/lib', topdown=False):
  445.                 try:
  446.             if re.match("/usr/lib/python\d\.\d($|/)", root.replace(self.pkgdir, "")):
  447.             os.rmdir(root)
  448.                 except OSError:
  449.                     pass
  450.         # remove empty dirs in /usr/share/pycentral
  451.         for root, dirs, files in os.walk(self.pkgdir + shared_base, topdown=False):
  452.             try:
  453.                 os.rmdir(root)
  454.             except OSError:
  455.                 pass
  456.  
  457.     def gen_substvars(self):
  458.         supported = [d[6:] for d in pyversions.supported_versions()
  459.                      if re.match(r'python\d\.\d', d)]
  460.         versions = ''
  461.         prversions = ''
  462.         self.depends = None
  463.         if len(self.has_shared_module) or len(self.has_shared_extension):
  464.             # shared modules / extensions
  465.             if len(self.has_shared_extension):
  466.                 versions = self.has_shared_extension.keys()
  467.             else:
  468.                 if self.sversion_info in ('current', 'current_ext'):
  469.                     versions = 'current'
  470.                 elif self.sversion_info == 'all':
  471.                     versions = 'all'
  472.                     prversions = supported
  473.                 else:
  474.                     versions = self.sversion_field
  475.                     prversions = list(self.sversion_info.intersection(supported))
  476.                     self.depends = version2depends(self.sversion_info)
  477.         elif self.has_private_module or self.has_private_extension:
  478.             if self.sversion_info == 'all':
  479.                 versions = 'current'
  480.             elif self.sversion_info == 'current':
  481.                 versions = 'current'
  482.             elif self.sversion_info == 'current_ext':
  483.                 versions = [pyversions.default_version(version_only=True)]
  484.             elif isinstance(self.sversion_info, list) or isinstance(self.sversion_info, set):
  485.                 # exact version info required, no enumeration, no relops
  486.                 if len(self.sversion_info) != 1 or not re.match(r'\d\.\d', self.sversion_info[0]):
  487.                     raise PyCentralError, 'no exact version for package with private modules'
  488.                 versions = [list(self.sversion_info)[0]]
  489.             else:
  490.                 raise PyCentralError, 'version error for package with private modules'
  491.         else:
  492.             # just "copy" it from the source field
  493.             if self.sversion_info == 'current':
  494.                 versions = 'current'
  495.             elif self.sversion_info == 'current_ext':
  496.                 versions = [pyversions.default_version(version_only=True)]
  497.             elif self.sversion_info == 'all':
  498.                 versions = 'all'
  499.                 prversions = supported
  500.             else:
  501.                 versions = self.sversion_field
  502.                 prversions = list(self.sversion_info.intersection(supported))
  503.                 self.depends = version2depends(self.sversion_info)
  504.  
  505.         if (len(self.has_shared_module) or len(self.has_shared_extension)) \
  506.            and self.has_private_module or self.has_private_extension:
  507.             # use case? use the information for the shared stuff
  508.             pass
  509.         if versions == '':
  510.             raise PyCentralError, 'unable to determine Python-Version attribute'
  511.         if isinstance(versions, list) or isinstance(versions, set):
  512.             self.version_field = ', '.join(versions)
  513.         else:
  514.             self.version_field = versions
  515.         if not self.depends:
  516.             self.depends = version2depends(versions)
  517.         if self.name.startswith('python-'):
  518.             if prversions == '':
  519.                 prversions = versions
  520.             self.provides = ', '.join([self.name.replace('python-', 'python%s-' % ver)
  521.                                        for ver in prversions])
  522.     
  523.     def read_version_info(self):
  524.         """Read the Python-Version information field"""
  525.         if self.version_field:
  526.             return
  527.         cmd = ['/usr/bin/dpkg-query', '-s', self.name]
  528.         try:
  529.             import subprocess
  530.             p = subprocess.Popen(cmd, bufsize=1,
  531.                                  shell=False, stdout=subprocess.PIPE)
  532.             fd = p.stdout
  533.         except ImportError:
  534.             fd = os.popen(' '.join(cmd))
  535.             
  536.         for line in fd:
  537.             if line.startswith('Python-Version:'):
  538.                 self.version_field = line.split(':', 1)[1].strip()
  539.                 break
  540.         fd.close()
  541.         if not self.version_field:
  542.             raise PyCentralError, "package has no field Python-Version"
  543.         if self.parse_versions:
  544.             self.version_info = pyversions.parse_versions(self.version_field)
  545.  
  546.     def set_default_runtime_from_version_info(self):
  547.         versions = list(pyversions.requested_versions(self.version_field, version_only=True))
  548.         if not versions:
  549.             raise PyCentralError, "no matching runtime for `%s'" % self.version_field
  550.         if len(versions) == 1:
  551.             self.default_runtime = get_runtime_for_version(versions[0])
  552.         elif pyversions.default_version(version_only=True) in versions:
  553.             self.default_runtime = get_default_runtime()
  554.         else:
  555.             self.default_runtime = get_runtime_for_version(versions[0])
  556.  
  557.     def byte_compile(self, bc_option, exclude_regex, ignore_errors=False):
  558.         """byte compiles all files not handled by pycentral"""
  559.  
  560.         assert self.oldstyle
  561.  
  562.         logging.debug("    byte-compile %s" % self.name)
  563.         for version, files in self.pylib_files.items():
  564.             logging.debug("bc for v%s (%d files)" % (version, len(files)))
  565.             rt = get_runtime_for_version(version)
  566.             rt.byte_compile(files, bc_option, exclude_regex)
  567.         if self.private_files:
  568.             logging.debug("bc private (%d files)" %
  569.                           (self.default_runtime.version, len(self.private_files)))
  570.             rt = self.default_runtime
  571.             rt.byte_compile(self.private_files, bc_option, exclude_regex)
  572.  
  573.     def remove_bytecode(self):
  574.         """remove all byte-compiled files not handled by pycentral"""
  575.         
  576.         assert self.oldstyle
  577.  
  578.         logging.debug("    remove byte-code for %s" % self.name)
  579.         pyfiles = []
  580.         for files in self.pylib_files.values():
  581.             pyfiles.extend(files)
  582.         pyfiles.extend(self.private_files)
  583.  
  584.         errors = False
  585.         for ext in ('c', 'o'):
  586.             for fn in pyfiles:
  587.                 fnc = fn + ext
  588.                 if os.path.exists(fnc):
  589.                     try:
  590.                         os.unlink(fnc)
  591.                     except OSError, e:
  592.                         print "Sorry", e
  593.                         errors = True
  594.         if errors:
  595.             raise PyCentralError
  596.  
  597.     def link_shared_files(self, rt):
  598.         #if samefs(rt.prefix, self.shared_files[0]):
  599.         #    link_cmd = os.link
  600.         #else:
  601.         #    link_cmd = os.symlink
  602.         logging.debug("\tlink shared files %s/%s" % (rt.name, self.name))
  603.         link_cmd = os.symlink
  604.         ppos = len(self.shared_prefix)
  605.         linked_files = []
  606.         try:
  607.             for fn in self.shared_files:
  608.                 fn2 = rt.prefix + fn[ppos:]
  609.                 if os.path.isdir(fn) and not os.path.islink(fn):
  610.                     if os.path.isdir(fn2):
  611.                         continue
  612.                     os.makedirs(fn2)
  613.                     linked_files.append(fn2)
  614.                 else:
  615.                     if os.path.exists(fn2):
  616.                         msg = "already exists: %s" % fn2
  617.                         if os.path.islink(fn2):
  618.                             link = os.readlink(fn2)
  619.                             if link == fn:
  620.                                 linked_files.append(fn2)
  621.                                 continue
  622.                             msg = msg + " -> %s" % link
  623.                         conf = get_debian_config()
  624.                         if conf.get('DEFAULT', 'overwrite-local') == '1':
  625.                             print "warning:", msg
  626.                             os.unlink(fn2)
  627.                         else:
  628.                             raise PyCentralError, msg
  629.                     # make sure that fn2 really does not exist; this is a
  630.                     # special hack to make pycentral work with fakechroot,
  631.                     # which has a slightly weird treatment of symlinks
  632.                     try:
  633.                         os.unlink(fn2)
  634.                     except OSError:
  635.                         pass
  636.                     link_cmd(fn, fn2)
  637.                     linked_files.append(fn2)
  638.         except PyCentralError, msg:
  639.             raise
  640.         except Exception, msg:
  641.             print msg
  642.             # FIXME: undo
  643.             linked_files.reverse()
  644.             return []
  645.         else:
  646.             return linked_files
  647.  
  648.     def unlink_shared_files(self, rt):
  649.         logging.debug('\tunlink_shared_files %s/%s' % (rt.name, self.name))
  650.         ppos = len(self.shared_prefix)
  651.         shared_files = self.shared_files[:]
  652.         shared_files.reverse()
  653.         for fn in shared_files:
  654.             fn2 = rt.prefix + fn[ppos:]
  655.             if os.path.isdir(fn2) and not os.path.islink(fn2):
  656.                 try:
  657.                     os.removedirs(fn2)
  658.                 except OSError:
  659.                     pass
  660.             else:
  661.                 if os.path.exists(fn2):
  662.                     os.unlink(fn2)
  663.  
  664.  
  665.     def install(self, runtimes, bc_option, exclude_regex,
  666.                 byte_compile_default=True, ignore_errors=False):
  667.         logging.debug('\tinstall package %s' % self.name)
  668.         # install shared .py files
  669.         if self.shared_files:
  670.             for rt in runtimes:
  671.                 linked_files = self.link_shared_files(rt)
  672.                 rt.byte_compile(linked_files, bc_option, exclude_regex, ignore_errors)
  673.         # byte compile files inside prefix
  674.         if self.pylib_files:
  675.             for pyver, files in self.pylib_files.items():
  676.                 rt = get_runtime_for_version(pyver)
  677.                 if rt in runtimes:
  678.                     rt.byte_compile(files, bc_option, exclude_regex, ignore_errors)
  679.         # byte compile with the default runtime for the package
  680.         if byte_compile_default:
  681.             if self.private_files:
  682.                 self.default_runtime.byte_compile(self.private_files, bc_option,
  683.                                                   exclude_regex, ignore_errors)
  684.  
  685.     def remove(self, runtimes, remove_script_files=True):
  686.         logging.debug('\tremove package %s' % self.name)
  687.         ppos = len(self.shared_prefix)
  688.         # remove shared .py files
  689.         if self.shared_files:
  690.             for rt in runtimes:
  691.                 linked_files = [ rt.prefix + fn[ppos:]
  692.                                  for fn in self.shared_files
  693.                                  if fn[-3:] == '.py']
  694.                 #print self.shared_files
  695.                 #print linked_files
  696.                 rt.remove_byte_code(linked_files)
  697.                 self.unlink_shared_files(rt)
  698.         # remove byte compiled files inside prefix
  699.         if self.pylib_files:
  700.             for pyver, files in self.pylib_files.items():
  701.                 rt = get_runtime_for_version(pyver)
  702.                 if rt in runtimes:
  703.                     rt.remove_byte_code(files)
  704.         # remove byte code for script files
  705.         if remove_script_files:
  706.             if self.private_files:
  707.                 default_runtime.remove_byte_code(self.private_files)
  708.  
  709.     def update_bytecode_files(self, runtimes, rt_default, bc_option):
  710.         # byte-compile with default python version
  711.         logging.debug('\tupdate byte-code for %s' % self.name)
  712.         ppos = len(self.shared_prefix)
  713.         exclude_regex = None
  714.         # update shared .py files
  715.         if self.shared_files:
  716.             for rt in runtimes:
  717.                 if rt == rt_default:
  718.                     linked_files = self.link_shared_files(rt)
  719.                     rt.byte_compile(linked_files, bc_option, exclude_regex)
  720.                 else:
  721.                     linked_files = [ rt.prefix + fn[ppos:]
  722.                                      for fn in self.shared_files
  723.                                      if fn[-3:] == '.py']
  724.                     rt.remove_byte_code(linked_files)
  725.                     self.unlink_shared_files(rt)
  726.         # byte compile with the default runtime for the package
  727.         if self.private_files:
  728.             self.default_runtime.byte_compile(self.private_files,
  729.                                               bc_option, exclude_regex)
  730.  
  731. known_actions = {}
  732. def register_action(action_class):
  733.     known_actions[action_class.name] = action_class
  734.  
  735. class Action:
  736.     _option_parser = None
  737.     name = None
  738.     help = ""
  739.     usage = "<options>"
  740.     def __init__(self):
  741.         self.errors_occured = 0
  742.         parser = self.get_option_parser()
  743.         parser.set_usage(
  744.             'usage: %s [<options> ...] %s %s' % (program, self.name, self.usage))
  745.  
  746.     def get_option_parser(self):
  747.         if not self._option_parser:
  748.             p = OptionParser()
  749.             self._option_parser = p
  750.         return self._option_parser
  751.  
  752.     def info(self, msg, stream=sys.stderr):
  753.         logging.info('%s %s: %s' % (program, self.name, msg))
  754.  
  755.     def warn(self, msg, stream=sys.stderr):
  756.         logging.warn('%s %s: %s' % (program, self.name, msg))
  757.  
  758.     def error(self, msg, stream=sys.stderr, go_on=False):
  759.         logging.error('%s %s: %s' % (program, self.name, msg))
  760.         self.errors_occured += 1
  761.         if not go_on:
  762.             sys.exit(1)
  763.  
  764.     def parse_args(self, arguments):
  765.         self.options, self.args = self._option_parser.parse_args(arguments)
  766.         return self.options, self.args
  767.  
  768.     def check_args(self, global_options):
  769.         return self.errors_occured
  770.  
  771.     def run(self, global_opts):
  772.         pass
  773.  
  774.  
  775. class ActionByteCompile(Action):
  776.     """byte compile the *.py files in <package> using the the
  777.     default python version (or use the version specified with -v.
  778.     Any additional directory arguments are ignored (only files
  779.     found in the package are byte compiled. Files in
  780.     /usr/lib/pythonX.Y are compiled with the matching python version.
  781.  
  782.     bccompile is a replacement for the current byte compilation
  783.     generated by the dh_python debhelper script.
  784.     """
  785.     name = 'bccompile'
  786.     help = 'byte compile .py files in a package'
  787.     usage = '[<options>] <package> [<dir> ...]'
  788.  
  789.     def get_option_parser(self):
  790.         if not self._option_parser:
  791.             p = OptionParser()
  792.             p.add_option('-x', '--exclude',
  793.                          help="skip files matching the regular expression",
  794.                          default=None, action='store', dest='exclude')
  795.             p.add_option('-V', '--version',
  796.                          help="byte compile using this python version",
  797.                          default='current', action='store', dest='version')
  798.             self._option_parser = p
  799.         return self._option_parser
  800.  
  801.     def check_args(self, global_options):
  802.         if len(self.args) < 1:
  803.             self._option_parser.print_help()
  804.             sys.exit(1)
  805.         self.pkgname = self.args[0]
  806.         self.runtime = get_runtime_for_version(self.options.version)
  807.         if not self.runtime:
  808.             self.error("unknown runtime version %s" % self.options.version)
  809.  
  810.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  811.             self.error("package %s is not installed" % self.pkgname)
  812.         self.pkg = DebPackage('package', self.pkgname, oldstyle=True,
  813.                               default_runtime=self.runtime)
  814.  
  815.         try:
  816.             self.pkg.read_version_info()
  817.         except PyCentralError:
  818.             self.warn('package with `Python-Version information should'
  819.                       ' use pkginstall instead of bccompile')
  820.             #self.version_field = 'current'
  821.         return self.errors_occured
  822.  
  823.     def run(self, global_options):
  824.         logging.debug('bccompile %s' % self.pkgname)
  825.         config = get_debian_config()
  826.         bc_option = config.get('DEFAULT', 'byte-compile')
  827.  
  828.         # called with directories as arguments
  829.         if 0 and self.directories:
  830.             try:
  831.                 for version, dirs in self.pylib_dirs.items():
  832.                     rt = get_runtime_for_version(version)
  833.                     rt.byte_compile_dirs(dirs, bc_option, self.options.exclude)
  834.                 if self.private_dirs:
  835.                     version = pkg.version_field
  836.                     if version == 'current':
  837.                         version = pyversions.default_version(version_only=True)
  838.                     rt = get_runtime_for_version(version)
  839.                     rt.byte_compile_dirs(private_dirs, bc_option, self.options.exclude)
  840.             except PyCentralError:
  841.                 self.error("error byte-compiling package `%s'" % self.pkgname)
  842.             return
  843.  
  844.         try:
  845.             self.pkg.byte_compile(bc_option, self.options.exclude)
  846.         except PyCentralError:
  847.             self.error("error byte-compiling package `%s'" % self.pkgname)
  848.  
  849. register_action(ActionByteCompile)
  850.  
  851. class ActionPkgInstall(Action):
  852.     name = 'pkginstall'
  853.     help = 'make a package available for all supported runtimes'
  854.     usage = '[<options>] <package>'
  855.     
  856.     def get_option_parser(self):
  857.         if not self._option_parser:
  858.             p = OptionParser()
  859.             p.add_option('-x', '--exclude',
  860.                          help="skip files matching the regular expression",
  861.                          default=None, action='store', dest='exclude')
  862.             self._option_parser = p
  863.         return self._option_parser
  864.  
  865.     def check_args(self, global_options):
  866.         if len(self.args) != 1:
  867.             self._option_parser.print_help()
  868.             sys.exit(1)
  869.         self.pkgname = self.args[0]
  870.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  871.             self.error("package %s is not installed" % self.pkgname)
  872.         return self.errors_occured
  873.  
  874.     def run(self, global_options):
  875.         runtimes = get_installed_runtimes()
  876.         config = get_debian_config()
  877.         bc_option = config.get('DEFAULT', 'byte-compile')
  878.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  879.         pkg.read_version_info()
  880.         requested = pyversions.requested_versions_for_runtime(pkg.version_field, version_only=True)
  881.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  882.         try:
  883.             pkg.set_default_runtime_from_version_info()
  884.         except ValueError:
  885.         # Package doesn't provide support for any supported runtime
  886.         if len(used_runtimes) == 0:
  887.         self.error('%s needs unavailable runtime (%s)'
  888.                % (self.pkgname, pkg.version_field))
  889.         else:
  890.         # Still byte compile for the available runtimes (with the
  891.         # first matching runtime)
  892.          pkg.default_runtime = get_runtime_for_version(used_runtimes[0])
  893.         logging.debug('\tavail=%s, pkg=%s, install=%s'
  894.                       % ([rt.short_name for rt in runtimes],
  895.                          pkg.version_field,
  896.                          [rt.short_name for rt in used_runtimes]))
  897.         try:
  898.             pkg.install(used_runtimes, bc_option,
  899.                         self.options.exclude, byte_compile_default=True)
  900.         except PyCentralError, msg:
  901.             self.error(msg)
  902.             
  903.  
  904. register_action(ActionPkgInstall)
  905.  
  906.  
  907. class ActionBCRemove(Action):
  908.     """remove the byte-compiled files in <package>.
  909.  
  910.     bccompile is a replacement for the current byte compilation
  911.     generated by the dh_python debhelper script.
  912.     """
  913.     name = 'bcremove'
  914.     help = 'remove the byte compiled .py files'
  915.     usage = '<package>'
  916.  
  917.     def check_args(self, global_options):
  918.         if len(self.args) != 1:
  919.             self._option_parser.print_help()
  920.             sys.exit(1)
  921.         self.pkgname = self.args[0]
  922.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  923.             self.error("package %s is not installed" % self.pkgname)
  924.         return self.errors_occured
  925.  
  926.     def run(self, global_options):
  927.         pkg = DebPackage('package', self.args[0], oldstyle=True)
  928.         try:
  929.             pkg.remove_bytecode()
  930.         except PyCentralError, msg:
  931.             self.error(msg)
  932.  
  933. register_action(ActionBCRemove)
  934.  
  935.  
  936. class ActionPkgRemove(Action):
  937.     """
  938.     """
  939.     name = 'pkgremove'
  940.     help = 'remove a package installed for all supported runtimes'
  941.     usage = '<package>'
  942.  
  943.     def check_args(self, global_options):
  944.         if len(self.args) != 1:
  945.             self._option_parser.print_help()
  946.             sys.exit(1)
  947.         self.pkgname = self.args[0]
  948.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  949.             self.error("package %s is not installed" % self.pkgname)
  950.         return self.errors_occured
  951.  
  952.     def run(self, global_options):
  953.         runtimes = get_installed_runtimes(with_unsupported=True)
  954.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  955.         pkg.read_version_info()
  956.         try:
  957.             pkg.set_default_runtime_from_version_info()
  958.         except ValueError:
  959.             # original runtime is already removed, use the default for removal
  960.             pkg.default_runtime = get_default_runtime()
  961.         try:
  962.             pkg.remove(runtimes, remove_script_files=True)
  963.         except PyCentralError, msg:
  964.             self.error(msg)
  965.  
  966. register_action(ActionPkgRemove)
  967.  
  968.  
  969. class ActionRuntimeInstall(Action):
  970.     name = 'rtinstall'
  971.     help = 'make installed packages available for this runtime'
  972.  
  973.     def get_option_parser(self):
  974.         if not self._option_parser:
  975.             p = OptionParser()
  976.             p.add_option('-i', '--ignore-errors',
  977.                          help="ignore errors during byte compilations",
  978.                          default=None, action='store', dest='ignore')
  979.             self._option_parser = p
  980.         return self._option_parser
  981.  
  982.     def check_args(self, global_options):
  983.         if len(self.args) != 1:
  984.             self._option_parser.print_help()
  985.             sys.exit(1)
  986.         self.rtname = self.args[0]
  987.         if self.rtname[-8:] == '-minimal':
  988.             self.rtname = self.rtname[:-8]
  989.         self.runtime = None
  990.         for rt in get_installed_runtimes():
  991.             if rt.name == self.rtname:
  992.                 self.runtime = rt
  993.                 break
  994.         if not self.runtime:
  995.             self.error('installed runtime %s not found' % self.rtname)
  996.         return self.errors_occured
  997.  
  998.     def run(self, global_options):
  999.         packages = [(p, v) for p, v in read_dpkg_status()
  1000.                     if not p in (self.rtname, self.rtname+'-minimal')]
  1001.         needed_packages = []
  1002.         for pkgname, vstring in packages:
  1003.             try:
  1004.                 requested = list(pyversions.requested_versions(vstring, version_only=True))
  1005.             except ValueError:
  1006.                 logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
  1007.                 continue
  1008.             if self.runtime.short_name in requested:
  1009.                 needed_packages.append((pkgname, vstring, requested))
  1010.         logging.info('\t%d packages with Python-Version info installed, %d for %s'
  1011.                      % (len(packages), len(needed_packages), self.rtname))
  1012.  
  1013.         # XXX not needed for an upgrade of a runtime
  1014.         byte_compile_for_default = (self.runtime == default_runtime)
  1015.  
  1016.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  1017.         for pkgname, vstring, vinfo in needed_packages:
  1018.             try:
  1019.                 logging.info('\tsupport %s for %s' % (pkgname, self.rtname))
  1020.                 pkg = DebPackage('package', pkgname, oldstyle=False)
  1021.                 pkg.read_version_info()
  1022.                 try:
  1023.                     pkg.set_default_runtime_from_version_info()
  1024.                 except ValueError:
  1025.                     logging.warn('\t%s not available for %s (%s)'
  1026.                                  % (pkgname, self.rtname, pkg.version_field))
  1027.                 pkg.install([self.runtime], bc_option, None,
  1028.                             byte_compile_for_default,
  1029.                             ignore_errors = self.options.ignore != None)
  1030.             except PyCentralError, msg:
  1031.                 self.error('package %s: %s' % (pkgname, msg))
  1032.  
  1033. register_action(ActionRuntimeInstall)
  1034.  
  1035. class ActionRuntimeRemove(Action):
  1036.     name = 'rtremove'
  1037.     help = 'remove packages installed for this runtime'
  1038.  
  1039.     def check_args(self, global_options):
  1040.         if len(self.args) != 1:
  1041.             self._option_parser.print_help()
  1042.             sys.exit(1)
  1043.         self.rtname = self.args[0]
  1044.         if self.rtname[-8:] == '-minimal':
  1045.             self.rtname = self.rtname[:-8]
  1046.         self.runtime = None
  1047.         for rt in get_installed_runtimes(with_unsupported=True):
  1048.             if rt.name == self.rtname:
  1049.                 self.runtime = rt
  1050.                 break
  1051.         if not self.runtime:
  1052.             self.error('installed runtime %s not found' % self.rtname)
  1053.         return self.errors_occured
  1054.  
  1055.     def run(self, global_options):
  1056.         packages = [(p, v) for p, v in read_dpkg_status()
  1057.                     if not p in (self.rtname, self.rtname+'-minimal')]
  1058.         needed_packages = []
  1059.         for pkgname, vstring in packages:
  1060.             try:
  1061.                 requested = list(pyversions.requested_versions(vstring, version_only=True))
  1062.             except ValueError:
  1063.                 logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
  1064.                 continue
  1065.             if self.runtime.short_name in requested:
  1066.                 needed_packages.append((pkgname, vstring, requested))
  1067.         logging.info('\t%d pycentral supported packages installed, %d for %s'
  1068.                      % (len(packages), len(needed_packages), self.rtname))
  1069.         try:
  1070.             for pkgname, vstring, vinfo in needed_packages:
  1071.                 logging.info('\trtremove: remove package %s for %s' % (pkgname, self.rtname))
  1072.                 pkg = DebPackage('package', pkgname)
  1073.                 pkg.read_version_info()
  1074.                 try:
  1075.                     pkg.set_default_runtime_from_version_info()
  1076.                 except ValueError:
  1077.                     # original runtime is already removed, use the default for removal
  1078.                     pkg.default_runtime = get_default_runtime()
  1079.                 pkg.remove([self.runtime], remove_script_files=False)
  1080.         except PyCentralError, msg:
  1081.             self.error(msg)
  1082.  
  1083. register_action(ActionRuntimeRemove)
  1084.  
  1085.  
  1086. class ActionUpdateDefault(Action):
  1087.     name = 'updatedefault'
  1088.     help = 'update the default python version'
  1089.     usage = '<old runtime> <new runtime>'
  1090.     
  1091.     def check_args(self, global_options):
  1092.         if len(self.args) != 2:
  1093.             self._option_parser.print_help()
  1094.             sys.exit(1)
  1095.         self.oldrtname = self.args[0]
  1096.         self.rtname = self.args[1]
  1097.         packages = read_dpkg_status()
  1098.         self.needed_packages = []
  1099.         for pkgname, vstring in packages:
  1100.             if vstring.find('current') == -1:
  1101.                 continue
  1102.             try:
  1103.                 versions = pyversions.requested_versions(vstring, version_only=True)
  1104.             except ValueError:
  1105.                 self.error("package %s is not ready to be updated for %s"
  1106.                            % (pkgname, self.runtime.name))
  1107.                 continue
  1108.             pkg = DebPackage('package', pkgname)
  1109.             self.needed_packages.append(pkg)
  1110.         return self.errors_occured
  1111.  
  1112.     def run(self, global_options):
  1113.         logging.info('\tupdate default: update %d packages for %s'
  1114.                      % (len(self.needed_packages), self.rtname))
  1115.         runtimes = get_installed_runtimes()
  1116.         default_rt = get_default_runtime()
  1117.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  1118.         try:
  1119.             for pkg in self.needed_packages:
  1120.                 pkg.read_version_info()
  1121.                 pkg.set_default_runtime_from_version_info()
  1122.                 if pkg.shared_files or pkg.private_files:
  1123.                     pkg.update_bytecode_files(runtimes, default_rt, bc_option)
  1124.         except PyCentralError, msg:
  1125.             self.error(msg)
  1126.  
  1127. register_action(ActionUpdateDefault)
  1128.  
  1129.  
  1130. class ActionShowDefault(Action):
  1131.     name = 'showdefault'
  1132.     help = 'Show default python version number'
  1133.  
  1134.     def check_args(self, global_options):
  1135.         if len(self.args) != 0:
  1136.             self._option_parser.print_help()
  1137.             sys.exit(1)
  1138.         return self.errors_occured
  1139.  
  1140.     def run(self, global_options):
  1141.         print pyversions.default_version(version_only=True)
  1142.         sys.stderr.write("pycentral showdefault is deprecated, use `pyversions -vd'\n")
  1143.  
  1144. register_action(ActionShowDefault)
  1145.  
  1146.  
  1147. class ActionShowVersions(Action):
  1148.     name = 'showversions'
  1149.     help = 'Show version numbers of supported python versions'
  1150.  
  1151.     def check_args(self, global_options):
  1152.         if len(self.args) != 0:
  1153.             self._option_parser.print_help()
  1154.             sys.exit(1)
  1155.  
  1156.         return self.errors_occured
  1157.  
  1158.     def run(self, global_options):
  1159.         supported = pyversions.supported_versions()
  1160.         versions = [d[6:] for d in supported if re.match(r'python\d\.\d', d)]
  1161.         print ' '.join(versions)
  1162.         sys.stderr.write("pycentral showversions is deprecated, use `pyversions -vs'\n")
  1163.  
  1164. register_action(ActionShowVersions)
  1165.  
  1166. class ActionShowSupported(Action):
  1167.     name = 'showsupported'
  1168.     help = 'Show the supported python versions'
  1169.  
  1170.     def check_args(self, global_options):
  1171.         if len(self.args) != 0:
  1172.             self._option_parser.print_help()
  1173.             sys.exit(1)
  1174.         return self.errors_occured
  1175.  
  1176.     def run(self, global_options):
  1177.         supported = pyversions.supported_versions()
  1178.         print ' '.join(supported)
  1179.         sys.stderr.write("pycentral showsupported is deprecated, use `pyversions -s'\n")
  1180.  
  1181. register_action(ActionShowSupported)
  1182.  
  1183.  
  1184. class ActionPyCentralDir(Action):
  1185.     name = 'pycentraldir'
  1186.     help = 'Show the pycentral installation directory for the package'
  1187.     usage = '<package>'
  1188.  
  1189.     def check_args(self, global_options):
  1190.         if len(self.args) != 1:
  1191.             self._option_parser.print_help()
  1192.             sys.exit(1)
  1193.         self.pkgname = self.args[0]
  1194.         return self.errors_occured
  1195.  
  1196.     def run(self, global_options):
  1197.         print os.path.join(shared_base, self.pkgname)
  1198.  
  1199. register_action(ActionPyCentralDir)
  1200.  
  1201. class ActionDebhelper(Action):
  1202.     name = 'debhelper'
  1203.     help = 'move files to pycentral location, variable substitutions'
  1204.     usage = '[-p|--provides] [--no-move] <package> [<package directory>]'
  1205.  
  1206.     def get_option_parser(self):
  1207.         if not self._option_parser:
  1208.             envvar = os.environ.get('DH_PYCENTRAL', '')
  1209.             substvars_default = 'no'
  1210.             if 'substvars=file' in envvar:
  1211.                 substvars_default = 'file'
  1212.             if 'substvars=stdout' in envvar:
  1213.                 substvars_default = 'stdout'
  1214.  
  1215.             p = OptionParser()
  1216.             p.add_option('-p', '--provides',
  1217.                          help="generate substitution for python:Provides",
  1218.                          default='add-provides' in envvar, action='store_true', dest='provides')
  1219.             p.add_option('--no-move', '--nomove',
  1220.                          help="do not move files to pycentral location",
  1221.                          default='no-move' in envvar or 'nomove' in envvar, action='store_true', dest='nomove')
  1222.             p.add_option('--stdout',
  1223.                          help="just print substitution variables to stdout",
  1224.                          default='stdout' in envvar, action='store_true', dest='stdout')
  1225.             p.add_option('--substvars',
  1226.                          help="where to print substitution vars (no, file, stdout)",
  1227.                          default=substvars_default, dest='substvars')
  1228.             p.add_option('--no-act', '--dry-run',
  1229.                          help="dry run",
  1230.                          default=('dry-run' in envvar) or ('no-act' in envvar),
  1231.                          action='store_true', dest='dryrun')
  1232.             self._option_parser = p
  1233.         return self._option_parser
  1234.  
  1235.     def check_args(self, global_options):
  1236.         if not len(self.args) in (1, 2):
  1237.             self._option_parser.print_help()
  1238.             sys.exit(1)
  1239.         if 'file' in self.options.substvars:
  1240.             self.options.substvars = 'file'
  1241.         if 'stdout' in self.options.substvars:
  1242.             self.options.substvars = 'stdout'
  1243.         return self.errors_occured
  1244.  
  1245.     def run(self, global_options):
  1246.         if len(self.args) < 2:
  1247.             pkgdir = 'debian/' + self.args[0]
  1248.         else:
  1249.             pkgdir = self.args[1]
  1250.         try:
  1251.             pkg = DebPackage('package', self.args[0], pkgdir=pkgdir,
  1252.                              parse_versions=self.options.substvars!='no')
  1253.             if not self.options.nomove:
  1254.                 pkg.move_files()
  1255.             pkg.read_pyfiles()
  1256.             if self.options.substvars!='no':
  1257.                 pkg.gen_substvars()
  1258.         except PyCentralVersionMissingError, msg:
  1259.             self.warn(msg)
  1260.             return
  1261.         except PyCentralError, msg:
  1262.             self.error(msg)
  1263.  
  1264.         out = None
  1265.         if self.options.stdout or self.options.substvars == 'stdout':
  1266.             out = sys.stdout
  1267.         elif self.options.substvars == 'file':
  1268.             out = file('debian/%s.substvars' % pkg.name, 'a+')
  1269.         if out:
  1270.             out.write('python:Versions=%s\n' % pkg.version_field)
  1271.             out.write('python:Depends=%s\n' % pkg.depends)
  1272.             out.write('python:Provides=%s\n' % pkg.provides)
  1273.  
  1274.  
  1275. register_action(ActionDebhelper)
  1276.  
  1277. # match a string with the list of available actions
  1278. def action_matches(action, actions):
  1279.     prog = re.compile('[^-]*?-'.join(action.split('-')))
  1280.     return [a for a in actions if prog.match(a)]
  1281.  
  1282. def usage(stream, msg=None):
  1283.     print >>stream, msg
  1284.     print >>stream, "use `%s help' for help on actions and arguments" % program
  1285.     print >>stream
  1286.     sys.exit(1)
  1287.  
  1288. # parse command line arguments
  1289. def parse_options(args):
  1290.     shortusage = 'usage: %s [<option> ...] <action> <pkgname>' % program
  1291.     parser = OptionParser(usage=shortusage)
  1292.     parser.disable_interspersed_args()
  1293.  
  1294.     # setup the parsers object
  1295.     parser.remove_option('-h')
  1296.     parser.add_option('-h', '--help',
  1297.                       help='help screen',
  1298.                       action='store_true', dest='help')
  1299.     parser.add_option('-v', '--verbose',
  1300.                       help='verbose mode',
  1301.                       action='store_true', dest='verbose')
  1302.  
  1303.     global_options, args = parser.parse_args()
  1304.     # Print the help screen and exit
  1305.     if len(args) == 0 or global_options.help:
  1306.         parser.print_help()
  1307.         print "\nactions:"
  1308.         action_names = known_actions.keys()
  1309.         action_names.sort()
  1310.         for n in action_names:
  1311.             print "  %-21s %s" % (n, known_actions[n].help)
  1312.         print ""
  1313.         sys.exit(1)
  1314.  
  1315.     # check if the specified action really exists
  1316.     action_name = args[0]
  1317.     del args[0]
  1318.     matching_actions = action_matches(action_name, known_actions.keys())
  1319.     if len(matching_actions) == 0:
  1320.         usage(sys.stderr, "unknown action `%s'" % action_name)
  1321.     elif len(matching_actions) > 1:
  1322.         usage(sys.stderr,
  1323.               "ambiguous action `%s', matching actions: %s"
  1324.               % (action_name, strlist(matching_actions)))
  1325.     else:
  1326.         action_name = matching_actions[0]
  1327.  
  1328.     # instantiate an object for the action and parse the remaining arguments
  1329.     action = known_actions[action_name]()
  1330.     action_options, action_names = action.parse_args(args)
  1331.  
  1332.     return global_options, action
  1333.  
  1334. class Logging:
  1335.     DEBUG, INFO, WARN, ERROR = range(4)
  1336.     def __init__(self, level=WARN):
  1337.         self.fd = None
  1338.         self.level = level
  1339.         try:
  1340.             self.fd = file('/var/log/pycentral.log', 'a+')
  1341.         except IOError:
  1342.             self.fd = None
  1343.     def msg(self, level, s):
  1344.         if level < self.level:
  1345.             return
  1346.         d = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  1347.         if self.fd:
  1348.             self.fd.write('%s %s %s\n' % (d, level, s))
  1349.             self.fd.flush()
  1350.         sys.stdout.write('pycentral: %s\n' % (s))
  1351.         sys.stdout.flush()
  1352.     def info(self, s):
  1353.         self.msg(self.INFO, s)
  1354.     def warn(self, s):
  1355.         self.msg(self.WARN, s)
  1356.     def error(self, s):
  1357.         self.msg(self.ERROR, s)
  1358.         sys.stderr.write('%s\n' % s)
  1359.     def debug(self, s):
  1360.         self.msg(self.DEBUG, s)
  1361.  
  1362. def setup_logging(loglevel=Logging.WARN, verbose=False):
  1363.     levels = ['debug', 'info', 'warn', 'error']
  1364.     env_level = os.environ.get('PYCENTRAL', 'warn').lower()
  1365.     for i in range(len(levels)):
  1366.         if env_level.find(levels[i]) != -1:
  1367.             loglevel = i
  1368.     if verbose:
  1369.         loglevel = Logging.DEBUG
  1370.     global logging
  1371.     logging = Logging(loglevel)
  1372.  
  1373. def main():
  1374.     global_options, action = parse_options(sys.argv[1:])
  1375.  
  1376.     # setup logging stuff
  1377.     setup_logging(Logging.WARN, global_options.verbose)
  1378.     if action.name == 'debhelper' or action.name.startswith('show'):
  1379.         pass
  1380.     else:
  1381.         logging.debug('pycentral ' + ' '.join(sys.argv[1:]))
  1382.  
  1383.     # check the arguments according to the action called
  1384.     if action.check_args(global_options):
  1385.         sys.exit(1)
  1386.  
  1387.     # run the action and exit
  1388.     rv = action.run(global_options)
  1389.     sys.exit(rv)
  1390.  
  1391.  
  1392. # call the main routine
  1393. if __name__ == '__main__':
  1394.     main()
  1395.