Gaudi Framework, version v23r6

Home   Generated: Wed Jan 30 2013
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
install.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 Script used to install files keeping track of the files that have
4 been installed, so that at the next installation the file removed
5 from the source directory will also be removed from the destination
6 directory.
7 The script provide also the "uninstall" functionality to remove all
8 and only the files that it installed for the package.
9 
10 Command line:
11 
12  install.py [-x exclusion1 [-x exclusion2 ...]] [-l logfile] source1 [source2 ...] dest
13  install.py -u [-l logfile] [dest1 ...]
14 
15 @author: Marco Clemencic <marco.clemencic@cern.ch>
16 """
17 _version = "$Id: install.py,v 1.15 2008/10/28 17:24:39 marcocle Exp $"
18 
19 import os, sys
20 from os import (makedirs, listdir, rmdir, walk, sep)
21 from os.path import (exists, isdir, getmtime, split, join, realpath, dirname,
22  normpath, splitext, splitdrive)
23 from pickle import (dump, load)
24 from fnmatch import fnmatch
25 import itertools, shutil
26 
27 def main():
28  from optparse import OptionParser
29  parser = OptionParser()
30  parser.add_option("-x","--exclude",action="append",
31  metavar="PATTERN", default = [],
32  dest="exclusions", help="which files/directories to avoid to install")
33  parser.add_option("-l","--log",action="store",
34  dest="logfile", default="install.log",
35  help="where to store the informations about installed files [default: %default]")
36  parser.add_option("-d","--destname",action="store",
37  dest="destname", default=None,
38  help="name to use when installing the source into the destination directory [default: source name]")
39  parser.add_option("-u","--uninstall",action="store_true",
40  dest="uninstall", default=False,
41  help="do uninstall")
42  parser.add_option("-s","--symlink",action="store_true",
43  dest="symlink", default=False,
44  help="create symlinks instead of copy")
45  #parser.add_option("-p","--permission",action="store",
46  # metavar="PERM",
47  # dest="permission",
48  # help="modify the permission of the destination file (see 'man chown'). Unix only.")
49  (opts,args) = parser.parse_args()
50 
51  # options consistency check
52  if opts.uninstall:
53  if opts.exclusions:
54  parser.error("Exclusion list does not make sense for uninstall")
55  opts.destination = args
56  try:
57  log = load(open(opts.logfile,"rb"))
58  except:
59  log = LogFile()
60  uninstall(log,opts.destination,realpath(dirname(opts.logfile)))
61  if log:
62  dump(log,open(opts.logfile,"wb"))
63  else:
64  from os import remove
65  try:
66  remove(opts.logfile)
67  except OSError, x:
68  if x.errno != 2 : raise
69  else : # install mode
70  if len(args) < 2:
71  parser.error("Specify at least one source and (only) one destination")
72  opts.destination = args[-1]
73  opts.sources = args[:-1]
74  try:
75  log = load(open(opts.logfile,"rb"))
76  except:
77  log = LogFile()
78  if opts.symlink :
79  if len(opts.sources) != 1:
80  parser.error("no more that 2 args with --symlink")
81  opts.destination, opts.destname = split(opts.destination)
82  install(opts.sources,opts.destination,
83  log,opts.exclusions,opts.destname,
84  opts.symlink, realpath(dirname(opts.logfile)))
85  dump(log,open(opts.logfile,"wb"))
86 
87 class LogFile:
88  """
89  Class to incapsulate the logfile functionalities.
90  """
91  def __init__(self):
92  self._installed_files = {}
93 
94  def get_dest(self,source):
95  try:
96  return self._installed_files[source]
97  except KeyError:
98  return None
99 
100  def set_dest(self,source,dest):
101  self._installed_files[source] = dest
102 
103  def get_sources(self):
104  return self._installed_files.keys()
105 
106  def remove(self,source):
107  try:
108  del self._installed_files[source]
109  except KeyError:
110  pass
111 
112  def __len__(self):
113  return self._installed_files.__len__()
114 
115 def filename_match(name,patterns,default=False):
116  """
117  Check if the name is matched by any of the patterns in exclusions.
118  """
119  for x in patterns:
120  if fnmatch(name,x):
121  return True
122  return default
123 
124 def expand_source_dir(source, destination, exclusions = [],
125  destname = None, logdir = realpath(".")):
126  """
127  Generate the list of copies.
128  """
129  expansion = {}
130  src_path,src_name = split(source)
131  if destname:
132  to_replace = source
133  replacement = join(destination,destname)
134  else:
135  to_replace = src_path
136  replacement = destination
137 
138  for dirname, dirs, files in walk(source):
139  if to_replace:
140  dest_path=dirname.replace(to_replace,replacement)
141  else:
142  dest_path=join(destination,dirname)
143  # remove excluded dirs from the list
144  dirs[:] = [ d for d in dirs if not filename_match(d,exclusions) ]
145  # loop over files
146  for f in files:
147  if filename_match(f,exclusions): continue
148  key = getRelativePath(dest_path, join(dirname,f))
149  value = getRelativePath(logdir, join(dest_path,f))
150  expansion[key] = value
151  return expansion
152 
153 def remove(file, logdir):
154  file = normpath(join(logdir, file))
155  try:
156  print "Remove '%s'"%file
157  os.remove(file)
158  # For python files, remove the compiled versions too
159  if splitext(file)[-1] == ".py":
160  for c in ['c', 'o']:
161  if exists(file + c):
162  print "Remove '%s'" % (file+c)
163  os.remove(file+c)
164  file_path = split(file)[0]
165  while file_path and (len(listdir(file_path)) == 0):
166  print "Remove empty dir '%s'"%file_path
167  rmdir(file_path)
168  file_path = split(file_path)[0]
169  except OSError, x: # ignore file-not-found errors
170  if x.errno in [2, 13] :
171  print "Previous removal ignored"
172  else:
173  raise
174 
175 
176 def getCommonPath(dirname, filename):
177  # if the 2 components are on different drives (windows)
178  if splitdrive(dirname)[0] != splitdrive(filename)[0]:
179  return None
180  dirl = dirname.split(sep)
181  filel = filename.split(sep)
182  commpth = []
183  for d, f in itertools.izip(dirl, filel):
184  if d == f :
185  commpth.append(d)
186  else :
187  break
188  commpth = sep.join(commpth)
189  if not commpth:
190  commpth = sep
191  elif commpth[-1] != sep:
192  commpth += sep
193  return commpth
194 
195 def getRelativePath(dirname, filename):
196  """ calculate the relative path of filename with regards to dirname """
197  # Translate the filename to the realpath of the parent directory + basename
198  filepath,basename = os.path.split(filename)
199  filename = os.path.join(os.path.realpath(filepath),basename)
200  # Get the absolute pathof the destination directory
201  dirname = os.path.realpath(dirname)
202  commonpath = getCommonPath(dirname, filename)
203  # for windows if the 2 components are on different drives
204  if not commonpath:
205  return filename
206  relname = filename[len(commonpath):]
207  reldir = dirname[len(commonpath):]
208  if reldir:
209  relname = (os.path.pardir+os.path.sep)*len(reldir.split(os.path.sep)) \
210  + relname
211  return relname
212 
213 def update(src,dest,old_dest = None, syml = False, logdir = realpath(".")):
214  realdest = normpath(join(logdir, dest))
215  dest_path = split(realdest)[0]
216  realsrc = normpath(join(dest_path,src))
217  # The modification time is compared only with the precision of the second
218  # to avoid a bug in Python 2.5 + Win32 (Fixed in Python 2.5.1).
219  # See:
220  # http://bugs.python.org/issue1671965
221  # http://bugs.python.org/issue1565150
222  if (not exists(realdest)) or (int(getmtime(realsrc)) > int(getmtime(realdest))):
223  if not isdir(dest_path):
224  print "Create dir '%s'"%(dest_path)
225  makedirs(dest_path)
226  # the destination file is missing or older than the source
227  if syml and sys.platform != "win32" :
228  if exists(realdest):
229  remove(realdest,logdir)
230  print "Create Link to '%s' in '%s'"%(src,dest_path)
231  os.symlink(src,realdest)
232  else:
233  print "Copy '%s' -> '%s'"%(src, realdest)
234  if exists(realdest):
235  # If the destination path exists it is better to remove it before
236  # doing the copy (shutil.copystat fails if the destination file
237  # is not owned by the current user).
238  os.remove(realdest)
239  shutil.copy2(realsrc, realdest) # do the copy (cp -p src dest)
240 
241  #if old_dest != dest: # the file was installed somewhere else
242  # # remove the old destination
243  # if old_dest is not None:
244  # remove(old_dest,logdir)
245 
246 def install(sources, destination, logfile, exclusions = [],
247  destname = None, syml = False, logdir = realpath(".")):
248  """
249  Copy sources to destination keeping track of what has been done in logfile.
250  The destination must be a directory and sources are copied into it.
251  If exclusions is not empty, the files matching one of its elements are not
252  copied.
253  """
254  for s in sources:
255  src_path, src_name = split(s)
256  if not exists(s):
257  continue # silently ignore missing sources
258  elif not isdir(s): # copy the file, without logging (?)
259  if destname is None:
260  dest = join(destination,src_name)
261  else:
262  dest = join(destination,destname)
263  src = getRelativePath(destination,s)
264  dest = getRelativePath(logdir,dest)
265  old_dest = logfile.get_dest(src)
266  update(src,dest,old_dest,syml,logdir)
267  logfile.set_dest(src,dest) # update log
268  else: # for directories
269  # expand the content of the directory as a dictionary
270  # mapping sources to destinations
271  to_do = expand_source_dir(s,destination,exclusions,destname, logdir)
272  src = getRelativePath(destination,s)
273  last_done = logfile.get_dest(src)
274  if last_done is None: last_done = {}
275  for k in to_do:
276  try:
277  old_dest = last_done[k]
278  del last_done[k]
279  except KeyError:
280  old_dest = None
281  update(k,to_do[k],old_dest,syml,logdir)
282  # remove files that were copied but are not anymore in the list
283  for old_dest in last_done.values():
284  remove(old_dest,logdir)
285  logfile.set_dest(src,to_do) # update log
286 
287 def uninstall(logfile, destinations = [], logdir=realpath(".")):
288  """
289  Remove copied files using logfile to know what to remove.
290  If destinations is not empty, only the files/directories specified are
291  removed.
292  """
293  for s in logfile.get_sources():
294  dest = logfile.get_dest(s)
295  if type(dest) is str:
296  if filename_match(dest,destinations,default=True):
297  remove(dest, logdir)
298  logfile.remove(s)
299  else:
300  for subs in dest.keys():
301  subdest = dest[subs]
302  if filename_match(subdest,destinations,default=True):
303  remove(subdest,logdir)
304  del dest[subs]
305  if not dest:
306  logfile.remove(s)
307 
308 if __name__ == "__main__":
309  main()

Generated at Wed Jan 30 2013 17:13:41 for Gaudi Framework, version v23r6 by Doxygen version 1.8.2 written by Dimitri van Heesch, © 1997-2004