Exemple #1
0
    def test_architecture_options(self, mock_platform_machine,
                                  mock_platform_architecture):
        mock_platform_machine.return_value = self.machine
        mock_platform_architecture.return_value = self.architecture
        options = snapcraft.ProjectOptions()
        self.assertThat(options.arch_triplet,
                        Equals(self.expected_arch_triplet))
        self.assertThat(options.deb_arch, Equals(self.expected_deb_arch))
        self.assertThat(options.kernel_arch, Equals(self.expected_kernel_arch))

        # The core dynamic linker is correct.  Guard against stray absolute
        # paths, as they cause os.path.join to discard the previous
        # argument.
        self.assertFalse(os.path.isabs(self.expected_core_dynamic_linker))
        with mock.patch("os.path.lexists") as mock_lexists:
            mock_lexists.return_value = True
            with mock.patch("os.path.islink") as mock_islink:
                mock_islink.return_value = False
                self.assertThat(
                    options.get_core_dynamic_linker("core"),
                    Equals(
                        os.path.join(
                            common.get_core_path("core"),
                            self.expected_core_dynamic_linker,
                        )),
                )
Exemple #2
0
def _setup_core(deb_arch):
    core_path = common.get_core_path()
    if os.path.exists(core_path) and os.listdir(core_path):
        logger.debug(
            '{!r} already exists, skipping core setup'.format(core_path))
        return
    snap_cache = SnapCache(project_name='snapcraft-core')

    # Try to get the latest revision.
    core_snap = snap_cache.get(deb_arch=deb_arch)
    if core_snap:
        # The current hash matches the filename
        current_hash = os.path.splitext(os.path.basename(core_snap))[0]
    else:
        current_hash = ''

    with TemporaryDirectory() as d:
        download_path = os.path.join(d, 'core.snap')
        download_hash = snapcraft.download('core',
                                           'stable',
                                           download_path,
                                           deb_arch,
                                           except_hash=current_hash)
        if download_hash != current_hash:
            snap_cache.cache(snap_filename=download_path)
            snap_cache.prune(deb_arch=deb_arch, keep_hash=download_hash)

    core_snap = snap_cache.get(deb_arch=deb_arch)

    # Now unpack
    logger.info('Setting up {!r} in {!r}'.format(core_snap, core_path))
    if os.path.exists(core_path) and not os.listdir(core_path):
        check_call(['sudo', 'rmdir', core_path])
    check_call(['sudo', 'mkdir', '-p', os.path.dirname(core_path)])
    check_call(['sudo', 'unsquashfs', '-d', core_path, core_snap])
Exemple #3
0
    def test_architecture_options(
        self, mock_platform_machine, mock_platform_architecture
    ):
        mock_platform_machine.return_value = self.machine
        mock_platform_architecture.return_value = self.architecture
        options = snapcraft.ProjectOptions()
        self.assertThat(options.arch_triplet, Equals(self.expected_arch_triplet))
        self.assertThat(options.deb_arch, Equals(self.expected_deb_arch))
        self.assertThat(options.kernel_arch, Equals(self.expected_kernel_arch))

        # The core dynamic linker is correct.  Guard against stray absolute
        # paths, as they cause os.path.join to discard the previous
        # argument.
        self.assertFalse(os.path.isabs(self.expected_core_dynamic_linker))
        with mock.patch("os.path.lexists") as mock_lexists:
            mock_lexists.return_value = True
            with mock.patch("os.path.islink") as mock_islink:
                mock_islink.return_value = False
                self.assertThat(
                    options.get_core_dynamic_linker("core"),
                    Equals(
                        os.path.join(
                            common.get_core_path("core"),
                            self.expected_core_dynamic_linker,
                        )
                    ),
                )
Exemple #4
0
    def get_core_dynamic_linker(self):
        """Returns the dynamic linker used for the targetted core.
        If not found realpath for `/lib/ld-linux.so.2` is returned.
        However if core is not installed None will be returned.
        """
        core_path = common.get_core_path()
        dynamic_linker_path = os.path.join(
            core_path,
            self.__machine_info.get('core-dynamic-linker',
                                    'lib/ld-linux.so.2'))

        # We can't use os.path.realpath because any absolute symlinks
        # have to be interpreted relative to core_path, not the real
        # root.
        seen_paths = set()
        while True:
            if dynamic_linker_path in seen_paths:
                raise SnapcraftEnvironmentError(
                    "found symlink loop resolving dynamic linker path")

            seen_paths.add(dynamic_linker_path)
            if not os.path.lexists(dynamic_linker_path):
                return None
            if not os.path.islink(dynamic_linker_path):
                return dynamic_linker_path

            link_contents = os.readlink(dynamic_linker_path)
            if os.path.isabs(link_contents):
                dynamic_linker_path = os.path.join(
                    core_path, link_contents.lstrip('/'))
            else:
                dynamic_linker_path = os.path.join(
                    os.path.dirname(dynamic_linker_path), link_contents)
Exemple #5
0
    def get_core_dynamic_linker(self):
        """Returns the dynamic linker used for the targeted core.
        If not found realpath for `/lib/ld-linux.so.2` is returned.
        However if core is not installed None will be returned.
        """
        core_path = common.get_core_path()
        dynamic_linker_path = os.path.join(
            core_path,
            self.__machine_info.get('core-dynamic-linker',
                                    'lib/ld-linux.so.2'))

        # We can't use os.path.realpath because any absolute symlinks
        # have to be interpreted relative to core_path, not the real
        # root.
        seen_paths = set()
        while True:
            if dynamic_linker_path in seen_paths:
                raise SnapcraftEnvironmentError(
                    "found symlink loop resolving dynamic linker path")

            seen_paths.add(dynamic_linker_path)
            if not os.path.lexists(dynamic_linker_path):
                return None
            if not os.path.islink(dynamic_linker_path):
                return dynamic_linker_path

            link_contents = os.readlink(dynamic_linker_path)
            if os.path.isabs(link_contents):
                dynamic_linker_path = os.path.join(
                    core_path, link_contents.lstrip('/'))
            else:
                dynamic_linker_path = os.path.join(
                    os.path.dirname(dynamic_linker_path), link_contents)
Exemple #6
0
def _setup_core(deb_arch):
    core_path = common.get_core_path()
    if os.path.exists(core_path) and os.listdir(core_path):
        logger.debug('{!r} already exists, skipping core setup'.format(
            core_path))
        return
    snap_cache = SnapCache(project_name='snapcraft-core')

    # Try to get the latest revision.
    core_snap = snap_cache.get(deb_arch=deb_arch)
    if core_snap:
        # The current hash matches the filename
        current_hash = os.path.splitext(os.path.basename(core_snap))[0]
    else:
        current_hash = ''

    with TemporaryDirectory() as d:
        download_path = os.path.join(d, 'core.snap')
        download_hash = snapcraft.download('core', 'stable', download_path,
                                           deb_arch, except_hash=current_hash)
        if download_hash != current_hash:
            snap_cache.cache(snap_filename=download_path)
            snap_cache.prune(deb_arch=deb_arch, keep_hash=download_hash)

    core_snap = snap_cache.get(deb_arch=deb_arch)

    # Now unpack
    logger.info('Setting up {!r} in {!r}'.format(core_snap, core_path))
    if os.path.exists(core_path) and not os.listdir(core_path):
        check_call(['sudo', 'rmdir', core_path])
    check_call(['sudo', 'mkdir', '-p', os.path.dirname(core_path)])
    check_call(['sudo', 'unsquashfs', '-d', core_path, core_snap])
Exemple #7
0
    def prime(self, force=False) -> None:
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        # TODO: base snap support
        core_path = common.get_core_path()

        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(root_path=self.primedir,
                                           core_base_path=core_path))

        # Split the necessary dependencies into their corresponding location.
        # We'll both migrate and track the system dependencies, but we'll only
        # track the part and staged dependencies, since they should have
        # already been primed by other means, and migrating them again could
        # potentially override the `stage` or `snap` filtering.
        (in_part, staged, primed, system) = _split_dependencies(
            all_dependencies, self.installdir, self.stagedir, self.primedir)

        part_dependency_paths = {os.path.dirname(d) for d in in_part}
        staged_dependency_paths = {os.path.dirname(d) for d in staged}

        dependency_paths = part_dependency_paths | staged_dependency_paths

        if not self._build_attributes.no_system_libraries():
            system_dependency_paths = {os.path.dirname(d) for d in system}
            dependency_paths.update(system_dependency_paths)

            if system:
                # Lots of dependencies are linked with a symlink, so we need to
                # make sure we follow those symlinks when we migrate the
                # dependencies.
                _migrate_files(system, system_dependency_paths, '/',
                               self.primedir, follow_symlinks=True)
                formatted_system = '\n'.join(sorted(system))
                logger.warning(
                    'Files from the build host were migrated into the snap to '
                    'satisfy dependencies that would otherwise not be met. '
                    'This feature will be removed in a future release. If '
                    'these libraries are needed in the final snap, ensure '
                    'that the following are either satisfied by a '
                    'stage-packages entry or through a part:\n{}'.format(
                        formatted_system))

        if self._confinement == 'classic':
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker,
                                      root_path=self.primedir)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
Exemple #8
0
def env_for_classic(arch_triplet: str) -> List[str]:
    """Set the required environment variables for a classic confined build."""
    env = []

    core_path = common.get_core_path()
    paths = common.get_library_paths(core_path, arch_triplet,
                                     existing_only=False)
    env.append(formatting_utils.format_path_variable(
        'LD_LIBRARY_PATH', paths, prepend='', separator=':'))

    return env
Exemple #9
0
def env_for_classic(base: str, arch_triplet: str) -> List[str]:
    """Set the required environment variables for a classic confined build."""
    env = []

    core_path = common.get_core_path(base)
    paths = common.get_library_paths(core_path, arch_triplet, existing_only=False)
    env.append(
        formatting_utils.format_path_variable(
            "LD_LIBRARY_PATH", paths, prepend="", separator=":"
        )
    )

    return env
Exemple #10
0
def _build_env(root, snap_name, confinement, arch_triplet,
               core_dynamic_linker=None):
    """Set the environment variables required for building.

    This is required for the current parts installdir due to stage-packages
    and also to setup the stagedir.
    """
    env = []

    paths = common.get_include_paths(root, arch_triplet)
    if paths:
        for envvar in ['CPPFLAGS', 'CFLAGS', 'CXXFLAGS']:
            env.append(formatting_utils.format_path_variable(
                envvar, paths, prepend='-I', separator=' '))

    if confinement == 'classic':
        if not core_dynamic_linker:
            raise EnvironmentError(
                'classic confinement requires the core snap to be installed. '
                'Install it by running `snap install core`.')

        core_path = common.get_core_path()
        core_rpaths = common.get_library_paths(core_path, arch_triplet,
                                               existing_only=False)
        snap_path = os.path.join('/snap', snap_name, 'current')
        snap_rpaths = common.get_library_paths(snap_path, arch_triplet,
                                               existing_only=False)

        # snap_rpaths before core_rpaths to prefer libraries from the snap.
        rpaths = formatting_utils.combine_paths(
            snap_rpaths + core_rpaths, prepend='', separator=':')
        env.append('LDFLAGS="$LDFLAGS '
                   # Building tools to continue the build becomes problematic
                   # with nodefaultlib.
                   # '-Wl,-z,nodefaultlib '
                   '-Wl,--dynamic-linker={0} '
                   '-Wl,-rpath,{1}"'.format(core_dynamic_linker, rpaths))

    paths = common.get_library_paths(root, arch_triplet)
    if paths:
        env.append(formatting_utils.format_path_variable(
            'LDFLAGS', paths, prepend='-L', separator=' '))

    paths = common.get_pkg_config_paths(root, arch_triplet)
    if paths:
        env.append(formatting_utils.format_path_variable(
            'PKG_CONFIG_PATH', paths, prepend='', separator=':'))

    return env
Exemple #11
0
def _build_env(root, snap_name, confinement, arch_triplet,
               core_dynamic_linker=None):
    """Set the environment variables required for building.

    This is required for the current parts installdir due to stage-packages
    and also to setup the stagedir.
    """
    env = []

    paths = common.get_include_paths(root, arch_triplet)
    if paths:
        for envvar in ['CPPFLAGS', 'CFLAGS', 'CXXFLAGS']:
            env.append(formatting_utils.format_path_variable(
                envvar, paths, prepend='-I', separator=' '))

    if confinement == 'classic':
        if not core_dynamic_linker:
            raise EnvironmentError(
                'classic confinement requires the core snap to be installed. '
                'Install it by running `snap install core`.')

        core_path = common.get_core_path()
        core_rpaths = common.get_library_paths(core_path, arch_triplet,
                                               existing_only=False)
        snap_path = os.path.join('/snap', snap_name, 'current')
        snap_rpaths = common.get_library_paths(snap_path, arch_triplet,
                                               existing_only=False)

        # snap_rpaths before core_rpaths to prefer libraries from the snap.
        rpaths = formatting_utils.combine_paths(
            snap_rpaths + core_rpaths, prepend='', separator=':')
        env.append('LDFLAGS="$LDFLAGS '
                   # Building tools to continue the build becomes problematic
                   # with nodefaultlib.
                   # '-Wl,-z,nodefaultlib '
                   '-Wl,--dynamic-linker={0} '
                   '-Wl,-rpath,{1}"'.format(core_dynamic_linker, rpaths))

    paths = common.get_library_paths(root, arch_triplet)
    if paths:
        env.append(formatting_utils.format_path_variable(
            'LDFLAGS', paths, prepend='-L', separator=' '))

    paths = common.get_pkg_config_paths(root, arch_triplet)
    if paths:
        env.append(formatting_utils.format_path_variable(
            'PKG_CONFIG_PATH', paths, prepend='', separator=':'))

    return env
Exemple #12
0
    def get_core_dynamic_linker(self, base: str, expand: bool = True) -> str:
        """Returns the dynamic linker used for the targeted core.

        :param str base: the base core snap to search for linker.
        :param bool expand: expand the linker to the actual linker if True,
                            else the main entry point to the linker for the
                            projects architecture.
        :return: the absolute path to the linker
        :rtype: str
        :raises snapcraft.internal.errors.SnapcraftMissingLinkerInBaseError:
            if the linker cannot be found in the base.
        :raises snapcraft.internal.errors.SnapcraftEnvironmentError:
            if a loop is found while resolving the real path to the linker.
        """
        core_path = common.get_core_path(base)
        dynamic_linker_path = os.path.join(
            core_path,
            self.__machine_info.get("core-dynamic-linker", "lib/ld-linux.so.2"),
        )

        # return immediately if we do not need to expand
        if not expand:
            return dynamic_linker_path

        # We can't use os.path.realpath because any absolute symlinks
        # have to be interpreted relative to core_path, not the real
        # root.
        seen_paths = set()  # type: Set[str]
        while True:
            if dynamic_linker_path in seen_paths:
                raise errors.SnapcraftEnvironmentError(
                    "found symlink loop resolving dynamic linker path"
                )

            seen_paths.add(dynamic_linker_path)
            if not os.path.lexists(dynamic_linker_path):
                raise errors.SnapcraftMissingLinkerInBaseError(
                    base=base, linker_path=dynamic_linker_path
                )
            if not os.path.islink(dynamic_linker_path):
                return dynamic_linker_path

            link_contents = os.readlink(dynamic_linker_path)
            if os.path.isabs(link_contents):
                dynamic_linker_path = os.path.join(core_path, link_contents.lstrip("/"))
            else:
                dynamic_linker_path = os.path.join(
                    os.path.dirname(dynamic_linker_path), link_contents
                )
Exemple #13
0
    def _handle_elf(self, snap_files: Sequence[str]) -> Set[str]:
        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        # TODO: base snap support
        core_path = common.get_core_path(self._base)

        # Clear the cache of all libs that aren't already in the primedir
        self._soname_cache.reset_except_root(self.primedir)
        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(
                    root_path=self.primedir,
                    core_base_path=core_path,
                    soname_cache=self._soname_cache,
                )
            )

        dependency_paths = self._handle_dependencies(all_dependencies)

        if not self._build_attributes.keep_execstack():
            clear_execstack(elf_files=elf_files)

        if self._build_attributes.no_patchelf():
            logger.warning(
                "The primed files for part {!r} will not be verified for "
                "correctness or patched: build-attributes: [no-patchelf] "
                "is set.".format(self.name)
            )
        else:
            part_patcher = PartPatcher(
                elf_files=elf_files,
                plugin=self.plugin,
                project=self._project_options,
                confinement=self._confinement,
                core_base=self._base,
                snap_base_path=self._snap_base_path,
                stagedir=self.stagedir,
                primedir=self.primedir,
                stage_packages=self._part_properties.get("stage-packages", []),
            )
            part_patcher.patch()

        return dependency_paths
Exemple #14
0
    def _handle_elf(self, snap_files: Sequence[str]) -> Set[str]:
        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        core_path = common.get_core_path(self._base)

        # Clear the cache of all libs that aren't already in the primedir
        self._soname_cache.reset_except_root(self.primedir)
        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(
                    root_path=self.primedir,
                    core_base_path=core_path,
                    soname_cache=self._soname_cache,
                )
            )

        dependency_paths = self._handle_dependencies(all_dependencies)

        if not self._build_attributes.keep_execstack():
            clear_execstack(elf_files=elf_files)

        if self._build_attributes.no_patchelf():
            logger.warning(
                "The primed files for part {!r} will not be verified for "
                "correctness or patched: build-attributes: [no-patchelf] "
                "is set.".format(self.name)
            )
        else:
            part_patcher = PartPatcher(
                elf_files=elf_files,
                plugin=self.plugin,
                project=self._project_options,
                confinement=self._confinement,
                core_base=self._base,
                snap_base_path=self._snap_base_path,
                stagedir=self.stagedir,
                primedir=self.primedir,
                stage_packages=self._part_properties.get("stage-packages", []),
            )
            part_patcher.patch()

        return dependency_paths
Exemple #15
0
    def build(self):
        super().build()

        self.run(["shards", "install", "--production"], self.builddir)
        self.run(["shards", "build", "--production"], self.builddir)

        output_bin = os.path.join(self.builddir, "bin")
        if not os.path.exists(output_bin):
            raise errors.SnapcraftEnvironmentError(
                "No binaries were built. Ensure the shards.yaml contains valid targets."
            )

        install_bin_path = os.path.join(self.installdir, "bin")

        bin_paths = (os.path.join(output_bin, b)
                     for b in os.listdir(output_bin))
        elf_files = (elf.ElfFile(path=b) for b in bin_paths
                     if elf.ElfFile.is_elf(b))

        os.makedirs(install_bin_path, exist_ok=True)

        for elf_file in elf_files:
            shutil.copy2(
                elf_file.path,
                os.path.join(install_bin_path,
                             os.path.basename(elf_file.path)),
            )

            elf_dependencies_path = elf_file.load_dependencies(
                root_path=self.installdir,
                core_base_path=common.get_core_path(
                    self.project.info.get_build_base()),
            )
            for elf_dependency_path in elf_dependencies_path:
                lib_install_path = os.path.join(self.installdir,
                                                elf_dependency_path[1:])
                os.makedirs(os.path.dirname(lib_install_path), exist_ok=True)
                if not os.path.exists(lib_install_path):
                    file_utils.link_or_copy(elf_dependency_path,
                                            lib_install_path,
                                            follow_symlinks=True)
Exemple #16
0
def _setup_core(deb_arch, base):
    core_path = common.get_core_path(base)
    if os.path.exists(core_path) and os.listdir(core_path):
        logger.debug(
            "{!r} already exists, skipping core setup".format(core_path))
        return

    # for backwards compatibility
    if base == "core":
        snap_cache = SnapCache(project_name="snapcraft-core")
    else:
        snap_cache = SnapCache(project_name=base)

    # Try to get the latest revision.
    core_snap = snap_cache.get(deb_arch=deb_arch)
    if core_snap:
        # The current hash matches the filename
        current_hash = os.path.splitext(os.path.basename(core_snap))[0]
    else:
        current_hash = ""

    with TemporaryDirectory() as d:
        download_path = os.path.join(d, "{}.snap".format(base))
        download_hash = snapcraft.download(base,
                                           "stable",
                                           download_path,
                                           deb_arch,
                                           except_hash=current_hash)
        if download_hash != current_hash:
            snap_cache.cache(snap_filename=download_path)
            snap_cache.prune(deb_arch=deb_arch, keep_hash=download_hash)

    core_snap = snap_cache.get(deb_arch=deb_arch)

    # Now unpack
    logger.info("Setting up {!r} in {!r}".format(core_snap, core_path))
    if os.path.exists(core_path) and not os.listdir(core_path):
        check_call(["sudo", "rmdir", core_path])
    check_call(["sudo", "mkdir", "-p", os.path.dirname(core_path)])
    unsquashfs_path = snapcraft.file_utils.get_tool_path("unsquashfs")
    check_call(["sudo", unsquashfs_path, "-d", core_path, core_snap])
Exemple #17
0
def _setup_core(deb_arch, base):
    core_path = common.get_core_path(base)
    if os.path.exists(core_path) and os.listdir(core_path):
        logger.debug("{!r} already exists, skipping core setup".format(core_path))
        return

    # for backwards compatibility
    if base == "core":
        snap_cache = SnapCache(project_name="snapcraft-core")
    else:
        snap_cache = SnapCache(project_name=base)

    # Try to get the latest revision.
    core_snap = snap_cache.get(deb_arch=deb_arch)
    if core_snap:
        # The current hash matches the filename
        current_hash = os.path.splitext(os.path.basename(core_snap))[0]
    else:
        current_hash = ""

    with TemporaryDirectory() as d:
        download_path = os.path.join(d, "{}.snap".format(base))
        download_hash = snapcraft.download(
            base, "stable", download_path, deb_arch, except_hash=current_hash
        )
        if download_hash != current_hash:
            snap_cache.cache(snap_filename=download_path)
            snap_cache.prune(deb_arch=deb_arch, keep_hash=download_hash)

    core_snap = snap_cache.get(deb_arch=deb_arch)

    # Now unpack
    logger.info("Setting up {!r} in {!r}".format(core_snap, core_path))
    if os.path.exists(core_path) and not os.listdir(core_path):
        check_call(["sudo", "rmdir", core_path])
    check_call(["sudo", "mkdir", "-p", os.path.dirname(core_path)])
    unsquashfs_path = snapcraft.file_utils.get_tool_path("unsquashfs")
    check_call(["sudo", unsquashfs_path, "-d", core_path, core_snap])
Exemple #18
0
    def prime(self, force=False) -> None:
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        for elf_file in elf_files:
            all_dependencies.update(elf_file.load_dependencies())

        # Split the necessary dependencies into their corresponding location.
        # We'll both migrate and track the system dependencies, but we'll only
        # track the part and staged dependencies, since they should have
        # already been primed by other means, and migrating them again could
        # potentially override the `stage` or `snap` filtering.
        (in_part, staged, primed, system) = _split_dependencies(
            all_dependencies, self.installdir, self.stagedir, self.primedir)

        part_dependency_paths = {os.path.dirname(d) for d in in_part}
        staged_dependency_paths = {os.path.dirname(d) for d in staged}

        dependency_paths = part_dependency_paths | staged_dependency_paths

        if not self._build_attributes.no_system_libraries():
            system_dependency_paths = {os.path.dirname(d) for d in system}
            dependency_paths.update(system_dependency_paths)

            if system:
                # Lots of dependencies are linked with a symlink, so we need to
                # make sure we follow those symlinks when we migrate the
                # dependencies.
                _migrate_files(system, system_dependency_paths, '/',
                               self.primedir, follow_symlinks=True)

        # TODO: base snap support
        core_path = common.get_core_path()

        def mangle_library_path(library_path: str, elf_file_path: str) -> str:
            # If the path is is in the core snap, use the absolute path,
            # if the path is primed, use $ORIGIN, and last if the dependency
            # is not anywhere return an empty string.
            #
            # Once we move away from the system library grabbing logic
            # we can move to a smarter library capturing mechanism.
            library_path = library_path.replace(self.installdir, self.primedir)
            if library_path.startswith(core_path):
                return os.path.dirname(library_path)
            elif (library_path.startswith(self.primedir) and
                  os.path.exists(library_path)):
                rel_library_path = os.path.relpath(library_path, elf_file_path)
                rel_library_path_dir = os.path.dirname(rel_library_path)
                # return the dirname, with the first .. replace with $ORIGIN
                return rel_library_path_dir.replace('..', '$ORIGIN', 1)
            else:
                return ''

        if self._confinement == 'classic':
            core_rpaths = common.get_library_paths(
                core_path, self._project_options.arch_triplet,
                existing_only=False)
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker,
                                      library_path_func=mangle_library_path,
                                      base_rpaths=core_rpaths)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
Exemple #19
0
    def prime(self, force=False) -> None:  # noqa: C901
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        # TODO: base snap support
        core_path = common.get_core_path()

        # Reset to take into account new data inside prime provided by other
        # parts.
        self._soname_cache.reset()
        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(root_path=self.primedir,
                                           core_base_path=core_path,
                                           soname_cache=self._soname_cache))

        dependency_paths = self._handle_dependencies(all_dependencies)

        if not self._build_attributes.keep_execstack():
            clear_execstack(elf_files=elf_files)

        # TODO revisit if we need to support variations and permutations
        #  of this
        staged_patchelf_path = os.path.join(self.stagedir, 'bin', 'patchelf')
        if not os.path.exists(staged_patchelf_path):
            staged_patchelf_path = None
        # We need to verify now that the GLIBC version would be compatible
        # with that of the base.
        # TODO the linker version depends on the chosen base, but that
        # base may not be installed so we cannot depend on
        # get_core_dynamic_linker to resolve the final path for which
        # we resort to our only working base 16, ld-2.23.so.
        linker_incompat = dict()  # type: Dict[str, str]
        for elf_file in elf_files:
            if not elf_file.is_linker_compatible(linker='ld-2.23.so'):
                linker_incompat[elf_file.path] = elf_file.get_required_glibc()
        # If libc6 is staged, to avoid symbol mixups we will resort to
        # glibc mangling.
        libc6_staged = 'libc6' in self._part_properties.get(
            'stage-packages', [])
        is_classic = self._confinement == 'classic'
        # classic confined snaps built on anything but a host supporting the
        # the target base will require glibc mangling.
        classic_mangling_needed = (
            is_classic
            and not self._project_options.is_host_compatible_with_base)
        if linker_incompat:
            formatted_items = [
                '- {} (requires GLIBC {})'.format(k, v)
                for k, v in linker_incompat.items()
            ]
            logger.warning(
                'The GLIBC version of the targeted core is 2.23. A newer '
                'libc will be required for the following files:\n{}'.format(
                    '\n'.join(formatted_items)))
        if (linker_incompat or libc6_staged or classic_mangling_needed):
            if not libc6_staged:
                raise errors.StagePackageMissingError(package='libc6')
            handle_glibc_mismatch(elf_files=elf_files,
                                  root_path=self.primedir,
                                  snap_base_path=self._snap_base_path,
                                  core_base_path=core_path,
                                  preferred_patchelf_path=staged_patchelf_path,
                                  soname_cache=self._soname_cache)
        elif is_classic:
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(
                dynamic_linker=dynamic_linker,
                root_path=self.primedir,
                preferred_patchelf_path=staged_patchelf_path)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
Exemple #20
0
    def prime(self, force=False) -> None:
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        # TODO: base snap support
        core_path = common.get_core_path()

        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(root_path=self.primedir,
                                           core_base_path=core_path))

        # Split the necessary dependencies into their corresponding location.
        # We'll both migrate and track the system dependencies, but we'll only
        # track the part and staged dependencies, since they should have
        # already been primed by other means, and migrating them again could
        # potentially override the `stage` or `snap` filtering.
        (in_part, staged, primed,
         system) = _split_dependencies(all_dependencies, self.installdir,
                                       self.stagedir, self.primedir)

        part_dependency_paths = {os.path.dirname(d) for d in in_part}
        staged_dependency_paths = {os.path.dirname(d) for d in staged}

        dependency_paths = part_dependency_paths | staged_dependency_paths

        if not self._build_attributes.no_system_libraries():
            system_dependency_paths = {os.path.dirname(d) for d in system}
            dependency_paths.update(system_dependency_paths)

            if system:
                # Lots of dependencies are linked with a symlink, so we need to
                # make sure we follow those symlinks when we migrate the
                # dependencies.
                _migrate_files(system,
                               system_dependency_paths,
                               '/',
                               self.primedir,
                               follow_symlinks=True)
                formatted_system = '\n'.join(sorted(system))
                logger.warning(
                    'Files from the build host were migrated into the snap to '
                    'satisfy dependencies that would otherwise not be met. '
                    'This feature will be removed in a future release. If '
                    'these libraries are needed in the final snap, ensure '
                    'that the following are either satisfied by a '
                    'stage-packages entry or through a part:\n{}'.format(
                        formatted_system))

        # TODO revisit if we need to support variations and permutations
        #  of this
        staged_patchelf_path = os.path.join(self.stagedir, 'bin', 'patchelf')
        if not os.path.exists(staged_patchelf_path):
            staged_patchelf_path = None
        # We need to verify now that the GLIBC version would be compatible
        # with that of the base.
        # TODO the linker version depends on the chosen base, but that
        # base may not be installed so we cannot depend on
        # get_core_dynamic_linker to resolve the final path for which
        # we resort to our only working base 16, ld-2.23.so.
        linker_compatible = (e.is_linker_compatible(linker='ld-2.23.so')
                             for e in elf_files)
        if not all((x for x in linker_compatible)):
            handle_glibc_mismatch(elf_files=elf_files,
                                  root_path=self.primedir,
                                  snap_base_path=self._snap_base_path,
                                  core_base_path=core_path,
                                  preferred_patchelf_path=staged_patchelf_path)
        elif self._confinement == 'classic':
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(
                dynamic_linker=dynamic_linker,
                root_path=self.primedir,
                preferred_patchelf_path=staged_patchelf_path)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)