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

Side by Side Diff: recipe_modules/file/resources/fileutil.py

Issue 2918463002: [recipe_modules/file] add real file module to engine. (Closed)
Patch Set: fix rmtree 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 for p in (os.path.join(path, x) for x in os.listdir(path)):
dnj 2017/05/31 16:50:40 nit: should we chmod 0770 everything we're about t
iannucci 2017/05/31 22:59:26 Done.
51 if os.path.isdir(p):
52 _RmTree(p)
53 else:
54 os.unlink(p)
55
56
57 def _RmTree(path):
58 """Recursively removes a directory, even if it's marked read-only.
59
60 Remove the directory located at path, if it exists.
61
62 shutil.rmtree() doesn't work on Windows if any of the files or directories
63 are read-only, which svn repositories and some .svn files are. We need to
64 be able to force the files to be writable (i.e., deletable) as we traverse
65 the tree.
66
67 Even with all this, Windows still sometimes fails to delete a file, citing
68 a permission error (maybe something to do with antivirus scans or disk
69 indexing). The best suggestion any of the user forums had was to wait a
70 bit and try again, so we do that too. It's hand-waving, but sometimes it
71 works. :/
72 """
73 if not os.path.exists(path):
74 print('WARNING: Failed to find %s during rmtree. Ignoring.\n' % path)
75 return
76
77 if sys.platform == 'win32':
78 # Give up and use cmd.exe's rd command.
79 path = os.path.normcase(path)
80 for _ in xrange(3):
81 print('RemoveDirectory running %s' % (' '.join(
82 ['cmd.exe', '/c', 'rd', '/q', '/s', path])))
83 if not subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path]):
dnj 2017/05/31 16:50:40 nit: let's actually define this once and reference
iannucci 2017/05/31 22:59:26 Done.
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 main(args):
150 parser = argparse.ArgumentParser()
151 parser.add_argument('--json-output', required=True,
152 type=argparse.FileType('w'),
153 help="path to JSON output file")
154
155 subparsers = parser.add_subparsers()
156
157 # Subcommand: rmtree
158 subparser = subparsers.add_parser('rmtree',
159 help='Recursively remove a directory.')
160 subparser.add_argument('source', help='A path to remove.')
161 subparser.set_defaults(func=lambda opts: _RmTree(opts.source))
162
163 # Subcommand: rmcontents
164 subparser = subparsers.add_parser('rmcontents',
165 help='Recursively remove the contents of a directory.')
166 subparser.add_argument('source', help='The target directory.')
167 subparser.set_defaults(func=lambda opts: _RmContents(opts.source))
168
169 # Subcommand: rmwildcard
170 subparser = subparsers.add_parser('rmglob',
171 help='Recursively remove the contents of a directory.')
172 subparser.add_argument('root', help='The directory to search through.')
173 subparser.add_argument('wildcard', help='The wildcard expression to remove.')
174 subparser.set_defaults(func=lambda opts:
175 _RmGlob(opts.wildcard, opts.root))
176
177 # Subcommand: copy
178 subparser = subparsers.add_parser('copy',
179 help='Copy one file to another. Behaves like shutil.copy().')
180 subparser.add_argument('source', help='The file to copy.')
181 subparser.add_argument('dest', help='The destination to copy to.')
182 subparser.set_defaults(func=lambda opts: shutil.copy(opts.source, opts.dest))
183
184 # Subcommand: copytree
185 subparser = subparsers.add_parser('copytree',
186 help='Recursively copy a file tree. Behaves like shutil.copytree().')
187 subparser.add_argument('--symlinks', action='store_true',
188 help='Copy symlinks as symlinks.')
189 subparser.add_argument('source', help='The directory to copy.')
190 subparser.add_argument('dest', help='The destination directory to copy to.')
191 subparser.set_defaults(
192 func=lambda opts: shutil.copytree(opts.source, opts.dest, opts.symlinks))
193
194 # Subcommand: move
195 subparser = subparsers.add_parser('move',
196 help='Moves/renames a file. Behaves like shutil.move().')
197 subparser.add_argument('source', help='The item to move.')
198 subparser.add_argument('dest', help='The destination name.')
199 subparser.set_defaults(
200 func=lambda opts: shutil.move(opts.source, opts.dest))
201
202 # Subcommand: glob
203 subparser = subparsers.add_parser('glob',
204 help='Prints a list of absolute paths with match the pattern.')
205 subparser.add_argument('pattern', help='The glob patern to expand.')
206 subparser.set_defaults(
207 func=lambda opts: map(
208 print, glob.glob(os.path.abspath(opts.pattern))))
209
210 # Subcommand: remove
211 subparser = subparsers.add_parser('remove',
212 help='Remove a file')
213 subparser.add_argument('source', help='The file to remove.')
214 subparser.set_defaults(
215 func=lambda opts: os.remove(opts.source))
216
217 # Subcommand: listdir
218 subparser = subparsers.add_parser('listdir',
219 help='Print all entries in the given folder to stdout.')
220 subparser.add_argument('source', help='The dir to list.')
221 subparser.set_defaults(
222 func=lambda opts: map(print, os.listdir(opts.source)))
223
224 # Subcommand: ensure-directory
225 subparser = subparsers.add_parser('ensure-directory',
226 help='Ensures that the given path is a directory.')
227 subparser.add_argument('--mode', help='The octal mode of the directory.',
228 type=lambda s: int(s, 8))
229 subparser.add_argument('dest', help='The dir to ensure.')
230 subparser.set_defaults(func=lambda opts: _EnsureDir(opts.mode, opts.dest))
231
232 # Parse arguments.
233 opts = parser.parse_args(args)
234
235 # Actually do the thing.
236 data = {
237 'ok': False,
238 'errno_name': '',
239 'message': '',
240 }
241 try:
242 opts.func(opts)
243 data['ok'] = True
244 except OSError as e:
245 data['errno_name'] = errno.errorcode[e.errno]
246 data['message'] = str(e)
247 except shutil.Error as e:
248 data['message'] = e.message
249 except Exception as e:
250 data['message'] = 'UNKNOWN: %s' % e
251
252 with opts.json_output:
253 json.dump(data, opts.json_output)
254
255 return 0
256
257
258 if __name__ == '__main__':
259 sys.exit(main(sys.argv[1:]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698