Skip to content

Commit 15f2810

Browse files
committed
Add distribution builder
1 parent 7bcc0ee commit 15f2810

2 files changed

Lines changed: 118 additions & 19 deletions

File tree

src/Package/PackageDistributionResolver.php

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55
namespace CodedMonkey\Dirigent\Package;
66

77
use CodedMonkey\Dirigent\Composer\ComposerClient;
8+
use CodedMonkey\Dirigent\Composer\ConfigFactory;
89
use CodedMonkey\Dirigent\Doctrine\Entity\Distribution;
910
use CodedMonkey\Dirigent\Doctrine\Entity\Metadata;
1011
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
1112
use CodedMonkey\Dirigent\Doctrine\Entity\Version;
1213
use CodedMonkey\Dirigent\Doctrine\Repository\DistributionRepository;
14+
use CodedMonkey\Dirigent\Entity\PackageFetchStrategy;
1315
use CodedMonkey\Dirigent\Message\ResolveDistribution;
16+
use Composer\IO\NullIO;
17+
use Composer\Pcre\Preg;
18+
use Composer\Util\Filesystem as ComposerFilesystem;
19+
use Composer\Util\Git as GitUtility;
20+
use Composer\Util\ProcessExecutor;
21+
use Composer\Util\Url;
1422
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1523
use Symfony\Component\Filesystem\Filesystem;
1624
use Symfony\Component\Lock\LockFactory;
@@ -28,6 +36,10 @@ public function __construct(
2836
private ComposerClient $composer,
2937
private DistributionRepository $distributionRepository,
3038
private LockFactory $lockFactory,
39+
#[Autowire(param: 'dirigent.distributions.build')]
40+
private bool $buildDistributions,
41+
#[Autowire(param: 'dirigent.distributions.mirror')]
42+
private bool $mirrorDistributions,
3143
#[Autowire(param: 'dirigent.distributions.dev_versions')]
3244
private bool $includeDevVersions,
3345
#[Autowire(param: 'dirigent.storage.path')]
@@ -37,7 +49,7 @@ public function __construct(
3749
$this->storagePath = "$storagePath/distribution";
3850
}
3951

40-
public function exists(Metadata|string $metadataOrPath, string $reference, string $type): bool
52+
public function exists(Metadata|string $metadataOrPath, ?string $reference = null, ?string $type = null): bool
4153
{
4254
if ($metadataOrPath instanceof Metadata) {
4355
return $this->filesystem->exists($this->path($metadataOrPath, $reference, $type));
@@ -102,15 +114,20 @@ public function removeVersion(Version $version): void
102114

103115
public function resolve(Metadata $metadata, string $reference, string $type, bool $async): bool
104116
{
105-
if ($this->exists($metadata, $reference, $type)) {
117+
$path = $this->path($metadata, $reference, $type);
118+
119+
if ($this->exists($path)) {
106120
return true;
107121
}
108122

109-
if ($reference !== $metadata->getDistributionReference() || $type !== $metadata->getDistributionType()) {
123+
if (null === $strategy = $this->getFetchStrategy($metadata)) {
110124
return false;
111125
}
112126

113-
if ($metadata->getVersion()->isDevelopment() && !$this->includeDevVersions) {
127+
$currentReference = $strategy->isMirror() ? $metadata->getDistributionReference() : $metadata->getSourceReference();
128+
$currentType = $strategy->isMirror() ? $metadata->getDistributionType() : 'zip';
129+
130+
if ($reference !== $currentReference || $type !== $currentType) {
114131
return false;
115132
}
116133

@@ -124,41 +141,119 @@ public function resolve(Metadata $metadata, string $reference, string $type, boo
124141
return false;
125142
}
126143

127-
$distributionUrl = $metadata->getDistributionUrl();
128-
$path = $this->path($metadata, $reference, $type);
129-
130144
$lock = $this->createDistributionLock($path);
131145

132146
try {
133-
if ($this->filesystem->exists($path)) {
134-
return true;
135-
}
136-
137147
if (null === $distribution = $this->distributionRepository->findOneByReferenceAndType($metadata, $reference, $type)) {
138148
$distribution = new Distribution($metadata, $reference, $type);
139149
}
140150

141-
$this->filesystem->mkdir(dirname($path));
142-
143-
$httpDownloader = $this->composer->createHttpDownloader();
144-
$httpDownloader->copy($distributionUrl, $path);
151+
$result = false;
152+
153+
// Build the distribution from VCS source
154+
if ($strategy->isVcs()) {
155+
$result = $this->build($distribution);
156+
157+
if (
158+
!$result
159+
&& $this->mirrorDistributions
160+
&& $metadata->hasDistribution()
161+
&& $reference === $metadata->getDistributionReference()
162+
&& $type === $metadata->getDistributionType()
163+
) {
164+
// Mirror the distribution if it failed to build from source
165+
// todo log fallback
166+
$strategy = PackageFetchStrategy::Mirror;
167+
}
168+
}
145169

146-
$distribution->setSource($distributionUrl);
147-
$distribution->setResolvedAt();
170+
if ($strategy->isMirror()) {
171+
$result = $this->mirror($distribution, $path);
172+
}
148173

149-
$this->distributionRepository->save($distribution, true);
174+
if ($result) {
175+
$distribution->setResolvedAt();
176+
$this->distributionRepository->save($distribution, true);
177+
}
150178

151-
return true;
179+
return $result;
152180
} finally {
153181
$lock->release();
154182
}
155183
}
156184

185+
private function build(Distribution $distribution): bool
186+
{
187+
$metadata = $distribution->getMetadata();
188+
$reference = $distribution->getReference();
189+
190+
$package = $metadata->getPackage();
191+
$repositoryUrl = $package->getRepositoryUrl();
192+
$distributionPath = $this->path($metadata, $reference, 'zip');
193+
194+
$composerConfig = ConfigFactory::createForVcsRepository($repositoryUrl, $package->getRepositoryCredentials());
195+
196+
$gitUtility = new GitUtility(
197+
$io = new NullIO(),
198+
$composerConfig,
199+
$process = new ProcessExecutor($io),
200+
new ComposerFilesystem($process),
201+
);
202+
203+
$cacheRepositoryName = Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($repositoryUrl));
204+
$cachePath = $composerConfig->get('cache-vcs-dir') . '/' . $cacheRepositoryName . '/';
205+
206+
$this->filesystem->mkdir(dirname($distributionPath));
207+
208+
$gitUtility->runCommands([
209+
['git', 'archive', '--format=zip', "--output=$distributionPath", $reference],
210+
], $repositoryUrl, $cachePath);
211+
212+
$distribution->setSource(null);
213+
214+
return true;
215+
}
216+
217+
private function mirror(Distribution $distribution, string $path): bool
218+
{
219+
$url = $distribution->getMetadata()->getDistributionUrl();
220+
221+
$this->filesystem->mkdir(dirname($path));
222+
223+
$httpDownloader = $this->composer->createHttpDownloader();
224+
$httpDownloader->copy($url, $path);
225+
226+
$distribution->setSource($url);
227+
228+
return true;
229+
}
230+
157231
private function createDistributionLock(string $path): SharedLockInterface
158232
{
159233
$lock = $this->lockFactory->createLock('distribution.' . hash('sha256', $path), ttl: null);
160234
$lock->acquire(blocking: true);
161235

162236
return $lock;
163237
}
238+
239+
private function getFetchStrategy(Metadata $metadata): ?PackageFetchStrategy
240+
{
241+
$fetchStrategy = $metadata->getPackage()->getFetchStrategy();
242+
243+
if (!$this->includeDevVersions && $metadata->getVersion()->isDevelopment()) {
244+
// Development versions are not supported by the configuration
245+
return null;
246+
} elseif ($this->buildDistributions && $fetchStrategy->isVcs()) {
247+
// Only build distributions if the fetch strategy is VCS (not source because it might not contain VCS data)
248+
return PackageFetchStrategy::Vcs;
249+
} elseif (
250+
$this->mirrorDistributions
251+
&& ($fetchStrategy->isMirror() || $metadata->hasDistribution())
252+
) {
253+
// Always mirror distributions if building from source is not supported and a distribution is available
254+
return PackageFetchStrategy::Mirror;
255+
}
256+
257+
return null;
258+
}
164259
}

tests/UnitTests/Package/PackageDistributionResolverTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public function testRemoveDeletesDistributionFile(): void
4646
$this->createStub(DistributionRepository::class),
4747
$lockFactory = $this->createMock(LockFactory::class),
4848
true,
49+
true,
50+
true,
4951
$this->storagePath,
5052
);
5153

@@ -77,6 +79,8 @@ public function testRemoveKeepsNonEmptyPackageDirectory(): void
7779
$this->createStub(DistributionRepository::class),
7880
$this->createStub(LockFactory::class),
7981
true,
82+
true,
83+
true,
8084
$this->storagePath,
8185
);
8286

0 commit comments

Comments
 (0)