Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 92 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
Cleanup | |
0.00% |
0 / 92 |
|
0.00% |
0 / 7 |
1260 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
cleanup | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
cleanupVendorInstalledJson | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
72 | |||
deleteEmptyDirectories | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
72 | |||
dirIsEmpty | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doIsDeleteVendorPackages | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
56 | |||
doIsDeleteVendorFiles | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * Deletes source files and empty directories. |
4 | */ |
5 | |
6 | namespace BrianHenryIE\Strauss\Pipeline; |
7 | |
8 | use BrianHenryIE\Strauss\Composer\ComposerPackage; |
9 | use BrianHenryIE\Strauss\Config\CleanupConfigInterface; |
10 | use BrianHenryIE\Strauss\Files\File; |
11 | use BrianHenryIE\Strauss\Files\FileWithDependency; |
12 | use BrianHenryIE\Strauss\Helpers\FileSystem; |
13 | use BrianHenryIE\Strauss\Pipeline\Cleanup\InstalledJson; |
14 | use BrianHenryIE\Strauss\Types\DiscoveredSymbols; |
15 | use League\Flysystem\FilesystemException; |
16 | use Psr\Log\LoggerAwareTrait; |
17 | use Psr\Log\LoggerInterface; |
18 | |
19 | class Cleanup |
20 | { |
21 | use LoggerAwareTrait; |
22 | |
23 | protected Filesystem $filesystem; |
24 | |
25 | protected bool $isDeleteVendorFiles; |
26 | protected bool $isDeleteVendorPackages; |
27 | |
28 | protected CleanupConfigInterface $config; |
29 | |
30 | public function __construct( |
31 | CleanupConfigInterface $config, |
32 | Filesystem $filesystem, |
33 | LoggerInterface $logger |
34 | ) { |
35 | $this->config = $config; |
36 | $this->logger = $logger; |
37 | |
38 | $this->isDeleteVendorFiles = $config->isDeleteVendorFiles() && $config->getTargetDirectory() !== $config->getVendorDirectory(); |
39 | $this->isDeleteVendorPackages = $config->isDeleteVendorPackages() && $config->getTargetDirectory() !== $config->getVendorDirectory(); |
40 | |
41 | $this->filesystem = $filesystem; |
42 | } |
43 | |
44 | /** |
45 | * Maybe delete the source files that were copied (depending on config), |
46 | * then delete empty directories. |
47 | * |
48 | * @param File[] $files |
49 | * |
50 | * @throws FilesystemException |
51 | */ |
52 | public function cleanup(array $files): void |
53 | { |
54 | if (!$this->isDeleteVendorPackages && !$this->isDeleteVendorFiles) { |
55 | $this->logger->info('No cleanup required.'); |
56 | return; |
57 | } |
58 | |
59 | $this->logger->info('Beginning cleanup.'); |
60 | |
61 | if ($this->isDeleteVendorPackages) { |
62 | $this->doIsDeleteVendorPackages($files); |
63 | } elseif ($this->isDeleteVendorFiles) { |
64 | $this->doIsDeleteVendorFiles($files); |
65 | } |
66 | |
67 | $this->deleteEmptyDirectories($files); |
68 | } |
69 | |
70 | /** @param array<string,ComposerPackage> $flatDependencyTree */ |
71 | public function cleanupVendorInstalledJson(array $flatDependencyTree, DiscoveredSymbols $discoveredSymbols): void |
72 | { |
73 | $installedJson = new InstalledJson( |
74 | $this->config, |
75 | $this->filesystem, |
76 | $this->logger |
77 | ); |
78 | |
79 | if ($this->config->getTargetDirectory() !== $this->config->getVendorDirectory() |
80 | && !$this->config->isDeleteVendorFiles() && !$this->config->isDeleteVendorPackages() |
81 | ) { |
82 | $installedJson->createAndCleanTargetDirInstalledJson($flatDependencyTree, $discoveredSymbols); |
83 | } elseif ($this->config->getTargetDirectory() !== $this->config->getVendorDirectory() |
84 | && |
85 | ($this->config->isDeleteVendorFiles() ||$this->config->isDeleteVendorPackages()) |
86 | ) { |
87 | $installedJson->createAndCleanTargetDirInstalledJson($flatDependencyTree, $discoveredSymbols); |
88 | $installedJson->cleanupVendorInstalledJson($flatDependencyTree, $discoveredSymbols); |
89 | } elseif ($this->config->getTargetDirectory() === $this->config->getVendorDirectory()) { |
90 | $installedJson->cleanupVendorInstalledJson($flatDependencyTree, $discoveredSymbols); |
91 | } |
92 | } |
93 | |
94 | /** |
95 | * @throws FilesystemException |
96 | */ |
97 | protected function deleteEmptyDirectories(array $files) |
98 | { |
99 | $this->logger->info('Deleting empty directories.'); |
100 | |
101 | $sourceFiles = array_map( |
102 | fn($file) => $file->getSourcePath(), |
103 | $files |
104 | ); |
105 | |
106 | // Get the root folders of the moved files. |
107 | $rootSourceDirectories = []; |
108 | foreach ($sourceFiles as $sourceFile) { |
109 | $arr = explode("/", $sourceFile, 2); |
110 | $dir = $arr[0]; |
111 | $rootSourceDirectories[ $dir ] = $dir; |
112 | } |
113 | $rootSourceDirectories = array_map( |
114 | function (string $path): string { |
115 | return $this->config->getVendorDirectory() . $path; |
116 | }, |
117 | array_keys($rootSourceDirectories) |
118 | ); |
119 | |
120 | foreach ($rootSourceDirectories as $rootSourceDirectory) { |
121 | if (!$this->filesystem->directoryExists($rootSourceDirectory) || is_link($rootSourceDirectory)) { |
122 | continue; |
123 | } |
124 | |
125 | $dirList = $this->filesystem->listContents($rootSourceDirectory, true); |
126 | |
127 | $allFilePaths = array_map( |
128 | fn($file) => $file->path(), |
129 | $dirList->toArray() |
130 | ); |
131 | |
132 | // Sort by longest path first, so subdirectories are deleted before the parent directories are checked. |
133 | usort( |
134 | $allFilePaths, |
135 | fn($a, $b) => count(explode('/', $b)) - count(explode('/', $a)) |
136 | ); |
137 | |
138 | foreach ($allFilePaths as $filePath) { |
139 | if ($this->filesystem->directoryExists($filePath) |
140 | && $this->dirIsEmpty($filePath) |
141 | ) { |
142 | $this->logger->debug('Deleting empty directory ' . $filePath); |
143 | $this->filesystem->deleteDirectory($filePath); |
144 | } |
145 | } |
146 | } |
147 | |
148 | // foreach ($this->filesystem->listContents($this->getAbsoluteVendorDir()) as $dirEntry) { |
149 | // if ($dirEntry->isDir() && $this->dirIsEmpty($dirEntry->path()) && !is_link($dirEntry->path())) { |
150 | // $this->logger->info('Deleting empty directory ' . $dirEntry->path()); |
151 | // $this->filesystem->deleteDirectory($dirEntry->path()); |
152 | // } else { |
153 | // $this->logger->debug('Skipping non-empty directory ' . $dirEntry->path()); |
154 | // } |
155 | // } |
156 | } |
157 | |
158 | // TODO: Move to FileSystem class. |
159 | protected function dirIsEmpty(string $dir): bool |
160 | { |
161 | // TODO BUG this deletes directories with only symlinks inside. How does it behave with hidden files? |
162 | return empty($this->filesystem->listContents($dir)->toArray()); |
163 | } |
164 | |
165 | /** |
166 | * @param array<File> $files |
167 | */ |
168 | protected function doIsDeleteVendorPackages(array $files) |
169 | { |
170 | $this->logger->info('Deleting original vendor packages.'); |
171 | |
172 | $packages = []; |
173 | foreach ($files as $file) { |
174 | if ($file instanceof FileWithDependency) { |
175 | $packages[ $file->getDependency()->getPackageName() ] = $file->getDependency(); |
176 | } |
177 | } |
178 | |
179 | /** @var ComposerPackage $package */ |
180 | foreach ($packages as $package) { |
181 | // Normal package. |
182 | if ($this->filesystem->isSubDirOf($this->config->getVendorDirectory(), $package->getPackageAbsolutePath())) { |
183 | $this->logger->info('Deleting ' . $package->getPackageAbsolutePath()); |
184 | |
185 | $this->filesystem->deleteDirectory($package->getPackageAbsolutePath()); |
186 | } else { |
187 | // TODO: log _where_ the symlink is pointing to. |
188 | $this->logger->info('Deleting symlink at ' . $package->getRelativePath()); |
189 | |
190 | // If it's a symlink, remove the symlink in the directory |
191 | $symlinkPath = |
192 | rtrim( |
193 | $this->config->getVendorDirectory() . $package->getRelativePath(), |
194 | '/' |
195 | ); |
196 | |
197 | if (false !== strpos('WIN', PHP_OS)) { |
198 | /** |
199 | * `unlink()` will not work on Windows. `rmdir()` will not work if there are files in the directory. |
200 | * "On windows, take care that `is_link()` returns false for Junctions." |
201 | * |
202 | * @see https://www.php.net/manual/en/function.is-link.php#113263 |
203 | * @see https://stackoverflow.com/a/18262809/336146 |
204 | */ |
205 | rmdir($symlinkPath); |
206 | } else { |
207 | unlink($symlinkPath); |
208 | } |
209 | } |
210 | if ($this->dirIsEmpty(dirname($package->getPackageAbsolutePath()))) { |
211 | $this->logger->info('Deleting empty directory ' . dirname($package->getPackageAbsolutePath())); |
212 | $this->filesystem->deleteDirectory(dirname($package->getPackageAbsolutePath())); |
213 | } |
214 | } |
215 | } |
216 | |
217 | /** |
218 | * @param array $files |
219 | * |
220 | * @throws FilesystemException |
221 | */ |
222 | public function doIsDeleteVendorFiles(array $files) |
223 | { |
224 | $this->logger->info('Deleting original vendor files.'); |
225 | |
226 | foreach ($files as $file) { |
227 | if (! $file->isDoDelete()) { |
228 | $this->logger->debug('Skipping/preserving ' . $file->getSourcePath()); |
229 | continue; |
230 | } |
231 | |
232 | $sourceRelativePath = $file->getSourcePath(); |
233 | |
234 | $this->logger->info('Deleting ' . $sourceRelativePath); |
235 | |
236 | $this->filesystem->delete($file->getSourcePath()); |
237 | |
238 | $file->setDidDelete(true); |
239 | } |
240 | } |
241 | } |