Skip to content

Commit

Permalink
Machine architecture checks
Browse files Browse the repository at this point in the history
- Prevent running on 32bit Windows or 32bit Python on 64bit Windows
- Support i386 for AppImage
- Support building Linux System packages on i386
- Support installing JDK for armv7/8
- Let users know when Android SDK must be manually installed
  • Loading branch information
rmartin16 committed Jul 20, 2023
1 parent 43fd921 commit 06c9afc
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 39 deletions.
1 change: 1 addition & 0 deletions src/briefcase/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class BaseCommand(ABC):
cmd_line = "briefcase {command} {platform} {output_format}"
supported_host_os = {"Darwin", "Linux", "Windows"}
supported_host_os_reason = f"This command is not supported on {platform.system()}."
supports_32bit_python = False
# defined by platform-specific subclasses
command: str
description: str
Expand Down
12 changes: 12 additions & 0 deletions src/briefcase/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ def __init__(self, tool):
super().__init__(msg=f"Unable to locate {tool!r}. Has it been installed?")


class IncompatibleToolError(BriefcaseCommandError):
def __init__(self, tool: str, env_var: str):
self.tool = tool
super().__init__(
msg=f"""\
Briefcase cannot install {tool} on this machine.
Install {tool} manually and specify the installation directory in the {env_var} environment variable.
"""
)


class NonManagedToolError(BriefcaseCommandError):
def __init__(self, tool):
self.tool = tool
Expand Down
34 changes: 24 additions & 10 deletions src/briefcase/integrations/android_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from briefcase.console import InputDisabled, select_option
from briefcase.exceptions import (
BriefcaseCommandError,
IncompatibleToolError,
InvalidDeviceError,
MissingToolError,
)
Expand Down Expand Up @@ -57,15 +58,28 @@ def __init__(self, tools: ToolCache, root_path: Path):

@property
def cmdline_tools_url(self) -> str:
"""The Android SDK Command-Line Tools URL appropriate to the current operating
system."""
platform_name = self.tools.host_os.lower()
if self.tools.host_os.lower() == "darwin":
platform_name = "mac"
elif self.tools.host_os.lower() == "windows": # pragma: no branch
platform_name = "win"
"""The Android SDK Command-Line Tools URL appropriate for the current machine.
return f"https://dl.google.com/android/repository/commandlinetools-{platform_name}-{self.cmdline_tools_version}_latest.zip" # noqa: E501
The SDK largely only supports typical development environments; if a machine is
using an unsupported architecture, `sdkmanager` will error while installing the
emulator as a dependency of the build-tools. However, for some of the platforms
that are unsupported by sdkmanager, users can set up their own SDK install.
"""
try:
platform_name = {
"Darwin": {"x86_64": "mac", "arm64": "mac"},
"Linux": {"x86_64": "linux"},
"Windows": {"AMD64": "win"},
}[self.tools.host_os][self.tools.host_arch]
except KeyError as e:
raise IncompatibleToolError(
tool=self.full_name, env_var="ANDROID_HOME"
) from e

return (
f"https://dl.google.com/android/repository/commandlinetools-"
f"{platform_name}-{self.cmdline_tools_version}_latest.zip"
)

@property
def cmdline_tools_path(self) -> Path:
Expand All @@ -74,11 +88,11 @@ def cmdline_tools_path(self) -> Path:
@property
def cmdline_tools_version(self) -> str:
# This is the version of the Android SDK Command-line tools that
# are current as of May 2022. These tools can generally self-update,
# are current as of July 2023. These tools can generally self-update,
# so using a fixed download URL isn't a problem.
# However, if/when this version number is changed, ensure that the
# checks done during verification include any required upgrade steps.
return "8092744"
return "9477386"

@property
def cmdline_tools_version_path(self) -> Path:
Expand Down
57 changes: 34 additions & 23 deletions src/briefcase/integrations/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
import subprocess
from pathlib import Path

from briefcase.exceptions import BriefcaseCommandError, MissingToolError
from briefcase.exceptions import (
BriefcaseCommandError,
IncompatibleToolError,
MissingToolError,
)
from briefcase.integrations.base import ManagedTool, ToolCache


class JDK(ManagedTool):
name = "java"
full_name = "Java JDK"

# As of 12 May 2023, 17.0.7+7 is the current OpenJDK
# As of July 2023, 17.0.8+7 is the current OpenJDK
# https://adoptium.net/temurin/releases/
JDK_MAJOR_VER = "17"
JDK_RELEASE = "17.0.7"
JDK_RELEASE = "17.0.8"
JDK_BUILD = "7"
JDK_INSTALL_DIR_NAME = f"java{JDK_MAJOR_VER}"

Expand All @@ -26,23 +30,30 @@ def __init__(self, tools: ToolCache, java_home: Path):

@property
def OpenJDK_download_url(self):
arch = {
"x86_64": "x64", # Linux\macOS x86-64
"aarch64": "aarch64", # Linux arm64
"armv6l": "arm", # Linux arm
"arm64": "aarch64", # macOS arm64
"AMD64": "x64", # Windows x86-64
}.get(self.tools.host_arch)

platform = {
"Darwin": "mac",
"Windows": "windows",
"Linux": "linux",
}.get(self.tools.host_os)

extension = {
"Windows": "zip",
}.get(self.tools.host_os, "tar.gz")
"""The OpenJDK download URL appropriate for the current machine."""
try:
arch = {
"armv7l": "arm", # Linux armv7
"armv8l": "arm", # Linux armv8
"aarch64": "aarch64", # Linux arm64
"arm64": "aarch64", # macOS arm64
"x86_64": "x64", # Linux/macOS x86-64
"AMD64": "x64", # Windows x86-64
}[self.tools.host_arch]

platform = {
"Darwin": "mac",
"Windows": "windows",
"Linux": "linux",
}[self.tools.host_os]

extension = {
"Darwin": "tar.gz",
"Linux": "tar.gz",
"Windows": "zip",
}[self.tools.host_os]
except KeyError as e:
raise IncompatibleToolError(tool=self.full_name, env_var="JAVA_HOME") from e

return (
f"https://github.com/adoptium/temurin{self.JDK_MAJOR_VER}-binaries/"
Expand All @@ -64,15 +75,15 @@ def version_from_path(cls, tools: ToolCache, java_path: str | Path) -> str:
:param tools: ToolCache of available tools
:param java_path: File path to a candidate JDK install
:return: JDK release version; e.g. "17.0.7"
:return: JDK release version; e.g. "17.0.8"
"""
output = tools.subprocess.check_output(
[
os.fsdecode(Path(java_path) / "bin" / "javac"),
"-version",
],
)
# javac's output should look like "javac 17.0.7\n"
# javac's output should look like "javac 17.0.8\n"
return output.strip("\n").split(" ")[1]

@classmethod
Expand Down Expand Up @@ -279,7 +290,7 @@ def install(self):

jdk_zip_path.unlink() # Zip file no longer needed once unpacked.

# The tarball will unpack into <briefcase data dir>/tools/jdk-17.0.7+7
# The tarball will unpack into <briefcase data dir>/tools/jdk-17.0.8+7
# (or whatever name matches the current release).
# We turn this into <briefcase data dir>/tools/java so we have a consistent name.
java_unpack_path = (
Expand Down
17 changes: 15 additions & 2 deletions src/briefcase/integrations/linuxdeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
BriefcaseCommandError,
CorruptToolError,
MissingToolError,
UnsupportedHostError,
)
from briefcase.integrations.base import ManagedTool, Tool, ToolCache

Expand Down Expand Up @@ -48,6 +49,18 @@ def download_url(self) -> str:
def file_path(self) -> Path:
"""The folder on the local filesystem that contains the file_name."""

@classmethod
def arch(cls, host_os: str, host_arch: str):
# always use the x86-64 arch on macOS since Docker
# containers are always run in an x86-64 VM
arch = host_arch if host_os != "Darwin" else "x86_64"
try:
return {"x86_64": "x86_64", "i686": "i386"}[arch]
except KeyError as e:
raise UnsupportedHostError(
f"Linux AppImages cannot be built on {host_arch}."
) from e

def exists(self) -> bool:
return (self.file_path / self.file_name).is_file()

Expand Down Expand Up @@ -208,7 +221,7 @@ class LinuxDeployQtPlugin(LinuxDeployPluginBase, ManagedTool):

@property
def file_name(self) -> str:
return f"linuxdeploy-plugin-qt-{self.tools.host_arch}.AppImage"
return f"linuxdeploy-plugin-qt-{self.arch(self.tools.host_os, self.tools.host_arch)}.AppImage"

@property
def download_url(self) -> str:
Expand Down Expand Up @@ -319,7 +332,7 @@ def file_path(self) -> Path:

@property
def file_name(self) -> str:
return f"linuxdeploy-{self.tools.host_arch}.AppImage"
return f"linuxdeploy-{self.arch(self.tools.host_os, self.tools.host_arch)}.AppImage"

@property
def download_url(self) -> str:
Expand Down
5 changes: 4 additions & 1 deletion src/briefcase/platforms/linux/appimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def project_path(self, app):

def binary_name(self, app):
safe_name = app.formal_name.replace(" ", "_")
return f"{safe_name}-{app.version}-{self.tools.host_arch}.AppImage"
arch = LinuxDeploy.arch(self.tools.host_os, self.tools.host_arch)
return f"{safe_name}-{app.version}-{arch}.AppImage"

def binary_path(self, app):
return self.bundle_path(app) / self.binary_name(app)
Expand Down Expand Up @@ -134,6 +135,8 @@ def verify_app_tools(self, app: AppConfig):


class LinuxAppImageMixin(LinuxAppImageMostlyPassiveMixin):
supports_32bit_python = True

def verify_host(self):
"""If we're *not* using Docker, verify that we're actually on Linux."""
super().verify_host()
Expand Down
2 changes: 2 additions & 0 deletions src/briefcase/platforms/linux/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class LinuxSystemPassiveMixin(LinuxMixin):
# Docker exists. It is used by commands that are "passive" from the
# perspective of the build system (e.g., Run).
output_format = "system"
supports_32bit_python = True
supported_host_os = {"Darwin", "Linux"}
supported_host_os_reason = (
"Linux system projects can only be built on Linux, or on macOS using Docker."
Expand Down Expand Up @@ -61,6 +62,7 @@ def linux_arch(self):
"x86_64": "amd64",
"aarch64": "arm64",
"armv6l": "armhf",
"i686": "i386",
}.get(self.tools.host_arch, self.tools.host_arch)

def build_path(self, app):
Expand Down
25 changes: 22 additions & 3 deletions src/briefcase/platforms/windows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from briefcase.commands import CreateCommand, PackageCommand, RunCommand
from briefcase.config import AppConfig, parsed_version
from briefcase.exceptions import BriefcaseCommandError
from briefcase.exceptions import BriefcaseCommandError, UnsupportedHostError
from briefcase.integrations.windows_sdk import WindowsSDK
from briefcase.integrations.wix import WiX

Expand All @@ -27,15 +27,34 @@ def distribution_path(self, app):
suffix = "zip" if app.packaging_format == "zip" else "msi"
return self.dist_path / f"{app.formal_name}-{app.version}.{suffix}"

def verify_host(self):
super().verify_host()
# the stub app only supports x86-64 right now
if self.tools.host_arch != "AMD64":
raise UnsupportedHostError(
f"Windows applications cannot be built on a {self.tools.host_arch} machine."
)
# Python is 32bit if its pointers can only address with 32 bits or fewer.
# 64bit Python is required to ensure 64bit wheels are installed/created for the app.
if self.tools.sys.maxsize <= 2**32:
raise UnsupportedHostError(
"""\
Windows applications cannot be built on a 32bit version of Python.
Install a 64bit version of Python and run Briefcase again.
"""
)


class WindowsCreateCommand(CreateCommand):
def support_package_filename(self, support_revision):
return f"python-{self.python_version_tag}.{support_revision}-embed-amd64.zip"

def support_package_url(self, support_revision):
return (
f"https://www.python.org/ftp/python/{self.python_version_tag}.{support_revision}/"
+ self.support_package_filename(support_revision)
f"https://www.python.org/ftp/python/"
f"{self.python_version_tag}.{support_revision}/"
f"{self.support_package_filename(support_revision)}"
)

def output_format_template_context(self, app: AppConfig):
Expand Down

0 comments on commit 06c9afc

Please sign in to comment.