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

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 stuff 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 _ListDir(source):
150 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.
151 print('\n'.join(sorted(os.path.join(source, p) for p in os.listdir(source))))
152
153
154 def main(args):
155 parser = argparse.ArgumentParser()
156 parser.add_argument('--json-output', required=True,
157 type=argparse.FileType('w'),
158 help="path to JSON output file")
159
160 subparsers = parser.add_subparsers()
161
162 # Subcommand: rmtree
163 subparser = subparsers.add_parser('rmtree',
164 help='Recursively remove a directory.')
165 subparser.add_argument('source', help='A path to remove.')
166 subparser.set_defaults(func=lambda opts: _RmTree(opts.source))
167
168 # Subcommand: rmcontents
169 subparser = subparsers.add_parser('rmcontents',
170 help='Recursively remove the contents of a directory.')
171 subparser.add_argument('source', help='The target directory.')
172 subparser.set_defaults(func=lambda opts: _RmContents(opts.source))
173
174 # Subcommand: rmwildcard
175 subparser = subparsers.add_parser('rmglob',
176 help='Recursively remove the contents of a directory.')
177 subparser.add_argument('root', help='The directory to search through.')
178 subparser.add_argument('wildcard', help='The wildcard expression to remove.')
179 subparser.set_defaults(func=lambda opts:
180 _RmGlob(opts.wildcard, opts.root))
181
182 # Subcommand: copy
183 subparser = subparsers.add_parser('copy',
184 help='Copy one file to another. Behaves like shutil.copy().')
185 subparser.add_argument('source', help='The file to copy.')
186 subparser.add_argument('dest', help='The destination to copy to.')
187 subparser.set_defaults(func=lambda opts: shutil.copy(opts.source, opts.dest))
188
189 # Subcommand: copytree
190 subparser = subparsers.add_parser('copytree',
191 help='Recursively copy a file tree. Behaves like shutil.copytree().')
192 subparser.add_argument('--symlinks', action='store_true',
193 help='Copy symlinks as symlinks.')
194 subparser.add_argument('source', help='The directory to copy.')
195 subparser.add_argument('dest', help='The destination directory to copy to.')
196 subparser.set_defaults(
197 func=lambda opts: shutil.copytree(opts.source, opts.dest, opts.symlinks))
198
199 # Subcommand: move
200 subparser = subparsers.add_parser('move',
201 help='Moves/renames a file. Behaves like shutil.move().')
202 subparser.add_argument('source', help='The item to move.')
203 subparser.add_argument('dest', help='The destination name.')
204 subparser.set_defaults(
205 func=lambda opts: shutil.move(opts.source, opts.dest))
206
207 # Subcommand: glob
208 subparser = subparsers.add_parser('glob',
209 help='Prints a list of absolute paths with match the pattern.')
210 subparser.add_argument('pattern', help='The glob patern to expand.')
211 subparser.set_defaults(
212 func=lambda opts: map(
213 print, glob.glob(os.path.abspath(opts.pattern))))
214
215 # Subcommand: remove
216 subparser = subparsers.add_parser('remove',
217 help='Remove a file')
218 subparser.add_argument('source', help='The file to remove.')
219 subparser.set_defaults(
220 func=lambda opts: os.remove(opts.source))
221
222 # Subcommand: listdir
223 subparser = subparsers.add_parser('listdir',
224 help='Print all entries in the given folder to stdout.')
225 subparser.add_argument('source', help='The dir to list.')
226 subparser.set_defaults(func=lambda opts: _ListDir(opts.source))
227
228 # Subcommand: ensure-directory
229 subparser = subparsers.add_parser('ensure-directory',
230 help='Ensures that the given path is a directory.')
231 subparser.add_argument('--mode', help='The octal mode of the directory.',
232 type=lambda s: int(s, 8))
233 subparser.add_argument('dest', help='The dir to ensure.')
234 subparser.set_defaults(func=lambda opts: _EnsureDir(opts.mode, opts.dest))
235
236 # Parse arguments.
237 opts = parser.parse_args(args)
238
239 # Actually do the thing.
240 data = {
241 'ok': False,
242 'errno_name': '',
243 'message': '',
244 }
245 try:
246 opts.func(opts)
247 data['ok'] = True
248 except OSError as e:
249 data['errno_name'] = errno.errorcode[e.errno]
250 data['message'] = str(e)
251 except shutil.Error as e:
252 data['message'] = e.message
253 except Exception as e:
254 data['message'] = 'UNKNOWN: %s' % e
255
256 with opts.json_output:
257 json.dump(data, opts.json_output)
258
259 return 0
260
261
262 if __name__ == '__main__':
263 sys.exit(main(sys.argv[1:]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698