| # Gaudi Framework, version v20r4
Generated: 8 Jan 2009

# 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()