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

Side by Side Diff: recipe_modules/file/resources/fileutil.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
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2017 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file.
5
6 """Utility exporting basic filesystem operations.
7
8 This file was cut from "scripts/common/chromium_utils.py" at:
9 91310531c31fa645256b4fb5d44b460c42b3e151
10 """
11
12 from __future__ import print_function
13
14 import argparse
15 import errno
16 import fnmatch
17 import glob
18 import json
19 import os
20 import shutil
21 import subprocess
22 import sys
23 import time
24
25
26 def _LocateFiles(pattern, root):
27 """Yeilds files matching pattern found in root and its subdirectories.
28
29 An exception is thrown if root doesn't exist."""
30 for path, _, files in os.walk(os.path.abspath(root)):
31 for filename in fnmatch.filter(files, pattern):
32 yield os.path.join(path, filename)
33
34
35 def _RmGlob(file_wildcard, root):
36 """Removes files matching 'file_wildcard' in root and its subdirectories, if
37 any exists.
38
39 An exception is thrown if root doesn't exist."""
40 for item in _LocateFiles(file_wildcard, root):
41 try:
42 os.remove(item)
43 except OSError, e:
44 if e.errno != errno.ENOENT:
45 raise
46
47
48 def _RmContents(path):
49 if os.path.exists(path):
50 os.chmod(path, 0770)
51 for p in (os.path.join(path, x) for x in os.listdir(path)):
52 if os.path.isdir(p):
53 _RmTree(p)
54 else:
55 os.unlink(p)
56
57
58 def _RmTree(path):
59 """Recursively removes a directory, even if it's marked read-only.
60
61 Remove the directory located at path, if it exists.
62
63 shutil.rmtree() doesn't work on Windows if any of the files or directories
64 are read-only, which svn repositories and some .svn files are. We need to
65 be able to force the files to be writable (i.e., deletable) as we traverse
66 the tree.
67
68 Even with all this, Windows still sometimes fails to delete a file, citing
69 a permission error (maybe something to do with antivirus scans or disk
70 indexing). The best suggestion any of the user forums had was to wait a
71 bit and try again, so we do that too. It's hand-waving, but sometimes it
72 works. :/
73 """
74 if not os.path.exists(path):
75 print('WARNING: Failed to find %s during rmtree. Ignoring.\n' % path)
76 return
77
78 if sys.platform == 'win32':
79 # Give up and use cmd.exe's rd command.
80 cmd = ['cmd.exe', '/c', 'rd', '/q', '/s', os.path.normcase(path)]
81 for _ in xrange(3):
82 print('RemoveDirectory running %s' % (' '.join(cmd)))
83 if not subprocess.call(cmd):
84 break
85 print(' Failed')
86 time.sleep(3)
87 return
88
89 def RemoveWithRetry_non_win(rmfunc, path):
90 if os.path.islink(path):
91 return os.remove(path)
92 else:
93 return rmfunc(path)
94
95 remove_with_retry = RemoveWithRetry_non_win
96
97 def RmTreeOnError(function, path, excinfo):
98 r"""This works around a problem whereby python 2.x on Windows has no ability
99 to check for symbolic links. os.path.islink always returns False. But
100 shutil.rmtree will fail if invoked on a symbolic link whose target was
101 deleted before the link. E.g., reproduce like this:
102 > mkdir test
103 > mkdir test\1
104 > mklink /D test\current test\1
105 > python -c "import chromium_utils; chromium_utils.RemoveDirectory('test')"
106 To avoid this issue, we pass this error-handling function to rmtree. If
107 we see the exact sort of failure, we ignore it. All other failures we re-
108 raise.
109 """
110
111 exception_type = excinfo[0]
112 exception_value = excinfo[1]
113 # If shutil.rmtree encounters a symbolic link on Windows, os.listdir will
114 # fail with a WindowsError exception with an ENOENT errno (i.e., file not
115 # found). We'll ignore that error. Note that WindowsError is not defined
116 # for non-Windows platforms, so we use OSError (of which it is a subclass)
117 # to avoid lint complaints about an undefined global on non-Windows
118 # platforms.
119 if (function is os.listdir) and issubclass(exception_type, OSError):
120 if exception_value.errno == errno.ENOENT:
121 # File does not exist, and we're trying to delete, so we can ignore the
122 # failure.
123 print('WARNING: Failed to list %s during rmtree. Ignoring.\n' % path)
124 else:
125 raise
126 else:
127 raise
128
129 for root, dirs, files in os.walk(path, topdown=False):
130 # For POSIX: making the directory writable guarantees removability.
131 # Windows will ignore the non-read-only bits in the chmod value.
132 os.chmod(root, 0770)
133 for name in files:
134 remove_with_retry(os.remove, os.path.join(root, name))
135 for name in dirs:
136 remove_with_retry(lambda p: shutil.rmtree(p, onerror=RmTreeOnError),
137 os.path.join(root, name))
138
139 remove_with_retry(os.rmdir, path)
140
141
142 def _EnsureDir(mode, dest):
143 if not os.path.isdir(dest):
144 if os.path.exists(dest):
145 raise OSError(errno.EEXIST, os.strerror(errno.EEXIST))
146 os.makedirs(dest, mode)
147
148
149 def _Glob(base, pattern):
150 cwd = os.getcwd()
151 try:
152 os.chdir(base)
153 print('\n'.join(sorted(glob.glob(pattern))))
154 finally:
155 os.chdir(cwd)
156
157
158 def main(args):
159 parser = argparse.ArgumentParser()
160 parser.add_argument('--json-output', required=True,
161 type=argparse.FileType('w'),
162 help="path to JSON output file")
163
164 subparsers = parser.add_subparsers()
165
166 # Subcommand: rmtree
167 subparser = subparsers.add_parser('rmtree',
168 help='Recursively remove a directory.')
169 subparser.add_argument('source', help='A path to remove.')
170 subparser.set_defaults(func=lambda opts: _RmTree(opts.source))
171
172 # Subcommand: rmcontents
173 subparser = subparsers.add_parser('rmcontents',
174 help='Recursively remove the contents of a directory.')
175 subparser.add_argument('source', help='The target directory.')
176 subparser.set_defaults(func=lambda opts: _RmContents(opts.source))
177
178 # Subcommand: rmwildcard
179 subparser = subparsers.add_parser('rmglob',
180 help='Recursively remove the contents of a directory.')
181 subparser.add_argument('root', help='The directory to search through.')
182 subparser.add_argument('wildcard', help='The wildcard expression to remove.')
183 subparser.set_defaults(func=lambda opts:
184 _RmGlob(opts.wildcard, opts.root))
185
186 # Subcommand: copy
187 subparser = subparsers.add_parser('copy',
188 help='Copy one file to another. Behaves like shutil.copy().')
189 subparser.add_argument('source', help='The file to copy.')
190 subparser.add_argument('dest', help='The destination to copy to.')
191 subparser.set_defaults(func=lambda opts: shutil.copy(opts.source, opts.dest))
192
193 # Subcommand: copytree
194 subparser = subparsers.add_parser('copytree',
195 help='Recursively copy a file tree. Behaves like shutil.copytree().')
196 subparser.add_argument('--symlinks', action='store_true',
197 help='Copy symlinks as symlinks.')
198 subparser.add_argument('source', help='The directory to copy.')
199 subparser.add_argument('dest', help='The destination directory to copy to.')
200 subparser.set_defaults(
201 func=lambda opts: shutil.copytree(opts.source, opts.dest, opts.symlinks))
202
203 # Subcommand: move
204 subparser = subparsers.add_parser('move',
205 help='Moves/renames a file. Behaves like shutil.move().')
206 subparser.add_argument('source', help='The item to move.')
207 subparser.add_argument('dest', help='The destination name.')
208 subparser.set_defaults(
209 func=lambda opts: shutil.move(opts.source, opts.dest))
210
211 # Subcommand: glob
212 subparser = subparsers.add_parser('glob',
213 help='Prints a list of absolute paths with match the pattern.')
214 subparser.add_argument('base', help='The directory to glob in.')
215 subparser.add_argument('pattern', help='The glob patern to expand.')
216 subparser.set_defaults(func=lambda opts: _Glob(opts.base, opts.pattern))
217
218 # Subcommand: remove
219 subparser = subparsers.add_parser('remove',
220 help='Remove a file')
221 subparser.add_argument('source', help='The file to remove.')
222 subparser.set_defaults(
223 func=lambda opts: os.remove(opts.source))
224
225 # Subcommand: listdir
226 subparser = subparsers.add_parser('listdir',
227 help='Print all entries in the given folder to stdout.')
228 subparser.add_argument('source', help='The dir to list.')
229 subparser.set_defaults(
230 func=lambda opts: print('\n'.join(sorted(os.listdir(opts.source)))))
231
232 # Subcommand: ensure-directory
233 subparser = subparsers.add_parser('ensure-directory',
234 help='Ensures that the given path is a directory.')
235 subparser.add_argument('--mode', help='The octal mode of the directory.',
236 type=lambda s: int(s, 8))
237 subparser.add_argument('dest', help='The dir to ensure.')
238 subparser.set_defaults(func=lambda opts: _EnsureDir(opts.mode, opts.dest))
239
240 # Parse arguments.
241 opts = parser.parse_args(args)
242
243 # Actually do the thing.
244 data = {
245 'ok': False,
246 'errno_name': '',
247 'message': '',
248 }
249 try:
250 opts.func(opts)
251 data['ok'] = True
252 except OSError as e:
253 data['errno_name'] = errno.errorcode[e.errno]
254 data['message'] = str(e)
255 except shutil.Error as e:
256 data['message'] = e.message
257 except Exception as e:
258 data['message'] = 'UNKNOWN: %s' % e
259
260 with opts.json_output:
261 json.dump(data, opts.json_output)
262
263 return 0
264
265
266 if __name__ == '__main__':
267 sys.exit(main(sys.argv[1:]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698