diff --git a/rockcraft/services/lifecycle.py b/rockcraft/services/lifecycle.py index 10639f877..880115ac6 100644 --- a/rockcraft/services/lifecycle.py +++ b/rockcraft/services/lifecycle.py @@ -119,4 +119,32 @@ def _post_prime_callback(step_info: StepInfo) -> bool: files = step_info.state.files if step_info.state else set() layers.prune_prime_files(prime_dir, files, base_layer_dir) + + _python_usrmerge_fix(step_info) + return True + + +def _python_usrmerge_fix(step_info: StepInfo): + """Fix 'lib64' symlinks created by the Python plugin on ubuntu@24.04 projects.""" + if step_info.project_info.base != "ubuntu@24.04": + # The issue only affects rocks with 24.04 bases. + return + + state = step_info.state + if state is None: + # Can't inspect the files without a StepState. + return + + if state.part_properties["plugin"] != "python": + # Be conservative and don't try to fix the files if they didn't come + # from the Python plugin. + return + + if "lib64" not in state.files: + return + + prime_dir = step_info.prime_dir + lib64 = prime_dir / "lib64" + if lib64.is_symlink() and lib64.readlink() == Path("lib"): + lib64.unlink() diff --git a/tests/unit/services/test_lifecycle.py b/tests/unit/services/test_lifecycle.py index 5480f791f..c9b78490b 100644 --- a/tests/unit/services/test_lifecycle.py +++ b/tests/unit/services/test_lifecycle.py @@ -13,11 +13,22 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os from pathlib import Path from unittest import mock import pytest -from craft_parts import LifecycleManager, callbacks +from craft_parts import ( + LifecycleManager, + Part, + PartInfo, + ProjectDirs, + ProjectInfo, + Step, + StepInfo, + callbacks, +) +from craft_parts.state_manager.prime_state import PrimeState from rockcraft.services import lifecycle as lifecycle_module @@ -78,3 +89,40 @@ def test_lifecycle_package_repositories( mock_callback.assert_called_once_with( lifecycle_module._install_overlay_repositories ) + + +def test_python_usrmerge_fix(tmp_path): + # The test setup is rather involved because we need to recreate/mock an + # exact set of circumstances here: + + # 1) Create a project with 24.04 base; + dirs = ProjectDirs(work_dir=tmp_path) + project_info = ProjectInfo( + project_dirs=dirs, + application_name="test", + cache_dir=tmp_path, + strict_mode=False, + base="ubuntu@24.04", + ) + + # 2) Create a part using the Python plugin; + part = Part("p1", {"source": ".", "plugin": "python"}) + part_info = PartInfo(project_info=project_info, part=part) + + prime_dir = dirs.prime_dir + prime_dir.mkdir() + + # 3) Setup a 'prime' directory where "lib64" is a symlink to "lib"; + (prime_dir / "lib").mkdir() + (prime_dir / "lib64").symlink_to("lib") + + # 4) Create a StepInfo that contains all of this. + step_info = StepInfo(part_info=part_info, step=Step.PRIME) + step_info.state = PrimeState(part_properties=part.spec.marshal(), files={"lib64"}) + + assert sorted(os.listdir(prime_dir)) == ["lib", "lib64"] + + lifecycle_module._python_usrmerge_fix(step_info) + + # After running the fix the "lib64" symlink must be gone + assert sorted(os.listdir(prime_dir)) == ["lib"]