Skip to content

Commit

Permalink
Improve support for arbitrary machine architectures
Browse files Browse the repository at this point in the history
- Prevent running on 32bit Windows or 32bit Python on 64bit Windows
- Support building an AppImage for i386; verify LinuxDeploy for all Commands
- Support building Linux System packages on i386
- Support installing JDK for armv7/8
- Let users know when Android SDK must be manually installed
- Derive the Linux system target distro architecture from the build environment
- Download a 32bit JDK and 32bit standalone Python when host Python is 32bit
- Ensure `makepkg` creates a `.pkg.tar.zst` distributable
  • Loading branch information
rmartin16 committed Jul 25, 2023
1 parent 6527195 commit df0c0f6
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 95 deletions.
2 changes: 2 additions & 0 deletions src/briefcase/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ def _download_support_package(self, app: AppConfig):
python_version_tag=self.python_version_tag,
platform=self.platform,
host_arch=self.tools.host_arch,
is_32bit=self.tools.is_32bit_python,
)

support_package_url = self.support_package_url(support_revision)
Expand Down Expand Up @@ -384,6 +385,7 @@ def _download_support_package(self, app: AppConfig):
python_version_tag=self.python_version_tag,
platform=self.platform,
host_arch=self.tools.host_arch,
is_32bit=self.tools.is_32bit_python,
) from e

def _write_requirements_file(
Expand Down
20 changes: 16 additions & 4 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 Expand Up @@ -153,16 +165,16 @@ def __init__(self, app_bundle_path):


class MissingSupportPackage(BriefcaseCommandError):
def __init__(self, python_version_tag, platform, host_arch):
def __init__(self, python_version_tag, platform, host_arch, is_32bit):
self.python_version_tag = python_version_tag
self.platform = platform
self.platform = f"{'32 bit ' if is_32bit else ''}{platform}"
self.host_arch = host_arch
super().__init__(
f"""\
Unable to download {self.platform} support package for Python {self.python_version_tag} on {self.host_arch}.
This is likely because either Python {self.python_version_tag} and/or {self.host_arch}
is not yet supported on {self.platform}. You will need to:
This is likely because either Python {self.python_version_tag} and/or {self.host_arch} is not yet
supported on {self.platform}. You will need to:
* Use an older version of Python; or
* Compile your own custom support package.
"""
Expand Down
39 changes: 30 additions & 9 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,35 @@ 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"

return f"https://dl.google.com/android/repository/commandlinetools-{platform_name}-{self.cmdline_tools_version}_latest.zip" # noqa: E501
"""The Android SDK Command-Line Tools URL appropriate for the current machine.
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": {
"arm64": "mac",
"x86_64": "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 Down
2 changes: 2 additions & 0 deletions src/briefcase/integrations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ def __init__(

self.host_arch = self.platform.machine()
self.host_os = self.platform.system()
# Python is 32bit if its pointers can only address with 32 bits or fewer
self.is_32bit_python = self.sys.maxsize <= 2**32

self.app_tools: DefaultDict[AppConfig, ToolCache] = defaultdict(
lambda: ToolCache(
Expand Down
54 changes: 35 additions & 19 deletions src/briefcase/integrations/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
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


Expand All @@ -26,28 +30,40 @@ 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."""
system_arch = self.tools.host_arch
# use a 32bit JDK if using 32bit Python on 64bit hardware
if self.tools.is_32bit_python and self.tools.host_arch == "aarch64":
system_arch = "armv7l"

try:
jdk_download_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
}[system_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/"
f"releases/download/jdk-{self.JDK_RELEASE}+{self.JDK_BUILD}/"
f"OpenJDK{self.JDK_MAJOR_VER}U-jdk_{arch}_{platform}_hotspot_"
f"OpenJDK{self.JDK_MAJOR_VER}U-jdk_{jdk_download_arch}_{platform}_hotspot_"
f"{self.JDK_RELEASE}_{self.JDK_BUILD}.{extension}"
)

Expand Down
20 changes: 18 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,21 @@ 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) -> 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 +224,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 +335,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
9 changes: 8 additions & 1 deletion src/briefcase/platforms/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,17 @@ def support_package_url(self, support_revision):
System packages don't use a support package; this is defined by
the template, so this method won't be invoked
"""
python_download_arch = self.tools.host_arch
# use a 32bit Python if using 32bit Python on 64bit hardware
if self.tools.is_32bit_python and self.tools.host_arch == "aarch64":
python_download_arch = "armv7"
elif self.tools.is_32bit_python and self.tools.host_arch == "x86_64":
python_download_arch = "i686"

version, datestamp = support_revision.split("+")
return (
"https://github.com/indygreg/python-build-standalone/releases/download/"
f"{datestamp}/cpython-{support_revision}-{self.tools.host_arch}-unknown-linux-gnu-install_only.tar.gz"
f"{datestamp}/cpython-{support_revision}-{python_download_arch}-unknown-linux-gnu-install_only.tar.gz"
)

def vendor_details(self, freedesktop_info):
Expand Down
13 changes: 7 additions & 6 deletions src/briefcase/platforms/linux/appimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,20 @@ 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)

def distribution_path(self, app):
return self.dist_path / self.binary_name(app)

def verify_tools(self):
"""Verify the AppImage LinuxDeploy tool and its plugins exist."""
super().verify_tools()
LinuxDeploy.verify(tools=self.tools)

def add_options(self, parser):
super().add_options(parser)
parser.add_argument(
Expand Down Expand Up @@ -213,11 +219,6 @@ class LinuxAppImageOpenCommand(LinuxAppImageMostlyPassiveMixin, DockerOpenComman
class LinuxAppImageBuildCommand(LinuxAppImageMixin, BuildCommand):
description = "Build a Linux AppImage."

def verify_tools(self):
"""Verify the AppImage linuxdeploy tool and plugins exist."""
super().verify_tools()
LinuxDeploy.verify(tools=self.tools)

def build_app(self, app: AppConfig, **kwargs): # pragma: no-cover-if-is-windows
"""Build an application.
Expand Down
Loading

0 comments on commit df0c0f6

Please sign in to comment.