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

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

Issue 2918463002: [recipe_modules/file] add real file module to engine. (Closed)
Patch Set: rebase 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
« no previous file with comments | « recipe_modules/file/__init__.py ('k') | recipe_modules/file/example.disable » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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(add_json_log=False)
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 result.presentation.step_text = j['message']
40 # pylint thinks this isn't a standard exception... silly pylint.
41 # pylint: disable=nonstandard-exception
42 raise self.Error(name, j['errno_name'], j['message'])
43 return result
44
45 def copy(self, name, source, dest):
46 """Copies a file (including mode bits) from source to destination on the
47 local filesystem.
48
49 Behaves identically to shutil.copy.
50
51 Args:
52 name (str) - The name of the step.
53 source (Path) - The path to the file you want to copy.
54 dest (Path) - The path to the destination file name. If this path exists
55 and is a directory, the basename of `source` will be appended to derive
56 a path to a destination file.
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])
63 self.m.path.mock_copy_paths(source, dest)
64
65 def copytree(self, name, source, dest, symlinks=False):
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 symlinks (bool) - Preserve symlinks. No effect on Windows.
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 []
Lei Zhang 2017/06/27 05:03:05 On https://build.chromium.org/p/chromium.infra.cro
83 self._run(name, prefix+['copytree', source, dest])
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])
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 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 self.m.path.mock_add_paths(dest)
136
137 def glob_paths(self, name, source, pattern, test_data=()):
138 """Performs glob expansion on `pattern`.
139
140 glob rules for `pattern` follow the same syntax as for the python `glob`
141 stdlib module.
142
143 Args:
144 name (str) - The name of the step.
145 source (Path) - The directory whose contents should be globbed.
146 pattern (str) - The glob pattern to apply under `source`.
147 test_data (iterable[str]) - Some default data for this step to return when
148 running under simulation. This should be the list of file items found
149 in this directory.
150
151 Returns list[Path] - All paths found.
152
153 Raises file.Error.
154 """
155 ret = self._run(
156 name, ['glob', source, pattern],
157 lambda: self.test_api.glob_paths(test_data),
158 self.m.raw_io.output_text())
159 return [
160 source.join(x) for x in ret.stdout.splitlines()
161 ]
162
163 def remove(self, name, source):
164 """Remove a file.
165
166 Args:
167 name (str) - The name of the step.
168 source (Path) - The file to remove.
169
170 Raises file.Error.
171 """
172 self.m.path.assert_absolute(source)
173 self._run(name, ['remove', source])
174 self.m.path.mock_remove_paths(source)
175
176 def listdir(self, name, source, test_data=()):
177 """List all files inside a directory.
178
179 Args:
180 name (str) - The name of the step.
181 source (Path) - The directory to list.
182 test_data (iterable[str]) - Some default data for this step to return when
183 running under simulation. This should be the list of file items found
184 in this directory.
185
186 Returns list[Path]
187
188 Raises file.Error.
189 """
190 self.m.path.assert_absolute(source)
191 ret = self._run(
192 name, ['listdir', source],
193 lambda: self.test_api.listdir(test_data),
194 self.m.raw_io.output_text())
195 return [source.join(x) for x in ret.stdout.splitlines()]
196
197 def ensure_directory(self, name, dest, mode=0777):
198 """Ensures that `dest` exists and is a directory.
199
200 Args:
201 name (str) - The name of the step.
202 dest (Path) - The directory to ensure.
203 mode (int) - The mode to use if the directory doesn't exist. This method
204 does not ensure the mode if the directory already exists (if you need
205 that behaviour, file a bug).
206
207 Raises file.Error if the path exists but is not a directory.
208 """
209 self.m.path.assert_absolute(dest)
210 self._run(
211 name, ['ensure-directory', '--mode', oct(mode), dest])
212 self.m.path.mock_add_paths(dest)
213
214 def rmtree(self, name, source):
215 """Recursively removes a directory.
216
217 This uses a native python on Linux/Mac, and uses `rd` on Windows to avoid
218 issues w.r.t. path lengths and read-only attributes. If the directory is
219 gone already, this returns without error.
220
221 Args:
222 name (str) - The name of the step.
223 source (Path) - The directory to remove.
224
225 Raises file.Error.
226 """
227 self.m.path.assert_absolute(source)
228 self._run(name, ['rmtree', source])
229 self.m.path.mock_remove_paths(str(source))
230
231 def rmcontents(self, name, source):
232 """Similar to rmtree, but removes only contents not the directory.
233
234 This is useful e.g. when removing contents of current working directory.
235 Deleting current working directory makes all further getcwd calls fail
236 until chdir is called. chdir would be tricky in recipes, so we provide
237 a call that doesn't delete the directory itself.
238
239 Args:
240 name (str) - The name of the step.
241 source (Path) - The directory whose contents should be removed.
242
243 Raises file.Error.
244 """
245 self.m.path.assert_absolute(source)
246 self._run(name, ['rmcontents', source])
247 self.m.path.mock_remove_paths(str(source)+self.m.path.sep)
248
249 def rmglob(self, name, source, pattern):
250 """Removes all entries in `source` matching the glob `pattern`.
251
252 Args:
253 name (str) - The name of the step.
254 source (Path) - The directory whose contents should be filtered and
255 removed.
256 pattern (str) - The glob pattern to apply under `source`. Anything
257 matching this pattern will be removed.
258
259 Raises file.Error.
260 """
261 self.m.path.assert_absolute(source)
262 self._run(name, ['rmglob', source, pattern])
263
264 src = str(source)
265 def filt(p):
266 assert p.startswith(src), (src, p)
267 return fnmatch.fnmatch(p[len(src)+1:].split(os.path.sep)[0], pattern)
268 self.m.path.mock_remove_paths(str(source), filt)
OLDNEW
« no previous file with comments | « recipe_modules/file/__init__.py ('k') | recipe_modules/file/example.disable » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698