| OLD | NEW |
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Utilities to work with importable python zip packages.""" | 5 """Utilities to work with importable python zip packages.""" |
| 6 | 6 |
| 7 import atexit | 7 import atexit |
| 8 import collections | 8 import collections |
| 9 import cStringIO as StringIO | 9 import cStringIO as StringIO |
| 10 import hashlib | 10 import hashlib |
| (...skipping 17 matching lines...) Expand all Loading... |
| 28 r'.*\.pyc$', | 28 r'.*\.pyc$', |
| 29 r'.*\.pyo$', | 29 r'.*\.pyo$', |
| 30 ) | 30 ) |
| 31 | 31 |
| 32 | 32 |
| 33 # Temporary files extracted by extract_resource. Removed in atexit hook. | 33 # Temporary files extracted by extract_resource. Removed in atexit hook. |
| 34 _extracted_files = [] | 34 _extracted_files = [] |
| 35 _extracted_files_lock = threading.Lock() | 35 _extracted_files_lock = threading.Lock() |
| 36 | 36 |
| 37 | 37 |
| 38 # Patch zipimport.zipimporter hook to accept unicode strings |
| 39 def zipimporter_unicode(archivepath): |
| 40 if isinstance(archivepath, unicode): |
| 41 archivepath = archivepath.encode(sys.getfilesystemencoding()) |
| 42 return zipimport.zipimporter(archivepath) |
| 43 |
| 44 |
| 45 for i, hook in enumerate(sys.path_hooks): |
| 46 if hook is zipimport.zipimporter: |
| 47 sys.path_hooks[i] = zipimporter_unicode |
| 48 |
| 49 |
| 38 class ZipPackageError(RuntimeError): | 50 class ZipPackageError(RuntimeError): |
| 39 """Failed to create a zip package.""" | 51 """Failed to create a zip package.""" |
| 40 | 52 |
| 41 | 53 |
| 42 class ZipPackage(object): | 54 class ZipPackage(object): |
| 43 """A set of files that can be zipped to file on disk or into memory buffer. | 55 """A set of files that can be zipped to file on disk or into memory buffer. |
| 44 | 56 |
| 45 Usage: | 57 Usage: |
| 46 package = ZipPackage(root) | 58 package = ZipPackage(root) |
| 47 package.add_file('some_file.py', '__main__.py') | 59 package.add_file('some_file.py', '__main__.py') |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 231 def get_main_script_path(): | 243 def get_main_script_path(): |
| 232 """If running from zip returns path to a zip file, else path to __main__. | 244 """If running from zip returns path to a zip file, else path to __main__. |
| 233 | 245 |
| 234 Basically returns path to a file passed to python for execution | 246 Basically returns path to a file passed to python for execution |
| 235 as in 'python <main_script>' considering a case of executable zip package. | 247 as in 'python <main_script>' considering a case of executable zip package. |
| 236 | 248 |
| 237 Returns path relative to a current directory of when process was started. | 249 Returns path relative to a current directory of when process was started. |
| 238 """ | 250 """ |
| 239 # If running from interactive console __file__ is not defined. | 251 # If running from interactive console __file__ is not defined. |
| 240 main = sys.modules['__main__'] | 252 main = sys.modules['__main__'] |
| 241 return get_module_zip_archive(main) or getattr(main, '__file__', None) | 253 path = get_module_zip_archive(main) |
| 254 if path: |
| 255 return path |
| 256 |
| 257 path = getattr(main, '__file__', None) |
| 258 if path: |
| 259 return path.decode(sys.getfilesystemencoding()) |
| 242 | 260 |
| 243 | 261 |
| 244 def _write_temp_data(name, data, temp_dir): | 262 def _write_temp_data(name, data, temp_dir): |
| 245 """Writes content-addressed file in `temp_dir` if relevant.""" | 263 """Writes content-addressed file in `temp_dir` if relevant.""" |
| 246 filename = '%s-%s' % (hashlib.sha1(data).hexdigest(), name) | 264 filename = '%s-%s' % (hashlib.sha1(data).hexdigest(), name) |
| 247 filepath = os.path.join(temp_dir, filename) | 265 filepath = os.path.join(temp_dir, filename) |
| 248 if os.path.isfile(filepath): | 266 if os.path.isfile(filepath): |
| 249 with open(filepath, 'rb') as f: | 267 with open(filepath, 'rb') as f: |
| 250 if f.read() == data: | 268 if f.read() == data: |
| 251 # It already exists. | 269 # It already exists. |
| (...skipping 20 matching lines...) Expand all Loading... |
| 272 package: is a python module object that represents a package. | 290 package: is a python module object that represents a package. |
| 273 resource: should be a relative filename, using '/'' as the path separator. | 291 resource: should be a relative filename, using '/'' as the path separator. |
| 274 temp_dir: if set, it will extra the file in this directory with the filename | 292 temp_dir: if set, it will extra the file in this directory with the filename |
| 275 being the hash of the content. Otherwise, it uses tempfile.mkstemp(). | 293 being the hash of the content. Otherwise, it uses tempfile.mkstemp(). |
| 276 | 294 |
| 277 Raises ValueError if no such resource. | 295 Raises ValueError if no such resource. |
| 278 """ | 296 """ |
| 279 # For regular non-zip packages just construct an absolute path. | 297 # For regular non-zip packages just construct an absolute path. |
| 280 if not is_zipped_module(package): | 298 if not is_zipped_module(package): |
| 281 # Package's __file__ attribute is always an absolute path. | 299 # Package's __file__ attribute is always an absolute path. |
| 282 path = os.path.join(os.path.dirname(package.__file__), | 300 ppath = package.__file__.decode(sys.getfilesystemencoding()) |
| 301 path = os.path.join(os.path.dirname(ppath), |
| 283 resource.replace('/', os.sep)) | 302 resource.replace('/', os.sep)) |
| 284 if not os.path.exists(path): | 303 if not os.path.exists(path): |
| 285 raise ValueError('No such resource in %s: %s' % (package, resource)) | 304 raise ValueError('No such resource in %s: %s' % (package, resource)) |
| 286 return path | 305 return path |
| 287 | 306 |
| 288 # For zipped packages extract the resource into a temp file. | 307 # For zipped packages extract the resource into a temp file. |
| 289 data = pkgutil.get_data(package.__name__, resource) | 308 data = pkgutil.get_data(package.__name__, resource) |
| 290 if data is None: | 309 if data is None: |
| 291 raise ValueError('No such resource in zipped %s: %s' % (package, resource)) | 310 raise ValueError('No such resource in zipped %s: %s' % (package, resource)) |
| 292 | 311 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 336 h = hashlib.sha1() | 355 h = hashlib.sha1() |
| 337 with zipfile.ZipFile(get_main_script_path(), 'r') as z: | 356 with zipfile.ZipFile(get_main_script_path(), 'r') as z: |
| 338 for name in sorted(z.namelist()): | 357 for name in sorted(z.namelist()): |
| 339 with z.open(name) as f: | 358 with z.open(name) as f: |
| 340 h.update(str(len(name))) | 359 h.update(str(len(name))) |
| 341 h.update(name) | 360 h.update(name) |
| 342 content = f.read() | 361 content = f.read() |
| 343 h.update(str(len(content))) | 362 h.update(str(len(content))) |
| 344 h.update(content) | 363 h.update(content) |
| 345 return h.hexdigest() | 364 return h.hexdigest() |
| OLD | NEW |