| OLD | NEW |
| (Empty) |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """Miscellaneous utility functions.""" | |
| 6 | |
| 7 | |
| 8 import contextlib | |
| 9 import errno | |
| 10 import json | |
| 11 import os | |
| 12 import shutil | |
| 13 import subprocess | |
| 14 import sys | |
| 15 import tempfile | |
| 16 import time | |
| 17 | |
| 18 | |
| 19 def read_json_as_utf8(filename=None, text=None): | |
| 20 """Read and deserialize a json file or string. | |
| 21 | |
| 22 This function is different from json.load and json.loads in that it | |
| 23 returns utf8-encoded string for keys and values instead of unicode. | |
| 24 | |
| 25 Args: | |
| 26 filename (str): path of a file to parse | |
| 27 text (str): json string to parse | |
| 28 | |
| 29 ``filename`` and ``text`` are mutually exclusive. ValueError is raised if | |
| 30 both are provided. | |
| 31 """ | |
| 32 | |
| 33 if filename is not None and text is not None: | |
| 34 raise ValueError('Only one of "filename" and "text" can be provided at ' | |
| 35 'the same time') | |
| 36 | |
| 37 if filename is None and text is None: | |
| 38 raise ValueError('One of "filename" and "text" must be provided') | |
| 39 | |
| 40 def to_utf8(obj): | |
| 41 if isinstance(obj, dict): | |
| 42 return {to_utf8(key): to_utf8(value) for key, value in obj.iteritems()} | |
| 43 if isinstance(obj, list): | |
| 44 return [to_utf8(item) for item in obj] | |
| 45 if isinstance(obj, unicode): | |
| 46 return obj.encode('utf-8') | |
| 47 return obj | |
| 48 | |
| 49 if filename: | |
| 50 with open(filename, 'rb') as f: | |
| 51 obj = json.load(f) | |
| 52 else: | |
| 53 obj = json.loads(text) | |
| 54 | |
| 55 return to_utf8(obj) | |
| 56 | |
| 57 | |
| 58 # TODO(hinoka): Add tests crbug.com/500781 | |
| 59 def rmtree(file_path): # pragma: no cover | |
| 60 """Recursively removes a directory, even if it's marked read-only. | |
| 61 | |
| 62 Remove the directory located at file_path, if it exists. | |
| 63 | |
| 64 shutil.rmtree() doesn't work on Windows if any of the files or directories | |
| 65 are read-only, which svn repositories and some .svn files are. We need to | |
| 66 be able to force the files to be writable (i.e., deletable) as we traverse | |
| 67 the tree. | |
| 68 | |
| 69 Even with all this, Windows still sometimes fails to delete a file, citing | |
| 70 a permission error (maybe something to do with antivirus scans or disk | |
| 71 indexing). The best suggestion any of the user forums had was to wait a | |
| 72 bit and try again, so we do that too. It's hand-waving, but sometimes it | |
| 73 works. :/ | |
| 74 """ | |
| 75 if not os.path.exists(file_path): | |
| 76 return | |
| 77 | |
| 78 if sys.platform == 'win32': | |
| 79 # Give up and use cmd.exe's rd command. | |
| 80 file_path = os.path.normcase(file_path) | |
| 81 for _ in xrange(3): | |
| 82 if not subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', file_path]): | |
| 83 break | |
| 84 time.sleep(3) | |
| 85 return | |
| 86 | |
| 87 def remove_with_retry(rmfunc, path): | |
| 88 if os.path.islink(path): | |
| 89 return os.remove(path) | |
| 90 else: | |
| 91 return rmfunc(path) | |
| 92 | |
| 93 def rmtree_on_error(function, _, excinfo): | |
| 94 """This works around a problem whereby python 2.x on Windows has no ability | |
| 95 to check for symbolic links. os.path.islink always returns False. But | |
| 96 shutil.rmtree will fail if invoked on a symbolic link whose target was | |
| 97 deleted before the link. E.g., reproduce like this: | |
| 98 > mkdir test | |
| 99 > mkdir test\1 | |
| 100 > mklink /D test\current test\1 | |
| 101 > python -c "import infra_libs; infra_libs.rmtree('test')" | |
| 102 To avoid this issue, we pass this error-handling function to rmtree. If | |
| 103 we see the exact sort of failure, we ignore it. All other failures we re- | |
| 104 raise. | |
| 105 """ | |
| 106 | |
| 107 exception_type = excinfo[0] | |
| 108 exception_value = excinfo[1] | |
| 109 # If shutil.rmtree encounters a symbolic link on Windows, os.listdir will | |
| 110 # fail with a WindowsError exception with an ENOENT errno (i.e., file not | |
| 111 # found). We'll ignore that error. Note that WindowsError is not defined | |
| 112 # for non-Windows platforms, so we use OSError (of which it is a subclass) | |
| 113 # to avoid lint complaints about an undefined global on non-Windows | |
| 114 # platforms. | |
| 115 if (function is os.listdir) and issubclass(exception_type, OSError): | |
| 116 if exception_value.errno != errno.ENOENT: | |
| 117 raise | |
| 118 else: | |
| 119 raise | |
| 120 | |
| 121 for root, dirs, files in os.walk(file_path, topdown=False): | |
| 122 # For POSIX: making the directory writable guarantees removability. | |
| 123 # Windows will ignore the non-read-only bits in the chmod value. | |
| 124 os.chmod(root, 0770) | |
| 125 for name in files: | |
| 126 remove_with_retry(os.remove, os.path.join(root, name)) | |
| 127 for name in dirs: | |
| 128 remove_with_retry(lambda p: shutil.rmtree(p, onerror=rmtree_on_error), | |
| 129 os.path.join(root, name)) | |
| 130 | |
| 131 remove_with_retry(os.rmdir, file_path) | |
| 132 | |
| 133 | |
| 134 # We're trying to be compatible with Python3 tempfile.TemporaryDirectory | |
| 135 # context manager here. And they used 'dir' as a keyword argument. | |
| 136 # pylint: disable=redefined-builtin | |
| 137 @contextlib.contextmanager | |
| 138 def temporary_directory(suffix="", prefix="tmp", dir=None, | |
| 139 keep_directory=False): | |
| 140 """Create and return a temporary directory. This has the same | |
| 141 behavior as mkdtemp but can be used as a context manager. For | |
| 142 example: | |
| 143 | |
| 144 with temporary_directory() as tmpdir: | |
| 145 ... | |
| 146 | |
| 147 Upon exiting the context, the directory and everything contained | |
| 148 in it are removed. | |
| 149 | |
| 150 Args: | |
| 151 suffix, prefix, dir: same arguments as for tempfile.mkdtemp. | |
| 152 keep_directory (bool): if True, do not delete the temporary directory | |
| 153 when exiting. Useful for debugging. | |
| 154 | |
| 155 Returns: | |
| 156 tempdir (str): full path to the temporary directory. | |
| 157 """ | |
| 158 tempdir = None # Handle mkdtemp raising an exception | |
| 159 try: | |
| 160 tempdir = tempfile.mkdtemp(suffix, prefix, dir) | |
| 161 yield tempdir | |
| 162 | |
| 163 finally: | |
| 164 if tempdir and not keep_directory: # pragma: no branch | |
| 165 try: | |
| 166 # TODO(pgervais,496347) Make this work reliably on Windows. | |
| 167 shutil.rmtree(tempdir, ignore_errors=True) | |
| 168 except OSError as ex: # pragma: no cover | |
| 169 print >> sys.stderr, ( | |
| 170 "ERROR: {!r} while cleaning up {!r}".format(ex, tempdir)) | |
| OLD | NEW |