| OLD | NEW |
| 1 # Copyright (C) 2010 Google Inc. All rights reserved. | 1 # Copyright (C) 2010 Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
| 5 # met: | 5 # met: |
| 6 # | 6 # |
| 7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
| (...skipping 10 matching lines...) Expand all Loading... |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | 28 |
| 29 """Wrapper object for the file system / source tree.""" | 29 """Wrapper object for the file system / source tree.""" |
| 30 | 30 |
| 31 import stat | |
| 32 import codecs | 31 import codecs |
| 33 import errno | 32 import errno |
| 34 import exceptions | 33 import exceptions |
| 35 import glob | 34 import glob |
| 36 import hashlib | 35 import hashlib |
| 36 import logging |
| 37 import os | 37 import os |
| 38 import shutil | 38 import shutil |
| 39 import stat |
| 39 import sys | 40 import sys |
| 40 import tempfile | 41 import tempfile |
| 41 import time | 42 import time |
| 42 | 43 |
| 43 | 44 |
| 45 _log = logging.getLogger(__name__) |
| 46 |
| 47 |
| 44 class FileSystem(object): | 48 class FileSystem(object): |
| 45 """FileSystem interface for webkitpy. | 49 """FileSystem interface for webkitpy. |
| 46 | 50 |
| 47 Unless otherwise noted, all paths are allowed to be either absolute | 51 Unless otherwise noted, all paths are allowed to be either absolute |
| 48 or relative. | 52 or relative. |
| 49 """ | 53 """ |
| 50 sep = os.sep | 54 sep = os.sep |
| 51 pardir = os.pardir | 55 pardir = os.pardir |
| 52 | 56 |
| 53 def abspath(self, path): | 57 def abspath(self, path): |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 129 | 133 |
| 130 def isdir(self, path): | 134 def isdir(self, path): |
| 131 return os.path.isdir(path) | 135 return os.path.isdir(path) |
| 132 | 136 |
| 133 def join(self, *comps): | 137 def join(self, *comps): |
| 134 return os.path.join(*comps) | 138 return os.path.join(*comps) |
| 135 | 139 |
| 136 def listdir(self, path): | 140 def listdir(self, path): |
| 137 return os.listdir(path) | 141 return os.listdir(path) |
| 138 | 142 |
| 139 def walk(self, top): | 143 def walk(self, top, topdown=True, onerror=None, followlinks=False): |
| 140 return os.walk(top) | 144 return os.walk(top, topdown=topdown, onerror=onerror, followlinks=follow
links) |
| 141 | 145 |
| 142 def mkdtemp(self, **kwargs): | 146 def mkdtemp(self, **kwargs): |
| 143 """Create and return a uniquely named directory. | 147 """Create and return a uniquely named directory. |
| 144 | 148 |
| 145 This is like tempfile.mkdtemp, but if used in a with statement | 149 This is like tempfile.mkdtemp, but if used in a with statement |
| 146 the directory will self-delete at the end of the block (if the | 150 the directory will self-delete at the end of the block (if the |
| 147 directory is empty; non-empty directories raise errors). The | 151 directory is empty; non-empty directories raise errors). The |
| 148 directory can be safely deleted inside the block as well, if so | 152 directory can be safely deleted inside the block as well, if so |
| 149 desired. | 153 desired. |
| 150 | 154 |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 239 def sha1(self, path): | 243 def sha1(self, path): |
| 240 contents = self.read_binary_file(path) | 244 contents = self.read_binary_file(path) |
| 241 return hashlib.sha1(contents).hexdigest() | 245 return hashlib.sha1(contents).hexdigest() |
| 242 | 246 |
| 243 def relpath(self, path, start='.'): | 247 def relpath(self, path, start='.'): |
| 244 return os.path.relpath(path, start) | 248 return os.path.relpath(path, start) |
| 245 | 249 |
| 246 class _WindowsError(exceptions.OSError): | 250 class _WindowsError(exceptions.OSError): |
| 247 """Fake exception for Linux and Mac.""" | 251 """Fake exception for Linux and Mac.""" |
| 248 | 252 |
| 249 def remove(self, path, osremove=os.remove): | 253 def remove(self, path, osremove=os.remove, retry=True): |
| 250 """On Windows, if a process was recently killed and it held on to a | 254 """On Windows, if a process was recently killed and it held on to a |
| 251 file, the OS will hold on to the file for a short while. This makes | 255 file, the OS will hold on to the file for a short while. This makes |
| 252 attempts to delete the file fail. To work around that, this method | 256 attempts to delete the file fail. To work around that, this method |
| 253 will retry for a few seconds until Windows is done with the file. | 257 will retry for a few seconds until Windows is done with the file. |
| 254 """ | 258 """ |
| 255 try: | 259 try: |
| 256 exceptions.WindowsError | 260 exceptions.WindowsError |
| 257 except AttributeError: | 261 except AttributeError: |
| 258 exceptions.WindowsError = FileSystem._WindowsError | 262 exceptions.WindowsError = FileSystem._WindowsError |
| 259 | 263 |
| 260 retry_timeout_sec = 3.0 | 264 retry_timeout_sec = 3.0 |
| 261 sleep_interval = 0.1 | 265 sleep_interval = 0.1 |
| 262 while True: | 266 while True: |
| 263 try: | 267 try: |
| 264 osremove(path) | 268 osremove(path) |
| 265 return True | 269 return True |
| 266 except exceptions.WindowsError: | 270 except exceptions.WindowsError: |
| 267 time.sleep(sleep_interval) | 271 time.sleep(sleep_interval) |
| 268 retry_timeout_sec -= sleep_interval | 272 retry_timeout_sec -= sleep_interval |
| 269 if retry_timeout_sec < 0: | 273 if retry_timeout_sec < 0 and not retry: |
| 270 raise | 274 raise |
| 271 | 275 |
| 272 def rmtree(self, path): | 276 def rmtree(self, path, ignore_errors=True, onerror=None): |
| 273 """Delete the directory rooted at path, whether empty or not.""" | 277 """Delete the directory rooted at path, whether empty or not.""" |
| 274 shutil.rmtree(path, ignore_errors=True) | 278 shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror) |
| 279 |
| 280 def remove_contents(self, dirname): |
| 281 """Attempt to remove the contents of a directory tree. |
| 282 Args: |
| 283 dirname (string): Directory to remove the contents of. |
| 284 |
| 285 Returns: |
| 286 bool: True if the directory is now empty. |
| 287 """ |
| 288 return _remove_contents(self, dirname) |
| 275 | 289 |
| 276 def copytree(self, source, destination): | 290 def copytree(self, source, destination): |
| 277 shutil.copytree(source, destination) | 291 shutil.copytree(source, destination) |
| 278 | 292 |
| 279 def split(self, path): | 293 def split(self, path): |
| 280 """Return (dirname, basename + '.' + ext)""" | 294 """Return (dirname, basename + '.' + ext)""" |
| 281 return os.path.split(path) | 295 return os.path.split(path) |
| 282 | 296 |
| 283 def splitext(self, path): | 297 def splitext(self, path): |
| 284 """Return (dirname + os.sep + basename, '.' + ext)""" | 298 """Return (dirname + os.sep + basename, '.' + ext)""" |
| 285 return os.path.splitext(path) | 299 return os.path.splitext(path) |
| 286 | 300 |
| 287 def make_executable(self, file_path): | 301 def make_executable(self, file_path): |
| 288 os.chmod(file_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_
IRGRP | stat.S_IXGRP) | 302 os.chmod(file_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_
IRGRP | stat.S_IXGRP) |
| 303 |
| 304 |
| 305 # _remove_contents is implemented in terms of other FileSystem functions. To |
| 306 # allow it to be reused on the MockFileSystem object, we define it here and |
| 307 # then call it in both FileSystem and MockFileSystem classes. |
| 308 def _remove_contents(fs, dirname, sleep=time.sleep): |
| 309 # We try multiple times, because on Windows a process which is |
| 310 # currently closing could still have a file open in the directory. |
| 311 _log.info('Removing contents of %s', dirname) |
| 312 errors = [] |
| 313 |
| 314 def onerror(func, path, exc_info): |
| 315 errors.append(path) |
| 316 _log.exception('Failed at %s %s: %r', func, path, exc_info) |
| 317 |
| 318 attempts = 0 |
| 319 while attempts < 5: |
| 320 del errors[:] |
| 321 |
| 322 for name in fs.listdir(dirname): |
| 323 fullname = fs.join(dirname, name) |
| 324 |
| 325 isdir = True |
| 326 try: |
| 327 isdir = fs.isdir(fullname) |
| 328 except os.error: |
| 329 onerror(fs.isdir, fullname, sys.exc_info()) |
| 330 continue |
| 331 |
| 332 if isdir: |
| 333 try: |
| 334 _log.debug('Removing directory %s', fullname) |
| 335 fs.rmtree(fullname, ignore_errors=False, onerror=onerror) |
| 336 except os.error: |
| 337 onerror(fs.rmtree, fullname, sys.exc_info()) |
| 338 continue |
| 339 else: |
| 340 try: |
| 341 _log.debug('Removing file %s', fullname) |
| 342 fs.remove(fullname, retry=False) |
| 343 except os.error: |
| 344 onerror(fs.remove, fullname, sys.exc_info()) |
| 345 continue |
| 346 |
| 347 if not errors: |
| 348 break |
| 349 |
| 350 _log.warning('Contents removal failed, retrying in 1 second.') |
| 351 attempts += 1 |
| 352 sleep(1) |
| 353 |
| 354 # Check the path is gone. |
| 355 if not fs.listdir(dirname): |
| 356 return True |
| 357 |
| 358 _log.warning('Unable to remove %s', dirname) |
| 359 for dirpath, dirnames, filenames in fs.walk(dirname, onerror=onerror, topdow
n=False): |
| 360 for fname in filenames: |
| 361 _log.warning('File %s still in output dir.', fs.join(dirpath, fname)
) |
| 362 for dname in dirnames: |
| 363 _log.warning('Dir %s still in output dir.', fs.join(dirpath, dname)) |
| 364 |
| 365 return False |
| OLD | NEW |