Repository: canonical/snapcraft

Issue:
patchelf to handle RUNPATH
When `RUNPATH` is set we need to retrieve it, clear it and set it as an RPATH


Discussion:
Upstream issue https://github.com/NixOS/patchelf/issues/94

Resolving patch:
diff --git a/snapcraft/internal/elf.py b/snapcraft/internal/elf.py
--- a/snapcraft/internal/elf.py
+++ b/snapcraft/internal/elf.py
@@ -384,13 +384,17 @@ def patch(self, *, elf_file: ElfFile) -> None:
         if elf_file.interp:
             patchelf_args.extend(['--set-interpreter',  self._dynamic_linker])
         if elf_file.dependencies:
+            rpath = self._get_rpath(elf_file)
+            # Due to https://github.com/NixOS/patchelf/issues/94 we need
+            # to first clear the current rpath
+            self._run_patchelf(patchelf_args=['--remove-rpath'],
+                               elf_file_path=elf_file.path)
             # Parameters:
             # --force-rpath: use RPATH instead of RUNPATH.
             # --shrink-rpath: will remove unneeded entries, with the
             #                 side effect of preferring host libraries
             #                 so we simply do not use it.
             # --set-rpath: set the RPATH to the colon separated argument.
-            rpath = self._get_rpath(elf_file)
             patchelf_args.extend(['--force-rpath', '--set-rpath', rpath])
 
         # no patchelf_args means there is nothing to do.


Test patch:
diff --git a/tests/integration/general/test_elf.py b/tests/integration/general/test_elf.py
--- a/tests/integration/general/test_elf.py
+++ b/tests/integration/general/test_elf.py
@@ -20,7 +20,7 @@
 from testtools.matchers import (Annotate, Contains, DirExists, Equals,
                                 FileExists, MatchesRegex, Not)
 
-from tests import integration, fixture_setup
+from tests import integration, fixture_setup, skip
 
 
 class RpathTestCase(integration.TestCase):
@@ -88,6 +88,41 @@ def test_origin(self):
                 Annotate('A duplicate rpath was found!', Not(Contains(i))))
             seen.add(i)
 
+    @skip.skip_unless_codename('xenial', 'we currently only have core')
+    def test_origin_runpath_migrated_to_rpath(self):
+        # We stage libc6 for this to work on non xenial
+        snapcraft_yaml = fixture_setup.SnapcraftYaml(
+            self.path, confinement='classic')
+        snapcraft_yaml.update_part('test-part', {
+            'plugin': 'nil',
+            'build-packages': ['patchelf'],
+            'stage-packages': ['hello'],
+            # Without --force-rpath this is essentially a RUNPATH
+            'override-build': (
+                "patchelf --set-rpath '$ORIGIN/foobar' "
+                "$SNAPCRAFT_PART_INSTALL/usr/bin/hello")
+        })
+        self.useFixture(snapcraft_yaml)
+
+        self.run_snapcraft('prime')
+
+        bin_path = os.path.join(self.prime_dir, 'usr', 'bin', 'hello')
+        self.assertThat(bin_path, FileExists())
+
+        # Verify there aren't any duplicate rpath entries
+        readelf_d = subprocess.check_output([
+            'readelf', '-d', bin_path]).decode().strip()
+        lines_with_runpath = [l for l in readelf_d.splitlines()
+                              if 'RUNPATH' in l]
+        lines_with_rpath = [l for l in readelf_d.splitlines()
+                            if 'RPATH' in l]
+        self.assertThat(
+            len(lines_with_runpath),
+            Annotate('Expected no RUNPATH entries', Equals(0)))
+        self.assertThat(
+            len(lines_with_rpath),
+            Annotate('Expected one RPATH entry', Equals(1)))
+
 
 class ExecStackTestCase(integration.TestCase):