| # Gaudi Framework, version v21r10p1 |

Home |  | Generated: 29 Jul 2010

# 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 _version = "$Id: install.py,v 1.15 2008/10/28 17:24:39 marcocle Exp $"
    00018
    00019 import os, sys
    00020 from os import (makedirs, listdir, rmdir, walk, sep)
    00021 from os.path import (exists, isdir, getmtime, split, join, realpath, dirname,
    00022                      normpath, splitext, splitdrive)
    00023 from pickle import (dump, load)
    00024 from fnmatch import fnmatch
    00025 import itertools, shutil
    00026
    00027 def main():
    00028     from optparse import OptionParser
    00029     parser = OptionParser()
    00030     parser.add_option("-x","--exclude",action="append",
    00031                       metavar="PATTERN", default = [],
    00032                       dest="exclusions", help="which files/directories to avoid to install")
    00033     parser.add_option("-l","--log",action="store",
    00034                       dest="logfile", default="install.log",
    00035                       help="where to store the informations about installed files [default: %default]")
    00036     parser.add_option("-d","--destname",action="store",
    00037                       dest="destname", default=None,
    00038                       help="name to use when installing the source into the destination directory [default: source name]")
    00039     parser.add_option("-u","--uninstall",action="store_true",
    00040                       dest="uninstall", default=False,
    00041                       help="do uninstall")
    00042     parser.add_option("-s","--symlink",action="store_true",
    00043                       dest="symlink", default=False,
    00044                       help="create symlinks instead of copy")
    00045     #parser.add_option("-p","--permission",action="store",
    00046     #                  metavar="PERM",
    00047     #                  dest="permission",
    00048     #                  help="modify the permission of the destination file (see 'man chown'). Unix only.")
    00049     (opts,args) = parser.parse_args()
    00050
    00051     # options consistency check
    00052     if opts.uninstall:
    00053         if opts.exclusions:
    00054             parser.error("Exclusion list does not make sense for uninstall")
    00055         opts.destination = args
    00056         try:
    00057             log = load(open(opts.logfile,"rb"))
    00058         except:
    00059             log = LogFile()
    00060         uninstall(log,opts.destination,realpath(dirname(opts.logfile)))
    00061         if log:
    00062             dump(log,open(opts.logfile,"wb"))
    00063         else:
    00064             from os import remove
    00065             try:
    00066                 remove(opts.logfile)
    00067             except OSError, x:
    00068                 if x.errno != 2 : raise
    00069     else : # install mode
    00070         if len(args) < 2:
    00071             parser.error("Specify at least one source and (only) one destination")
    00072         opts.destination = args[-1]
    00073         opts.sources = args[:-1]
    00074         try:
    00075             log = load(open(opts.logfile,"rb"))
    00076         except:
    00077             log = LogFile()
    00078         if opts.symlink :
    00079             if len(opts.sources) != 1:
    00080                 parser.error("no more that 2 args with --symlink")
    00081             opts.destination, opts.destname = split(opts.destination)
    00082         install(opts.sources,opts.destination,
    00083                 log,opts.exclusions,opts.destname,
    00084                 opts.symlink, realpath(dirname(opts.logfile)))
    00085         dump(log,open(opts.logfile,"wb"))
    00086
    00087 class LogFile:
    00088     """
    00089     Class to incapsulate the logfile functionalities.
    00090     """
    00091     def __init__(self):
    00092         self._installed_files = {}
    00093
    00094     def get_dest(self,source):
    00095         try:
    00096             return self._installed_files[source]
    00097         except KeyError:
    00098             return None
    00099
    00100     def set_dest(self,source,dest):
    00101         self._installed_files[source] = dest
    00102
    00103     def get_sources(self):
    00104         return self._installed_files.keys()
    00105
    00106     def remove(self,source):
    00107         try:
    00108             del  self._installed_files[source]
    00109         except KeyError:
    00110             pass
    00111
    00112     def __len__(self):
    00113         return self._installed_files.__len__()
    00114
    00115 def filename_match(name,patterns,default=False):
    00116     """
    00117     Check if the name is matched by any of the patterns in exclusions.
    00118     """
    00119     for x in patterns:
    00120         if fnmatch(name,x):
    00121             return True
    00122     return default
    00123
    00124 def expand_source_dir(source, destination, exclusions = [],
    00125                       destname = None, logdir = realpath(".")):
    00126     """
    00127     Generate the list of copies.
    00128     """
    00129     expansion = {}
    00130     src_path,src_name = split(source)
    00131     if destname:
    00132         to_replace = source
    00133         replacement = join(destination,destname)
    00134     else:
    00135         to_replace = src_path
    00136         replacement = destination
    00137
    00138     for dirname, dirs, files in walk(source):
    00139         if to_replace:
    00140             dest_path=dirname.replace(to_replace,replacement)
    00141         else:
    00142             dest_path=join(destination,dirname)
    00143         # remove excluded dirs from the list
    00144         dirs[:] = [ d for d in dirs if not filename_match(d,exclusions) ]
    00145         # loop over files
    00146         for f in files:
    00147             if filename_match(f,exclusions): continue
    00148             key = getRelativePath(dest_path, join(dirname,f))
    00149             value = getRelativePath(logdir, join(dest_path,f))
    00150             expansion[key] = value
    00151     return expansion
    00152
    00153 def remove(file, logdir):
    00154     file = normpath(join(logdir, file))
    00155     try:
    00156         print "Remove '%s'"%file
    00157         os.remove(file)
    00158         # For python files, remove the compiled versions too
    00159         if splitext(file)[-1] == ".py":
    00160             for c in ['c', 'o']:
    00161                 if exists(file + c):
    00162                     print "Remove '%s'" % (file+c)
    00163                     os.remove(file+c)
    00164         file_path = split(file)[0]
    00165         while file_path and (len(listdir(file_path)) == 0):
    00166             print "Remove empty dir '%s'"%file_path
    00167             rmdir(file_path)
    00168             file_path = split(file_path)[0]
    00169     except OSError, x: # ignore file-not-found errors
    00170         if x.errno in [2, 13] :
    00171             print "Previous removal ignored"
    00172         else:
    00173             raise
    00174
    00175
    00176 def getCommonPath(dirname, filename):
    00177     # if the 2 components are on different drives (windows)
    00178     if splitdrive(dirname)[0] != splitdrive(filename)[0]:
    00179         return None
    00180     dirl = dirname.split(sep)
    00181     filel = filename.split(sep)
    00182     commpth = []
    00183     for d, f in itertools.izip(dirl, filel):
    00184         if d == f :
    00185             commpth.append(d)
    00186         else :
    00187             break
    00188     commpth = sep.join(commpth)
    00189     if not commpth:
    00190         commpth = sep
    00191     elif commpth[-1] != sep:
    00192         commpth += sep
    00193     return commpth
    00194
    00195 def getRelativePath(dirname, filename):
    00196     """ calculate the relative path of filename with regards to dirname """
    00197     # Translate the filename to the realpath of the parent directory + basename
    00198     filepath,basename = os.path.split(filename)
    00199     filename = os.path.join(os.path.realpath(filepath),basename)
    00200     # Get the absolute pathof the destination directory
    00201     dirname = os.path.realpath(dirname)
    00202     commonpath = getCommonPath(dirname, filename)
    00203     # for windows if the 2 components are on different drives
    00204     if not commonpath:
    00205         return filename
    00206     relname = filename[len(commonpath):]
    00207     reldir = dirname[len(commonpath):]
    00208     if reldir:
    00209         relname = (os.path.pardir+os.path.sep)*len(reldir.split(os.path.sep)) \
    00210              + relname
    00211     return relname
    00212
    00213 def update(src,dest,old_dest = None, syml = False, logdir = realpath(".")):
    00214     realdest = normpath(join(logdir, dest))
    00215     dest_path = split(realdest)[0]
    00216     realsrc = normpath(join(dest_path,src))
    00217     # The modification time is compared only with the precision of the second
    00218     # to avoid a bug in Python 2.5 + Win32 (Fixed in Python 2.5.1).
    00219     # See:
    00220     #   http://bugs.python.org/issue1671965
    00221     #   http://bugs.python.org/issue1565150
    00222     if (not exists(realdest)) or (int(getmtime(realsrc)) > int(getmtime(realdest))):
    00223         if not isdir(dest_path):
    00224             print "Create dir '%s'"%(dest_path)
    00225             makedirs(dest_path)
    00226         # the destination file is missing or older than the source
    00227         if syml and sys.platform != "win32" :
    00228             if exists(realdest):
    00229                 remove(realdest,logdir)
    00230             print "Create Link to '%s' in '%s'"%(src,dest_path)
    00231             os.symlink(src,realdest)
    00232         else:
    00233             print "Copy '%s' -> '%s'"%(src, realdest)
    00234             if exists(realdest):
    00235                 # If the destination path exists it is better to remove it before
    00236                 # doing the copy (shutil.copystat fails if the destination file
    00237                 # is not owned by the current user).
    00238                 os.remove(realdest)
    00239             shutil.copy2(realsrc, realdest) # do the copy (cp -p src dest)
    00240
    00241     #if old_dest != dest: # the file was installed somewhere else
    00242     #    # remove the old destination
    00243     #    if old_dest is not None:
    00244     #        remove(old_dest,logdir)
    00245
    00246 def install(sources, destination, logfile, exclusions = [],
    00247             destname = None, syml = False, logdir = realpath(".")):
    00248     """
    00249     Copy sources to destination keeping track of what has been done in logfile.
    00250     The destination must be a directory and sources are copied into it.
    00251     If exclusions is not empty, the files matching one of its elements are not
    00252     copied.
    00253     """
    00254     for s in sources:
    00255         src_path, src_name = split(s)
    00256         if not exists(s):
    00257             continue # silently ignore missing sources
    00258         elif not isdir(s): # copy the file, without logging (?)
    00259             if destname is None:
    00260                 dest = join(destination,src_name)
    00261             else:
    00262                 dest = join(destination,destname)
    00263             src = getRelativePath(destination,s)
    00264             dest = getRelativePath(logdir,dest)
    00265             old_dest = logfile.get_dest(src)
    00266             update(src,dest,old_dest,syml,logdir)
    00267             logfile.set_dest(src,dest) # update log
    00268         else: # for directories
    00269             # expand the content of the directory as a dictionary
    00270             # mapping sources to destinations
    00271             to_do = expand_source_dir(s,destination,exclusions,destname, logdir)
    00272             src = getRelativePath(destination,s)
    00273             last_done = logfile.get_dest(src)
    00274             if last_done is None: last_done = {}
    00275             for k in to_do:
    00276                 try:
    00277                     old_dest = last_done[k]
    00278                     del last_done[k]
    00279                 except KeyError:
    00280                     old_dest = None
    00281                 update(k,to_do[k],old_dest,syml,logdir)
    00282             # remove files that were copied but are not anymore in the list
    00283             for old_dest in last_done.values():
    00284                 remove(old_dest,logdir)
    00285             logfile.set_dest(src,to_do) # update log
    00286
    00287 def uninstall(logfile, destinations = [], logdir=realpath(".")):
    00288     """
    00289     Remove copied files using logfile to know what to remove.
    00290     If destinations is not empty, only the files/directories specified are
    00291     removed.
    00292     """
    00293     for s in logfile.get_sources():
    00294         dest = logfile.get_dest(s)
    00295         if type(dest) is str:
    00296             if filename_match(dest,destinations,default=True):
    00297                 remove(dest, logdir)
    00298                 logfile.remove(s)
    00299         else:
    00300             for subs in dest.keys():
    00301                 subdest = dest[subs]
    00302                 if filename_match(subdest,destinations,default=True):
    00303                     remove(subdest,logdir)
    00304                     del dest[subs]
    00305             if not dest:
    00306                 logfile.remove(s)
    00307
    00308 if __name__ == "__main__":
    00309     main()