collect_executables.py (172 lines of code) (raw):
"""Get the link to download Fx or Geckodriver, for any supported platform.
Use -g to get geckodriver, otherwise you will get Fx. Use -n to just get the Fx version number.
Set env var FX_CHANNEL to get non-beta, blank string for Release.
Set env var FX_LOCALE to get a different locale build.
Set env var FX_PLATFORM to get a platform other than current system."""
import logging
from os import environ
from platform import uname
from sys import argv, exit
from time import sleep
import requests
from bs4 import BeautifulSoup
GECKO_API_URL = "https://api.github.com/repos/mozilla/geckodriver/releases/latest"
BACKSTOP = "135.0b9"
NUMBER_ONLY = False
def get_fx_platform():
u = uname()
_system = environ.get("FX_PLATFORM") or u.system
if _system == "Darwin":
return "mac"
if _system == "Linux":
# ARM specifications in uname().machine don't have 32/64
if "64" in u.machine:
return "linux-x86_64"
elif "arm" in u.machine.lower():
return "linux-aarch64"
return "linux-i686"
if _system == "Windows":
if "arm" in u.machine.lower():
return "win64-aarch64"
elif "64" in u.machine:
return "win64"
return "win32"
def get_fx_executable_extension():
u = uname()
_system = environ.get("FX_PLATFORM") or u.system
if _system == "Darwin":
return "dmg"
if _system == "Linux":
return "xz"
if _system == "Windows":
return "exe"
def get_gd_platform():
u = uname()
_system = environ.get("FX_PLATFORM") or u.system
if _system == "Darwin":
return "macos"
if _system == "Linux":
if u.machine == "AMD64":
return "linux-aarch64"
if "64" in u.machine:
return "linux64"
return "linux32"
if _system == "Windows":
if u.machine == "AMD64" and not environ.get("GITHUB_ACTIONS"):
return "win-aarch64"
if "64" in u.machine:
return "win64"
return "win32"
if "-g" in argv:
gecko_rs_obj = requests.get(GECKO_API_URL).json()
# In mac, sometimes this request fails to produce a link
for _ in range(4):
if gecko_rs_obj:
break
sleep(2)
gecko_rs_obj = requests.get(GECKO_API_URL).json()
# If we failed, just dump any old link, maybe update this on new gecko release
if not gecko_rs_obj.get("assets"):
gd_platform = get_gd_platform()
ext = "zip" if "win" in gd_platform else "tar.gz"
print(
f"https://github.com/mozilla/geckodriver/releases/download/v0.35.0/geckodriver-v0.35.0-{gd_platform}.{ext}"
)
exit()
urls = [
a.get("browser_download_url")
for a in gecko_rs_obj.get("assets")
if not a.get("browser_download_url").endswith(".asc")
]
gecko_download_url = [u for u in urls if get_gd_platform() in u][0]
print(gecko_download_url)
else:
if "-n" in argv:
NUMBER_ONLY = True
channel = environ.get("FX_CHANNEL")
# if channel doesn't exist use beta, if blank leave blank (for Release)
# ...otherwise prepend hyphen
if channel is None:
channel = "-beta"
elif channel:
channel = f"-{channel.lower()}"
language = environ.get("FX_LOCALE")
if not language:
language = "en-US"
if channel == "-devedition":
# Devedition has special requirements as it's testing a release
this_devedition = BACKSTOP
fx_download_dir_url = (
"https://archive.mozilla.org/pub/devedition/releases/135.0b5/"
)
while True:
(major, _) = this_devedition.split(".")
major = int(major)
this_devedition = f"{major + 1}.0b5"
next_candidate = f"https://archive.mozilla.org/pub/devedition/releases/{this_devedition}/"
rs = requests.get(next_candidate)
if rs.status_code > 399:
break
fx_download_dir_url = next_candidate
devedition_version = fx_download_dir_url.split("/")[-2]
fx_download_dir_url = f"{fx_download_dir_url}{get_fx_platform()}/{language}/"
else:
# Anything but devedition
candidate_exists = True
this_beta = BACKSTOP
while candidate_exists:
(major, minor_beta) = this_beta.split(".")
(minor, beta) = minor_beta.split("b")
major = int(major)
minor = int(minor)
beta = int(beta)
next_major = f"{major + 1}.0b1"
fx_download_dir_url = f"https://archive.mozilla.org/pub/firefox/candidates/{next_major}-candidates/build1/"
rs = requests.get(fx_download_dir_url)
if rs.status_code < 300:
latest_beta_ver = next_major
this_beta = next_major
continue
next_minor = f"{major}.{minor + 1}b1"
fx_download_dir_url = f"https://archive.mozilla.org/pub/firefox/candidates/{next_minor}-candidates/build1/"
rs = requests.get(fx_download_dir_url)
if rs.status_code < 300:
latest_beta_ver = next_minor
this_beta = next_minor
continue
next_beta = f"{major}.{minor}b{beta + 1}"
fx_download_dir_url = f"https://archive.mozilla.org/pub/firefox/candidates/{next_beta}-candidates/build1/"
rs = requests.get(fx_download_dir_url)
if rs.status_code < 300:
latest_beta_ver = next_beta
this_beta = next_beta
continue
candidate_exists = False
status = 200
build = 0
while status < 400 and build < 20:
build += 1
fx_download_dir_url = f"https://archive.mozilla.org/pub/firefox/candidates/{latest_beta_ver}-candidates/build{build}/"
# Fetch the page
response = requests.get(fx_download_dir_url)
status = response.status_code
# Correct build is the last one that didn't 404
build -= 1
fx_download_dir_url = f"https://archive.mozilla.org/pub/firefox/candidates/{latest_beta_ver}-candidates/build{build}/{get_fx_platform()}/{language}/"
response = requests.get(fx_download_dir_url)
status = response.status_code
response_text = None
for _ in range(3):
if status < 300:
response_text = response.text
else:
sleep(3)
response = requests.get(fx_download_dir_url)
status = response.status_code
logging.warning(f"Collecting executable at {fx_download_dir_url}")
if response_text is None:
exit(f"Could not find build at {fx_download_dir_url}.")
# Parse the HTML content
soup = BeautifulSoup(response_text, "html.parser")
executable_name = ""
# Extract the text of each line
for line in soup.find_all("a"):
line_text = line.getText().split(".")
if not line_text[0]:
continue
# Get the executable name
if line_text[-1] == get_fx_executable_extension():
executable_name = line.getText().replace(" ", "%20")
fx_download_executable_url = rf"{fx_download_dir_url}{executable_name}"
if NUMBER_ONLY:
if channel == "-devedition":
print(devedition_version)
else:
number_cand = fx_download_dir_url.split("/")[6]
number = number_cand.split("-")[0]
print(f"{number}-build{build}")
else:
print(fx_download_executable_url)