Repository: php/pie (php)

Issue:
`pie build` crashes if the SO file does not exist after the build
Running on #83:

Despite the code detecting that the .so is missing, it still attempts to use it and then emits a PHP Warnung and then crashes with a TypeError.

```
Build complete, but expected /root/.pie/php8.3_redacted/vendor/redacted/redacted/modules/redacted.so does not exist - however, this may be normal if this extension outputs the .so file in a different location.

Warning: hash_file(/root/.pie/php8.3_redacted/vendor/redacted/redacted/modules/redacted.so): Failed to open stream: No such file or directory in /pwd/src/BinaryFile.php on line 33

Fatal error: Uncaught TypeError: Php\Pie\BinaryFile::__construct(): Argument #2 ($checksum) must be of type string, false given, called in /pwd/src/BinaryFile.php on line 31 and defined in /pwd/src/BinaryFile.php:22
Stack trace:
#0 /pwd/src/BinaryFile.php(31): Php\Pie\BinaryFile->__construct('/root/.pie/php8...', false)
#1 /pwd/src/Building/UnixBuild.php(76): Php\Pie\BinaryFile::fromFileWithSha256Checksum('/root/.pie/php8...')
#2 /pwd/src/ComposerIntegration/InstallAndBuildProcess.php(53): Php\Pie\Building\UnixBuild->__invoke(Object(Php\Pie\Downloading\DownloadedPackage), Object(Php\Pie\Platform\TargetPlatform), Array, Object(Symfony\Component\Console\Output\ConsoleOutput))
#3 /pwd/src/ComposerIntegration/PiePackageInstaller.php(60): Php\Pie\ComposerIntegration\InstallAndBuildProcess->__invoke(Object(Composer\Composer), Object(Php\Pie\ComposerIntegration\PieComposerRequest), Object(Composer\Package\CompletePackage), '/root/.pie/php8...')
#4 /pwd/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Php\Pie\ComposerIntegration\PiePackageInstaller->Php\Pie\ComposerIntegration\{closure}(NULL)
#5 /pwd/src/ComposerIntegration/PiePackageInstaller.php(38): React\Promise\Internal\FulfilledPromise->then(Object(Closure))
#6 /pwd/vendor/composer/composer/src/Composer/Installer/InstallationManager.php(467): Php\Pie\ComposerIntegration\PiePackageInstaller->install(Object(Composer\Repository\InstalledFilesystemRepository), Object(Composer\Package\CompletePackage))
#7 /pwd/vendor/composer/composer/src/Composer/Installer/InstallationManager.php(381): Composer\Installer\InstallationManager->install(Object(Composer\Repository\InstalledFilesystemRepository), Object(Composer\DependencyResolver\Operation\InstallOperation))
#8 /pwd/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Composer\Installer\InstallationManager->Composer\Installer\{closure}(NULL)
#9 /pwd/vendor/composer/composer/src/Composer/Installer/InstallationManager.php(380): React\Promise\Internal\FulfilledPromise->then(Object(Closure))
#10 /pwd/vendor/composer/composer/src/Composer/Installer/InstallationManager.php(322): Composer\Installer\InstallationManager->executeBatch(Object(Composer\Repository\InstalledFilesystemRepository), Array, Array, false, true, Array)
#11 /pwd/vendor/composer/composer/src/Composer/Installer/InstallationManager.php(221): Composer\Installer\InstallationManager->downloadAndExecuteBatch(Object(Composer\Repository\InstalledFilesystemRepository), Array, Array, false, true, false, Array)
#12 /pwd/vendor/composer/composer/src/Composer/Installer.php(839): Composer\Installer\InstallationManager->execute(Object(Composer\Repository\InstalledFilesystemRepository), Array, false, true, false)
#13 /pwd/vendor/composer/composer/src/Composer/Installer.php(649): Composer\Installer->doInstall(Object(Composer\Repository\InstalledFilesystemRepository), true)
#14 /pwd/vendor/composer/composer/src/Composer/Installer.php(298): Composer\Installer->doUpdate(Object(Composer\Repository\InstalledFilesystemRepository), true)
#15 /pwd/src/ComposerIntegration/ComposerIntegrationHandler.php(69): Composer\Installer->run()
#16 /pwd/src/Command/BuildCommand.php(79): Php\Pie\ComposerIntegration\ComposerIntegrationHandler->__invoke(Object(Php\Pie\DependencyResolver\Package), Object(Composer\Composer), Object(Php\Pie\Platform\TargetPlatform), Object(Php\Pie\DependencyResolver\RequestedPackageAndVersion))
#17 /pwd/vendor/symfony/console/Command/Command.php(326): Php\Pie\Command\BuildCommand->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#18 /pwd/vendor/symfony/console/Application.php(1078): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#19 /pwd/vendor/symfony/console/Application.php(324): Symfony\Component\Console\Application->doRunCommand(Object(Php\Pie\Command\BuildCommand), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#20 /pwd/vendor/symfony/console/Application.php(175): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#21 /pwd/bin/pie(64): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#22 {main}
  thrown in /pwd/src/BinaryFile.php on line 22
```

Pull request description:
Ensure built .so file matches the expected filename
Fixes #92 

Patch:
diff --git a/src/Building/ExtensionBinaryNotFound.php b/src/Building/ExtensionBinaryNotFound.php
new file mode 100644
index 0000000..1074361
--- /dev/null
+++ b/src/Building/ExtensionBinaryNotFound.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Php\Pie\Building;
+
+use RuntimeException;
+
+use function sprintf;
+
+class ExtensionBinaryNotFound extends RuntimeException
+{
+    public static function fromExpectedBinary(string $expectedBinaryName): self
+    {
+        return new self(sprintf(
+            'Build complete, but expected %s does not exist.',
+            $expectedBinaryName,
+        ));
+    }
+}
diff --git a/src/Building/UnixBuild.php b/src/Building/UnixBuild.php
index 83637a5..f8bcfc2 100644
--- a/src/Building/UnixBuild.php
+++ b/src/Building/UnixBuild.php
@@ -68,12 +68,7 @@ final class UnixBuild implements Build
         $expectedSoFile = $downloadedPackage->extractedSourcePath . '/modules/' . $downloadedPackage->package->extensionName->name() . '.so';
 
         if (! file_exists($expectedSoFile)) {
-            $output->writeln(sprintf(
-                'Build complete, but expected <comment>%s</comment> does not exist - however, this may be normal if this extension outputs the .so file in a different location.',
-                $expectedSoFile,
-            ));
-
-            return BinaryFile::fromFileWithSha256Checksum($expectedSoFile);
+            throw ExtensionBinaryNotFound::fromExpectedBinary($expectedSoFile);
         }
 
         $output->writeln(sprintf(


Test patch:
diff --git a/test/integration/Building/UnixBuildTest.php b/test/integration/Building/UnixBuildTest.php
index f56099d..68ec4fd 100644
--- a/test/integration/Building/UnixBuildTest.php
+++ b/test/integration/Building/UnixBuildTest.php
@@ -6,6 +6,7 @@ namespace Php\PieIntegrationTest\Building;
 
 use Composer\Package\CompletePackage;
 use Composer\Util\Platform;
+use Php\Pie\Building\ExtensionBinaryNotFound;
 use Php\Pie\Building\UnixBuild;
 use Php\Pie\ConfigureOption;
 use Php\Pie\DependencyResolver\Package;
@@ -74,4 +75,43 @@ final class UnixBuildTest extends TestCase
         (new Process(['make', 'clean'], $downloadedPackage->extractedSourcePath))->mustRun();
         (new Process(['phpize', '--clean'], $downloadedPackage->extractedSourcePath))->mustRun();
     }
+
+    public function testUnixBuildWillThrowExceptionWhenExpectedBinaryNameMismatches(): void
+    {
+        if (Platform::isWindows()) {
+            self::markTestSkipped('Unix build test cannot be run on Windows');
+        }
+
+        $output = new BufferedOutput();
+
+        $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
+            new Package(
+                $this->createMock(CompletePackage::class),
+                ExtensionType::PhpModule,
+                ExtensionName::normaliseFromString('mismatched_name'),
+                'pie_test_ext',
+                '0.1.0',
+                null,
+                [ConfigureOption::fromComposerJsonDefinition(['name' => 'enable-pie_test_ext'])],
+                true,
+                true,
+            ),
+            self::TEST_EXTENSION_PATH,
+        );
+
+        $unixBuilder = new UnixBuild();
+
+        $this->expectException(ExtensionBinaryNotFound::class);
+        try {
+            $unixBuilder->__invoke(
+                $downloadedPackage,
+                TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess(), null),
+                ['--enable-pie_test_ext'],
+                $output,
+            );
+        } finally {
+            (new Process(['make', 'clean'], $downloadedPackage->extractedSourcePath))->mustRun();
+            (new Process(['phpize', '--clean'], $downloadedPackage->extractedSourcePath))->mustRun();
+        }
+    }
 }