Ver Fonte

Fix file deletions to always use a delayed retry on windows, fixes #3074

Jordi Boggiano há 11 anos atrás
pai
commit
745dcbce33

+ 3 - 3
src/Composer/Cache.php

@@ -136,7 +136,7 @@ class Cache
     {
         $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
         if ($this->enabled && file_exists($this->root . $file)) {
-            return unlink($this->root . $file);
+            return $this->filesystem->unlink($this->root . $file);
         }
 
         return false;
@@ -150,7 +150,7 @@ class Cache
 
             $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
             foreach ($finder as $file) {
-                unlink($file->getPathname());
+                $this->filesystem->unlink($file->getPathname());
             }
 
             $totalSize = $this->filesystem->size($this->root);
@@ -159,7 +159,7 @@ class Cache
                 while ($totalSize > $maxSize && $iterator->valid()) {
                     $filepath = $iterator->current()->getPathname();
                     $totalSize -= $this->filesystem->size($filepath);
-                    unlink($filepath);
+                    $this->filesystem->unlink($filepath);
                     $iterator->next();
                 }
             }

+ 1 - 1
src/Composer/Downloader/ArchiveDownloader.php

@@ -48,7 +48,7 @@ abstract class ArchiveDownloader extends FileDownloader
                     throw $e;
                 }
 
-                unlink($fileName);
+                $this->filesystem->unlink($fileName);
 
                 $contentDir = $this->getFolderContent($temporaryDir);
 

+ 1 - 4
src/Composer/Downloader/FileDownloader.php

@@ -205,10 +205,7 @@ class FileDownloader implements DownloaderInterface
     {
         $this->io->write("  - Removing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
         if (!$this->filesystem->removeDirectory($path)) {
-            // retry after a bit on windows since it tends to be touchy with mass removals
-            if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(250000) && !$this->filesystem->removeDirectory($path))) {
-                throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
-            }
+            throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
         }
     }
 

+ 1 - 4
src/Composer/Downloader/VcsDownloader.php

@@ -162,10 +162,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
         $this->io->write("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
         $this->cleanChanges($package, $path, false);
         if (!$this->filesystem->removeDirectory($path)) {
-            // retry after a bit on windows since it tends to be touchy with mass removals
-            if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(250) && !$this->filesystem->removeDirectory($path))) {
-                throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
-            }
+            throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
         }
     }
 

+ 2 - 2
src/Composer/Installer/LibraryInstaller.php

@@ -259,10 +259,10 @@ class LibraryInstaller implements InstallerInterface
         foreach ($binaries as $bin) {
             $link = $this->binDir.'/'.basename($bin);
             if (is_link($link) || file_exists($link)) {
-                unlink($link);
+                $this->filesystem->unlink($link);
             }
             if (file_exists($link.'.bat')) {
-                unlink($link.'.bat');
+                $this->filesystem->unlink($link.'.bat');
             }
         }
     }

+ 1 - 1
src/Composer/Installer/PearInstaller.php

@@ -77,7 +77,7 @@ class PearInstaller extends LibraryInstaller
         if ($this->io->isVerbose()) {
             $this->io->write('    Cleaning up');
         }
-        unlink($packageArchive);
+        $this->filesystem->unlink($packageArchive);
     }
 
     protected function getBinaries(PackageInterface $package)

+ 59 - 11
src/Composer/Util/Filesystem.php

@@ -36,7 +36,7 @@ class Filesystem
         }
 
         if (file_exists($file)) {
-            return unlink($file);
+            return $this->unlink($file);
         }
 
         return false;
@@ -62,7 +62,7 @@ class Filesystem
     public function emptyDirectory($dir, $ensureDirectoryExists = true)
     {
         if (file_exists($dir) && is_link($dir)) {
-            unlink($dir);
+            $this->unlink($dir);
         }
 
         if ($ensureDirectoryExists) {
@@ -94,10 +94,10 @@ class Filesystem
     public function removeDirectory($directory)
     {
         if (file_exists($directory) && is_link($directory)) {
-            return unlink($directory);
+            return $this->unlink($directory);
         }
 
-        if (!is_dir($directory)) {
+        if (!file_exists($directory) || !is_dir($directory)) {
             return true;
         }
 
@@ -117,11 +117,11 @@ class Filesystem
 
         $result = $this->getProcess()->execute($cmd, $output) === 0;
 
-        if ($result) {
-            // clear stat cache because external processes aren't tracked by the php stat cache
-            clearstatcache();
+        // clear stat cache because external processes aren't tracked by the php stat cache
+        clearstatcache();
 
-            return !is_dir($directory);
+        if ($result && !file_exists($directory)) {
+            return true;
         }
 
         return $this->removeDirectoryPhp($directory);
@@ -144,13 +144,13 @@ class Filesystem
 
         foreach ($ri as $file) {
             if ($file->isDir()) {
-                rmdir($file->getPathname());
+                $this->rmdir($file->getPathname());
             } else {
-                unlink($file->getPathname());
+                $this->unlink($file->getPathname());
             }
         }
 
-        return rmdir($directory);
+        return $this->rmdir($directory);
     }
 
     public function ensureDirectoryExists($directory)
@@ -169,6 +169,54 @@ class Filesystem
         }
     }
 
+    /**
+     * Attempts to unlink a file and in case of failure retries after 350ms on windows
+     *
+     * @param  string $path
+     * @return bool
+     */
+    public function unlink($path)
+    {
+        if (!@unlink($path)) {
+            // retry after a bit on windows since it tends to be touchy with mass removals
+            if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(350000) && !@unlink($path))) {
+                $error = error_get_last();
+                $message = 'Could not delete '.$path.': ' . @$error['message'];
+                if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
+                }
+
+                throw new \RuntimeException($message);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempts to rmdir a file and in case of failure retries after 350ms on windows
+     *
+     * @param  string $path
+     * @return bool
+     */
+    public function rmdir($path)
+    {
+        if (!@rmdir($path)) {
+            // retry after a bit on windows since it tends to be touchy with mass removals
+            if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(350000) && !@rmdir($path))) {
+                $error = error_get_last();
+                $message = 'Could not delete '.$path.': ' . @$error['message'];
+                if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
+                }
+
+                throw new \RuntimeException($message);
+            }
+        }
+
+        return true;
+    }
+
     /**
      * Copy then delete is a non-atomic version of {@link rename}.
      *