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

Unified Diff: recipe_modules/file/api.py

Issue 2918463002: [recipe_modules/file] add real file module to engine. (Closed)
Patch Set: rebase Created 3 years, 7 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « recipe_modules/file/__init__.py ('k') | recipe_modules/file/example.disable » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: recipe_modules/file/api.py
diff --git a/recipe_modules/file/api.py b/recipe_modules/file/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..795a8a91b46a94fa006bf6f5a5fd25e82bf13afa
--- /dev/null
+++ b/recipe_modules/file/api.py
@@ -0,0 +1,268 @@
+# Copyright 2017 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+
+from recipe_engine import recipe_api
+
+
+import os
+import fnmatch
+
+# TODO(iannucci): Introduce the concept of a 'native step' and implement these
+# directly in the current python interpreter without the need for a subprocess
+# invocation.
+
+class FileApi(recipe_api.RecipeApi):
+ """FileApi contains helper functions for reading and writing files."""
+
+ class Error(recipe_api.InfraFailure):
+ """Error is an InfraFailure, except that it also contains an errno field
+ indicating the errno name (i.e. 'EEXIST') of the underlying error.
+ """
+ def __init__(self, step_name, errno_name, message):
+ reason = 'Step(%r) failed %r with: %s' % (step_name, errno_name, message)
+ super(FileApi.Error, self).__init__(reason)
+ self.errno_name = errno_name
+
+ def _run(self, name, args, step_test_data=None, stdout=None):
+ if not step_test_data:
+ step_test_data = self.test_api.errno
+ args = [
+ '--json-output', self.m.json.output(add_json_log=False)
+ ] + args
+ result = self.m.python(
+ name, self.resource('fileutil.py'), args=args,
+ step_test_data=step_test_data, stdout=stdout,
+ infra_step=True)
+ j = result.json.output
+ if not j['ok']:
+ result.presentation.step_text = j['message']
+ # pylint thinks this isn't a standard exception... silly pylint.
+ # pylint: disable=nonstandard-exception
+ raise self.Error(name, j['errno_name'], j['message'])
+ return result
+
+ def copy(self, name, source, dest):
+ """Copies a file (including mode bits) from source to destination on the
+ local filesystem.
+
+ Behaves identically to shutil.copy.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The path to the file you want to copy.
+ dest (Path) - The path to the destination file name. If this path exists
+ and is a directory, the basename of `source` will be appended to derive
+ a path to a destination file.
+
+ Raises file.Error
+ """
+ self.m.path.assert_absolute(source)
+ self.m.path.assert_absolute(dest)
+ self._run(name, ['copy', source, dest])
+ self.m.path.mock_copy_paths(source, dest)
+
+ def copytree(self, name, source, dest, symlinks=False):
+ """Recursively copies a directory tree.
+
+ Behaves identically to shutil.copytree.
+ `dest` must not exist.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The path of the directory to copy.
+ dest (Path) - The place where you want the recursive copy to show up. This
+ must not already exist.
+ symlinks (bool) - Preserve symlinks. No effect on Windows.
+
+ Raises file.Error
+ """
+ self.m.path.assert_absolute(source)
+ self.m.path.assert_absolute(dest)
+ prefix = ['--symlinks'] if symlinks else []
Lei Zhang 2017/06/27 05:03:05 On https://build.chromium.org/p/chromium.infra.cro
+ self._run(name, prefix+['copytree', source, dest])
+ self.m.path.mock_copy_paths(source, dest)
+
+ def move(self, name, source, dest):
+ """Moves a file or directory.
+
+ Behaves identically to shutil.move.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The path of the item to move.
+ dest (Path) - The new name of the item.
+
+ Raises file.Error
+ """
+ self.m.path.assert_absolute(source)
+ self.m.path.assert_absolute(dest)
+ self._run(name, ['move', source, dest])
+ self.m.path.mock_copy_paths(source, dest)
+ self.m.path.mock_remove_paths(source)
+
+ def read_text(self, name, source, test_data=''):
+ """Reads a file as UTF-8 encoded text.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The path of the file to read.
+ test_data (str) - Some default data for this step to return when running
+ under simulation.
+
+ Returns (str) - The content of the file.
+
+ Raises file.Error
+ """
+ self.m.path.assert_absolute(source)
+ step_test_data = lambda: self.test_api.read_text(test_data)
+ result = self._run(name, ['copy', source, self.m.raw_io.output_text()],
+ step_test_data=step_test_data)
+ return result.raw_io.output_text
+
+ def write_text(self, name, dest, text_data):
+ """Write the given UTF-8 encoded `text_data` to `dest`.
+
+ Args:
+ name (str) - The name of the step.
+ dest (Path) - The path of the file to write.
+ text_data (str) - The UTF-8 encoded data to write.
+
+ Raises file.Error.
+ """
+ self.m.path.assert_absolute(dest)
+ self._run(name, ['copy', self.m.raw_io.input_text(text_data), dest])
+ self.m.path.mock_add_paths(dest)
+
+ def glob_paths(self, name, source, pattern, test_data=()):
+ """Performs glob expansion on `pattern`.
+
+ glob rules for `pattern` follow the same syntax as for the python `glob`
+ stdlib module.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The directory whose contents should be globbed.
+ pattern (str) - The glob pattern to apply under `source`.
+ test_data (iterable[str]) - Some default data for this step to return when
+ running under simulation. This should be the list of file items found
+ in this directory.
+
+ Returns list[Path] - All paths found.
+
+ Raises file.Error.
+ """
+ ret = self._run(
+ name, ['glob', source, pattern],
+ lambda: self.test_api.glob_paths(test_data),
+ self.m.raw_io.output_text())
+ return [
+ source.join(x) for x in ret.stdout.splitlines()
+ ]
+
+ def remove(self, name, source):
+ """Remove a file.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The file to remove.
+
+ Raises file.Error.
+ """
+ self.m.path.assert_absolute(source)
+ self._run(name, ['remove', source])
+ self.m.path.mock_remove_paths(source)
+
+ def listdir(self, name, source, test_data=()):
+ """List all files inside a directory.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The directory to list.
+ test_data (iterable[str]) - Some default data for this step to return when
+ running under simulation. This should be the list of file items found
+ in this directory.
+
+ Returns list[Path]
+
+ Raises file.Error.
+ """
+ self.m.path.assert_absolute(source)
+ ret = self._run(
+ name, ['listdir', source],
+ lambda: self.test_api.listdir(test_data),
+ self.m.raw_io.output_text())
+ return [source.join(x) for x in ret.stdout.splitlines()]
+
+ def ensure_directory(self, name, dest, mode=0777):
+ """Ensures that `dest` exists and is a directory.
+
+ Args:
+ name (str) - The name of the step.
+ dest (Path) - The directory to ensure.
+ mode (int) - The mode to use if the directory doesn't exist. This method
+ does not ensure the mode if the directory already exists (if you need
+ that behaviour, file a bug).
+
+ Raises file.Error if the path exists but is not a directory.
+ """
+ self.m.path.assert_absolute(dest)
+ self._run(
+ name, ['ensure-directory', '--mode', oct(mode), dest])
+ self.m.path.mock_add_paths(dest)
+
+ def rmtree(self, name, source):
+ """Recursively removes a directory.
+
+ This uses a native python on Linux/Mac, and uses `rd` on Windows to avoid
+ issues w.r.t. path lengths and read-only attributes. If the directory is
+ gone already, this returns without error.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The directory to remove.
+
+ Raises file.Error.
+ """
+ self.m.path.assert_absolute(source)
+ self._run(name, ['rmtree', source])
+ self.m.path.mock_remove_paths(str(source))
+
+ def rmcontents(self, name, source):
+ """Similar to rmtree, but removes only contents not the directory.
+
+ This is useful e.g. when removing contents of current working directory.
+ Deleting current working directory makes all further getcwd calls fail
+ until chdir is called. chdir would be tricky in recipes, so we provide
+ a call that doesn't delete the directory itself.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The directory whose contents should be removed.
+
+ Raises file.Error.
+ """
+ self.m.path.assert_absolute(source)
+ self._run(name, ['rmcontents', source])
+ self.m.path.mock_remove_paths(str(source)+self.m.path.sep)
+
+ def rmglob(self, name, source, pattern):
+ """Removes all entries in `source` matching the glob `pattern`.
+
+ Args:
+ name (str) - The name of the step.
+ source (Path) - The directory whose contents should be filtered and
+ removed.
+ pattern (str) - The glob pattern to apply under `source`. Anything
+ matching this pattern will be removed.
+
+ Raises file.Error.
+ """
+ self.m.path.assert_absolute(source)
+ self._run(name, ['rmglob', source, pattern])
+
+ src = str(source)
+ def filt(p):
+ assert p.startswith(src), (src, p)
+ return fnmatch.fnmatch(p[len(src)+1:].split(os.path.sep)[0], pattern)
+ self.m.path.mock_remove_paths(str(source), filt)
« 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