llmanifest.py
上传用户:king477883
上传日期:2021-03-01
资源大小:9553k
文件大小:25k
源码类别:

游戏引擎

开发平台:

C++ Builder

  1. """
  2. @file llmanifest.py
  3. @author Ryan Williams
  4. @brief Library for specifying operations on a set of files.
  5. $LicenseInfo:firstyear=2007&license=mit$
  6. Copyright (c) 2007-2010, Linden Research, Inc.
  7. Permission is hereby granted, free of charge, to any person obtaining a copy
  8. of this software and associated documentation files (the "Software"), to deal
  9. in the Software without restriction, including without limitation the rights
  10. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. copies of the Software, and to permit persons to whom the Software is
  12. furnished to do so, subject to the following conditions:
  13. The above copyright notice and this permission notice shall be included in
  14. all copies or substantial portions of the Software.
  15. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. THE SOFTWARE.
  22. $/LicenseInfo$
  23. """
  24. import commands
  25. import errno
  26. import filecmp
  27. import fnmatch
  28. import getopt
  29. import glob
  30. import os
  31. import re
  32. import shutil
  33. import sys
  34. import tarfile
  35. import errno
  36. def path_ancestors(path):
  37.     drive, path = os.path.splitdrive(os.path.normpath(path))
  38.     result = []
  39.     while len(path) > 0 and path != os.path.sep:
  40.         result.append(drive+path)
  41.         path, sub = os.path.split(path)
  42.     return result
  43. def proper_windows_path(path, current_platform = sys.platform):
  44.     """ This function takes an absolute Windows or Cygwin path and
  45.     returns a path appropriately formatted for the platform it's
  46.     running on (as determined by sys.platform)"""
  47.     path = path.strip()
  48.     drive_letter = None
  49.     rel = None
  50.     match = re.match("/cygdrive/([a-z])/(.*)", path)
  51.     if not match:
  52.         match = re.match('([a-zA-Z]):\(.*)', path)
  53.     if not match:
  54.         return None         # not an absolute path
  55.     drive_letter = match.group(1)
  56.     rel = match.group(2)
  57.     if current_platform == "cygwin":
  58.         return "/cygdrive/" + drive_letter.lower() + '/' + rel.replace('\', '/')
  59.     else:
  60.         return drive_letter.upper() + ':\' + rel.replace('/', '\')
  61. def get_default_platform(dummy):
  62.     return {'linux2':'linux',
  63.             'linux1':'linux',
  64.             'cygwin':'windows',
  65.             'win32':'windows',
  66.             'darwin':'darwin'
  67.             }[sys.platform]
  68. def get_default_version(srctree):
  69.     # look up llversion.h and parse out the version info
  70.     paths = [os.path.join(srctree, x, 'llversionviewer.h') for x in ['llcommon', '../llcommon', '../../indra/llcommon.h']]
  71.     for p in paths:
  72.         if os.path.exists(p):
  73.             contents = open(p, 'r').read()
  74.             major = re.search("LL_VERSION_MAJORs=s([0-9]+)", contents).group(1)
  75.             minor = re.search("LL_VERSION_MINORs=s([0-9]+)", contents).group(1)
  76.             patch = re.search("LL_VERSION_PATCHs=s([0-9]+)", contents).group(1)
  77.             build = re.search("LL_VERSION_BUILDs=s([0-9]+)", contents).group(1)
  78.             return major, minor, patch, build
  79. def get_channel(srctree):
  80.     # look up llversionserver.h and parse out the version info
  81.     paths = [os.path.join(srctree, x, 'llversionviewer.h') for x in ['llcommon', '../llcommon', '../../indra/llcommon.h']]
  82.     for p in paths:
  83.         if os.path.exists(p):
  84.             contents = open(p, 'r').read()
  85.             channel = re.search("LL_CHANNELs=s"(.+)";s*$", contents, flags = re.M).group(1)
  86.             return channel
  87.     
  88. DEFAULT_SRCTREE = os.path.dirname(sys.argv[0])
  89. DEFAULT_CHANNEL = 'Second Life Release'
  90. DEFAULT_CHANNEL_SNOWGLOBE = 'Snowglobe Release'
  91. ARGUMENTS=[
  92.     dict(name='actions',
  93.          description="""This argument specifies the actions that are to be taken when the
  94.         script is run.  The meaningful actions are currently:
  95.           copy     - copies the files specified by the manifest into the
  96.                      destination directory.
  97.           package  - bundles up the files in the destination directory into
  98.                      an installer for the current platform
  99.           unpacked - bundles up the files in the destination directory into
  100.                      a simple tarball
  101.         Example use: %(name)s --actions="copy unpacked" """,
  102.          default="copy package"),
  103.     dict(name='arch',
  104.          description="""This argument is appended to the platform string for
  105.         determining which manifest class to run.
  106.         Example use: %(name)s --arch=i686
  107.         On Linux this would try to use Linux_i686Manifest.""",
  108.          default=""),
  109.     dict(name='build', description='Build directory.', default=DEFAULT_SRCTREE),
  110.     dict(name='buildtype', description='Build type (i.e. Debug, Release, RelWithDebInfo).', default=None),
  111.     dict(name='branding_id', description="""Identifier for the branding set to 
  112.         use.  Currently, 'secondlife' or 'snowglobe')""", 
  113.          default='secondlife'),
  114.     dict(name='configuration',
  115.          description="""The build configuration used.""",
  116.          default="Release"),
  117.     dict(name='dest', description='Destination directory.', default=DEFAULT_SRCTREE),
  118.     dict(name='grid',
  119.          description="""Which grid the client will try to connect to. Even
  120.         though it's not strictly a grid, 'firstlook' is also an acceptable
  121.         value for this parameter.""",
  122.          default=""),
  123.     dict(name='channel',
  124.          description="""The channel to use for updates, packaging, settings name, etc.""",
  125.          default=get_channel),
  126.     dict(name='login_channel',
  127.          description="""The channel to use for login handshake/updates only.""",
  128.          default=None),
  129.     dict(name='installer_name',
  130.          description=""" The name of the file that the installer should be
  131.         packaged up into. Only used on Linux at the moment.""",
  132.          default=None),
  133.     dict(name='login_url',
  134.          description="""The url that the login screen displays in the client.""",
  135.          default=None),
  136.     dict(name='platform',
  137.          description="""The current platform, to be used for looking up which
  138.         manifest class to run.""",
  139.          default=get_default_platform),
  140.     dict(name='source',
  141.          description='Source directory.',
  142.          default=DEFAULT_SRCTREE),
  143.     dict(name='artwork', description='Artwork directory.', default=DEFAULT_SRCTREE),
  144.     dict(name='touch',
  145.          description="""File to touch when action is finished. Touch file will
  146.         contain the name of the final package in a form suitable
  147.         for use by a .bat file.""",
  148.          default=None),
  149.     dict(name='version',
  150.          description="""This specifies the version of Second Life that is
  151.         being packaged up.""",
  152.          default=get_default_version)
  153.     ]
  154. def usage(srctree=""):
  155.     nd = {'name':sys.argv[0]}
  156.     print """Usage:
  157.     %(name)s [options] [destdir]
  158.     Options:
  159.     """ % nd
  160.     for arg in ARGUMENTS:
  161.         default = arg['default']
  162.         if hasattr(default, '__call__'):
  163.             default = "(computed value) "" + str(default(srctree)) + '"'
  164.         elif default is not None:
  165.             default = '"' + default + '"'
  166.         print "t--%s        Default: %snt%sn" % (
  167.             arg['name'],
  168.             default,
  169.             arg['description'] % nd)
  170. def main():
  171.     option_names = [arg['name'] + '=' for arg in ARGUMENTS]
  172.     option_names.append('help')
  173.     options, remainder = getopt.getopt(sys.argv[1:], "", option_names)
  174.     # convert options to a hash
  175.     args = {'source':  DEFAULT_SRCTREE,
  176.             'artwork': DEFAULT_SRCTREE,
  177.             'build':   DEFAULT_SRCTREE,
  178.             'dest':    DEFAULT_SRCTREE }
  179.     for opt in options:
  180.         args[opt[0].replace("--", "")] = opt[1]
  181.     for k in 'artwork build dest source'.split():
  182.         args[k] = os.path.normpath(args[k])
  183.     print "Source tree:", args['source']
  184.     print "Artwork tree:", args['artwork']
  185.     print "Build tree:", args['build']
  186.     print "Destination tree:", args['dest']
  187.     # early out for help
  188.     if 'help' in args:
  189.         # *TODO: it is a huge hack to pass around the srctree like this
  190.         usage(args['source'])
  191.         return
  192.     # defaults
  193.     for arg in ARGUMENTS:
  194.         if arg['name'] not in args:
  195.             default = arg['default']
  196.             if hasattr(default, '__call__'):
  197.                 default = default(args['source'])
  198.             if default is not None:
  199.                 args[arg['name']] = default
  200.     # fix up version
  201.     if isinstance(args.get('version'), str):
  202.         args['version'] = args['version'].split('.')
  203.         
  204.     # default and agni are default
  205.     if args['grid'] in ['default', 'agni']:
  206.         args['grid'] = ''
  207.     if 'actions' in args:
  208.         args['actions'] = args['actions'].split()
  209.     # debugging
  210.     for opt in args:
  211.         print "Option:", opt, "=", args[opt]
  212.     wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args)
  213.     wm.do(*args['actions'])
  214.     # Write out the package file in this format, so that it can easily be called
  215.     # and used in a .bat file - yeah, it sucks, but this is the simplest...
  216.     touch = args.get('touch')
  217.     if touch:
  218.         fp = open(touch, 'w')
  219.         fp.write('set package_file=%sn' % wm.package_file)
  220.         fp.close()
  221.         print 'touched', touch
  222.     return 0
  223. class LLManifestRegistry(type):
  224.     def __init__(cls, name, bases, dct):
  225.         super(LLManifestRegistry, cls).__init__(name, bases, dct)
  226.         match = re.match("(w+)Manifest", name)
  227.         if match:
  228.            cls.manifests[match.group(1).lower()] = cls
  229. class LLManifest(object):
  230.     __metaclass__ = LLManifestRegistry
  231.     manifests = {}
  232.     def for_platform(self, platform, arch = None):
  233.         if arch:
  234.             platform = platform + '_' + arch
  235.         return self.manifests[platform.lower()]
  236.     for_platform = classmethod(for_platform)
  237.     def __init__(self, args):
  238.         super(LLManifest, self).__init__()
  239.         self.args = args
  240.         self.file_list = []
  241.         self.excludes = []
  242.         self.actions = []
  243.         self.src_prefix = [args['source']]
  244.         self.artwork_prefix = [args['artwork']]
  245.         self.build_prefix = [args['build']]
  246.         self.dst_prefix = [args['dest']]
  247.         self.created_paths = []
  248.         self.package_name = "Unknown"
  249.         
  250.     def default_grid(self):
  251.         return self.args.get('grid', None) == ''
  252.     def default_channel(self):
  253.         return self.args.get('channel', None) == DEFAULT_CHANNEL
  254.     
  255.     def default_channel_for_brand(self):
  256.         if self.viewer_branding_id()=='secondlife':
  257.             return self.args.get('channel', None) == DEFAULT_CHANNEL
  258.         elif self.viewer_branding_id()=="snowglobe":
  259.             return self.args.get('channel', None) == DEFAULT_CHANNEL_SNOWGLOBE
  260.         raise ValueError, "Invalid branding id: " + self.viewer_branding_id()
  261.     def construct(self):
  262.         """ Meant to be overriden by LLManifest implementors with code that
  263.         constructs the complete destination hierarchy."""
  264.         pass # override this method
  265.     def exclude(self, glob):
  266.         """ Excludes all files that match the glob from being included
  267.         in the file list by path()."""
  268.         self.excludes.append(glob)
  269.     def prefix(self, src='', build=None, dst=None):
  270.         """ Pushes a prefix onto the stack.  Until end_prefix is
  271.         called, all relevant method calls (esp. to path()) will prefix
  272.         paths with the entire prefix stack.  Source and destination
  273.         prefixes can be different, though if only one is provided they
  274.         are both equal.  To specify a no-op, use an empty string, not
  275.         None."""
  276.         if dst is None:
  277.             dst = src
  278.         if build is None:
  279.             build = src
  280.         self.src_prefix.append(src)
  281.         self.artwork_prefix.append(src)
  282.         self.build_prefix.append(build)
  283.         self.dst_prefix.append(dst)
  284.         return True  # so that you can wrap it in an if to get indentation
  285.     def end_prefix(self, descr=None):
  286.         """Pops a prefix off the stack.  If given an argument, checks
  287.         the argument against the top of the stack.  If the argument
  288.         matches neither the source or destination prefixes at the top
  289.         of the stack, then misnesting must have occurred and an
  290.         exception is raised."""
  291.         # as an error-prevention mechanism, check the prefix and see if it matches the source or destination prefix.  If not, improper nesting may have occurred.
  292.         src = self.src_prefix.pop()
  293.         artwork = self.artwork_prefix.pop()
  294.         build = self.build_prefix.pop()
  295.         dst = self.dst_prefix.pop()
  296.         if descr and not(src == descr or build == descr or dst == descr):
  297.             raise ValueError, "End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'"
  298.     def get_src_prefix(self):
  299.         """ Returns the current source prefix."""
  300.         return os.path.join(*self.src_prefix)
  301.     def get_artwork_prefix(self):
  302.         """ Returns the current artwork prefix."""
  303.         return os.path.join(*self.artwork_prefix)
  304.     def get_build_prefix(self):
  305.         """ Returns the current build prefix."""
  306.         return os.path.join(*self.build_prefix)
  307.     def get_dst_prefix(self):
  308.         """ Returns the current destination prefix."""
  309.         return os.path.join(*self.dst_prefix)
  310.     def src_path_of(self, relpath):
  311.         """Returns the full path to a file or directory specified
  312.         relative to the source directory."""
  313.         return os.path.join(self.get_src_prefix(), relpath)
  314.     def build_path_of(self, relpath):
  315.         """Returns the full path to a file or directory specified
  316.         relative to the build directory."""
  317.         return os.path.join(self.get_build_prefix(), relpath)
  318.     def dst_path_of(self, relpath):
  319.         """Returns the full path to a file or directory specified
  320.         relative to the destination directory."""
  321.         return os.path.join(self.get_dst_prefix(), relpath)
  322.     def ensure_src_dir(self, reldir):
  323.         """Construct the path for a directory relative to the
  324.         source path, and ensures that it exists.  Returns the
  325.         full path."""
  326.         path = os.path.join(self.get_src_prefix(), reldir)
  327.         self.cmakedirs(path)
  328.         return path
  329.     def ensure_dst_dir(self, reldir):
  330.         """Construct the path for a directory relative to the
  331.         destination path, and ensures that it exists.  Returns the
  332.         full path."""
  333.         path = os.path.join(self.get_dst_prefix(), reldir)
  334.         self.cmakedirs(path)
  335.         return path
  336.     def run_command(self, command):
  337.         """ Runs an external command, and returns the output.  Raises
  338.         an exception if the command reurns a nonzero status code.  For
  339.         debugging/informational purpoases, prints out the command's
  340.         output as it is received."""
  341.         print "Running command:", command
  342.         fd = os.popen(command, 'r')
  343.         lines = []
  344.         while True:
  345.             lines.append(fd.readline())
  346.             if lines[-1] == '':
  347.                 break
  348.             else:
  349.                 print lines[-1],
  350.         output = ''.join(lines)
  351.         status = fd.close()
  352.         if status:
  353.             raise RuntimeError(
  354.                 "Command %s returned non-zero status (%s) noutput:n%s"
  355.                 % (command, status, output) )
  356.         return output
  357.     def created_path(self, path):
  358.         """ Declare that you've created a path in order to
  359.           a) verify that you really have created it
  360.           b) schedule it for cleanup"""
  361.         if not os.path.exists(path):
  362.             raise RuntimeError, "Should be something at path " + path
  363.         self.created_paths.append(path)
  364.     def put_in_file(self, contents, dst):
  365.         # write contents as dst
  366.         f = open(self.dst_path_of(dst), "wb")
  367.         f.write(contents)
  368.         f.close()
  369.     def replace_in(self, src, dst=None, searchdict={}):
  370.         if dst == None:
  371.             dst = src
  372.         # read src
  373.         f = open(self.src_path_of(src), "rbU")
  374.         contents = f.read()
  375.         f.close()
  376.         # apply dict replacements
  377.         for old, new in searchdict.iteritems():
  378.             contents = contents.replace(old, new)
  379.         self.put_in_file(contents, dst)
  380.         self.created_paths.append(dst)
  381.     def copy_action(self, src, dst):
  382.         if src and (os.path.exists(src) or os.path.islink(src)):
  383.             # ensure that destination path exists
  384.             self.cmakedirs(os.path.dirname(dst))
  385.             self.created_paths.append(dst)
  386.             if not os.path.isdir(src):
  387.                 self.ccopy(src,dst)
  388.             else:
  389.                 # src is a dir
  390.                 self.ccopytree(src,dst)
  391.         else:
  392.             print "Doesn't exist:", src
  393.     def package_action(self, src, dst):
  394.         pass
  395.     def copy_finish(self):
  396.         pass
  397.     def package_finish(self):
  398.         pass
  399.     def unpacked_finish(self):
  400.         unpacked_file_name = "unpacked_%(plat)s_%(vers)s.tar" % {
  401.             'plat':self.args['platform'],
  402.             'vers':'_'.join(self.args['version'])}
  403.         print "Creating unpacked file:", unpacked_file_name
  404.         # could add a gz here but that doubles the time it takes to do this step
  405.         tf = tarfile.open(self.src_path_of(unpacked_file_name), 'w:')
  406.         # add the entire installation package, at the very top level
  407.         tf.add(self.get_dst_prefix(), "")
  408.         tf.close()
  409.     def cleanup_finish(self):
  410.         """ Delete paths that were specified to have been created by this script"""
  411.         for c in self.created_paths:
  412.             # *TODO is this gonna be useful?
  413.             print "Cleaning up " + c
  414.     def process_file(self, src, dst):
  415.         if self.includes(src, dst):
  416. #            print src, "=>", dst
  417.             for action in self.actions:
  418.                 methodname = action + "_action"
  419.                 method = getattr(self, methodname, None)
  420.                 if method is not None:
  421.                     method(src, dst)
  422.             self.file_list.append([src, dst])
  423.             return 1
  424.         else:
  425.             sys.stdout.write(" (excluding %r, %r)" % (src, dst))
  426.             sys.stdout.flush()
  427.             return 0
  428.     def process_directory(self, src, dst):
  429.         if not self.includes(src, dst):
  430.             sys.stdout.write(" (excluding %r, %r)" % (src, dst))
  431.             sys.stdout.flush()
  432.             return 0
  433.         names = os.listdir(src)
  434.         self.cmakedirs(dst)
  435.         errors = []
  436.         count = 0
  437.         for name in names:
  438.             srcname = os.path.join(src, name)
  439.             dstname = os.path.join(dst, name)
  440.             if os.path.isdir(srcname):
  441.                 count += self.process_directory(srcname, dstname)
  442.             else:
  443.                 count += self.process_file(srcname, dstname)
  444.         return count
  445.     def includes(self, src, dst):
  446.         if src:
  447.             for excl in self.excludes:
  448.                 if fnmatch.fnmatch(src, excl):
  449.                     return False
  450.         return True
  451.     def remove(self, *paths):
  452.         for path in paths:
  453.             if os.path.exists(path):
  454.                 print "Removing path", path
  455.                 if os.path.isdir(path):
  456.                     shutil.rmtree(path)
  457.                 else:
  458.                     os.remove(path)
  459.     def ccopy(self, src, dst):
  460.         """ Copy a single file or symlink.  Uses filecmp to skip copying for existing files."""
  461.         if os.path.islink(src):
  462.             linkto = os.readlink(src)
  463.             if os.path.islink(dst) or os.path.exists(dst):
  464.                 os.remove(dst)  # because symlinking over an existing link fails
  465.             os.symlink(linkto, dst)
  466.         else:
  467.             # Don't recopy file if it's up-to-date.
  468.             # If we seem to be not not overwriting files that have been
  469.             # updated, set the last arg to False, but it will take longer.
  470.             if os.path.exists(dst) and filecmp.cmp(src, dst, True):
  471.                 return
  472.             # only copy if it's not excluded
  473.             if self.includes(src, dst):
  474.                 try:
  475.                     os.unlink(dst)
  476.                 except OSError, err:
  477.                     if err.errno != errno.ENOENT:
  478.                         raise
  479.                 shutil.copy2(src, dst)
  480.     def ccopytree(self, src, dst):
  481.         """Direct copy of shutil.copytree with the additional
  482.         feature that the destination directory can exist.  It
  483.         is so dumb that Python doesn't come with this. Also it
  484.         implements the excludes functionality."""
  485.         if not self.includes(src, dst):
  486.             return
  487.         names = os.listdir(src)
  488.         self.cmakedirs(dst)
  489.         errors = []
  490.         for name in names:
  491.             srcname = os.path.join(src, name)
  492.             dstname = os.path.join(dst, name)
  493.             try:
  494.                 if os.path.isdir(srcname):
  495.                     self.ccopytree(srcname, dstname)
  496.                 else:
  497.                     self.ccopy(srcname, dstname)
  498.                     # XXX What about devices, sockets etc.?
  499.             except (IOError, os.error), why:
  500.                 errors.append((srcname, dstname, why))
  501.         if errors:
  502.             raise RuntimeError, errors
  503.     def cmakedirs(self, path):
  504.         """Ensures that a directory exists, and doesn't throw an exception
  505.         if you call it on an existing directory."""
  506. #        print "making path: ", path
  507.         path = os.path.normpath(path)
  508.         self.created_paths.append(path)
  509.         if not os.path.exists(path):
  510.             os.makedirs(path)
  511.     def find_existing_file(self, *list):
  512.         for f in list:
  513.             if os.path.exists(f):
  514.                 return f
  515.         # didn't find it, return last item in list
  516.         if len(list) > 0:
  517.             return list[-1]
  518.         else:
  519.             return None
  520.     def contents_of_tar(self, src_tar, dst_dir):
  521.         """ Extracts the contents of the tarfile (specified
  522.         relative to the source prefix) into the directory
  523.         specified relative to the destination directory."""
  524.         self.check_file_exists(src_tar)
  525.         tf = tarfile.open(self.src_path_of(src_tar), 'r')
  526.         for member in tf.getmembers():
  527.             tf.extract(member, self.ensure_dst_dir(dst_dir))
  528.             # TODO get actions working on these dudes, perhaps we should extract to a temporary directory and then process_directory on it?
  529.             self.file_list.append([src_tar,
  530.                            self.dst_path_of(os.path.join(dst_dir,member.name))])
  531.         tf.close()
  532.     def wildcard_regex(self, src_glob, dst_glob):
  533.         src_re = re.escape(src_glob)
  534.         src_re = src_re.replace('*', '([-a-zA-Z0-9._ ]*)')
  535.         dst_temp = dst_glob
  536.         i = 1
  537.         while dst_temp.count("*") > 0:
  538.             dst_temp = dst_temp.replace('*', 'g<' + str(i) + '>', 1)
  539.             i = i+1
  540.         return re.compile(src_re), dst_temp
  541.     def check_file_exists(self, path):
  542.         if not os.path.exists(path) and not os.path.islink(path):
  543.             raise RuntimeError("Path %s doesn't exist" % (
  544.                 os.path.normpath(os.path.join(os.getcwd(), path)),))
  545.     wildcard_pattern = re.compile('*')
  546.     def expand_globs(self, src, dst):
  547.         src_list = glob.glob(src)
  548.         src_re, d_template = self.wildcard_regex(src.replace('\', '/'),
  549.                                                  dst.replace('\', '/'))
  550.         for s in src_list:
  551.             d = src_re.sub(d_template, s.replace('\', '/'))
  552.             yield os.path.normpath(s), os.path.normpath(d)
  553.     def path(self, src, dst=None):
  554.         sys.stdout.write("Processing %s => %s ... " % (src, dst))
  555.         sys.stdout.flush()
  556.         if src == None:
  557.             raise RuntimeError("No source file, dst is " + dst)
  558.         if dst == None:
  559.             dst = src
  560.         dst = os.path.join(self.get_dst_prefix(), dst)
  561.         def try_path(src):
  562.             # expand globs
  563.             count = 0
  564.             if self.wildcard_pattern.search(src):
  565.                 for s,d in self.expand_globs(src, dst):
  566.                     assert(s != d)
  567.                     count += self.process_file(s, d)
  568.             else:
  569.                 # if we're specifying a single path (not a glob),
  570.                 # we should error out if it doesn't exist
  571.                 self.check_file_exists(src)
  572.                 # if it's a directory, recurse through it
  573.                 if os.path.isdir(src):
  574.                     count += self.process_directory(src, dst)
  575.                 else:
  576.                     count += self.process_file(src, dst)
  577.             return count
  578.         try:
  579.             count = try_path(os.path.join(self.get_src_prefix(), src))
  580.         except RuntimeError:
  581.             try:
  582.                 count = try_path(os.path.join(self.get_artwork_prefix(), src))
  583.             except RuntimeError:
  584.                 count = try_path(os.path.join(self.get_build_prefix(), src))
  585.         print "%d files" % count
  586.     def do(self, *actions):
  587.         self.actions = actions
  588.         self.construct()
  589.         # perform finish actions
  590.         for action in self.actions:
  591.             methodname = action + "_finish"
  592.             method = getattr(self, methodname, None)
  593.             if method is not None:
  594.                 method()
  595.         return self.file_list