diff --git a/changes/1564.feature.rst b/changes/1564.feature.rst new file mode 100644 index 000000000..9bcbae2db --- /dev/null +++ b/changes/1564.feature.rst @@ -0,0 +1 @@ +AppImages can now be built for the ARM architecture. diff --git a/changes/1564.removal.rst b/changes/1564.removal.rst new file mode 100644 index 000000000..5a0ffc116 --- /dev/null +++ b/changes/1564.removal.rst @@ -0,0 +1 @@ +New projects will now use ``manylinux_2_28`` instead of ``manylinux2014`` to create AppImages in Docker. diff --git a/docs/reference/platforms/index.rst b/docs/reference/platforms/index.rst index fbce930dd..6019e2cac 100644 --- a/docs/reference/platforms/index.rst +++ b/docs/reference/platforms/index.rst @@ -64,7 +64,7 @@ Platform support +---------+-----------------+--------+-------+-----+--------+-------+-----+--------+-----+-------+ | iOS | |iOS|_ | |f| | |y| | | | | | | | | +---------+-----------------+--------+-------+-----+--------+-------+-----+--------+-----+-------+ -| Linux | |AppImage|_ | |v| | | | | | |v| | |v| | | | +| Linux | |AppImage|_ | |v| | |v| | | | | |v| | |v| | |v| | |v| | + +-----------------+--------+-------+-----+--------+-------+-----+--------+-----+-------+ | | |Flatpak|_ | | | | | | |v| | |f| | |v| | |v| | + +-----------------+--------+-------+-----+--------+-------+-----+--------+-----+-------+ diff --git a/docs/reference/platforms/linux/appimage.rst b/docs/reference/platforms/linux/appimage.rst index 14ef78151..22130bb25 100644 --- a/docs/reference/platforms/linux/appimage.rst +++ b/docs/reference/platforms/linux/appimage.rst @@ -9,7 +9,7 @@ AppImage +--------+-------+-----+--------+-------+-----+--------+-----+-------+ | x86‑64 | arm64 | x86 | x86‑64 | arm64 | x86 | x86‑64 | arm | arm64 | +========+=======+=====+========+=======+=====+========+=====+=======+ -| |v| | | | | | |v| | |v| | | | +| |v| | |v| | | | | |v| | |v| | |v| | |v| | +--------+-------+-----+--------+-------+-----+--------+-----+-------+ .. admonition:: Best effort support diff --git a/src/briefcase/bootstraps/pursuedpybear.py b/src/briefcase/bootstraps/pursuedpybear.py index 15f682d17..18300468f 100644 --- a/src/briefcase/bootstraps/pursuedpybear.py +++ b/src/briefcase/bootstraps/pursuedpybear.py @@ -121,7 +121,7 @@ def pyproject_table_linux_system_arch(self): def pyproject_table_linux_appimage(self): return """ -manylinux = "manylinux2014" +manylinux = "manylinux_2_28" system_requires = [ # ?? FIXME diff --git a/src/briefcase/bootstraps/pygame.py b/src/briefcase/bootstraps/pygame.py index 51f7367ef..f009fbea6 100644 --- a/src/briefcase/bootstraps/pygame.py +++ b/src/briefcase/bootstraps/pygame.py @@ -122,7 +122,7 @@ def pyproject_table_linux_system_arch(self): def pyproject_table_linux_appimage(self): return """ -manylinux = "manylinux2014" +manylinux = "manylinux_2_28" system_requires = [ ] diff --git a/src/briefcase/bootstraps/toga.py b/src/briefcase/bootstraps/toga.py index 71e20aba0..58e541dba 100644 --- a/src/briefcase/bootstraps/toga.py +++ b/src/briefcase/bootstraps/toga.py @@ -158,7 +158,7 @@ def pyproject_table_linux_system_arch(self): def pyproject_table_linux_appimage(self): return """ -manylinux = "manylinux2014" +manylinux = "manylinux_2_28" system_requires = [ # Needed to compile pycairo wheel diff --git a/src/briefcase/integrations/linuxdeploy.py b/src/briefcase/integrations/linuxdeploy.py index 5be759104..1eb780ac7 100644 --- a/src/briefcase/integrations/linuxdeploy.py +++ b/src/briefcase/integrations/linuxdeploy.py @@ -50,16 +50,32 @@ def file_path(self) -> Path: """The folder on the local filesystem that contains the file_name.""" @classmethod - def arch(cls, host_arch: str) -> str: + def arch(cls, tools: ToolCache) -> str: """The architecture defined (and supported) by linuxdeploy for AppImages.""" + system_arch = tools.host_arch + + # If Python is 32 bit, then use 32 bit linuxdeploy regardless of hardware. + # It is non-trivial to determine if Linux is 32 bit or 64 bit; so, this uses + # Python's bitness as a proxy for Linux's bitness. Furthermore, though, pip + # will install 32 bit packages if Python is 32 bit. So, using 32 bit + # linuxdeploy in this case ensures the entire resulting AppImage is consistent. + if tools.is_32bit_python: + system_arch = { + "aarch64": "armv8l", + "x86_64": "i686", + }.get(tools.host_arch, tools.host_arch) + try: return { "x86_64": "x86_64", "i686": "i386", - }[host_arch] + "armv7l": "armhf", + "armv8l": "armhf", + "aarch64": "aarch64", + }[system_arch] except KeyError as e: raise UnsupportedHostError( - f"Linux AppImages cannot be built on {host_arch}." + f"Linux AppImages cannot be built on {tools.host_arch}." ) from e def exists(self) -> bool: @@ -222,7 +238,7 @@ class LinuxDeployQtPlugin(LinuxDeployPluginBase, ManagedTool): @property def file_name(self) -> str: - return f"linuxdeploy-plugin-qt-{self.arch(self.tools.host_arch)}.AppImage" + return f"linuxdeploy-plugin-qt-{self.arch(self.tools)}.AppImage" @property def download_url(self) -> str: @@ -333,7 +349,7 @@ def file_path(self) -> Path: @property def file_name(self) -> str: - return f"linuxdeploy-{self.arch(self.tools.host_arch)}.AppImage" + return f"linuxdeploy-{self.arch(self.tools)}.AppImage" @property def download_url(self) -> str: diff --git a/src/briefcase/platforms/linux/appimage.py b/src/briefcase/platforms/linux/appimage.py index 09447cebc..463b01a48 100644 --- a/src/briefcase/platforms/linux/appimage.py +++ b/src/briefcase/platforms/linux/appimage.py @@ -44,7 +44,7 @@ def project_path(self, app): def binary_name(self, app): safe_name = app.formal_name.replace(" ", "_") - arch = LinuxDeploy.arch(self.tools.host_arch) + arch = LinuxDeploy.arch(self.tools) return f"{safe_name}-{app.version}-{arch}.AppImage" def binary_path(self, app): @@ -175,13 +175,21 @@ class LinuxAppImageCreateCommand( def output_format_template_context(self, app: AppConfig): context = super().output_format_template_context(app) - # Add the manylinux tag to the template context. try: - tag = getattr(app, "manylinux_image_tag", "latest") manylinux_arch = { "x86_64": "x86_64", "i386": "i686", - }[LinuxDeploy.arch(self.tools.host_arch)] + "aarch64": "aarch64", + }[LinuxDeploy.arch(self.tools)] + except KeyError: + manylinux_arch = LinuxDeploy.arch(self.tools) + self.logger.warning( + f"There is no manylinux base image for {manylinux_arch}" + ) + + # Add the manylinux tag to the template context. + try: + tag = getattr(app, "manylinux_image_tag", "latest") context["manylinux_image"] = f"{app.manylinux}_{manylinux_arch}:{tag}" if app.manylinux in {"manylinux1", "manylinux2010", "manylinux2014"}: context["vendor_base"] = "centos" @@ -190,9 +198,7 @@ def output_format_template_context(self, app: AppConfig): elif app.manylinux.startswith("manylinux_2_"): context["vendor_base"] = "almalinux" else: - raise BriefcaseConfigError( - f"""Unknown manylinux tag {app.manylinux!r}""" - ) + raise BriefcaseConfigError(f"Unknown manylinux tag {app.manylinux!r}") except AttributeError: pass diff --git a/tests/commands/new/test_build_context.py b/tests/commands/new/test_build_context.py index 05f44a473..20b51e178 100644 --- a/tests/commands/new/test_build_context.py +++ b/tests/commands/new/test_build_context.py @@ -198,7 +198,7 @@ def main(): ] """, pyproject_table_linux_appimage=""" -manylinux = "manylinux2014" +manylinux = "manylinux_2_28" system_requires = [ # Needed to compile pycairo wheel @@ -578,7 +578,7 @@ def main(): ] """, pyproject_table_linux_appimage=""" -manylinux = "manylinux2014" +manylinux = "manylinux_2_28" system_requires = [ # ?? FIXME @@ -748,7 +748,7 @@ def main(): ] """, pyproject_table_linux_appimage=""" -manylinux = "manylinux2014" +manylinux = "manylinux_2_28" system_requires = [ ] @@ -1178,7 +1178,7 @@ def main(): ] """, pyproject_table_linux_appimage=""" -manylinux = "manylinux2014" +manylinux = "manylinux_2_28" system_requires = [ # Needed to compile pycairo wheel diff --git a/tests/integrations/linuxdeploy/test_LinuxDeploy__properties.py b/tests/integrations/linuxdeploy/test_LinuxDeploy__properties.py index bfbe57bc7..6560c029b 100644 --- a/tests/integrations/linuxdeploy/test_LinuxDeploy__properties.py +++ b/tests/integrations/linuxdeploy/test_LinuxDeploy__properties.py @@ -19,17 +19,23 @@ def test_file_path(linuxdeploy, mock_tools): @pytest.mark.parametrize( - "host_os, host_arch, linuxdeploy_arch", + "host_os, host_arch, is_32bit_python, linuxdeploy_arch", [ - ("Linux", "x86_64", "x86_64"), - ("Linux", "i686", "i386"), - ("Darwin", "x86_64", "x86_64"), + ("Linux", "x86_64", False, "x86_64"), + ("Linux", "x86_64", True, "i386"), + ("Linux", "i686", True, "i386"), + ("Linux", "aarch64", True, "armhf"), + ("Linux", "aarch64", False, "aarch64"), + ("Linux", "armv7l", True, "armhf"), + ("Linux", "armv8l", True, "armhf"), + ("Darwin", "x86_64", False, "x86_64"), ], ) -def test_file_name(mock_tools, host_os, host_arch, linuxdeploy_arch): +def test_file_name(mock_tools, host_os, host_arch, is_32bit_python, linuxdeploy_arch): """Linuxdeploy filename is architecture dependent.""" mock_tools.host_os = host_os mock_tools.host_arch = host_arch + mock_tools.is_32bit_python = is_32bit_python linuxdeploy = LinuxDeploy(mock_tools) diff --git a/tests/platforms/linux/appimage/test_create.py b/tests/platforms/linux/appimage/test_create.py index 0ab0bd169..95d0c0203 100644 --- a/tests/platforms/linux/appimage/test_create.py +++ b/tests/platforms/linux/appimage/test_create.py @@ -175,6 +175,32 @@ def test_finalize_nodocker(create_command, first_app_config, capsys): "use_non_root_user": True, }, ), + # Linux on aarch64 hardware + ( + "manylinux_2_28", + None, + "Linux", + "aarch64", + False, + { + "manylinux_image": "manylinux_2_28_aarch64:latest", + "vendor_base": "almalinux", + "use_non_root_user": True, + }, + ), + # Linux on arm hardware + ( + "manylinux_2_28", + None, + "Linux", + "armv7l", + False, + { + "manylinux_image": "manylinux_2_28_armhf:latest", + "vendor_base": "almalinux", + "use_non_root_user": True, + }, + ), # macOS on x86_64 ( "manylinux2014",