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

Unified Diff: recipe_modules/file/resources/fileutil.py

Issue 2918463002: [recipe_modules/file] add real file module to engine. (Closed)
Patch Set: fix stuff 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
Index: recipe_modules/file/resources/fileutil.py
diff --git a/recipe_modules/file/resources/fileutil.py b/recipe_modules/file/resources/fileutil.py
new file mode 100755
index 0000000000000000000000000000000000000000..c0ffecac6589d12908f30e41c5a42bef4340754c
--- /dev/null
+++ b/recipe_modules/file/resources/fileutil.py
@@ -0,0 +1,263 @@
+#!/usr/bin/env python
+# 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.
+
+"""Utility exporting basic filesystem operations.
+
+This file was cut from "scripts/common/chromium_utils.py" at:
+91310531c31fa645256b4fb5d44b460c42b3e151
+"""
+
+from __future__ import print_function
+
+import argparse
+import errno
+import fnmatch
+import glob
+import json
+import os
+import shutil
+import subprocess
+import sys
+import time
+
+
+def _LocateFiles(pattern, root):
+ """Yeilds files matching pattern found in root and its subdirectories.
+
+ An exception is thrown if root doesn't exist."""
+ for path, _, files in os.walk(os.path.abspath(root)):
+ for filename in fnmatch.filter(files, pattern):
+ yield os.path.join(path, filename)
+
+
+def _RmGlob(file_wildcard, root):
+ """Removes files matching 'file_wildcard' in root and its subdirectories, if
+ any exists.
+
+ An exception is thrown if root doesn't exist."""
+ for item in _LocateFiles(file_wildcard, root):
+ try:
+ os.remove(item)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+
+
+def _RmContents(path):
+ if os.path.exists(path):
+ os.chmod(path, 0770)
+ for p in (os.path.join(path, x) for x in os.listdir(path)):
+ if os.path.isdir(p):
+ _RmTree(p)
+ else:
+ os.unlink(p)
+
+
+def _RmTree(path):
+ """Recursively removes a directory, even if it's marked read-only.
+
+ Remove the directory located at path, if it exists.
+
+ shutil.rmtree() doesn't work on Windows if any of the files or directories
+ are read-only, which svn repositories and some .svn files are. We need to
+ be able to force the files to be writable (i.e., deletable) as we traverse
+ the tree.
+
+ Even with all this, Windows still sometimes fails to delete a file, citing
+ a permission error (maybe something to do with antivirus scans or disk
+ indexing). The best suggestion any of the user forums had was to wait a
+ bit and try again, so we do that too. It's hand-waving, but sometimes it
+ works. :/
+ """
+ if not os.path.exists(path):
+ print('WARNING: Failed to find %s during rmtree. Ignoring.\n' % path)
+ return
+
+ if sys.platform == 'win32':
+ # Give up and use cmd.exe's rd command.
+ cmd = ['cmd.exe', '/c', 'rd', '/q', '/s', os.path.normcase(path)]
+ for _ in xrange(3):
+ print('RemoveDirectory running %s' % (' '.join(cmd)))
+ if not subprocess.call(cmd):
+ break
+ print(' Failed')
+ time.sleep(3)
+ return
+
+ def RemoveWithRetry_non_win(rmfunc, path):
+ if os.path.islink(path):
+ return os.remove(path)
+ else:
+ return rmfunc(path)
+
+ remove_with_retry = RemoveWithRetry_non_win
+
+ def RmTreeOnError(function, path, excinfo):
+ r"""This works around a problem whereby python 2.x on Windows has no ability
+ to check for symbolic links. os.path.islink always returns False. But
+ shutil.rmtree will fail if invoked on a symbolic link whose target was
+ deleted before the link. E.g., reproduce like this:
+ > mkdir test
+ > mkdir test\1
+ > mklink /D test\current test\1
+ > python -c "import chromium_utils; chromium_utils.RemoveDirectory('test')"
+ To avoid this issue, we pass this error-handling function to rmtree. If
+ we see the exact sort of failure, we ignore it. All other failures we re-
+ raise.
+ """
+
+ exception_type = excinfo[0]
+ exception_value = excinfo[1]
+ # If shutil.rmtree encounters a symbolic link on Windows, os.listdir will
+ # fail with a WindowsError exception with an ENOENT errno (i.e., file not
+ # found). We'll ignore that error. Note that WindowsError is not defined
+ # for non-Windows platforms, so we use OSError (of which it is a subclass)
+ # to avoid lint complaints about an undefined global on non-Windows
+ # platforms.
+ if (function is os.listdir) and issubclass(exception_type, OSError):
+ if exception_value.errno == errno.ENOENT:
+ # File does not exist, and we're trying to delete, so we can ignore the
+ # failure.
+ print('WARNING: Failed to list %s during rmtree. Ignoring.\n' % path)
+ else:
+ raise
+ else:
+ raise
+
+ for root, dirs, files in os.walk(path, topdown=False):
+ # For POSIX: making the directory writable guarantees removability.
+ # Windows will ignore the non-read-only bits in the chmod value.
+ os.chmod(root, 0770)
+ for name in files:
+ remove_with_retry(os.remove, os.path.join(root, name))
+ for name in dirs:
+ remove_with_retry(lambda p: shutil.rmtree(p, onerror=RmTreeOnError),
+ os.path.join(root, name))
+
+ remove_with_retry(os.rmdir, path)
+
+
+def _EnsureDir(mode, dest):
+ if not os.path.isdir(dest):
+ if os.path.exists(dest):
+ raise OSError(errno.EEXIST, os.strerror(errno.EEXIST))
+ os.makedirs(dest, mode)
+
+
+def _ListDir(source):
+ source = os.path.abspath(source)
dnj 2017/05/31 23:58:58 (As mentioned above, I'd still keep this to names)
iannucci 2017/06/01 00:31:03 Done.
+ print('\n'.join(sorted(os.path.join(source, p) for p in os.listdir(source))))
+
+
+def main(args):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--json-output', required=True,
+ type=argparse.FileType('w'),
+ help="path to JSON output file")
+
+ subparsers = parser.add_subparsers()
+
+ # Subcommand: rmtree
+ subparser = subparsers.add_parser('rmtree',
+ help='Recursively remove a directory.')
+ subparser.add_argument('source', help='A path to remove.')
+ subparser.set_defaults(func=lambda opts: _RmTree(opts.source))
+
+ # Subcommand: rmcontents
+ subparser = subparsers.add_parser('rmcontents',
+ help='Recursively remove the contents of a directory.')
+ subparser.add_argument('source', help='The target directory.')
+ subparser.set_defaults(func=lambda opts: _RmContents(opts.source))
+
+ # Subcommand: rmwildcard
+ subparser = subparsers.add_parser('rmglob',
+ help='Recursively remove the contents of a directory.')
+ subparser.add_argument('root', help='The directory to search through.')
+ subparser.add_argument('wildcard', help='The wildcard expression to remove.')
+ subparser.set_defaults(func=lambda opts:
+ _RmGlob(opts.wildcard, opts.root))
+
+ # Subcommand: copy
+ subparser = subparsers.add_parser('copy',
+ help='Copy one file to another. Behaves like shutil.copy().')
+ subparser.add_argument('source', help='The file to copy.')
+ subparser.add_argument('dest', help='The destination to copy to.')
+ subparser.set_defaults(func=lambda opts: shutil.copy(opts.source, opts.dest))
+
+ # Subcommand: copytree
+ subparser = subparsers.add_parser('copytree',
+ help='Recursively copy a file tree. Behaves like shutil.copytree().')
+ subparser.add_argument('--symlinks', action='store_true',
+ help='Copy symlinks as symlinks.')
+ subparser.add_argument('source', help='The directory to copy.')
+ subparser.add_argument('dest', help='The destination directory to copy to.')
+ subparser.set_defaults(
+ func=lambda opts: shutil.copytree(opts.source, opts.dest, opts.symlinks))
+
+ # Subcommand: move
+ subparser = subparsers.add_parser('move',
+ help='Moves/renames a file. Behaves like shutil.move().')
+ subparser.add_argument('source', help='The item to move.')
+ subparser.add_argument('dest', help='The destination name.')
+ subparser.set_defaults(
+ func=lambda opts: shutil.move(opts.source, opts.dest))
+
+ # Subcommand: glob
+ subparser = subparsers.add_parser('glob',
+ help='Prints a list of absolute paths with match the pattern.')
+ subparser.add_argument('pattern', help='The glob patern to expand.')
+ subparser.set_defaults(
+ func=lambda opts: map(
+ print, glob.glob(os.path.abspath(opts.pattern))))
+
+ # Subcommand: remove
+ subparser = subparsers.add_parser('remove',
+ help='Remove a file')
+ subparser.add_argument('source', help='The file to remove.')
+ subparser.set_defaults(
+ func=lambda opts: os.remove(opts.source))
+
+ # Subcommand: listdir
+ subparser = subparsers.add_parser('listdir',
+ help='Print all entries in the given folder to stdout.')
+ subparser.add_argument('source', help='The dir to list.')
+ subparser.set_defaults(func=lambda opts: _ListDir(opts.source))
+
+ # Subcommand: ensure-directory
+ subparser = subparsers.add_parser('ensure-directory',
+ help='Ensures that the given path is a directory.')
+ subparser.add_argument('--mode', help='The octal mode of the directory.',
+ type=lambda s: int(s, 8))
+ subparser.add_argument('dest', help='The dir to ensure.')
+ subparser.set_defaults(func=lambda opts: _EnsureDir(opts.mode, opts.dest))
+
+ # Parse arguments.
+ opts = parser.parse_args(args)
+
+ # Actually do the thing.
+ data = {
+ 'ok': False,
+ 'errno_name': '',
+ 'message': '',
+ }
+ try:
+ opts.func(opts)
+ data['ok'] = True
+ except OSError as e:
+ data['errno_name'] = errno.errorcode[e.errno]
+ data['message'] = str(e)
+ except shutil.Error as e:
+ data['message'] = e.message
+ except Exception as e:
+ data['message'] = 'UNKNOWN: %s' % e
+
+ with opts.json_output:
+ json.dump(data, opts.json_output)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))

Powered by Google App Engine
This is Rietveld 408576698