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
00018
00019 from __future__ import generators
00020 _version = "$Id: install.py,v 1.15 2008/10/28 17:24:39 marcocle Exp $"
00021
00022 def main():
00023 try:
00024
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
00043
00044
00045
00046 (opts,args) = parser.parse_args()
00047 except ImportError:
00048
00049
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
00079 parser.error()
00080
00081 class _DummyOptionsClass:
00082 def __init__(self):
00083
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
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 :
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
00156
00157
00158
00159
00160 try:
00161
00162
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
00244 dirs[:] = [ d for d in dirs if not filename_match(d,exclusions) ]
00245
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
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:
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
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
00304 filepath,basename = os.path.split(filename)
00305 filename = os.path.join(os.path.realpath(filepath),basename)
00306
00307 dirname = os.path.realpath(dirname)
00308 commonpath = getCommonPath(dirname, filename)
00309
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
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)
00341
00342
00343
00344
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
00358 elif not isdir(s):
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)
00368 else:
00369
00370
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
00383 for old_dest in last_done.values():
00384 remove(old_dest,logdir)
00385 logfile.set_dest(src,to_do)
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()