The Gaudi Framework  v29r0 (ff2e7097)
ZipPythonDir.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # file ZipPythonDir.py
4 # Script to generate a zip file that can replace a directory in the python path.
5 
6 import os
7 import sys
8 import zipfile
9 import logging
10 import stat
11 import time
12 import re
13 import codecs
14 from StringIO import StringIO
15 
16 # Class for generic exception coming from the zipdir() function
17 
18 
19 class ZipdirError(RuntimeError):
20  pass
21 
22 # Collect the changes to be applied to the zip file.
23 #
24 # @param directory: directory to be packed in the zip file
25 # @param infolist: list of ZipInfo objects already contained in the zip archive
26 #
27 # @return: tuple of (added, modified, untouched, removed) entries in the directory with respect to the zip file
28 #
29 
30 
31 def _zipChanges(directory, infolist):
32  # gets the dates of the files in the zip archive
33  infos = {}
34  for i in infolist:
35  fn = i.filename
36  if fn.endswith(".pyc"):
37  fn = fn[:-1]
38  infos[fn] = i.date_time
39 
40  # gets the changes
41  added = []
42  modified = []
43  untouched = []
44  removed = []
45  all_files = set()
46 
47  log = logging.getLogger("zipdir")
48  dirlen = len(directory) + 1
49  for root, dirs, files in os.walk(directory):
50  if "lib-dynload" in dirs:
51  # exclude the directory containing binary modules
52  dirs.remove("lib-dynload")
53  arcdir = root[dirlen:]
54  for f in files:
55  ext = os.path.splitext(f)[1]
56  if ext == ".py": # extensions that can enter the zip file
57  filename = os.path.join(arcdir, f)
58  all_files.add(filename)
59  if filename not in infos:
60  action = "A"
61  added.append(filename)
62  else:
63  filetime = time.localtime(
64  os.stat(os.path.join(directory, filename))[stat.ST_MTIME])[:6]
65  if filetime > infos[filename]:
66  action = "M"
67  modified.append(filename)
68  else:
69  action = "U"
70  untouched.append(filename)
71  if action in ['U']:
72  log.debug(" %s -> %s", action, filename)
73  else:
74  log.info(" %s -> %s", action, filename)
75  # cases that can be ignored
76  elif (ext not in [".pyc", ".pyo", ".stamp", ".cmtref", ".confdb"]
77  and not f.startswith('.__afs')):
78  raise ZipdirError(
79  "Cannot add '%s' to the zip file, only '.py' are allowed." % os.path.join(arcdir, f))
80  # check for removed files
81  for filename in infos:
82  if filename not in all_files:
83  removed.append(filename)
84  log.info(" %s -> %s", "R", filename)
85  return (added, modified, untouched, removed)
86 
87 
88 def checkEncoding(fileObj):
89  '''
90  Check that a file honors the declared encoding (default ASCII for Python 2
91  and UTF-8 for Python 3).
92 
93  Raises a UnicodeDecodeError in case of decoding problems and LookupError if
94  the specified codec does not exists.
95 
96  See http://www.python.org/dev/peps/pep-0263/
97  '''
98  from itertools import islice
99 
100  # default encoding
101  if sys.version_info[0] <= 2:
102  enc = 'ascii'
103  else:
104  enc = 'utf-8'
105 
106  # find the encoding of the file, if specified (in the first two lines)
107  enc_exp = re.compile(r"coding[:=]\s*([-\w.]+)")
108  for l in islice(fileObj, 2):
109  m = enc_exp.search(l)
110  if m:
111  enc = m.group(1)
112  break
113 
114  if hasattr(fileObj, 'name'):
115  logging.getLogger('checkEncoding').debug('checking encoding %s on %s',
116  enc, fileObj.name)
117  else:
118  logging.getLogger('checkEncoding').debug('checking encoding %s on file object',
119  enc)
120  # try to read the file with the declared encoding
121  fileObj.seek(0)
122  codecs.getreader(enc)(fileObj).read()
123 
124 
125 # Make a zip file out of a directory containing python modules
126 def zipdir(directory, no_pyc=False):
127  filename = os.path.realpath(directory + ".zip")
128  log = logging.getLogger("zipdir")
129  if not os.path.isdir(directory):
130  log.warning('directory %s missing, creating empty .zip file', directory)
131  open(filename, "ab").close()
132  return
133  msg = "Zipping directory '%s'"
134  if no_pyc:
135  msg += " (without pre-compilation)"
136  log.info(msg, directory)
137 
138  # Open the file in read an update mode
139  if os.path.exists(filename):
140  zipFile = open(filename, "r+b")
141  else:
142  # If the file does not exist, we need to create it.
143  # "append mode" ensures that, in case of two processes trying to
144  # create the file, they do not truncate each other file
145  zipFile = open(filename, "ab")
146 
147  try:
148  if zipfile.is_zipfile(filename):
149  infolist = zipfile.ZipFile(filename).infolist()
150  else:
151  infolist = []
152  (added, modified, untouched, removed) = _zipChanges(directory, infolist)
153  if added or modified or removed:
154  tempBuf = StringIO()
155  z = zipfile.PyZipFile(tempBuf, "w", zipfile.ZIP_DEFLATED)
156  for f in added + modified + untouched:
157  src = os.path.join(directory, f)
158  checkEncoding(open(src, 'rb'))
159  if no_pyc:
160  log.debug("adding '%s'", f)
161  z.write(src, f)
162  else:
163  # Remove the .pyc file to always force a re-compilation
164  if os.path.exists(src + 'c'):
165  log.debug("removing old .pyc for '%s'", f)
166  os.remove(src + 'c')
167  log.debug("adding '%s'", f)
168  z.writepy(src, os.path.dirname(f))
169  z.close()
170  zipFile.seek(0)
171  zipFile.write(tempBuf.getvalue())
172  zipFile.truncate()
173  log.info("File '%s' closed", filename)
174  else:
175  log.info("Nothing to do on '%s'", filename)
176  except UnicodeDecodeError, x:
177  log.error("Wrong encoding in file '%s':", src)
178  log.error(" %s", x)
179  log.error("Probably you forgot the line '# -*- coding: utf-8 -*-'")
180  sys.exit(1)
181  finally:
182  zipFile.close()
183 
184 # Main function of the script.
185 # Parse arguments and call zipdir() for each directory passed as argument
186 
187 
188 def main(argv=None):
189  from optparse import OptionParser
190  parser = OptionParser(usage="%prog [options] directory1 [directory2 ...]")
191  parser.add_option("--no-pyc", action="store_true",
192  help="copy the .py files without pre-compiling them")
193  parser.add_option("--quiet", action="store_true",
194  help="do not print info messages")
195  parser.add_option("--debug", action="store_true",
196  help="print debug messages (has priority over --quiet)")
197 
198  if argv is None:
199  argv = sys.argv
200  opts, args = parser.parse_args(argv[1:])
201 
202  if not args:
203  parser.error("Specify at least one directory to zip")
204 
205  # Initialize the logging module
206  level = logging.INFO
207  if opts.quiet:
208  level = logging.WARNING
209  if opts.debug:
210  level = logging.DEBUG
211  logging.basicConfig(level=level)
212 
213  # zip all the directories passed as arguments
214  for d in args:
215  zipdir(d, opts.no_pyc)
216 
217 
218 if __name__ == '__main__':
219  main()
def checkEncoding(fileObj)
Definition: ZipPythonDir.py:88
def read(f, regex='.*', skipevents=0)
Definition: hivetimeline.py:22
def main(argv=None)
def _zipChanges(directory, infolist)
Definition: ZipPythonDir.py:31
def zipdir(directory, no_pyc=False)