| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

In This Package:

install.py

Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 """
00003 Script used to install files keeping track of the files that have
00004 been installed, so that at the next installation the file removed
00005 from the source directory will also be removed from the destination
00006 directory.
00007 The script provide also the "uninstall" functionality to remove all
00008 and only the files that it installed for the package.
00009 
00010 Command line:
00011 
00012    install.py [-x exclusion1 [-x exclusion2 ...]] [-l logfile] source1 [source2 ...] dest  
00013    install.py -u [-l logfile] [dest1 ...]
00014    
00015 @author: Marco Clemencic <marco.clemencic@cern.ch>
00016 """
00017 # Needed to for the local copy of the function os.walk, introduced in Python 2.3
00018 # It must be removed when the support for Python 2.2 is dropped
00019 from __future__ import generators # should be at the first line to please Python 2.5
00020 _version = "$Id: install.py,v 1.15 2008/10/28 17:24:39 marcocle Exp $"
00021 
00022 def main():
00023     try:
00024         # optparse is available only since Python 2.3
00025         from optparse import OptionParser
00026         parser = OptionParser()
00027         parser.add_option("-x","--exclude",action="append",
00028                           metavar="PATTERN", default = [],
00029                           dest="exclusions", help="which files/directories to avoid to install")
00030         parser.add_option("-l","--log",action="store",
00031                           dest="logfile", default="install.log",
00032                           help="where to store the informations about installed files [default: %default]")
00033         parser.add_option("-d","--destname",action="store",
00034                           dest="destname", default=None,
00035                           help="name to use when installing the source into the destination directory [default: source name]")
00036         parser.add_option("-u","--uninstall",action="store_true",
00037                           dest="uninstall", default=False,
00038                           help="do uninstall")
00039         parser.add_option("-s","--symlink",action="store_true",
00040                           dest="symlink", default=False,
00041                           help="create symlinks instead of copy")
00042         #parser.add_option("-p","--permission",action="store",
00043         #                  metavar="PERM",
00044         #                  dest="permission",
00045         #                  help="modify the permission of the destination file (see 'man chown'). Unix only.")
00046         (opts,args) = parser.parse_args()
00047     except ImportError:
00048         # Old style option parsing
00049         # It must be removed when the support for Python 2.2 is dropped
00050         from getopt import getopt, GetoptError
00051         from sys import argv,exit
00052         class _DummyParserClass:
00053             def __init__(self):
00054                 self.usage = "usage: install.py [options]"
00055                 self.help = """options:
00056   -h, --help            show this help message and exit
00057   -x PATTERN, --exclude=PATTERN
00058                         which files/directories to avoid to install
00059   -l LOGFILE, --log=LOGFILE
00060                         where to store the informations about installed files
00061                         [default: install.log]
00062   -d DESTNAME, --destname=DESTNAME
00063                         name to use when installing the source into the
00064                         destination directory [default: source name]
00065   -u, --uninstall       do uninstall
00066   -s, --symlink         create symlinks instead of copy"""
00067             def error(self,msg=None):
00068                 print self.usage + "\n"
00069                 if not msg:
00070                     msg = self.help
00071                 print msg
00072                 exit(1)
00073         parser = _DummyParserClass()
00074         try:
00075             optlist, args = getopt(argv[1:],"hx:l:d:us",
00076                                    ["help","exclude","log","destname","uninstall","symlink"])
00077         except GetoptError:
00078             # print help information and exit:
00079             parser.error()
00080         # Dummy option class
00081         class _DummyOptionsClass:
00082             def __init__(self):
00083                 # defaults
00084                 self.exclusions = []
00085                 self.uninstall = False
00086                 self.logfile = "install.log"
00087                 self.destname = None
00088                 self.symlink = False
00089         opts = _DummyOptionsClass()
00090         for opt,val in optlist:
00091             if opt in [ "-h", "--help" ]:
00092                 parser.error()
00093             elif opt in [ "-x", "--exclude" ]:
00094                 opts.exclusions.append(val)
00095             elif opt in [ "-l", "--log" ]:
00096                 opts.logfile = val
00097             elif opt in [ "-d", "--destname" ]:
00098                 opts.destname = val
00099             elif opt in [ "-u", "--uninstall" ]:
00100                 opts.uninstall = True
00101             elif opt in [ "-s", "--symlink" ]:
00102                 opts.symlink = True
00103     # options consistency check
00104     from pickle import dump,load
00105     from os.path import realpath
00106     if opts.uninstall:
00107         if opts.exclusions:
00108             parser.error("Exclusion list does not make sense for uninstall")
00109         opts.destination = args
00110         try:
00111             log = load(open(opts.logfile,"rb"))
00112         except:
00113             log = LogFile() 
00114         uninstall(log,opts.destination,realpath(dirname(opts.logfile)))
00115         if log:
00116             dump(log,open(opts.logfile,"wb"))
00117         else:
00118             from os import remove
00119             try:
00120                 remove(opts.logfile)
00121             except OSError, x:
00122                 if x.errno != 2 : raise
00123     else : # install mode
00124         if len(args) < 2:
00125             parser.error("Specify at least one source and (only) one destination")
00126         opts.destination = args[-1]
00127         opts.sources = args[:-1]
00128         try:
00129             log = load(open(opts.logfile,"rb"))
00130         except:
00131             log = LogFile() 
00132         if opts.symlink :
00133             if len(opts.sources) != 1:
00134                 parser.error("no more that 2 args with --symlink")
00135             opts.destination, opts.destname = split(opts.destination) 
00136         install(opts.sources,opts.destination,
00137                 log,opts.exclusions,opts.destname, 
00138                 opts.symlink, realpath(dirname(opts.logfile)))
00139         dump(log,open(opts.logfile,"wb"))
00140 
00141 from os import makedirs, listdir, rmdir
00142 from os.path import exists, isdir, getmtime, split, join, realpath, dirname
00143 
00144 try:
00145     from os import walk
00146 except ImportError:
00147     def walk(top, topdown=True, onerror=None):
00148         """Copied from Python 2.3 os.py (see original file for copyright)
00149         This function has been introduced in Python 2.3, and this copy should
00150         be removed once the support for Python 2.2 is dropped.
00151         """
00152     
00153         from os.path import join, isdir, islink
00154     
00155         # We may not have read permission for top, in which case we can't
00156         # get a list of the files the directory contains.  os.path.walk
00157         # always suppressed the exception then, rather than blow up for a
00158         # minor reason when (say) a thousand readable directories are still
00159         # left to visit.  That logic is copied here.
00160         try:
00161             # Note that listdir and error are globals in this module due
00162             # to earlier import-*.
00163             names = listdir(top)
00164         except error, err:
00165             if onerror is not None:
00166                 onerror(err)
00167             return
00168     
00169         dirs, nondirs = [], []
00170         for name in names:
00171             if isdir(join(top, name)):
00172                 dirs.append(name)
00173             else:
00174                 nondirs.append(name)
00175     
00176         if topdown:
00177             yield top, dirs, nondirs
00178         for name in dirs:
00179             path = join(top, name)
00180             if not islink(path):
00181                 for x in walk(path, topdown, onerror):
00182                     yield x
00183         if not topdown:
00184             yield top, dirs, nondirs
00185 
00186 class LogFile:
00187     """
00188     Class to incapsulate the logfile functionalities.
00189     """
00190     def __init__(self):
00191         self._installed_files = {}
00192         
00193     def get_dest(self,source):
00194         try:
00195             return self._installed_files[source]
00196         except KeyError:
00197             return None
00198 
00199     def set_dest(self,source,dest):
00200         self._installed_files[source] = dest
00201 
00202     def get_sources(self):
00203         return self._installed_files.keys()
00204     
00205     def remove(self,source):
00206         try:
00207             del  self._installed_files[source]
00208         except KeyError:
00209             pass
00210     
00211     def __len__(self):
00212         return self._installed_files.__len__()
00213         
00214 def filename_match(name,patterns,default=False):
00215     """
00216     Check if the name is matched by any of the patterns in exclusions.
00217     """
00218     from fnmatch import fnmatch
00219     for x in patterns:
00220         if fnmatch(name,x):
00221             return True
00222     return default
00223 
00224 def expand_source_dir(source, destination, exclusions = [], 
00225                       destname = None, logdir = realpath(".")):
00226     """
00227     Generate the list of copies. 
00228     """
00229     expansion = {}
00230     src_path,src_name = split(source)
00231     if destname:
00232         to_replace = source
00233         replacement = join(destination,destname)
00234     else:
00235         to_replace = src_path
00236         replacement = destination
00237     
00238     for dirname, dirs, files in walk(source):
00239         if to_replace:
00240             dest_path=dirname.replace(to_replace,replacement)
00241         else:
00242             dest_path=join(destination,dirname)
00243         # remove excluded dirs from the list
00244         dirs[:] = [ d for d in dirs if not filename_match(d,exclusions) ]
00245         # loop over files
00246         for f in files:
00247             if filename_match(f,exclusions): continue
00248             key = getRelativePath(dest_path, join(dirname,f))
00249             value = getRelativePath(logdir, join(dest_path,f))
00250             expansion[key] = value
00251     return expansion
00252 
00253 def remove(file, logdir):
00254     from os import remove
00255     from os.path import normpath, splitext, exists
00256     file = normpath(join(logdir, file))
00257     try:
00258         print "Remove '%s'"%file
00259         remove(file)
00260         # For python files, remove the compiled versions too 
00261         if splitext(file)[-1] == ".py":
00262             for c in ['c', 'o']:
00263                 if exists(file + c):
00264                     print "Remove '%s'" % (file+c)
00265                     remove(file+c)
00266         file_path = split(file)[0]
00267         while file_path and (len(listdir(file_path)) == 0):
00268             print "Remove empty dir '%s'"%file_path
00269             rmdir(file_path)
00270             file_path = split(file_path)[0]
00271     except OSError, x: # ignore file-not-found errors
00272         if x.errno in [2, 13] :
00273             print "Previous removal ignored"
00274         else: 
00275             raise
00276         
00277 
00278 def getCommonPath(dirname, filename):
00279     from os import sep
00280     from itertools import izip
00281     from os.path import splitdrive
00282     # if the 2 components are on different drives (windows)
00283     if splitdrive(dirname)[0] != splitdrive(filename)[0]:
00284         return None
00285     dirl = dirname.split(sep)
00286     filel = filename.split(sep)
00287     commpth = []
00288     for d, f in izip(dirl, filel):
00289         if d == f :
00290             commpth.append(d)
00291         else :
00292             break
00293     commpth = sep.join(commpth)
00294     if not commpth:
00295         commpth = sep
00296     elif commpth[-1] != sep:
00297         commpth += sep
00298     return commpth
00299 
00300 def getRelativePath(dirname, filename):
00301     """ calculate the relative path of filename with regards to dirname """
00302     import os.path
00303     # Translate the filename to the realpath of the parent directory + basename
00304     filepath,basename = os.path.split(filename)
00305     filename = os.path.join(os.path.realpath(filepath),basename)
00306     # Get the absolute pathof the destination directory
00307     dirname = os.path.realpath(dirname)
00308     commonpath = getCommonPath(dirname, filename)
00309     # for windows if the 2 components are on different drives
00310     if not commonpath:
00311         return filename
00312     relname = filename[len(commonpath):]
00313     reldir = dirname[len(commonpath):]
00314     if reldir:
00315         relname = (os.path.pardir+os.path.sep)*len(reldir.split(os.path.sep)) \
00316              + relname
00317     return relname
00318     
00319 def update(src,dest,old_dest = None, syml = False, logdir = realpath(".")):
00320     from shutil import copy2
00321     from sys import platform
00322     from os.path import normpath
00323     if platform != "win32":
00324         from os import symlink
00325     realdest = normpath(join(logdir, dest))
00326     dest_path = split(realdest)[0]
00327     realsrc = normpath(join(dest_path,src))
00328     if (not exists(realdest)) or (getmtime(realsrc) > getmtime(realdest)):
00329         if not isdir(dest_path):
00330             print "Create dir '%s'"%(dest_path)
00331             makedirs(dest_path)
00332         # the destination file is missing or older than the source
00333         if syml and platform != "win32" :
00334             if exists(realdest):
00335                 remove(realdest,logdir)
00336             print "Create Link to '%s' in '%s'"%(src,dest_path)
00337             symlink(src,realdest)
00338         else:
00339             print "Copy '%s' -> '%s'"%(src,realdest)
00340             copy2(realsrc,realdest) # do the copy (cp -p src dest)
00341     #if old_dest != dest: # the file was installed somewhere else
00342     #    # remove the old destination
00343     #    if old_dest is not None:
00344     #        remove(old_dest,logdir)
00345 
00346 def install(sources, destination, logfile, exclusions = [],
00347             destname = None, syml = False, logdir = realpath(".")):
00348     """
00349     Copy sources to destination keeping track of what has been done in logfile.
00350     The destination must be a directory and sources are copied into it.
00351     If exclusions is not empty, the files matching one of its elements are not
00352     copied.
00353     """
00354     for s in sources:
00355         src_path, src_name = split(s)
00356         if not exists(s):
00357             continue # silently ignore missing sources
00358         elif not isdir(s): # copy the file, without logging (?)
00359             if destname is None:
00360                 dest = join(destination,src_name)
00361             else:
00362                 dest = join(destination,destname)
00363             src = getRelativePath(destination,s)
00364             dest = getRelativePath(logdir,dest)
00365             old_dest = logfile.get_dest(src)
00366             update(src,dest,old_dest,syml,logdir)
00367             logfile.set_dest(src,dest) # update log
00368         else: # for directories
00369             # expand the content of the directory as a dictionary
00370             # mapping sources to destinations
00371             to_do = expand_source_dir(s,destination,exclusions,destname, logdir)
00372             src = getRelativePath(destination,s)
00373             last_done = logfile.get_dest(src)
00374             if last_done is None: last_done = {}
00375             for k in to_do:
00376                 try:
00377                     old_dest = last_done[k]
00378                     del last_done[k]
00379                 except KeyError:
00380                     old_dest = None  
00381                 update(k,to_do[k],old_dest,syml,logdir)
00382             # remove files that were copied but are not anymore in the list 
00383             for old_dest in last_done.values():
00384                 remove(old_dest,logdir)
00385             logfile.set_dest(src,to_do) # update log
00386 
00387 def uninstall(logfile, destinations = [], logdir=realpath(".")):
00388     """
00389     Remove copied files using logfile to know what to remove.
00390     If destinations is not empty, only the files/directories specified are
00391     removed.
00392     """
00393     for s in logfile.get_sources():
00394         dest = logfile.get_dest(s)
00395         if type(dest) is str:
00396             if filename_match(dest,destinations,default=True):
00397                 remove(dest, logdir)
00398                 logfile.remove(s)
00399         else:
00400             for subs in dest.keys():
00401                 subdest = dest[subs]
00402                 if filename_match(subdest,destinations,default=True):
00403                     remove(subdest,logdir)
00404                     del dest[subs]
00405             if not dest:
00406                 logfile.remove(s)
00407             
00408 if __name__ == "__main__":
00409     main()
| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

Generated on Mon Apr 11 19:56:51 2011 for GaudiPolicy by doxygen 1.4.7