diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 7387a94cc..f79642066 100644 --- a/src/Composer/Cache.php +++ b/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(); } } diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index db1fc674c..7c0a761c6 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/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); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 7d1c4f9a7..793ae6be0 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -205,10 +205,7 @@ class FileDownloader implements DownloaderInterface { $this->io->write(" - Removing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); 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.'); } } diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index ea7df270a..e653794ca 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -162,10 +162,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa $this->io->write(" - Removing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); $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.'); } } diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index b847eac69..4facfd494 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/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'); } } } diff --git a/src/Composer/Installer/PearInstaller.php b/src/Composer/Installer/PearInstaller.php index b44f61f14..defadd9cf 100644 --- a/src/Composer/Installer/PearInstaller.php +++ b/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) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index e9736c007..d167de463 100644 --- a/src/Composer/Util/Filesystem.php +++ b/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}. *