Repository: beeware/briefcase

Issue:
Add support for OpenSuSE
If you run Briefcase on OpenSuSE, you currently get an error:

### Describe the bug

Running a built app fails with:
```
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
Install path: <a project specific path>
Pre-initializing Python runtime...
PYTHONPATH:
- /usr/lib/python3.11
- /usr/lib/python3.11/lib-dynload
- <some project specific paths>
Configure argc/argv...
Initializing Python runtime...
ModuleNotFoundError: No module named 'encodings'
```

And we have:
```
> python3.11 -c 'import encodings; print(encodings.__file__)'
/usr/lib64/python3.11/encodings/__init__.py
```

Note the path `/usr/lib64/python3.11` is not in PYTHONPATH (`lib64` instead of `lib`).

### Steps to reproduce

1. Get the basic template
2. Run `briefcase run`

### Expected behavior

Building picks up the paths required for python to run.

### Screenshots

_No response_

### Environment

- Operating System: OpenSUSE
- Python version: 3.11
- Software versions:
  - Briefcase: 0.3.15
  - Toga: 0.3.1


### Logs

_No response_

### Additional context

_No response_

Discussion:

Patch:
diff --git a/changes/1416.feature.rst b/changes/1416.feature.rst
new file mode 100644
index 00000000..35c1f434
--- /dev/null
+++ b/changes/1416.feature.rst
@@ -0,0 +1,1 @@
+Support for OpenSuSE Linux distributions was added.
diff --git a/docs/reference/platforms/linux/system.rst b/docs/reference/platforms/linux/system.rst
index b372524b..12570f88 100644
--- a/docs/reference/platforms/linux/system.rst
+++ b/docs/reference/platforms/linux/system.rst
@@ -16,7 +16,7 @@ All modern Linux distributions have a native format for distributing packages
 that are integrated into their overall operating system:
 
 * ``.deb``, used by Debian, Ubuntu, Mint (and others)
-* ``.rpm``, used by Red Hat, Fedora, CentOS, AlmaLinux (and others)
+* ``.rpm``, used by Red Hat, Fedora, CentOS, AlmaLinux, openSUSE (and others)
 * ``.pkg.tar.zst``, used by Arch Linux and Manjaro Linux
 
 The Briefcase ``system`` backend provides a way to build your app in these
@@ -116,6 +116,7 @@ The following Linux vendors are known to work as Docker targets:
 * Fedora (e.g, ``fedora:37``)
 * AlmaLinux (e.g., ``almalinux:9``)
 * Red Hat Enterprise Linux (e.g., ``redhat/ubi9:9``)
+* openSUSE Tumbleweed (e.g., ``"opensuse/tumbleweed:latest"``)
 * Arch Linux (e.g., ``archlinux:latest``)
 * Manjaro Linux (e.g., ``manjarolinux/base:latest``)
 
@@ -131,10 +132,10 @@ If you need to override these settings for a specific target vendor, or for a
 specific distribution version, you can provide increasingly specific sections for
 vendor and version information. Each distribution is identified by:
 
-* Vendor base (e.g., ``debian``, ``rhel``, ``arch``)
-* Vendor (e.g, ``debian``, ``ubuntu``, ``rhel``, ``fedora``). The vendor
-  identifier *may* be the same as the vendor base (e.g, in the case of Debian or
-  Red Hat)
+* Vendor base (e.g., ``debian``, ``rhel``, ``arch``, ``suse``)
+* Vendor (e.g, ``debian``, ``ubuntu``, ``rhel``, ``fedora``, ``opensuse-tumbleweed``,
+  ``arch``, ``manjaro``). The vendor identifier *may* be the same as the vendor base
+  (e.g, in the case of Debian, Red Hat, or Arch)
 * Code name (e.g., a version number, or ``jammy``).
 
 For example, a full configuration for ``myapp`` running on Ubuntu 22.04 (jammy)
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index a46cf65e..be6bdd47 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -51,6 +51,7 @@ Manjaro
 manylinux
 Namespace
 natively
+openSUSE
 OSX
 passthrough
 Passthrough
diff --git a/src/briefcase/platforms/linux/__init__.py b/src/briefcase/platforms/linux/__init__.py
index 12a1338a..339b9fe9 100644
--- a/src/briefcase/platforms/linux/__init__.py
+++ b/src/briefcase/platforms/linux/__init__.py
@@ -15,6 +15,7 @@ DEFAULT_OUTPUT_FORMAT = "system"
 DEBIAN = "debian"
 RHEL = "rhel"
 ARCH = "arch"
+SUSE = "suse"
 
 
 def parse_freedesktop_os_release(content):
@@ -113,6 +114,8 @@ class LinuxMixin:
             vendor_base = RHEL
         elif vendor == ARCH or ARCH in id_like:
             vendor_base = ARCH
+        elif vendor == SUSE or SUSE in id_like:
+            vendor_base = SUSE
         else:
             vendor_base = None
 
diff --git a/src/briefcase/platforms/linux/system.py b/src/briefcase/platforms/linux/system.py
index 32629a86..c322ab7c 100644
--- a/src/briefcase/platforms/linux/system.py
+++ b/src/briefcase/platforms/linux/system.py
@@ -22,6 +22,7 @@ from briefcase.platforms.linux import (
     ARCH,
     DEBIAN,
     RHEL,
+    SUSE,
     DockerOpenCommand,
     LinuxMixin,
     LocalRequirementsMixin,
@@ -476,6 +477,13 @@ class LinuxSystemMostlyPassiveMixin(LinuxSystemPassiveMixin):
             ]
             system_verify = ["rpm", "-q"]
             system_installer = ["dnf", "install"]
+        elif app.target_vendor_base == SUSE:
+            base_system_packages = [
+                "python3-devel",
+                "patterns-devel-base-devel_basis",
+            ]
+            system_verify = ["rpm", "-q", "--whatprovides"]
+            system_installer = ["zypper", "install"]
         elif app.target_vendor_base == ARCH:
             base_system_packages = [
                 "python3",
@@ -852,6 +860,7 @@ class LinuxSystemPackageCommand(LinuxSystemMixin, PackageCommand):
                 DEBIAN: "deb",
                 RHEL: "rpm",
                 ARCH: "pkg",
+                SUSE: "rpm",
             }.get(app.target_vendor_base, None)
 
         if app.packaging_format is None:


Test patch:
diff --git a/tests/platforms/linux/system/test_mixin__verify_system_packages.py b/tests/platforms/linux/system/test_mixin__verify_system_packages.py
index 3b4196e6..715cab77 100644
--- a/tests/platforms/linux/system/test_mixin__verify_system_packages.py
+++ b/tests/platforms/linux/system/test_mixin__verify_system_packages.py
@@ -53,6 +53,18 @@ def test_rpm_requirements(build_command, first_app_config):
     ]
 
 
+def test_suse_requirements(build_command, first_app_config):
+    """SUSE requirements can be verified."""
+    first_app_config.target_vendor_base = "suse"
+
+    build_command.verify_system_packages(first_app_config)
+
+    assert build_command.tools.subprocess.check_output.mock_calls == [
+        call(["rpm", "-q", "--whatprovides", "python3-devel"]),
+        call(["rpm", "-q", "--whatprovides", "patterns-devel-base-devel_basis"]),
+    ]
+
+
 def test_arch_requirements(build_command, first_app_config, capsys):
     """Arch requirements can be verified."""
     first_app_config.target_vendor_base = "arch"
diff --git a/tests/platforms/linux/test_LinuxMixin__vendor_details.py b/tests/platforms/linux/test_LinuxMixin__vendor_details.py
index d6c09341..620629f4 100644
--- a/tests/platforms/linux/test_LinuxMixin__vendor_details.py
+++ b/tests/platforms/linux/test_LinuxMixin__vendor_details.py
@@ -14,8 +14,8 @@ from .os_release import OS_RELEASE
         ("rhel/ubi8:8.7", "rhel", "8", "rhel"),
         ("almalinux:9.1", "almalinux", "9", "rhel"),
         ("centos:8", "centos", "8", "rhel"),
-        ("opensuse/leap:15.4", "opensuse-leap", "15", None),
-        ("opensuse/tumbleweed:latest", "opensuse-tumbleweed", "20230304", None),
+        ("opensuse/leap:15.4", "opensuse-leap", "15", "suse"),
+        ("opensuse/tumbleweed:latest", "opensuse-tumbleweed", "20230304", "suse"),
         ("debian:11", "debian", "bullseye", "debian"),
         ("ubuntu:22.04", "ubuntu", "jammy", "debian"),
         ("pop:22.04", "pop", "jammy", "debian"),