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  if sys.platform != "darwin":
240  shutil.copy2(realsrc, realdest) # do the copy (cp -p src dest)
241  else:
242  shutil.copy(realsrc, realdest) # do the copy (cp src dest)
243 
244  #if old_dest != dest: # the file was installed somewhere else
245  # # remove the old destination
246  # if old_dest is not None:
247  # remove(old_dest,logdir)
248 
249 def install(sources, destination, logfile, exclusions = [],
250  destname = None, syml = False, logdir = realpath(".")):
251  """
252  Copy sources to destination keeping track of what has been done in logfile.
253  The destination must be a directory and sources are copied into it.
254  If exclusions is not empty, the files matching one of its elements are not
255  copied.
256  """
257  for s in sources:
258  src_path, src_name = split(s)
259  if not exists(s):
260  continue # silently ignore missing sources
261  elif not isdir(s): # copy the file, without logging (?)
262  if destname is None:
263  dest = join(destination,src_name)
264  else:
265  dest = join(destination,destname)
266  src = getRelativePath(destination,s)
267  dest = getRelativePath(logdir,dest)
268  old_dest = logfile.get_dest(src)
269  update(src,dest,old_dest,syml,logdir)
270  logfile.set_dest(src,dest) # update log
271  else: # for directories
272  # expand the content of the directory as a dictionary
273  # mapping sources to destinations
274  to_do = expand_source_dir(s,destination,exclusions,destname, logdir)
275  src = getRelativePath(destination,s)
276  last_done = logfile.get_dest(src)
277  if last_done is None: last_done = {}
278  for k in to_do:
279  try:
280  old_dest = last_done[k]
281  del last_done[k]
282  except KeyError:
283  old_dest = None
284  update(k,to_do[k],old_dest,syml,logdir)
285  # remove files that were copied but are not anymore in the list
286  for old_dest in last_done.values():
287  remove(old_dest,logdir)
288  logfile.set_dest(src,to_do) # update log
289 
290 def uninstall(logfile, destinations = [], logdir=realpath(".")):
291  """
292  Remove copied files using logfile to know what to remove.
293  If destinations is not empty, only the files/directories specified are
294  removed.
295  """
296  for s in logfile.get_sources():
297  dest = logfile.get_dest(s)
298  if type(dest) is str:
299  if filename_match(dest,destinations,default=True):
300  remove(dest, logdir)
301  logfile.remove(s)
302  else:
303  for subs in dest.keys():
304  subdest = dest[subs]
305  if filename_match(subdest,destinations,default=True):
306  remove(subdest,logdir)
307  del dest[subs]
308  if not dest:
309  logfile.remove(s)
310 
311 if __name__ == "__main__":
312  main()
def expand_source_dir
Definition: install.py:125
def main
Definition: install.py:27
def filename_match
Definition: install.py:115
def getRelativePath
Definition: install.py:195
string type
Definition: gaudirun.py:126
def remove
Definition: install.py:153
def uninstall
Definition: install.py:290
def update
Definition: install.py:213
def install
Definition: install.py:250
def getCommonPath
Definition: install.py:176