00001
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
00046
00047
00048
00049 (opts,args) = parser.parse_args()
00050
00051
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 :
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
00144 dirs[:] = [ d for d in dirs if not filename_match(d,exclusions) ]
00145
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
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:
00170 if x.errno in [2, 13] :
00171 print "Previous removal ignored"
00172 else:
00173 raise
00174
00175
00176 def getCommonPath(dirname, filename):
00177
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
00198 filepath,basename = os.path.split(filename)
00199 filename = os.path.join(os.path.realpath(filepath),basename)
00200
00201 dirname = os.path.realpath(dirname)
00202 commonpath = getCommonPath(dirname, filename)
00203
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
00218
00219
00220
00221
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
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
00236
00237
00238 os.remove(realdest)
00239 shutil.copy2(realsrc, realdest)
00240
00241
00242
00243
00244
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
00258 elif not isdir(s):
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)
00268 else:
00269
00270
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
00283 for old_dest in last_done.values():
00284 remove(old_dest,logdir)
00285 logfile.set_dest(src,to_do)
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()