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