Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace use of build-essential with constituent parts. #2096

Merged
merged 3 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading