Skip to content

Add a dedicated file deletion for unconfigure recipes #706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Configurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ class Configurator
{
private $composer;
private $io;
private $filesManager;
private $options;
private $configurators;
private $cache;

public function __construct(Composer $composer, IOInterface $io, Options $options)
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options)
{
$this->composer = $composer;
$this->io = $io;
$this->filesManager = $filesManager;
$this->options = $options;
// ordered list of configurators
$this->configurators = [
Expand Down Expand Up @@ -78,6 +80,6 @@ private function get($key): AbstractConfigurator

$class = $this->configurators[$key];

return $this->cache[$key] = new $class($this->composer, $this->io, $this->options);
return $this->cache[$key] = new $class($this->composer, $this->io, $this->filesManager, $this->options);
}
}
5 changes: 4 additions & 1 deletion src/Configurator/AbstractConfigurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Composer\Composer;
use Composer\IO\IOInterface;
use Symfony\Flex\FilesManager;
use Symfony\Flex\Lock;
use Symfony\Flex\Options;
use Symfony\Flex\Path;
Expand All @@ -25,13 +26,15 @@ abstract class AbstractConfigurator
{
protected $composer;
protected $io;
protected $filesManager;
protected $options;
protected $path;

public function __construct(Composer $composer, IOInterface $io, Options $options)
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options)
{
$this->composer = $composer;
$this->io = $io;
$this->filesManager = $filesManager;
$this->options = $options;
$this->path = new Path($options->get('root-dir'));
}
Expand Down
2 changes: 1 addition & 1 deletion src/Configurator/CopyFromPackageConfigurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private function copyDir(string $source, string $target, array $options)
public function copyFile(string $source, string $target, array $options)
{
$overwrite = $options['force'] ?? false;
if (!$this->options->shouldWriteFile($target, $overwrite)) {
if (!$this->filesManager->shouldWriteFile($target, $overwrite)) {
return;
}

Expand Down
30 changes: 2 additions & 28 deletions src/Configurator/CopyFromRecipeConfigurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,7 @@ public function configure(Recipe $recipe, $config, Lock $lock, array $options =
public function unconfigure(Recipe $recipe, $config, Lock $lock)
{
$this->write('Removing files from recipe');
$this->removeFiles($config, $this->getRemovableFilesFromRecipeAndLock($recipe, $lock), $this->options->get('root-dir'));
}

private function getRemovableFilesFromRecipeAndLock(Recipe $recipe, Lock $lock): array
{
$lockedFiles = array_unique(
array_reduce(
array_column($lock->all(), 'files'),
function (array $carry, array $package) {
return array_merge($carry, $package);
},
[]
)
);

$removableFiles = $recipe->getFiles();

$lockedFiles = array_map('realpath', $lockedFiles);

// Compare file paths by their real path to abstract OS differences
foreach (array_keys($removableFiles) as $file) {
if (\in_array(realpath($file), $lockedFiles)) {
unset($removableFiles[$file]);
}
}

return $removableFiles;
$this->removeFiles($config, $this->filesManager->getRemovableFilesFromRecipeAndLock($recipe), $this->options->get('root-dir'));
}

private function copyFiles(array $manifest, array $files, array $options): array
Expand Down Expand Up @@ -98,7 +72,7 @@ private function copyFile(string $to, string $contents, bool $executable, array
$basePath = $options['root-dir'] ?? '.';
$copiedFile = str_replace($basePath.\DIRECTORY_SEPARATOR, '', $to);

if (!$this->options->shouldWriteFile($to, $overwrite)) {
if (!$this->filesManager->shouldWriteFile($to, $overwrite)) {
return $copiedFile;
}

Expand Down
102 changes: 102 additions & 0 deletions src/FilesManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Flex;

use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;

/**
* @author Maxime Hélias <maximehelias16@gmail.com>
*/
class FilesManager
{
private $io;
protected $path;

private $writtenFiles = [];
private $files;

public function __construct(IOInterface $io, Lock $lock, string $rootDir)
{
$this->io = $io;

$this->path = new Path($rootDir);
$this->files = array_count_values(
array_map(
function (string $file) {
return realpath($file) ?: '';
}, array_reduce(
array_column($lock->all(), 'files'),
function (array $carry, array $package) {
return array_merge($carry, $package);
},
[]
)
)
);
}

public function shouldWriteFile(string $file, bool $overwrite): bool
{
if (isset($this->writtenFiles[$file])) {
return false;
}
$this->writtenFiles[$file] = true;

if (!file_exists($file)) {
return true;
}

if (!$overwrite) {
return false;
}

if (!filesize($file)) {
return true;
}

exec('git status --short --ignored --untracked-files=all -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status);

if (0 !== $status) {
return (bool) $this->io && $this->io->askConfirmation(sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false);
}

if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) {
return true;
}

$name = basename($file);
$name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name;

return (bool) $this->io && $this->io->askConfirmation(sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false);
}

public function getRemovableFilesFromRecipeAndLock(Recipe $recipe): array
{
$removableFiles = $recipe->getFiles();
// Compare file paths by their real path to abstract OS differences
foreach (array_keys($removableFiles) as $file) {
$file = realpath($file);
if (!isset($this->files[$file])) {
continue;
}

--$this->files[$file];

if ($this->files[$file] <= 0) {
unset($removableFiles[$file]);
}
}

return $removableFiles;
}
}
6 changes: 4 additions & 2 deletions src/Flex.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class Flex implements PluginInterface, EventSubscriberInterface

private $config;
private $options;
private $filesManager;
private $configurator;
private $downloader;
private $installer;
Expand Down Expand Up @@ -159,8 +160,9 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
$composer->setRepositoryManager($manager);
}

$this->configurator = new Configurator($composer, $io, $this->options);
$this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: str_replace('composer.json', 'symfony.lock', Factory::getComposerFile()));
$this->filesManager = new FilesManager($io, $this->lock, $this->options->get('root-dir'));
$this->configurator = new Configurator($composer, $io, $this->filesManager, $this->options);

$disable = true;
foreach (array_merge($composer->getPackage()->getRequires() ?? [], $composer->getPackage()->getDevRequires() ?? []) as $link) {
Expand Down Expand Up @@ -877,7 +879,7 @@ private function initOptions(): Options
'root-dir' => $extra['symfony']['root-dir'] ?? '.',
], $extra);

return new Options($options, $this->io);
return new Options($options);
}

private function getFlexId()
Expand Down
43 changes: 1 addition & 42 deletions src/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,16 @@

namespace Symfony\Flex;

use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;

/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Options
{
private $options;
private $writtenFiles = [];
private $io;

public function __construct(array $options = [], IOInterface $io = null)
public function __construct(array $options = [])
{
$this->options = $options;
$this->io = $io;
}

public function get(string $name)
Expand All @@ -46,41 +40,6 @@ public function expandTargetDir(string $target): string
}, $target);
}

public function shouldWriteFile(string $file, bool $overwrite): bool
{
if (isset($this->writtenFiles[$file])) {
return false;
}
$this->writtenFiles[$file] = true;

if (!file_exists($file)) {
return true;
}

if (!$overwrite) {
return false;
}

if (!filesize($file)) {
return true;
}

exec('git status --short --ignored --untracked-files=all -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status);

if (0 !== $status) {
return (bool) $this->io && $this->io->askConfirmation(sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false);
}

if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) {
return true;
}

$name = basename($file);
$name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name;

return (bool) $this->io && $this->io->askConfirmation(sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false);
}

public function toArray(): array
{
return $this->options;
Expand Down
2 changes: 2 additions & 0 deletions tests/Configurator/BundlesConfiguratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public function testConfigure()
$configurator = new BundlesConfigurator(
$this->getMockBuilder('Composer\Composer')->getMock(),
$this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);

Expand Down Expand Up @@ -64,6 +65,7 @@ public function testConfigureWhenBundlesAlreayExists()
$configurator = new BundlesConfigurator(
$this->getMockBuilder('Composer\Composer')->getMock(),
$this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);

Expand Down
6 changes: 6 additions & 0 deletions tests/Configurator/ContainerConfiguratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public function testConfigure()
$configurator = new ContainerConfigurator(
$this->getMockBuilder(Composer::class)->getMock(),
$this->getMockBuilder(IOInterface::class)->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);
$configurator->configure($recipe, ['locale' => 'en'], $lock);
Expand Down Expand Up @@ -78,6 +79,7 @@ public function testConfigureWithoutParametersKey()
$configurator = new ContainerConfigurator(
$this->getMockBuilder(Composer::class)->getMock(),
$this->getMockBuilder(IOInterface::class)->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);
$configurator->configure($recipe, ['locale' => 'en'], $lock);
Expand Down Expand Up @@ -118,6 +120,7 @@ public function testConfigureWithoutDuplicated()
$configurator = new ContainerConfigurator(
$this->getMockBuilder(Composer::class)->getMock(),
$this->getMockBuilder(IOInterface::class)->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);
$configurator->configure($recipe, ['locale' => 'en'], $lock);
Expand Down Expand Up @@ -162,6 +165,7 @@ public function testConfigureWithComplexContent()
$configurator = new ContainerConfigurator(
$this->getMockBuilder(Composer::class)->getMock(),
$this->getMockBuilder(IOInterface::class)->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);
$configurator->configure($recipe, ['locale' => 'en', 'foobar' => 'baz'], $lock);
Expand Down Expand Up @@ -212,6 +216,7 @@ public function testConfigureWithComplexContent2()
$configurator = new ContainerConfigurator(
$this->getMockBuilder(Composer::class)->getMock(),
$this->getMockBuilder(IOInterface::class)->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);
$configurator->configure($recipe, ['locale' => 'en', 'foobar' => 'baz', 'array' => ['key1' => 'value', 'key2' => "Escape ' one quote"], 'key1' => 'Keep It'], $lock);
Expand Down Expand Up @@ -262,6 +267,7 @@ public function testConfigureWithEnvVariable()
$configurator = new ContainerConfigurator(
$this->getMockBuilder(Composer::class)->getMock(),
$this->getMockBuilder(IOInterface::class)->getMock(),
$this->getMockBuilder('Symfony\Flex\FilesManager')->disableOriginalConstructor()->getMock(),
new Options(['config-dir' => 'config', 'root-dir' => FLEX_TEST_DIR])
);
$configurator->configure($recipe, ['env(APP_ENV)' => ''], $lock);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Composer\Package\PackageInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Flex\Configurator\CopyFromPackageConfigurator;
use Symfony\Flex\FilesManager;
use Symfony\Flex\Lock;
use Symfony\Flex\Options;
use Symfony\Flex\Recipe;
Expand Down Expand Up @@ -106,7 +107,7 @@ protected function tearDown(): void

private function createConfigurator(): CopyFromPackageConfigurator
{
return new CopyFromPackageConfigurator($this->composer, $this->io, new Options(['root-dir' => FLEX_TEST_DIR]));
return new CopyFromPackageConfigurator($this->composer, $this->io, new FilesManager($this->io, $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock(), FLEX_TEST_DIR), new Options(['root-dir' => FLEX_TEST_DIR]));
}

private function cleanUpTargetFiles()
Expand Down
3 changes: 2 additions & 1 deletion tests/Configurator/CopyFromPackageConfiguratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use LogicException;
use PHPUnit\Framework\TestCase;
use Symfony\Flex\Configurator\CopyFromPackageConfigurator;
use Symfony\Flex\FilesManager;
use Symfony\Flex\Lock;
use Symfony\Flex\Options;
use Symfony\Flex\Recipe;
Expand Down Expand Up @@ -166,7 +167,7 @@ protected function tearDown(): void

private function createConfigurator(): CopyFromPackageConfigurator
{
return new CopyFromPackageConfigurator($this->composer, $this->io, new Options(['root-dir' => FLEX_TEST_DIR], $this->io));
return new CopyFromPackageConfigurator($this->composer, $this->io, new FilesManager($this->io, $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock(), FLEX_TEST_DIR), new Options(['root-dir' => FLEX_TEST_DIR], $this->io));
}

private function cleanUpTargetFiles()
Expand Down
Loading