Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(44)

Side by Side Diff: recipe_modules/file/api.py

Issue 2918463002: [recipe_modules/file] add real file module to engine. (Closed)
Patch Set: fix rmtree Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2017 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file.
4
5 from recipe_engine import recipe_api
6
7
8 import os
9 import fnmatch
10
11 # TODO(iannucci): Introduce the concept of a 'native step' and implement these
12 # directly in the current python interpreter without the need for a subprocess
13 # invocation.
14
15 class FileApi(recipe_api.RecipeApi):
16 """FileApi contains helper functions for reading and writing files."""
17
18 class Error(recipe_api.InfraFailure):
19 """Error is an InfraFailure, except that it also contains an errno field
20 indicating the errno name (i.e. 'EEXIST') of the underlying error.
21 """
22 def __init__(self, step_name, errno_name, message):
23 reason = 'Step(%r) failed %r with: %s' % (step_name, errno_name, message)
24 super(FileApi.Error, self).__init__(reason)
25 self.errno_name = errno_name
26
27 def _run(self, name, args, timeout, step_test_data=None, stdout=None):
28 if not step_test_data:
29 step_test_data = self.test_api.errno
30 args = [
31 '--json-output', self.m.json.output(name='main', add_json_log=False)
32 ] + args
33 result = self.m.python(
34 name, self.resource('fileutil.py'), args=args,
35 timeout=timeout, step_test_data=step_test_data, stdout=stdout,
36 infra_step=True)
37 j = result.json.outputs['main']
dnj 2017/05/31 16:50:40 Only one output, WDYT about naming default?
iannucci 2017/05/31 22:59:26 at first I thought that other things would need th
38 if not j['ok']:
39 # pylint thinks this isn't a standard exception... silly pylint.
40 # pylint: disable=nonstandard-exception
41 raise self.Error(name, j['errno_name'], j['message'])
42 return result
43
44 def copy(self, name, source, dest, timeout=None):
45 """Copies a file (including mode bits) from source to destination on the
46 local filesystem.
47
48 Behaves identically to shutil.copy.
49
50 Args:
51 name (str) - The name of the step.
52 source (Path) - The path to the file you want to copy.
53 dest (Path) - The path to the destination file name. If this path exists
54 and is a directory, the basename of `source` will be appended to derive
55 a path to a destination file.
56 timeout (int) - Maximum number of seconds to allow this command to run.
57
58 Raises file.Error
59 """
60 self.m.path.assert_absolute(source)
61 self.m.path.assert_absolute(dest)
62 self._run(name, ['copy', source, dest], timeout)
63 self.m.path.mock_copy_paths(source, dest)
64
65 def copytree(self, name, source, dest, symlinks=False, timeout=None):
66 """Recursively copies a directory tree.
67
68 Behaves identically to shutil.copytree.
69 `dest` must not exist.
70
71 Args:
72 name (str) - The name of the step.
73 source (Path) - The path of the directory to copy.
74 dest (Path) - The place where you want the recursive copy to show up. This
75 must not already exist.
76 timeout (int) - Maximum number of seconds to allow this command to run.
dnj 2017/05/31 16:50:40 nit: Document "symlinks"
iannucci 2017/05/31 22:59:26 Done.
77
78 Raises file.Error
79 """
80 self.m.path.assert_absolute(source)
81 self.m.path.assert_absolute(dest)
82 prefix = ['--symlinks'] if symlinks else []
83 self._run(name, prefix+['copytree', source, dest], timeout)
84 self.m.path.mock_copy_paths(source, dest)
85
86 def move(self, name, source, dest):
87 """Moves a file or directory.
88
89 Behaves identically to shutil.move.
90
91 Args:
92 name (str) - The name of the step.
93 source (Path) - The path of the item to move.
94 dest (Path) - The new name of the item.
95
96 Raises file.Error
97 """
98 self.m.path.assert_absolute(source)
99 self.m.path.assert_absolute(dest)
100 self._run(name, ['move', source, dest], timeout=5)
dnj 2017/05/31 16:50:40 This timeout might actually be inadequate if it's
iannucci 2017/05/31 22:59:26 blergh, true. Removed.
101 self.m.path.mock_copy_paths(source, dest)
102 self.m.path.mock_remove_paths(source)
103
104 def read_text(self, name, source, test_data=''):
105 """Reads a file as UTF-8 encoded text.
106
107 Args:
108 name (str) - The name of the step.
109 source (Path) - The path of the file to read.
110 test_data (str) - Some default data for this step to return when running
111 under simulation.
112
113 Returns (str) - The content of the file.
114
115 Raises file.Error
116 """
117 self.m.path.assert_absolute(source)
118 step_test_data = lambda: self.test_api.read_text(test_data)
119 result = self._run(name, ['copy', source, self.m.raw_io.output_text()],
120 timeout=30, step_test_data=step_test_data)
121 return result.raw_io.output_text
122
123 def write_text(self, name, dest, text_data):
124 """Write the given UTF-8 encoded `text_data` to `dest`.
125
126 Args:
127 name (str) - The name of the step.
128 dest (Path) - The path of the file to write.
129 text_data (str) - The UTF-8 encoded data to write.
130
131 Raises file.Error.
132 """
133 self.m.path.assert_absolute(dest)
134 self._run(name, ['copy', self.m.raw_io.input_text(text_data), dest],
135 timeout=30)
136 self.m.path.mock_add_paths(dest)
137
138 def glob_paths(self, name, pattern, test_data=(), timeout=None):
139 """Performs glob expansion on `pattern`.
140
141 glob rules for `pattern` follow the same syntax as for the python `glob`
142 stdlib module.
143
144 Args:
145 name (str) - The name of the step.
146 pattern (str|Path) - The glob pattern to expand.
147 test_data (iterable[str|Path]) - Some default paths for this step to
148 return when running under simulation.
149 timeout (int) - Maximum number of seconds to allow this command to run.
150
151 Returns list[Path] - All paths found.
152
153 Raises file.Error.
154 """
155 ret = self._run(
156 name, ['glob', pattern], timeout,
157 lambda: self.test_api.glob_paths(test_data),
158 self.m.raw_io.output_text())
159 return map(self.m.path.abs_to_path, ret.stdout.splitlines())
160
161 def remove(self, name, source):
162 """Remove a file.
163
164 Args:
165 name (str) - The name of the step.
166 source (Path) - The file to remove.
167
168 Raises file.Error.
169 """
170 self.m.path.assert_absolute(source)
171 self._run(name, ['remove', source], timeout=5)
172 self.m.path.mock_remove_paths(source)
173
174 def listdir(self, name, source, test_data=(), timeout=None):
175 """List all files inside a directory.
176
177 Args:
178 name (str) - The name of the step.
179 source (Path) - The directory to list.
180 test_data (iterable[str]) - Some default data for this step to return when
181 running under simulation.
182 timeout (int) - Maximum number of seconds to allow this command to run.
dnj 2017/05/31 16:50:40 Is timeout useful here? I can't think of a meaning
183
184 Returns list[str] - The names of the entries in the source directory. Note
dnj 2017/05/31 16:50:40 QQ: why not Path objects?
iannucci 2017/05/31 22:59:26 Because the usage of listdir frequently deals with
185 that this is just the names of the files/folders, NOT Path objects. If you
186 want Path objects, join these with `source`, and use the path module's
187 `abs_to_path()` method.
188
189 Raises file.Error.
190 """
191 self.m.path.assert_absolute(source)
192 ret = self._run(
193 name, ['listdir', source], timeout,
194 lambda: self.test_api.listdir(test_data),
195 self.m.raw_io.output_text())
196 return ret.stdout.splitlines()
197
198 def ensure_directory(self, name, dest, mode=0777):
199 """Ensures that `dest` exists and is a directory.
200
201 Args:
202 name (str) - The name of the step.
203 dest (Path) - The directory to ensure.
204 mode (int) - The mode to use if the directory doesn't exist. This method
205 does not ensure the mode if the directory already exists (if you need
206 that behaviour, file a bug).
207
208 Raises file.Error if the path exists but is not a directory.
209 """
210 self.m.path.assert_absolute(dest)
211 self._run(
212 name, ['ensure-directory', '--mode', oct(mode), dest], timeout=5)
213 self.m.path.mock_add_paths(dest)
214
215 def rmtree(self, name, source, timeout=None):
216 """Recursively removes a directory.
217
218 This uses a native python on Linux/Mac, and uses `rd` on Windows to avoid
219 issues w.r.t. path lengths and read-only attributes. If the directory is
220 gone already, this returns without error.
221
222 Args:
223 name (str) - The name of the step.
224 source (Path) - The directory to remove.
225 timeout (int) - Maximum number of seconds to allow this command to run.
226
227 Raises file.Error.
228 """
229 self.m.path.assert_absolute(source)
230 self._run(name, ['rmtree', source], timeout)
231 self.m.path.mock_remove_paths(str(source))
232
233 def rmcontents(self, name, source, timeout=None):
234 """Similar to rmtree, but removes only contents not the directory.
235
236 This is useful e.g. when removing contents of current working directory.
237 Deleting current working directory makes all further getcwd calls fail
238 until chdir is called. chdir would be tricky in recipes, so we provide
239 a call that doesn't delete the directory itself.
240
241 Args:
242 name (str) - The name of the step.
243 source (Path) - The directory whose contents should be removed.
244 timeout (int) - Maximum number of seconds to allow this command to run.
245
246 Raises file.Error.
247 """
248 self.m.path.assert_absolute(source)
249 self._run(name, ['rmcontents', source], timeout)
250 self.m.path.mock_remove_paths(str(source)+self.m.path.sep)
251
252 def rmglob(self, name, source, pattern, timeout=None):
253 """Removes all entries in `source` matching the glob `pattern`.
254
255 Args:
256 name (str) - The name of the step.
257 source (Path) - The directory whose contents should be filtered and
258 removed.
259 pattern (str) - The glob pattern to apply under `source`. Anything
260 matching this pattern will be removed.
261 timeout (int) - Maximum number of seconds to allow this command to run.
262
263 Raises file.Error.
264 """
265 self.m.path.assert_absolute(source)
266 self._run(name, ['rmglob', source, pattern], timeout)
267
268 src = str(source)
269 def filt(p):
270 assert p.startswith(src), (src, p)
271 return fnmatch.fnmatch(p[len(src)+1:].split(os.path.sep)[0], pattern)
272 self.m.path.mock_remove_paths(str(source), filt)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698