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 |