Fix custom PHP binary deleted when binary path is the app root#117
Fix custom PHP binary deleted when binary path is the app root#117simonhamp wants to merge 6 commits into
Conversation
Trim the configured binary package path before pruning so values that normalize to a no-op (./, ., /, trailing /.) no longer collapse to the app root and delete the entire built app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@bmckay959 would you mind trying this fix out when you get a chance? |
| | Tests | ||
| |-------------------------------------------------------------------------- | ||
| */ | ||
| it('removes the custom php binary package directory', function () use ($buildPath, $command) { |
There was a problem hiding this comment.
I'm a bit confused by this test....why do we want the custom binary removed? Feels like this test is introducing the bug the opposite direction? Basically, we don't want to delete the directory where the NATIVEPHP_PHP_BINARY points....or am I missing something?
There was a problem hiding this comment.
Understandable confusion. The binary is moved to a location in the app bundle itself during build. So leaving it in the source directory would include it twice 👍🏻
|
LGTM. Happy to merge pending passing CI. |
|
|
||
| expect("$buildPath/app/php-bin")->not->toBeDirectory(); | ||
| expect("$buildPath/app/index.php")->toBeFile(); | ||
| })->after(fn () => putenv('NATIVEPHP_PHP_BINARY_PATH')); |
There was a problem hiding this comment.
Looks like we're not currently able to support the ->after() method here.
| $command->pruneVendorDirectory(); | ||
|
|
||
| expect("$buildPath/app/index.php")->toBeFile(); | ||
| expect("$buildPath/app/bin/win/x64/php.exe")->toBeFile(); |
There was a problem hiding this comment.
Based on @gwleuverink comment from above, do we actually want the /app/bin/win/x64/php.exe to be removed? So basically add the expect("$buildPath/app/bin")->not->toBeDirectory()?
I can confirm this solved the bug, but I believe this is now including ALL the PHP versions with the app this way(So my build goes from 127MB to 212MB). So, if the binary is getting copied to a 'special' place inside the electron app(?), then can we not have the /bin directory also removed?
There was a problem hiding this comment.
Good point. I may have misinterpreted the test. I'll have a another look at it to confirm
|
Dug into this a bit more. The PR fixes the crash, but Ben's spotted a second thing worth handling before we merge: the binaries are still ending up in the bundle, which is the 127 to 212MB jump he measured. What's happening is The wrinkle on my side is that we don't own $binDir = $this->buildPath('app/'.$this->binaryPackageDirectory().'bin');
$filesystem->remove(glob("{$binDir}/*/*/php-*.zip"));For @simonhamp happy to push this onto the branch and tweak the test to match if you're good with it. Or we iterate the os/arch matrix instead of the glob if you'd prefer, though |
|
Thanks for digging in more...as I mentioned in my initial issue, I'm still pretty new on the native php side of things, so I haven't fully wrapped my arms around the build process/order/when things get injected. This feels like the right approach to me. |
|
Wouldn't have spotted it without your keen eye! @bmckay959 🙏🏻 Here's my suggested fix for review #121 This targets only the archives we manage, at their fixed bin/{os}/{arch}/php-*.zip location, never the containing directory. so if you happen to keep other files in |
|
The failing tests seem unrelated to this PR. Looks like the PendingRequest contract has broken backwards compat in a minor release. (Only happens with L13 - prefer-stable) |
Laravel 13.13.0 tightened the HTTP client's header casting to throw on non-scalar values. The X-NativePHP-Secret header is set from config('nativephp-internal.secret'), which is null when NATIVEPHP_SECRET is unset, so every request blew up with an InvalidArgumentException.
Casting to a string keeps null as an empty string, which the client accepts. This is what older Laravel versions did implicitly.
|
Does it still work with absolute directory path ? |
|
Good point, relative paths work great today. Absolute paths are the snag: the path gets joined against the project root, so an absolute value doubles the base and php.js can't find the binary. The docs example actually shows an absolute path, so at minimum that's a docs bug. Two ways we could go: keep it relative-only and fix the docs, or also add support for absolute paths. I'm happy either way. Btw, this was the case before this PR. How do we want to handle this? |
|
I always used absolute paths for my projects (per the doc recommendation), so I consider this a breaking change personally 😅 |
|
Does the path point inside the project or outside? I just tried it in a new project and it did not work for me 🤔 Nothing here changes how the binary path is resolved, only change in this PR is how they are pruned (entire bin directory vs targeted binary inside bin) so this PR should not introduce anything breaking. Anything I assessed in the path handling above was there before this change Edit: give me a moment to verify again. just to be sure i'm not testing a stale branch or something |
|
inside, as a composer dependency, but that should work outside too, I believe |
|
can you confirm those projects are running v2? |
|
I checked, the project I had in mind is not on NativePHP Desktop v2 yet — it’s still using the old nativephp/electron package, locked at 0.8.7. One of my other projects does use v2 tailscale-notifier. But I don't recall building with custom binaries recently. |
Summary
Fixes #115.
When
NATIVEPHP_PHP_BINARY_PATHis set to a value that points at the project root (e.g../),native:builddeletes the entire built app right before electron-builder packages it.Root cause
In
PrunesVendorDirectory::pruneVendorDirectory(), the configured binary package directory was interpolated naively into"app/{$binaryPackageDirectory}". When the value normalizes to a no-op (./,.,/, or a trailing/.),Path::join('/build', 'app/./')collapses to/build/app, so the prune step removes the whole app directory. The previous! empty()guard did not catch this because strings like./are non-empty.native:runwas unaffected because it does not run the prune step.Fix
Trim the configured path of surrounding dots, slashes, and whitespace, then skip removal entirely when nothing meaningful remains:
Tests
Added
tests/Build/PruneVendorDirectoryTest.php:php-bin/) is still pruned../) no longer wipes the built app (regression test for Custom PHP binary gets deleted if in root of project #115).Verified the regression test fails against the old code and passes with the fix. Full suite (114 tests) passes and Pint is clean.
🤖 Generated with Claude Code