From 76957a9f5984a39b601ae876e3186190c0846958 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 2 Jan 2025 09:29:57 +0800 Subject: [PATCH] Replace use of build-essential with constituent parts. (#2096) Checks for the constituent parts of build-essential, rather than the build-essential meta package, when installing on Debian-derived distributions. --- changes/2096.bugfix.rst | 1 + src/briefcase/platforms/linux/system.py | 28 ++++++++++--- .../test_mixin__verify_system_packages.py | 39 +++++++++++++------ 3 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 changes/2096.bugfix.rst diff --git a/changes/2096.bugfix.rst b/changes/2096.bugfix.rst new file mode 100644 index 000000000..c2ef5afa5 --- /dev/null +++ b/changes/2096.bugfix.rst @@ -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. diff --git a/src/briefcase/platforms/linux/system.py b/src/briefcase/platforms/linux/system.py index dfa9bc873..940313c64 100644 --- a/src/briefcase/platforms/linux/system.py +++ b/src/briefcase/platforms/linux/system.py @@ -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: @@ -543,12 +551,22 @@ 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: @@ -556,7 +574,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(sorted(missing))} to install the missing dependencies, and re-run Briefcase. """ 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 53b8fe8ba..a0c2358e0 100644 --- a/tests/platforms/linux/system/test_mixin__verify_system_packages.py +++ b/tests/platforms/linux/system/test_mixin__verify_system_packages.py @@ -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"]), ] @@ -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 ", and - # installed using "system " + # 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 ", and installed using + # "system install_flag " 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"], ) @@ -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)