From 1cacd32e4c67ed74cd149eddfb6988bfc98ff397 Mon Sep 17 00:00:00 2001 From: Jan Rosier Date: Tue, 27 May 2025 21:35:43 +0200 Subject: [PATCH 1/2] CS modernize_strpos --- .php-cs-fixer.dist.php | 1 - src/Configurator/AbstractConfigurator.php | 6 +++--- src/Configurator/AddLinesConfigurator.php | 12 ++++++------ src/Configurator/CopyFromRecipeConfigurator.php | 6 +++--- src/Configurator/DockerComposeConfigurator.php | 4 ++-- src/Downloader.php | 8 ++++---- src/Flex.php | 2 +- src/GithubApi.php | 4 ++-- src/PackageResolver.php | 6 +++--- src/SymfonyBundle.php | 4 ++-- src/Update/RecipePatcher.php | 2 +- 11 files changed, 27 insertions(+), 28 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index d617f9f4c..536b47448 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -8,7 +8,6 @@ '@Symfony' => true, '@Symfony:risky' => true, 'fopen_flags' => false, - 'modernize_strpos' => false, // requires PHP 8 'protected_to_private' => false, ]) ->setRiskyAllowed(true) diff --git a/src/Configurator/AbstractConfigurator.php b/src/Configurator/AbstractConfigurator.php index 7608cab22..e88defaf2 100644 --- a/src/Configurator/AbstractConfigurator.php +++ b/src/Configurator/AbstractConfigurator.php @@ -56,7 +56,7 @@ protected function write($messages, $verbosity = IOInterface::VERBOSE) protected function isFileMarked(Recipe $recipe, string $file): bool { - return is_file($file) && false !== strpos(file_get_contents($file), \sprintf('###> %s ###', $recipe->getName())); + return is_file($file) && str_contains(file_get_contents($file), \sprintf('###> %s ###', $recipe->getName())); } protected function markData(Recipe $recipe, string $data): string @@ -66,7 +66,7 @@ protected function markData(Recipe $recipe, string $data): string protected function isFileXmlMarked(Recipe $recipe, string $file): bool { - return is_file($file) && false !== strpos(file_get_contents($file), \sprintf('###+ %s ###', $recipe->getName())); + return is_file($file) && str_contains(file_get_contents($file), \sprintf('###+ %s ###', $recipe->getName())); } protected function markXmlData(Recipe $recipe, string $data): string @@ -104,7 +104,7 @@ protected function updateDataString(string $contents, string $data): ?string $startMark = trim(reset($pieces)); $endMark = trim(end($pieces)); - if (false === strpos($contents, $startMark) || false === strpos($contents, $endMark)) { + if (!str_contains($contents, $startMark) || !str_contains($contents, $endMark)) { return null; } diff --git a/src/Configurator/AddLinesConfigurator.php b/src/Configurator/AddLinesConfigurator.php index 8a4bc6d62..59d7b6bbb 100644 --- a/src/Configurator/AddLinesConfigurator.php +++ b/src/Configurator/AddLinesConfigurator.php @@ -168,7 +168,7 @@ private function getPatchedContents(string $file, string $value, string $positio { $fileContents = $this->readFile($file); - if (false !== strpos($fileContents, $value)) { + if (str_contains($fileContents, $value)) { return $fileContents; // already includes value, skip } @@ -185,7 +185,7 @@ private function getPatchedContents(string $file, string $value, string $positio $lines = explode("\n", $fileContents); $targetFound = false; foreach ($lines as $key => $line) { - if (false !== strpos($line, $target)) { + if (str_contains($line, $target)) { array_splice($lines, $key + 1, 0, $value); $targetFound = true; @@ -214,13 +214,13 @@ private function getUnPatchedContents(string $file, $value): string { $fileContents = $this->readFile($file); - if (false === strpos($fileContents, $value)) { + if (!str_contains($fileContents, $value)) { return $fileContents; // value already gone! } - if (false !== strpos($fileContents, "\n".$value)) { + if (str_contains($fileContents, "\n".$value)) { $value = "\n".$value; - } elseif (false !== strpos($fileContents, $value."\n")) { + } elseif (str_contains($fileContents, $value."\n")) { $value .= "\n"; } @@ -249,7 +249,7 @@ private function isPackageInstalled($packages): bool private function relativize(string $path): string { $rootDir = $this->options->get('root-dir'); - if (0 === strpos($path, $rootDir)) { + if (str_starts_with($path, $rootDir)) { $path = substr($path, \strlen($rootDir) + 1); } diff --git a/src/Configurator/CopyFromRecipeConfigurator.php b/src/Configurator/CopyFromRecipeConfigurator.php index 004c06538..0e18096b5 100644 --- a/src/Configurator/CopyFromRecipeConfigurator.php +++ b/src/Configurator/CopyFromRecipeConfigurator.php @@ -58,7 +58,7 @@ public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array private function resolveTargetFolder(string $path, array $config): string { foreach ($config as $key => $target) { - if (0 === strpos($path, $key)) { + if (str_starts_with($path, $key)) { return $this->options->expandTargetDir($target).substr($path, \strlen($key)); } } @@ -116,7 +116,7 @@ private function copyDir(string $source, string $target, array $files, array $op { $copiedFiles = []; foreach ($files as $file => $data) { - if (0 === strpos($file, $source)) { + if (str_starts_with($file, $source)) { $file = $this->path->concatenate([$target, substr($file, \strlen($source))]); $copiedFiles[] = $this->copyFile($file, $data['contents'], $data['executable'], $options); } @@ -160,7 +160,7 @@ private function removeFiles(array $manifest, array $files, string $to) if ('/' === substr($source, -1)) { foreach (array_keys($files) as $file) { - if (0 === strpos($file, $source)) { + if (str_starts_with($file, $source)) { $this->removeFile($this->path->concatenate([$to, $target, substr($file, \strlen($source))])); } } diff --git a/src/Configurator/DockerComposeConfigurator.php b/src/Configurator/DockerComposeConfigurator.php index 4f471ed6d..9a13906e2 100644 --- a/src/Configurator/DockerComposeConfigurator.php +++ b/src/Configurator/DockerComposeConfigurator.php @@ -260,7 +260,7 @@ private function configureDockerCompose(Recipe $recipe, array $config, bool $upd } // Skip blank lines and comments - if (('' !== $ltrimedLine && 0 === strpos($ltrimedLine, '#')) || '' === trim($line)) { + if (('' !== $ltrimedLine && str_starts_with($ltrimedLine, '#')) || '' === trim($line)) { continue; } @@ -349,7 +349,7 @@ private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, $updatedContents = []; foreach ($files as $file) { $localPath = $file; - if (0 === strpos($file, $rootDir)) { + if (str_starts_with($file, $rootDir)) { $localPath = substr($file, \strlen($rootDir) + 1); } $localPath = ltrim($localPath, '/\\'); diff --git a/src/Downloader.php b/src/Downloader.php index e6bb56734..a0b971ee6 100644 --- a/src/Downloader.php +++ b/src/Downloader.php @@ -60,9 +60,9 @@ public function __construct(Composer $composer, IOInterface $io, HttpDownloader if (null === $endpoint = $composer->getPackage()->getExtra()['symfony']['endpoint'] ?? null) { $this->endpoints = self::DEFAULT_ENDPOINTS; - } elseif (\is_array($endpoint) || false !== strpos($endpoint, '.json') || 'flex://defaults' === $endpoint) { + } elseif (\is_array($endpoint) || str_contains($endpoint, '.json') || 'flex://defaults' === $endpoint) { $this->endpoints = array_values((array) $endpoint); - if (\is_string($endpoint) && false !== strpos($endpoint, '.json')) { + if (\is_string($endpoint) && str_contains($endpoint, '.json')) { $this->endpoints[] = 'flex://defaults'; } } else { @@ -71,7 +71,7 @@ public function __construct(Composer $composer, IOInterface $io, HttpDownloader if (false === $endpoint = getenv('SYMFONY_ENDPOINT')) { // no-op - } elseif (false !== strpos($endpoint, '.json') || 'flex://defaults' === $endpoint) { + } elseif (str_contains($endpoint, '.json') || 'flex://defaults' === $endpoint) { $this->endpoints ?? $this->endpoints = self::DEFAULT_ENDPOINTS; array_unshift($this->endpoints, $endpoint); $this->legacyEndpoint = null; @@ -174,7 +174,7 @@ public function getRecipes(array $operations): array if ($operation instanceof InformationOperation && $operation->getVersion()) { $version = $operation->getVersion(); } - if (0 === strpos($version, 'dev-') && isset($package->getExtra()['branch-alias'])) { + if (str_starts_with($version, 'dev-') && isset($package->getExtra()['branch-alias'])) { $branchAliases = $package->getExtra()['branch-alias']; if ( (isset($branchAliases[$version]) && $alias = $branchAliases[$version]) diff --git a/src/Flex.php b/src/Flex.php index a17aedec9..d1f36bf6a 100644 --- a/src/Flex.php +++ b/src/Flex.php @@ -351,7 +351,7 @@ public function install(Event $event) $runtime = $this->options->get('runtime'); $dotenvPath = $rootDir.'/'.($runtime['dotenv_path'] ?? '.env'); - if (!file_exists($dotenvPath) && !file_exists($dotenvPath.'.local') && file_exists($dotenvPath.'.dist') && false === strpos(file_get_contents($dotenvPath.'.dist'), '.env.local')) { + if (!file_exists($dotenvPath) && !file_exists($dotenvPath.'.local') && file_exists($dotenvPath.'.dist') && !str_contains(file_get_contents($dotenvPath.'.dist'), '.env.local')) { copy($dotenvPath.'.dist', $dotenvPath); } diff --git a/src/GithubApi.php b/src/GithubApi.php index 61d4edef9..e33c671d7 100644 --- a/src/GithubApi.php +++ b/src/GithubApi.php @@ -142,7 +142,7 @@ public function getPullRequestForCommit(string $commit, string $repo): ?array $bestItem = null; foreach ($data['items'] as $item) { // make sure the PR referenced isn't from a different repository - if (false === strpos($item['html_url'], \sprintf('%s/pull', $repositoryName))) { + if (!str_contains($item['html_url'], \sprintf('%s/pull', $repositoryName))) { continue; } @@ -186,7 +186,7 @@ private function requestGitHubApi(string $path) private function getRepositoryName(string $repo): ?string { // only supports public repository placement - if (0 !== strpos($repo, 'github.com')) { + if (!str_starts_with($repo, 'github.com')) { return null; } diff --git a/src/PackageResolver.php b/src/PackageResolver.php index 37987fe84..5286074a1 100644 --- a/src/PackageResolver.php +++ b/src/PackageResolver.php @@ -70,7 +70,7 @@ public function parseVersion(string $package, string $version, bool $isRequire): { $guess = 'guess' === ($version ?: 'guess'); - if (0 !== strpos($package, 'symfony/')) { + if (!str_starts_with($package, 'symfony/')) { return $guess ? '' : ':'.$version; } @@ -108,7 +108,7 @@ private function resolvePackageName(string $argument, int $position, bool $isReq $skippedPackages[] = 'lock'; } - if (false !== strpos($argument, '/') || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $argument) || preg_match('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $argument) || \in_array($argument, $skippedPackages)) { + if (str_contains($argument, '/') || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $argument) || preg_match('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $argument) || \in_array($argument, $skippedPackages)) { return $argument; } @@ -140,7 +140,7 @@ private function throwAlternatives(string $argument, int $position) $alternatives = []; foreach ($this->downloader->getAliases() as $alias => $package) { $lev = levenshtein($argument, $alias); - if ($lev <= \strlen($argument) / 3 || ('' !== $argument && false !== strpos($alias, $argument))) { + if ($lev <= \strlen($argument) / 3 || ('' !== $argument && str_contains($alias, $argument))) { $alternatives[$package][] = $alias; } } diff --git a/src/SymfonyBundle.php b/src/SymfonyBundle.php index 7d1d8a1c3..ed6a187fd 100644 --- a/src/SymfonyBundle.php +++ b/src/SymfonyBundle.php @@ -109,7 +109,7 @@ private function isBundleClass(string $class, string $path, bool $isPsr4): bool // heuristic that should work in almost all cases $classContents = file_get_contents($classPath); - return (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\Bundle')) - || (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\AbstractBundle')); + return str_contains($classContents, 'Symfony\Component\HttpKernel\Bundle\Bundle') + || str_contains($classContents, 'Symfony\Component\HttpKernel\Bundle\AbstractBundle'); } } diff --git a/src/Update/RecipePatcher.php b/src/Update/RecipePatcher.php index 69773a061..680fb0f32 100644 --- a/src/Update/RecipePatcher.php +++ b/src/Update/RecipePatcher.php @@ -233,7 +233,7 @@ private function _applyPatchFile(RecipePatch $patch) return true; } - if (false !== strpos($this->processExecutor->getErrorOutput(), 'with conflicts')) { + if (str_contains($this->processExecutor->getErrorOutput(), 'with conflicts')) { // successful with conflicts return false; } From 6443e311ecc3a63339c2be75847ae6a6182a71e1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 27 May 2025 14:23:05 +0200 Subject: [PATCH 2/2] Don't remove still-referenced files when unconfiguring recipes --- .../CopyFromRecipeConfigurator.php | 56 +++---------------- src/Flex.php | 24 ++++---- src/Options.php | 36 +++++++++++- .../CopyFromRecipeConfiguratorTest.php | 26 +++++---- 4 files changed, 71 insertions(+), 71 deletions(-) diff --git a/src/Configurator/CopyFromRecipeConfigurator.php b/src/Configurator/CopyFromRecipeConfigurator.php index 0e18096b5..6acf63772 100644 --- a/src/Configurator/CopyFromRecipeConfigurator.php +++ b/src/Configurator/CopyFromRecipeConfigurator.php @@ -31,7 +31,13 @@ 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')); + $rootDir = $this->options->get('root-dir'); + + foreach ($this->options->getRemovableFiles($recipe, $lock) as $file) { + if ('.git' !== $file) { // never remove the main Git directory, even if it was created by a recipe + $this->removeFile($this->path->concatenate([$rootDir, $file])); + } + } } public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void @@ -66,32 +72,6 @@ private function resolveTargetFolder(string $path, array $config): string return $path; } - 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; - } - private function copyFiles(array $manifest, array $files, array $options): array { $copiedFiles = []; @@ -148,28 +128,6 @@ private function copyFile(string $to, string $contents, bool $executable, array return $copiedFile; } - private function removeFiles(array $manifest, array $files, string $to) - { - foreach ($manifest as $source => $target) { - $target = $this->options->expandTargetDir($target); - - if ('.git' === $target) { - // never remove the main Git directory, even if it was created by a recipe - continue; - } - - if ('/' === substr($source, -1)) { - foreach (array_keys($files) as $file) { - if (str_starts_with($file, $source)) { - $this->removeFile($this->path->concatenate([$to, $target, substr($file, \strlen($source))])); - } - } - } else { - $this->removeFile($this->path->concatenate([$to, $target])); - } - } - } - private function removeFile(string $to) { if (!file_exists($to)) { diff --git a/src/Flex.php b/src/Flex.php index d1f36bf6a..fdaaf003a 100644 --- a/src/Flex.php +++ b/src/Flex.php @@ -111,13 +111,19 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) $this->composer = $composer; $this->io = $io; $this->config = $composer->getConfig(); + + $composerFile = Factory::getComposerFile(); + $composerLock = 'json' === pathinfo($composerFile, \PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile.'.lock'; + $symfonyLock = str_replace('composer', 'symfony', basename($composerLock)); + + $this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: \dirname($composerLock).'/'.(basename($composerLock) !== $symfonyLock ? $symfonyLock : 'symfony.lock')); $this->options = $this->initOptions(); // if Flex is being upgraded, the original operations from the original Flex // instance are stored in the static property, so we can reuse them now. - if (property_exists(self::class, 'storedOperations') && self::$storedOperations) { - $this->operations = self::$storedOperations; - self::$storedOperations = []; + if (property_exists(Flex::class, 'storedOperations') && Flex::$storedOperations) { + $this->operations = Flex::$storedOperations; + Flex::$storedOperations = []; } $symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? '')); @@ -130,12 +136,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) $this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader); } - $composerFile = Factory::getComposerFile(); - $composerLock = 'json' === pathinfo($composerFile, \PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile.'.lock'; - $symfonyLock = str_replace('composer', 'symfony', basename($composerLock)); - $this->configurator = new Configurator($composer, $io, $this->options); - $this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: \dirname($composerLock).'/'.(basename($composerLock) !== $symfonyLock ? $symfonyLock : 'symfony.lock')); $disable = true; foreach (array_merge($composer->getPackage()->getRequires() ?? [], $composer->getPackage()->getDevRequires() ?? []) as $link) { @@ -210,8 +211,9 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) */ public function deactivate(Composer $composer, IOInterface $io) { - // store operations in case Flex is being upgraded - self::$storedOperations = $this->operations; + // Using `Flex::` instead of `self::` to avoid issues when + // composer renames plugin classes when upgrading them + Flex::$storedOperations = $this->operations; self::$activated = false; } @@ -707,7 +709,7 @@ private function initOptions(): Options 'runtime' => $extra['runtime'] ?? [], ], $extra); - return new Options($options, $this->io); + return new Options($options, $this->io, $this->lock); } private function formatOrigin(Recipe $recipe): string diff --git a/src/Options.php b/src/Options.php index ee0bb3b3b..55aeae7af 100644 --- a/src/Options.php +++ b/src/Options.php @@ -22,11 +22,13 @@ class Options private $options; private $writtenFiles = []; private $io; + private $lockData; - public function __construct(array $options = [], ?IOInterface $io = null) + public function __construct(array $options = [], ?IOInterface $io = null, ?Lock $lock = null) { $this->options = $options; $this->io = $io; + $this->lockData = $lock?->all() ?? []; } public function get(string $name) @@ -101,6 +103,38 @@ public function shouldWriteFile(string $file, bool $overwrite, bool $skipQuestio return $this->io && $this->io->askConfirmation(\sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false); } + public function getRemovableFiles(Recipe $recipe, Lock $lock): array + { + if (null === $removableFiles = $this->lockData[$recipe->getName()]['files'] ?? null) { + $removableFiles = []; + foreach (array_keys($recipe->getFiles()) as $source => $target) { + if (str_ends_with($source, '/')) { + $removableFiles[] = $this->expandTargetDir($target); + } + } + } + + unset($this->lockData[$recipe->getName()]); + $lockedFiles = array_count_values(array_merge(...array_column($lock->all(), 'files'))); + + $nonRemovableFiles = []; + foreach ($removableFiles as $i => $file) { + if (isset($lockedFiles[$file])) { + $nonRemovableFiles[] = $file; + unset($removableFiles[$i]); + } + } + + if ($nonRemovableFiles && $this->io) { + $this->io?->writeError(' The following files are still referenced by other recipes, you might need to adjust them manually:'); + foreach ($nonRemovableFiles as $file) { + $this->io?->writeError(' - '.$file); + } + } + + return array_values($removableFiles); + } + public function toArray(): array { return $this->options; diff --git a/tests/Configurator/CopyFromRecipeConfiguratorTest.php b/tests/Configurator/CopyFromRecipeConfiguratorTest.php index 95f016567..2d6ba3c52 100644 --- a/tests/Configurator/CopyFromRecipeConfiguratorTest.php +++ b/tests/Configurator/CopyFromRecipeConfiguratorTest.php @@ -61,7 +61,7 @@ public function testConfigureLocksFiles() public function testConfigureAndOverwriteFiles() { if (!file_exists($this->targetDirectory)) { - mkdir($this->targetDirectory); + @mkdir($this->targetDirectory, 0777, true); } file_put_contents($this->targetFile, '-'); $lock = $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock(); @@ -99,17 +99,22 @@ public function testConfigure() public function testUnconfigureKeepsLockedFiles() { if (!file_exists($this->sourceDirectory)) { - mkdir($this->sourceDirectory); + @mkdir($this->sourceDirectory, 0777, true); + } + if (!file_exists($this->targetDirectory)) { + @mkdir($this->targetDirectory, 0777, true); } + file_put_contents($this->targetFile, ''); file_put_contents($this->sourceFile, '-'); - $this->assertFileExists($this->sourceFile); $lock = new Lock(FLEX_TEST_DIR.'/test.lock'); - $lock->set('other-recipe', ['files' => ['./'.$this->targetFileRelativePath]]); + $lock->set('other-recipe', ['files' => [$this->targetFileRelativePath]]); + $this->recipe->method('getName')->willReturn('test-recipe'); $this->createConfigurator()->unconfigure($this->recipe, [$this->targetFileRelativePath], $lock); $this->assertFileExists($this->sourceFile); + $this->assertFileExists($this->targetFile); } public function testUnconfigure() @@ -118,11 +123,12 @@ public function testUnconfigure() $this->io->expects($this->at(1))->method('writeError')->with([' Removed "./config/file"']); if (!file_exists($this->targetDirectory)) { - mkdir($this->targetDirectory); + @mkdir($this->targetDirectory, 0777, true); } file_put_contents($this->targetFile, ''); $this->assertFileExists($this->targetFile); $lock = $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock(); + $this->recipe->method('getName')->willReturn('test-recipe'); $this->createConfigurator()->unconfigure($this->recipe, [$this->targetFileRelativePath], $lock); $this->assertFileDoesNotExist($this->targetFile); } @@ -270,8 +276,6 @@ public function testUpdateResolveDirectories() protected function setUp(): void { - parent::setUp(); - $this->sourceDirectory = FLEX_TEST_DIR.'/source'; $this->sourceFileRelativePath = 'source/file'; $this->sourceFile = $this->sourceDirectory.'/file'; @@ -294,14 +298,16 @@ protected function setUp(): void protected function tearDown(): void { - parent::tearDown(); - $this->cleanUpTargetFiles(); } private function createConfigurator(): CopyFromRecipeConfigurator { - return new CopyFromRecipeConfigurator($this->getMockBuilder(Composer::class)->getMock(), $this->io, new Options(['root-dir' => FLEX_TEST_DIR, 'config-dir' => 'config'], $this->io)); + $lock = new Lock(FLEX_TEST_DIR.'/test.lock'); + $lock->set('test-recipe', ['files' => [$this->targetFileRelativePath]]); + $options = new Options(['root-dir' => FLEX_TEST_DIR, 'config-dir' => 'config'], $this->io, $lock); + + return new CopyFromRecipeConfigurator($this->getMockBuilder(Composer::class)->getMock(), $this->io, $options); } private function cleanUpTargetFiles()