Skip to content

Commit

Permalink
Merge pull request #1598 from rmartin16/opensuse-pkg
Browse files Browse the repository at this point in the history
Fix moving openSUSE rpm package to `dist` directory
  • Loading branch information
freakboy3742 committed Jan 13, 2024
2 parents 7d68920 + 4886cb8 commit be9d845
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 108 deletions.
1 change: 1 addition & 0 deletions changes/1595.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Linux System RPM packaging for openSUSE Tumbleweed no longer errors with ``FileNotFoundError``.
2 changes: 1 addition & 1 deletion src/briefcase/bootstraps/toga.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def pyproject_table_linux_system_suse(self):
# Needed to provide GTK
"gtk3",
# Needed to support Python bindings to GTK
"gobject-introspection", "typelib(Gtk)=3.0",
"gobject-introspection", "typelib(Gtk) = 3.0",
# Dependencies that GTK looks for at runtime
"libcanberra-gtk3-0",
# Needed to provide WebKit2 at runtime
Expand Down
79 changes: 47 additions & 32 deletions src/briefcase/platforms/linux/system.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations

import gzip
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import List

from briefcase.commands import (
BuildCommand,
Expand Down Expand Up @@ -261,18 +262,35 @@ def pkg_abi(self, app: AppConfig) -> str:
def distribution_filename(self, app: AppConfig) -> str:
if app.packaging_format == "deb":
return (
f"{app.app_name}_{app.version}-{getattr(app, 'revision', 1)}"
f"~{app.target_vendor}-{app.target_codename}_{self.deb_abi(app)}.deb"
f"{app.app_name}"
f"_{app.version}"
f"-{getattr(app, 'revision', 1)}"
f"~{app.target_vendor}"
f"-{app.target_codename}"
f"_{self.deb_abi(app)}"
".deb"
)
elif app.packaging_format == "rpm":
# openSUSE doesn't include a distro tag
if app.target_vendor_base == SUSE:
distro_tag = ""
else:
distro_tag = f".{self.rpm_tag(app)}"
return (
f"{app.app_name}-{app.version}-{getattr(app, 'revision', 1)}"
f".{self.rpm_tag(app)}.{self.rpm_abi(app)}.rpm"
f"{app.app_name}"
f"-{app.version}"
f"-{getattr(app, 'revision', 1)}"
f"{distro_tag}"
f".{self.rpm_abi(app)}"
f".rpm"
)
elif app.packaging_format == "pkg":
return (
f"{app.app_name}-{app.version}-{getattr(app, 'revision', 1)}"
f"-{self.pkg_abi(app)}.pkg.tar.zst"
f"{app.app_name}"
f"-{app.version}"
f"-{getattr(app, 'revision', 1)}"
f"-{self.pkg_abi(app)}"
".pkg.tar.zst"
)
else:
raise BriefcaseCommandError(
Expand Down Expand Up @@ -513,7 +531,7 @@ def verify_system_packages(self, app: AppConfig):
system_installer,
) = self._system_requirement_tools(app)

if system_verify is None:
if not (system_verify and self.tools.shutil.which(system_verify[0])):
self.logger.warning(
"""
*************************************************************************
Expand Down Expand Up @@ -545,7 +563,7 @@ def verify_system_packages(self, app: AppConfig):
f"""\
Unable to build {app.app_name} due to missing system dependencies. Run:
sudo {' '.join(system_installer)} {' '.join(missing)}
sudo {" ".join(system_installer)} {" ".join(missing)}
to install the missing dependencies, and re-run Briefcase.
"""
Expand Down Expand Up @@ -780,7 +798,7 @@ class LinuxSystemRunCommand(LinuxSystemPassiveMixin, RunCommand):
supported_host_os_reason = "Linux system projects can only be executed on Linux."

def run_app(
self, app: AppConfig, test_mode: bool, passthrough: List[str], **kwargs
self, app: AppConfig, test_mode: bool, passthrough: list[str], **kwargs
):
"""Start the application.
Expand Down Expand Up @@ -830,27 +848,25 @@ class LinuxSystemPackageCommand(LinuxSystemMixin, PackageCommand):
def packaging_formats(self):
return ["deb", "rpm", "pkg", "system"]

def _verify_deb_tools(self):
"""Verify that the local environment contains the debian packaging tools."""
if not Path("/usr/bin/dpkg-deb").exists():
raise BriefcaseCommandError(
"Can't find the dpkg tools. Try running `sudo apt install dpkg-dev`."
)

def _verify_rpm_tools(self):
"""Verify that the local environment contains the redhat packaging tools."""
if not Path("/usr/bin/rpmbuild").exists():
raise BriefcaseCommandError(
"Can't find the rpm-build tools. Try running `sudo dnf install rpm-build`."
)
def _verify_packaging_tools(self, app: AppConfig):
"""Verify that the local environment contains the packaging tools."""
tool_name, executable_name, package_name = {
"deb": ("dpkg", "dpkg-deb", "dpkg-dev"),
"rpm": ("rpm-build", "rpmbuild", "rpmbuild"),
"pkg": ("makepkg", "makepkg", "pacman"),
}[app.packaging_format]

def _verify_pkg_tools(self):
"""Verify that the local environment contains the arch packaging tools(ABS)."""
if not Path("/usr/bin/makepkg").exists():
raise BriefcaseCommandError(
"Can't find the `makepkg` tool. Try running `sudo pacman -Syu pacman`."
# makepkg is part of pacman package
)
if not self.tools.shutil.which(executable_name):
if install_cmd := self._system_requirement_tools(app)[2]:
raise BriefcaseCommandError(
f"Can't find the {tool_name} tools. "
f"Try running `sudo {' '.join(install_cmd)} {package_name}`."
)
else:
raise BriefcaseCommandError(
f"Can't find the {executable_name} tool. "
f"Install this first to package the {app.packaging_format}."
)

def verify_app_tools(self, app):
super().verify_app_tools(app)
Expand All @@ -871,8 +887,7 @@ def verify_app_tools(self, app):
)

if not self.use_docker:
# Check for the format-specific packaging tools.
getattr(self, f"_verify_{app.packaging_format}_tools")()
self._verify_packaging_tools(app)

def package_app(self, app: AppConfig, **kwargs):
if app.packaging_format == "deb":
Expand Down
4 changes: 2 additions & 2 deletions tests/commands/new/test_build_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def main():
# Needed to provide GTK
"gtk3",
# Needed to support Python bindings to GTK
"gobject-introspection", "typelib(Gtk)=3.0",
"gobject-introspection", "typelib(Gtk) = 3.0",
# Dependencies that GTK looks for at runtime
"libcanberra-gtk3-0",
# Needed to provide WebKit2 at runtime
Expand Down Expand Up @@ -1159,7 +1159,7 @@ def main():
# Needed to provide GTK
"gtk3",
# Needed to support Python bindings to GTK
"gobject-introspection", "typelib(Gtk)=3.0",
"gobject-introspection", "typelib(Gtk) = 3.0",
# Dependencies that GTK looks for at runtime
"libcanberra-gtk3-0",
# Needed to provide WebKit2 at runtime
Expand Down
13 changes: 9 additions & 4 deletions tests/platforms/linux/system/test_mixin__properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,24 +80,29 @@ def test_binary_path(create_command, first_app_config, tmp_path):


@pytest.mark.parametrize(
"packaging_format, filename",
"packaging_format, vendor_base, filename",
[
("deb", "first-app_0.0.1-1~somevendor-surprising_wonky.deb"),
("rpm", "first-app-0.0.1-1.elsurprising.wonky.rpm"),
("pkg", "first-app-0.0.1-1-wonky.pkg.tar.zst"),
("deb", "debian", "first-app_0.0.1-1~somevendor-surprising_wonky.deb"),
("rpm", "fedora", "first-app-0.0.1-1.elsurprising.wonky.rpm"),
("rpm", "suse", "first-app-0.0.1-1.wonky.rpm"),
("pkg", "arch", "first-app-0.0.1-1-wonky.pkg.tar.zst"),
],
)
def test_distribution_path(
create_command,
first_app_config,
packaging_format,
vendor_base,
filename,
tmp_path,
):
"""The distribution path contains vendor details."""
# Mock return value for ABI from packaging system
create_command._build_env_abi = MagicMock(return_value="wonky")

# Set vendor base (RPM package naming changes for openSUSE)
first_app_config.target_vendor_base = vendor_base

# Force a dummy vendor:codename for test purposes.
first_app_config.target_vendor = "somevendor"
first_app_config.target_codename = "surprising"
Expand Down
18 changes: 18 additions & 0 deletions tests/platforms/linux/system/test_mixin__verify_system_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def build_command(tmp_path, first_app):
command.tools.host_os = "Linux"
command.tools.host_arch = "wonky"

# All calls to `shutil.which()` succeed
command.tools.shutil.which = MagicMock(return_value="/path/to/exe")

# Mock subprocess
command.tools.subprocess = MagicMock()

Expand Down Expand Up @@ -124,6 +127,21 @@ def test_missing_packages(build_command, first_app_config, capsys):
build_command.verify_system_packages(first_app_config)


def test_missing_system_verify(build_command, first_app_config, capsys):
"""If the program to verify system packages doesn't exist, a warning is logged."""
# Mock the system verifier is missing
build_command.tools.shutil.which = MagicMock(return_value="")

build_command.verify_system_packages(first_app_config)

# No packages verified
build_command.tools.subprocess.check_output.assert_not_called()

# A warning was logged.
output = capsys.readouterr().out
assert "WARNING: Can't verify system packages" in output


def test_packages_installed(build_command, first_app_config, capsys):
"""If all required packages are installed, no error is raised."""
# Mock the system requirement tools; there's a base requirement of
Expand Down
4 changes: 1 addition & 3 deletions tests/platforms/linux/system/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ def package_command(monkeypatch, first_app, tmp_path):
command.verify_system_packages = mock.MagicMock()

# Mock the packaging tools.
command._verify_deb_tools = mock.MagicMock()
command._verify_rpm_tools = mock.MagicMock()
command._verify_pkg_tools = mock.MagicMock()
command._verify_packaging_tools = mock.MagicMock()

return command

Expand Down
52 changes: 30 additions & 22 deletions tests/platforms/linux/system/test_package__deb.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,41 +58,49 @@ def test_verify_no_docker(monkeypatch, package_command, first_app_deb):
# Mock not using docker
package_command.target_image = None

# Mock the path of dpkg-deb
dpkg_deb = mock.MagicMock()
dpkg_deb.exists.return_value = True

mock_Path = mock.MagicMock(return_value=dpkg_deb)
monkeypatch.setattr(system, "Path", mock_Path)
# Mock the existence of dpkg-deb
package_command.tools.shutil.which = mock.MagicMock(return_value="/mybin/dpkg-deb")

# App tools can be verified
package_command.verify_app_tools(first_app_deb)

# dpkg_deb was inspected
dpkg_deb.exists.assert_called_once()
package_command.tools.shutil.which.assert_called_once_with("dpkg-deb")


def test_verify_dpkg_deb_missing(monkeypatch, package_command, first_app_deb):
"""If dpkg_deb isn't installed, an error is raised."""
@pytest.mark.parametrize(
"vendor_base, error_msg",
[
(
"debian",
"Can't find the dpkg tools. Try running `sudo apt install dpkg-dev`.",
),
(None, "Can't find the dpkg-deb tool. Install this first to package the deb."),
],
)
def test_verify_dpkg_deb_missing(
monkeypatch,
package_command,
first_app_deb,
vendor_base,
error_msg,
):
"""If dpkg-deb isn't installed, an error is raised."""
# Mock distro so packager is found or not appropriately
first_app_deb.target_vendor_base = vendor_base

# Mock packager as missing
package_command.tools.shutil.which = mock.MagicMock(return_value="")

# Mock not using docker
package_command.target_image = None

# Mock the path of dpkg-deb
dpkg_deb = mock.MagicMock()
dpkg_deb.exists.return_value = False

mock_Path = mock.MagicMock(return_value=dpkg_deb)
monkeypatch.setattr(system, "Path", mock_Path)

# Verifying app tools will raise an error
with pytest.raises(
BriefcaseCommandError,
match=r"Can't find the dpkg tools. Try running `sudo apt install dpkg-dev`.",
):
with pytest.raises(BriefcaseCommandError, match=error_msg):
package_command.verify_app_tools(first_app_deb)

# dpkg_deb was inspected
dpkg_deb.exists.assert_called_once()
# which was called for dpkg-deb
package_command.tools.shutil.which.assert_called_once_with("dpkg-deb")


def test_verify_docker(monkeypatch, package_command, first_app_deb):
Expand Down
52 changes: 30 additions & 22 deletions tests/platforms/linux/system/test_package__pkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,41 +86,49 @@ def test_verify_no_docker(monkeypatch, package_command, first_app_pkg):
# Mock not using docker
package_command.target_image = None

# Mock the path of makepkg
makepkg = mock.MagicMock()
makepkg.exists.return_value = True

mock_Path = mock.MagicMock(return_value=makepkg)
monkeypatch.setattr(system, "Path", mock_Path)
# Mock the existence of makepkg
package_command.tools.shutil.which = mock.MagicMock(return_value="/mybin/makepkg")

# App tools can be verified
package_command.verify_app_tools(first_app_pkg)

# makepkg was inspected
makepkg.exists.assert_called_once()
package_command.tools.shutil.which.assert_called_once_with("makepkg")


@pytest.mark.parametrize(
"vendor_base, error_msg",
[
(
"arch",
"Can't find the makepkg tools. Try running `sudo pacman -Syu pacman`.",
),
(None, "Can't find the makepkg tool. Install this first to package the pkg."),
],
)
def test_verify_makepkg_missing(
monkeypatch,
package_command,
first_app_pkg,
vendor_base,
error_msg,
):
"""If makepkg isn't installed, an error is raised."""
# Mock distro so packager is found or not appropriately
first_app_pkg.target_vendor_base = vendor_base

# Mock packager as missing
package_command.tools.shutil.which = mock.MagicMock(return_value="")

def test_verify_makepkg_missing(monkeypatch, package_command, first_app_pkg):
"""If makepkg isn't installed, an error is raised."""
# Mock not using docker
package_command.target_image = None

# Mock the path of makepkg
makepkg = mock.MagicMock()
makepkg.exists.return_value = False

mock_Path = mock.MagicMock(return_value=makepkg)
monkeypatch.setattr(system, "Path", mock_Path)

# Verifying app tools will raise an error
with pytest.raises(
BriefcaseCommandError,
match=r"Can't find the `makepkg` tool. Try running `sudo pacman -Syu pacman`.",
):
with pytest.raises(BriefcaseCommandError, match=error_msg):
package_command.verify_app_tools(first_app_pkg)

# makepkg was inspected
makepkg.exists.assert_called_once()
# which was called for makepkg
package_command.tools.shutil.which.assert_called_once_with("makepkg")


def test_verify_docker(monkeypatch, package_command, first_app_pkg):
Expand Down

0 comments on commit be9d845

Please sign in to comment.