Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions lib/LocalBinary.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@
}

private function platform_url(){
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.

return 'https://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-darwin-x64';
}
else if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
return 'https://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal.exe';
if ((strtoupper(PHP_OS)) == "LINUX") {
Expand All @@ -66,7 +69,12 @@
}

public function download_binary($path) {
$url = $this->platform_url();
$urls = array($this->platform_url());
// If the arm64 binary is not published to the legacy bucket yet, fall
// back to the x64 binary, which still works on Apple Silicon via
// Rosetta 2 — releasing must not turn a working download into a 404.
if (substr($urls[0], -13) === '-darwin-arm64')
$urls[] = str_replace('-darwin-arm64', '-darwin-x64', $urls[0]);
if (!file_exists($path))
mkdir($path, 0777, true);

Expand All @@ -76,18 +84,29 @@
$dest_binary_name = $dest_binary_name. ".exe";
}
$dest_binary_path = $path. '/'. $dest_binary_name;
$file = fopen($dest_binary_path , "w+");
$ch = curl_init("");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FILE, $file);
$data = curl_exec ($ch);
curl_close ($ch);

fclose($file);
chmod($dest_binary_path, 0755);
return $dest_binary_path;
foreach ($urls as $url) {
$file = fopen($dest_binary_path , "w+");
$ch = curl_init("");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FILE, $file);
// Fail on HTTP >= 400 instead of writing the error body to disk —
// a saved error body would satisfy the file_exists() check in
// binary_path() and stick until the user deletes it by hand.
curl_setopt($ch, CURLOPT_FAILONERROR, true);
$data = curl_exec ($ch);
curl_close ($ch);

fclose($file);
if ($data !== false) {
chmod($dest_binary_path, 0755);
return $dest_binary_path;
}
// remove the empty/partial file so the next attempt (or next run) retries
unlink($dest_binary_path);
}
throw new LocalException("Failed to download BrowserStackLocal binary from: " . implode(", ", $urls));
}

private function get_available_dirs() {
Expand Down
Loading