Skip to content

LOC-7291: download darwin-arm64 binary on Apple Silicon - #32

Open
vivianludrick wants to merge 2 commits into
masterfrom
feat/LOC-7291-darwin-arm64-support
Open

LOC-7291: download darwin-arm64 binary on Apple Silicon#32
vivianludrick wants to merge 2 commits into
masterfrom
feat/LOC-7291-darwin-arm64-support

Conversation

@vivianludrick

@vivianludrick vivianludrick commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

What

On macOS, the binding now downloads BrowserStackLocal-darwin-arm64 when the runtime reports arm64; x64 runtimes (including x64-under-Rosetta) keep BrowserStackLocal-darwin-x64, preserving current behavior.

Detection: php_uname('m') in arm64/aarch64.

Why

The Local binary now ships a native Apple Silicon build (browserstack/browserStackTunnel#980). macOS 27 (fall 2026) removes Rosetta 2, so arm64 Macs need the native binary.

Testing

php -l clean; platform_url() returns the arm64 URL on an Apple Silicon Mac.

⚠ Release ordering

Do not release this before:

  • browserstack/browserStackTunnel#980 is merged and a master build has published binaries/master/BrowserStackLocal-darwin-arm64.zip (public ACL line is already in the Jenkins job) — otherwise arm64 Macs get download failures.
  • This binding downloads from the legacy S3 path (s3.amazonaws.com/browserStack/browserstack-local/). Per review, the download now verifies the HTTP result and falls back to the x64 binary (works via Rosetta 2) when the arm64 object is missing, and never leaves an HTTP error body behind as the "installed" binary — so release ordering is no longer a correctness gate here. Publishing the darwin-arm64 object to that bucket is still wanted so arm64 users get the native binary.

Related Jira

LOC-7291 / epic LOC-7287 (LOC-7285 = binary PR)

🤖 Generated with Claude Code

On macOS the binding now selects BrowserStackLocal-darwin-arm64 when
the runtime reports arm64/aarch64; x64 runtimes (including
x64-under-Rosetta) keep BrowserStackLocal-darwin-x64, preserving
current behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vivianludrick
vivianludrick requested a review from a team as a code owner August 18, 2026 06:26

@07souravkunda 07souravkunda left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detection logic is correct and idiomatic — php_uname('m') against arm64/aarch64 matches how the other bindings in this set do it, and x64-under-Rosetta correctly keeps the x64 binary.

No code objection. The one thing to resolve before release is the legacy-bucket gap you flagged yourself: unlike the bindings that resolve their URL from Rails, this one hardcodes s3.amazonaws.com/browserStack/browserstack-local/, so it regresses on release day rather than degrading later — and the resulting 404 is sticky because the error body gets written to the binary path and short-circuits every later run. Details inline.

Comment thread lib/LocalBinary.php
if (PHP_OS == "Darwin")
if (PHP_OS == "Darwin") {
if (in_array(php_uname('m'), array('arm64', 'aarch64')))
return 'https://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-darwin-arm64';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This URL has no object behind it and no publisher, so releasing this turns a working download into a hard 404 on Apple Silicon.

You've documented it in the PR body, but it's worth separating this binding from the others: nodejs/python/ruby/java/csharp resolve their source URL from Rails / bstack-local-prod, so they only degrade once Rosetta is gone. php and perl hardcode the legacy s3.amazonaws.com/browserStack/browserstack-local/ host, so they break immediately on release — an arm64 Mac that works today via the Rosetta x64 binary starts failing at download.

And the failure is sticky. download_binary() never checks the curl result, so S3's 404 body gets written to ~/.browserstack/BrowserStackLocal and chmod 0755'd. binary_path() then short-circuits on file_exists($binary_path) on every subsequent run, so the user stays broken until they delete the file by hand — no amount of retrying recovers it.

Fix — either:

  1. Gate the release on BrowserStackLocal-darwin-arm64 being published to that bucket, and added to whatever publishes the other legacy objects; or
  2. Fall back to the x64 URL when the arm64 download fails, so the regression can't fire even if the release ordering slips.

Given the sticky-file behaviour, option 2 looks worth doing regardless of ordering.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with option 2 (plus the sticky-file fix) — done in the latest commit:

  • CURLOPT_FAILONERROR stops HTTP error bodies from ever reaching disk.
  • A failed -darwin-arm64 download falls back to the -darwin-x64 URL (still works on Apple Silicon via Rosetta 2), so release ordering can't turn a working download into a 404.
  • If every attempt fails, the partial/empty file is unlink()ed and a LocalException is thrown — binary_path() can no longer short-circuit on a poisoned file.

Option 1 (publishing the arm64 object to the legacy bucket + wiring it into whatever publishes the others) is still worth doing so arm64 users get the native binary here too — tracking that with the release-ordering work, but it's no longer a correctness gate for this PR.

Per review on PR #32: this binding hardcodes the legacy S3 host, where
the arm64 object may not exist yet — and curl's result was ignored, so
an HTTP error body would be saved as the binary and satisfy
binary_path()'s file_exists() check forever. Now: CURLOPT_FAILONERROR
stops error bodies from reaching disk; a failed -darwin-arm64 download
falls back to the x64 binary (works on Apple Silicon via Rosetta 2);
if everything fails, the partial file is removed and LocalException is
thrown instead of returning a poisoned path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread lib/LocalBinary.php
return $dest_binary_path;
}
// remove the empty/partial file so the next attempt (or next run) retries
unlink($dest_binary_path);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants