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 shutil.copy2(realsrc,realdest)
00235
00236
00237
00238
00239
00240 def install(sources, destination, logfile, exclusions = [],
00241 destname = None, syml = False, logdir = realpath(".")):
00242 """
00243 Copy sources to destination keeping track of what has been done in logfile.
00244 The destination must be a directory and sources are copied into it.
00245 If exclusions is not empty, the files matching one of its elements are not
00246 copied.
00247 """
00248 for s in sources:
00249 src_path, src_name = split(s)
00250 if not exists(s):
00251 continue
00252 elif not isdir(s):
00253 if destname is None:
00254 dest = join(destination,src_name)
00255 else:
00256 dest = join(destination,destname)
00257 src = getRelativePath(destination,s)
00258 dest = getRelativePath(logdir,dest)
00259 old_dest = logfile.get_dest(src)
00260 update(src,dest,old_dest,syml,logdir)
00261 logfile.set_dest(src,dest)
00262 else:
00263
00264
00265 to_do = expand_source_dir(s,destination,exclusions,destname, logdir)
00266 src = getRelativePath(destination,s)
00267 last_done = logfile.get_dest(src)
00268 if last_done is None: last_done = {}
00269 for k in to_do:
00270 try:
00271 old_dest = last_done[k]
00272 del last_done[k]
00273 except KeyError:
00274 old_dest = None
00275 update(k,to_do[k],old_dest,syml,logdir)
00276
00277 for old_dest in last_done.values():
00278 remove(old_dest,logdir)
00279 logfile.set_dest(src,to_do)
00280
00281 def uninstall(logfile, destinations = [], logdir=realpath(".")):
00282 """
00283 Remove copied files using logfile to know what to remove.
00284 If destinations is not empty, only the files/directories specified are
00285 removed.
00286 """
00287 for s in logfile.get_sources():
00288 dest = logfile.get_dest(s)
00289 if type(dest) is str:
00290 if filename_match(dest,destinations,default=True):
00291 remove(dest, logdir)
00292 logfile.remove(s)
00293 else:
00294 for subs in dest.keys():
00295 subdest = dest[subs]
00296 if filename_match(subdest,destinations,default=True):
00297 remove(subdest,logdir)
00298 del dest[subs]
00299 if not dest:
00300 logfile.remove(s)
00301
00302 if __name__ == "__main__":
00303 main()