Source code

Revision control

Copy as Markdown

Other Tools

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import pathlib
import platform
import re
import subprocess
import mozversion
from mozperftest.utils import (
CHROME_DESKTOP_APPS,
CHROME_MOBILE_APPS,
FIREFOX_DESKTOP_APPS,
FIREFOX_MOBILE_APPS,
ON_TRY,
)
class MultipleApplicationSetups(Exception):
"""Raised when there are multiple setup classes for an application."""
pass
app_setups = {}
def binary_setup(klass):
for app in klass.apps:
if app in app_setups:
raise MultipleApplicationSetups(f"{app} already has a setup class")
app_setups[app] = klass
return klass
def get_app_setup(app):
app_setup = app_setups.get(app, None)
return app_setup
class BaseVersionProducer:
def __init__(self, application, logger):
self.application = application
self.logger = logger
def get_binary_version(self, binary, **kwargs):
# Try to get the version info through mozversion, and if that
# fails then let the Desktop, or Mobile handle it
application_info = mozversion.get_version(binary=binary)
return application_info.get("application_version")
class DesktopVersionProducer(BaseVersionProducer):
def get_binary_version(self, binary, **kwargs):
try:
return super(DesktopVersionProducer, self).get_binary_version(binary)
except Exception:
pass
version = None
try:
if "mac" in platform.system().lower():
import plistlib
for plist_file in ("version.plist", "Info.plist"):
try:
binary_path = pathlib.Path(binary)
plist_path = binary_path.parent.parent.joinpath(plist_file)
with plist_path.open("rb") as plist_file_content:
plist = plistlib.load(plist_file_content)
except FileNotFoundError:
pass
version = plist.get("CFBundleShortVersionString")
elif "linux" in platform.system().lower():
command = [binary, "--version"]
proc = subprocess.run(
command, timeout=10, capture_output=True, text=True
)
bmeta = proc.stdout.split("\n")
meta_re = re.compile(r"([A-z\s]+)\s+([\w.]*)")
if len(bmeta) != 0:
match = meta_re.match(bmeta[0])
if match:
version = match.group(2)
else:
# Define the PowerShell command. We use this method on Windows since WMIC will
# soon be deprecated.
binary_path = pathlib.Path(binary)
command = (
rf'(Get-ItemProperty -Path "{binary_path}").VersionInfo.FileVersion'
)
self.logger.info(
"Attempting to get browser application version with powershell..."
)
bmeta = subprocess.check_output(
["powershell", "-Command", command],
text=True,
)
if not bmeta:
self.logger.warning("Unable to acquire browser version")
else:
version = bmeta.strip()
self.logger.info(
"Successfully acquired browser version: %s" % version
)
except Exception as e:
self.logger.warning(
"Failed to get browser meta data through fallback method: %s-%s"
% (e.__class__.__name__, e)
)
raise e
return version
class MobileVersionProducer(BaseVersionProducer):
def get_binary_version(self, binary, apk_path=None, **kwargs):
try:
return super(MobileVersionProducer, self).get_binary_version(
apk_path or binary
)
except Exception:
pass
from mozdevice import ADBDeviceFactory
device = ADBDeviceFactory(verbose=True)
pkg_info = device.shell_output("dumpsys package %s" % binary)
version_matcher = re.compile(r".*versionName=([\d.]+)")
for line in pkg_info.split("\n"):
match = version_matcher.match(line)
if match:
return match.group(1)
return None
class BaseSetup:
version_producer = DesktopVersionProducer
def __init__(self, application, layer_obj):
self.application = application
self.logger = layer_obj
self.get_binary_path = layer_obj.mach_cmd.get_binary_path
def get_binary_version(self, binary, **kwargs):
return self.__class__.version_producer(
self.application, self.logger
).get_binary_version(binary, **kwargs)
@binary_setup
class FirefoxSetup(BaseSetup):
apps = FIREFOX_DESKTOP_APPS
def setup_binary(self):
if ON_TRY:
self.logger.warning(
"Cannot setup Firefox binary automatically in CI. Provide the path "
"with --binary."
)
return None
return self.get_binary_path()
@binary_setup
class ChromeSetup(BaseSetup):
apps = CHROME_DESKTOP_APPS
def setup_binary(self):
if ON_TRY:
self.logger.warning(
"Cannot setup Chrome binary automatically in CI. Provide the path "
"with --binary."
)
return None
if "win" in platform.system().lower():
self.logger.warning(
"Getting the chrome binary on windows may not work. "
"Use --binary to provide a path to it."
)
cmd = "where"
executable = "chrome.exe"
else:
cmd = "which"
executable = "google-chrome"
return subprocess.check_output([cmd, executable]).decode("utf-8").strip()
@binary_setup
class ChromeMobileSetup(BaseSetup):
apps = CHROME_MOBILE_APPS
version_producer = MobileVersionProducer
def setup_binary(self):
return "com.android.chrome"
@binary_setup
class FirefoxMobileSetup(BaseSetup):
apps = FIREFOX_MOBILE_APPS
version_producer = MobileVersionProducer
def setup_binary(self):
return {
"focus": "org.mozilla.focus",
"geckoview": "org.mozilla.geckoview_example",
"fenix": "org.mozilla.fenix",
}[self.application]