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  # To avoid race conditions on makedirs(), use EAFP (see GAUDI-1105)
218  if (not exists(realdest)) or (getmtime(realsrc) > getmtime(realdest)):
219  try:
220  makedirs(dest_path)
221  print "Created dir '{0}'".format(dest_path)
222  except OSError as e:
223  # OSerror no. 17 is "file exists" - harmless as long as the directory is there
224  if not(e.errno == 17 and isdir(dest_path)):
225  raise
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 
245 def install(sources, destination, logfile, exclusions = [],
246  destname = None, syml = False, logdir = realpath(".")):
247  """
248  Copy sources to destination keeping track of what has been done in logfile.
249  The destination must be a directory and sources are copied into it.
250  If exclusions is not empty, the files matching one of its elements are not
251  copied.
252  """
253  for s in sources:
254  src_path, src_name = split(s)
255  if not exists(s):
256  continue # silently ignore missing sources
257  elif not isdir(s): # copy the file, without logging (?)
258  if destname is None:
259  dest = join(destination,src_name)
260  else:
261  dest = join(destination,destname)
262  src = getRelativePath(destination,s)
263  dest = getRelativePath(logdir,dest)
264  old_dest = logfile.get_dest(src)
265  update(src,dest,old_dest,syml,logdir)
266  logfile.set_dest(src,dest) # update log
267  else: # for directories
268  # expand the content of the directory as a dictionary
269  # mapping sources to destinations
270  to_do = expand_source_dir(s,destination,exclusions,destname, logdir)
271  src = getRelativePath(destination,s)
272  last_done = logfile.get_dest(src)
273  if last_done is None: last_done = {}
274  for k in to_do:
275  try:
276  old_dest = last_done[k]
277  del last_done[k]
278  except KeyError:
279  old_dest = None
280  update(k,to_do[k],old_dest,syml,logdir)
281  # remove files that were copied but are not anymore in the list
282  for old_dest in last_done.values():
283  remove(old_dest,logdir)
284  logfile.set_dest(src,to_do) # update log
285 
286 def uninstall(logfile, destinations = [], logdir=realpath(".")):
287  """
288  Remove copied files using logfile to know what to remove.
289  If destinations is not empty, only the files/directories specified are
290  removed.
291  """
292  for s in logfile.get_sources():
293  dest = logfile.get_dest(s)
294  if type(dest) is str:
295  if filename_match(dest,destinations,default=True):
296  remove(dest, logdir)
297  logfile.remove(s)
298  else:
299  for subs in dest.keys():
300  subdest = dest[subs]
301  if filename_match(subdest,destinations,default=True):
302  remove(subdest,logdir)
303  del dest[subs]
304  if not dest:
305  logfile.remove(s)
306 
307 if __name__ == "__main__":
308  main()
def remove(file, logdir)
Definition: install.py:153
def getCommonPath(dirname, filename)
Definition: install.py:176
def expand_source_dir
Definition: install.py:125
def filename_match
Definition: install.py:115
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition: MsgStream.cpp:119
def uninstall
Definition: install.py:286
def update
Definition: install.py:213
def getRelativePath(dirname, filename)
Definition: install.py:195
def dirname(url)
string type
Definition: gaudirun.py:151
def install
Definition: install.py:246