Skip to content

Commit

Permalink
Replace use of build-essential with constituent parts. (#2096)
Browse files Browse the repository at this point in the history
Checks for the constituent parts of build-essential, rather than the build-essential meta package, when installing on Debian-derived distributions.
  • Loading branch information
freakboy3742 authored Jan 2, 2025
1 parent aa1f5d9 commit 76957a9
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 16 deletions.
1 change: 1 addition & 0 deletions changes/2096.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A Debian-based system that does *not* have ``build-essential`` installed, but *does* have the constituent packages of ``build-essential`` installed, can now build Briefcase system packages.
28 changes: 23 additions & 5 deletions src/briefcase/platforms/linux/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,15 @@ def _system_requirement_tools(self, app: AppConfig):
if the system cannot be identified.
"""
if app.target_vendor_base == DEBIAN:
base_system_packages = ["python3-dev", "build-essential"]
base_system_packages = [
"python3-dev",
# The consitutent parts of build-essential
("dpkg-dev", "build-essential"),
("g++", "build-essential"),
("gcc", "build-essential"),
("libc6-dev", "build-essential"),
("make", "build-essential"),
]
system_verify = ["dpkg", "-s"]
system_installer = ["apt", "install"]
elif app.target_vendor_base == RHEL:
Expand Down Expand Up @@ -543,20 +551,30 @@ def verify_system_packages(self, app: AppConfig):

# Run a check for each package listed in the app's system_requires,
# plus the baseline system packages that are required.
missing = []
missing = set()
for package in base_system_packages + getattr(app, "system_requires", []):
# Look for tuples in the package list. If there's a tuple, we're looking
# for the first name in the tuple on the installed list, but we install
# the package using the second name. This is to handle `build-essential`
# style installation aliases. If it's not a tuple, the package name is
# provided by the same name that we're checking for.
if isinstance(package, tuple):
installed, provided_by = package
else:
installed = provided_by = package

try:
self.tools.subprocess.check_output(system_verify + [package])
self.tools.subprocess.check_output(system_verify + [installed])
except subprocess.CalledProcessError:
missing.append(package)
missing.add(provided_by)

# If any required packages are missing, raise an error.
if missing:
raise BriefcaseCommandError(
f"""\
Unable to build {app.app_name} due to missing system dependencies. Run:
sudo {" ".join(system_installer)} {" ".join(missing)}
sudo {" ".join(system_installer)} {" ".join(sorted(missing))}
to install the missing dependencies, and re-run Briefcase.
"""
Expand Down
39 changes: 28 additions & 11 deletions tests/platforms/linux/system/test_mixin__verify_system_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ def test_deb_requirements(build_command, first_app_config):
# The packages were verified
assert build_command.tools.subprocess.check_output.mock_calls == [
call(["dpkg", "-s", "python3-dev"]),
call(["dpkg", "-s", "build-essential"]),
call(["dpkg", "-s", "dpkg-dev"]),
call(["dpkg", "-s", "g++"]),
call(["dpkg", "-s", "gcc"]),
call(["dpkg", "-s", "libc6-dev"]),
call(["dpkg", "-s", "make"]),
]


Expand Down Expand Up @@ -96,12 +100,20 @@ def test_unknown_requirements(build_command, first_app_config, capsys):

def test_missing_packages(build_command, first_app_config, capsys):
"""If there are missing system packages, an error is raised."""
# Mock the system requirement tools; there's a base requirement of
# a packaged called "compiler", verified using "check <pkg>", and
# installed using "system <pkg>"
# Mock the system requirement tools; there's a base requirement of packages called
# "compiler" and "compiler++", plus 3 packages provided by an installation alias
# "alias". These packages are verified using "check <pkg>", and installed using
# "system install_flag <pkg>"
build_command._system_requirement_tools = MagicMock(
return_value=(
["compiler"],
[
"compiler",
"compiler++",
# Three packages provided by the `alias` alias
("aliased-1", "alias"),
("aliased-2", "alias"),
("aliased-3", "alias"),
],
["check"],
["system", "install_flag"],
)
Expand All @@ -112,17 +124,22 @@ def test_missing_packages(build_command, first_app_config, capsys):

# Mock the side effect of checking those requirements.
build_command.tools.subprocess.check_output.side_effect = [
subprocess.CalledProcessError(cmd="check", returncode=1),
"installed",
subprocess.CalledProcessError(cmd="check", returncode=1),
"installed",
subprocess.CalledProcessError(cmd="check", returncode=1), # compiler
"installed", # compiler++
subprocess.CalledProcessError(cmd="check", returncode=1), # aliased-1
"installed", # aliased-2
subprocess.CalledProcessError(cmd="check", returncode=1), # aliased-3
"installed", # first
subprocess.CalledProcessError(cmd="check", returncode=1), # second
"installed", # third
]

# Verify the requirements. This will raise an error, but the error
# message will tell you how to install the system packages.
# message will tell you how to install the system packages. This includes
# using an alias for the install of aliased-1 and aliased-3.
with pytest.raises(
BriefcaseCommandError,
match=r" sudo system install_flag compiler second",
match=r" sudo system install_flag alias compiler second",
):
build_command.verify_system_packages(first_app_config)

Expand Down

0 comments on commit 76957a9

Please sign in to comment.