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

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

Issue 2918463002: [recipe_modules/file] add real file module to engine. (Closed)
Patch Set: fix stuff 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, 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()
dnj 2017/05/31 23:58:58 Still think this should be "add_json_log=False". I
iannucci 2017/06/01 00:31:03 changed to step_text only when it fails
32 ] + args
33 result = self.m.python(
34 name, self.resource('fileutil.py'), args=args,
35 step_test_data=step_test_data, stdout=stdout,
36 infra_step=True)
37 j = result.json.output
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):
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
57 Raises file.Error
58 """
59 self.m.path.assert_absolute(source)
60 self.m.path.assert_absolute(dest)
61 self._run(name, ['copy', source, dest])
62 self.m.path.mock_copy_paths(source, dest)
63
64 def copytree(self, name, source, dest, symlinks=False):
65 """Recursively copies a directory tree.
66
67 Behaves identically to shutil.copytree.
68 `dest` must not exist.
69
70 Args:
71 name (str) - The name of the step.
72 source (Path) - The path of the directory to copy.
73 dest (Path) - The place where you want the recursive copy to show up. This
74 must not already exist.
75 symlinks (bool) - Preserve symlinks. No effect on Windows.
76
77 Raises file.Error
78 """
79 self.m.path.assert_absolute(source)
80 self.m.path.assert_absolute(dest)
81 prefix = ['--symlinks'] if symlinks else []
82 self._run(name, prefix+['copytree', source, dest])
83 self.m.path.mock_copy_paths(source, dest)
84
85 def move(self, name, source, dest):
86 """Moves a file or directory.
87
88 Behaves identically to shutil.move.
89
90 Args:
91 name (str) - The name of the step.
92 source (Path) - The path of the item to move.
93 dest (Path) - The new name of the item.
94
95 Raises file.Error
96 """
97 self.m.path.assert_absolute(source)
98 self.m.path.assert_absolute(dest)
99 self._run(name, ['move', source, dest])
100 self.m.path.mock_copy_paths(source, dest)
101 self.m.path.mock_remove_paths(source)
102
103 def read_text(self, name, source, test_data=''):
104 """Reads a file as UTF-8 encoded text.
105
106 Args:
107 name (str) - The name of the step.
108 source (Path) - The path of the file to read.
109 test_data (str) - Some default data for this step to return when running
110 under simulation.
111
112 Returns (str) - The content of the file.
113
114 Raises file.Error
115 """
116 self.m.path.assert_absolute(source)
117 step_test_data = lambda: self.test_api.read_text(test_data)
118 result = self._run(name, ['copy', source, self.m.raw_io.output_text()],
119 step_test_data=step_test_data)
120 return result.raw_io.output_text
121
122 def write_text(self, name, dest, text_data):
123 """Write the given UTF-8 encoded `text_data` to `dest`.
124
125 Args:
126 name (str) - The name of the step.
127 dest (Path) - The path of the file to write.
128 text_data (str) - The UTF-8 encoded data to write.
129
130 Raises file.Error.
131 """
132 self.m.path.assert_absolute(dest)
133 self._run(name, ['copy', self.m.raw_io.input_text(text_data), dest])
134 self.m.path.mock_add_paths(dest)
135
136 def glob_paths(self, name, pattern, test_data=()):
137 """Performs glob expansion on `pattern`.
138
139 glob rules for `pattern` follow the same syntax as for the python `glob`
140 stdlib module.
141
142 Args:
143 name (str) - The name of the step.
144 pattern (str|Path) - The glob pattern to expand.
145 test_data (iterable[str|Path]) - Some default paths for this step to
146 return when running under simulation.
147
148 Returns list[Path] - All paths found.
149
150 Raises file.Error.
151 """
152 ret = self._run(
153 name, ['glob', pattern],
154 lambda: self.test_api.glob_paths(test_data),
155 self.m.raw_io.output_text())
156 return map(self.m.path.abs_to_path, ret.stdout.splitlines())
157
158 def remove(self, name, source):
159 """Remove a file.
160
161 Args:
162 name (str) - The name of the step.
163 source (Path) - The file to remove.
164
165 Raises file.Error.
166 """
167 self.m.path.assert_absolute(source)
168 self._run(name, ['remove', source])
169 self.m.path.mock_remove_paths(source)
170
171 def listdir(self, name, source, test_data=()):
172 """List all files inside a directory.
173
174 Args:
175 name (str) - The name of the step.
176 source (Path) - The directory to list.
177 test_data (iterable[str]) - Some default data for this step to return when
178 running under simulation.
179
180 Returns list[Path]
181
182 Raises file.Error.
183 """
184 self.m.path.assert_absolute(source)
185 ret = self._run(
186 name, ['listdir', source],
187 lambda: self.test_api.listdir(test_data),
188 self.m.raw_io.output_text())
189 return map(self.m.path.abs_to_path, ret.stdout.splitlines())
dnj 2017/05/31 23:58:58 If the process returns abspath, the test data will
iannucci 2017/06/01 00:31:03 Done.
190
191 def ensure_directory(self, name, dest, mode=0777):
192 """Ensures that `dest` exists and is a directory.
193
194 Args:
195 name (str) - The name of the step.
196 dest (Path) - The directory to ensure.
197 mode (int) - The mode to use if the directory doesn't exist. This method
198 does not ensure the mode if the directory already exists (if you need
199 that behaviour, file a bug).
200
201 Raises file.Error if the path exists but is not a directory.
202 """
203 self.m.path.assert_absolute(dest)
204 self._run(
205 name, ['ensure-directory', '--mode', oct(mode), dest])
206 self.m.path.mock_add_paths(dest)
207
208 def rmtree(self, name, source):
209 """Recursively removes a directory.
210
211 This uses a native python on Linux/Mac, and uses `rd` on Windows to avoid
212 issues w.r.t. path lengths and read-only attributes. If the directory is
213 gone already, this returns without error.
214
215 Args:
216 name (str) - The name of the step.
217 source (Path) - The directory to remove.
218
219 Raises file.Error.
220 """
221 self.m.path.assert_absolute(source)
222 self._run(name, ['rmtree', source])
223 self.m.path.mock_remove_paths(str(source))
224
225 def rmcontents(self, name, source):
226 """Similar to rmtree, but removes only contents not the directory.
227
228 This is useful e.g. when removing contents of current working directory.
229 Deleting current working directory makes all further getcwd calls fail
230 until chdir is called. chdir would be tricky in recipes, so we provide
231 a call that doesn't delete the directory itself.
232
233 Args:
234 name (str) - The name of the step.
235 source (Path) - The directory whose contents should be removed.
236
237 Raises file.Error.
238 """
239 self.m.path.assert_absolute(source)
240 self._run(name, ['rmcontents', source])
241 self.m.path.mock_remove_paths(str(source)+self.m.path.sep)
242
243 def rmglob(self, name, source, pattern):
244 """Removes all entries in `source` matching the glob `pattern`.
245
246 Args:
247 name (str) - The name of the step.
248 source (Path) - The directory whose contents should be filtered and
249 removed.
250 pattern (str) - The glob pattern to apply under `source`. Anything
251 matching this pattern will be removed.
252
253 Raises file.Error.
254 """
255 self.m.path.assert_absolute(source)
256 self._run(name, ['rmglob', source, pattern])
257
258 src = str(source)
259 def filt(p):
260 assert p.startswith(src), (src, p)
261 return fnmatch.fnmatch(p[len(src)+1:].split(os.path.sep)[0], pattern)
262 self.m.path.mock_remove_paths(str(source), filt)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698