Esempio n. 1
0
    def _crawl_for_path(self) -> Path:
        # Speed things up and return what was already found once.
        if (self.arch_tuple, self.soname) in self.soname_cache:
            return self.soname_cache[self.arch_tuple, self.soname]

        emit.debug(f"Crawling to find soname {self.soname!r}")

        valid_search_paths = [p for p in self.search_paths if p.exists()]
        in_search_paths = any(p in self.soname_path.parents
                              for p in valid_search_paths)

        # Expedite path crawling if we have a valid elf file that lives
        # inside the search paths.
        if in_search_paths and self._is_valid_elf(self.soname_path):
            self._update_soname_cache(self.soname_path)
            return self.soname_path

        for path in valid_search_paths:
            for root, _, files in os.walk(path):
                if self.soname not in files:
                    continue

                file_path = Path(root, self.soname.lstrip("/"))
                if self._is_valid_elf(file_path):
                    self._update_soname_cache(file_path)
                    return file_path

        # Required for libraries on the host and the fetching mechanism.
        self._update_soname_cache(self.soname_path)
        return self.soname_path
Esempio n. 2
0
    def __init__(self, *, path: Path) -> None:
        """Initialize an ElfFile instance.

        :param str path: path to an elf_file within a snapcraft project.
        """
        self.path = path
        self.dependencies: Set[_Library] = set()

        self.arch_tuple: Optional[_ElfArchitectureTuple] = None
        self.interp = ""
        self.soname = ""
        self.versions: Set[str] = set()
        self.needed: Dict[str, _NeededLibrary] = {}
        self.execstack_set = False
        self.is_dynamic = True
        self.build_id = ""
        self.has_debug_info: bool = False

        self._required_glibc = ""

        # String of elf enum type, e.g. "ET_DYN", "ET_EXEC", etc.
        self.elf_type: str = "ET_NONE"

        try:
            emit.debug(f"Extracting ELF attributes: {str(path)!r}")
            self._extract_attributes()
        except (UnicodeDecodeError, AttributeError,
                ConstructError) as exception:
            emit.debug(
                f"Extracting ELF attributes exception: {str(exception)}")
            raise errors.CorruptedElfFile(path, exception)
Esempio n. 3
0
    def install_package_repository_sources(
        self,
        *,
        package_repo: package_repository.PackageRepository,
    ) -> bool:
        """Install configured package repositories.

        :param package_repo: Repository to install the source configuration for.

        :returns: True if source configuration was changed.
        """
        emit.debug(f"Processing repo: {package_repo!r}")
        if isinstance(package_repo,
                      package_repository.PackageRepositoryAptPPA):
            return self._install_sources_ppa(package_repo=package_repo)

        if isinstance(package_repo, package_repository.PackageRepositoryApt):
            changed = self._install_sources_apt(package_repo=package_repo)
            architectures = cast(package_repository.PackageRepositoryApt,
                                 package_repo).architectures
            if changed and architectures:
                _add_architecture(architectures)
            return changed

        raise RuntimeError(
            f"unhandled package repository: {package_repository!r}")
Esempio n. 4
0
    def __init__(
        self,
        *,
        soname: str,
        soname_path: Path,
        search_paths: List[Path],
        base_path: Optional[Path],
        arch_tuple: _ElfArchitectureTuple,
        soname_cache: SonameCache,
    ) -> None:

        self.soname = soname
        self.soname_path = soname_path
        self.search_paths = search_paths
        self.base_path = base_path
        self.arch_tuple = arch_tuple
        self.soname_cache = soname_cache

        # Resolve path, if possible.
        self.path = self._crawl_for_path()

        if base_path is not None and base_path in self.path.parents:
            self.in_base_snap = True
        else:
            self.in_base_snap = False

        emit.debug(
            f"{soname} with original path {soname_path} found on {str(self.path)!r} "
            f"in base {self.in_base_snap!r}")
Esempio n. 5
0
    def install_key(self, *, key: str) -> None:
        """Install given key.

        :param key: Key to install.

        :raises: AptGPGKeyInstallError if unable to install key.
        """
        cmd = [
            "apt-key",
            "--keyring",
            str(self._gpg_keyring),
            "add",
            "-",
        ]

        try:
            emit.debug(f"Executing: {cmd!r}")
            env = {}
            env["LANG"] = "C.UTF-8"
            subprocess.run(
                cmd,
                input=key.encode(),
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                check=True,
                env=env,
            )
        except subprocess.CalledProcessError as error:
            raise errors.AptGPGKeyInstallError(error.output.decode(), key=key)

        emit.debug(f"Installed apt repository key:\n{key}")
Esempio n. 6
0
def get_os_platform(filepath=pathlib.Path("/etc/os-release")):
    """Determine a system/release combo for an OS using /etc/os-release if available."""
    system = platform.system()
    release = platform.release()
    machine = platform.machine()

    if system == "Linux":
        try:
            with filepath.open("rt", encoding="utf-8") as release_file:
                lines = release_file.readlines()
        except FileNotFoundError:
            emit.debug(
                "Unable to locate 'os-release' file, using default values")
        else:
            os_release = {}
            for line in lines:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                key, value = line.rstrip().split("=", 1)
                if value[0] == value[-1] and value[0] in ('"', "'"):
                    value = value[1:-1]
                os_release[key] = value
            system = os_release.get("ID", system)
            release = os_release.get("VERSION_ID", release)

    return OSPlatform(system=system, release=release, machine=machine)
Esempio n. 7
0
def _run_in_provider(project: Project, command_name: str,
                     parsed_args: "argparse.Namespace") -> None:
    """Pack image in provider instance."""
    emit.debug("Checking build provider availability")
    provider_name = "lxd" if parsed_args.use_lxd else None
    provider = providers.get_provider(provider_name)
    provider.ensure_provider_is_available()

    cmd = ["snapcraft", command_name]

    if hasattr(parsed_args, "parts"):
        cmd.extend(parsed_args.parts)

    if getattr(parsed_args, "output", None):
        cmd.extend(["--output", parsed_args.output])

    if emit.get_mode() == EmitterMode.VERBOSE:
        cmd.append("--verbose")
    elif emit.get_mode() == EmitterMode.QUIET:
        cmd.append("--quiet")
    elif emit.get_mode() == EmitterMode.DEBUG:
        cmd.append("--verbosity=debug")
    elif emit.get_mode() == EmitterMode.TRACE:
        cmd.append("--verbosity=trace")

    if parsed_args.debug:
        cmd.append("--debug")
    if getattr(parsed_args, "shell", False):
        cmd.append("--shell")
    if getattr(parsed_args, "shell_after", False):
        cmd.append("--shell-after")

    if getattr(parsed_args, "enable_manifest", False):
        cmd.append("--enable-manifest")
    build_information = getattr(parsed_args, "manifest_build_information",
                                None)
    if build_information:
        cmd.append("--manifest-build-information")
        cmd.append(build_information)

    output_dir = utils.get_managed_environment_project_path()

    emit.progress("Launching instance...")
    with provider.launched_environment(
            project_name=project.name,
            project_path=Path().absolute(),
            base=project.get_effective_base(),
            bind_ssh=parsed_args.bind_ssh,
            build_on=get_host_architecture(),
            build_for=get_host_architecture(),
    ) as instance:
        try:
            with emit.pause():
                instance.execute_run(cmd, check=True, cwd=output_dir)
            capture_logs_from_instance(instance)
        except subprocess.CalledProcessError as err:
            capture_logs_from_instance(instance)
            raise providers.ProviderError(
                f"Failed to execute {command_name} in instance.") from err
Esempio n. 8
0
def _copy_file(source: Path, destination: Path, **kwargs) -> None:
    """Copy file if source and destination are not the same file."""
    if destination.exists() and source.samefile(destination):
        emit.debug(
            f"skip copying {str(source)!r}: source and destination are the same file"
        )
    else:
        shutil.copy(source, destination, **kwargs)
Esempio n. 9
0
    def run(self, parsed_args):
        """Run the command."""
        if not self.name:
            raise RuntimeError("command name not specified")

        emit.debug(
            f"lifecycle command: {self.name!r}, arguments: {parsed_args!r}")
        parts_lifecycle.run(self.name, parsed_args)
Esempio n. 10
0
 def is_linker_compatible(self, *, linker_version: str) -> bool:
     """Determine if the linker will work given the required glibc version."""
     version_required = self.get_required_glibc()
     is_compatible = parse_version(version_required) <= parse_version(
         linker_version)
     emit.debug(
         f"Check if linker {linker_version!r} works with GLIBC_{version_required} "
         f"required by {str(self.path)!r}: {is_compatible}")
     return is_compatible
Esempio n. 11
0
    def load_dependencies(
        self,
        root_path: Path,
        base_path: Optional[Path],
        content_dirs: Set[Path],
        arch_triplet: str,
        soname_cache: Optional[SonameCache] = None,
    ) -> Set[str]:
        """Load the set of libraries that are needed to satisfy elf's runtime.

        This may include libraries contained within the project.
        The object's .dependencies attribute is set after loading.

        :param root_path: the root path to search for missing dependencies.
        :param base_path: the core base path to search for missing dependencies.
        :param soname_cache: a cache of previously search dependencies.

        :returns: a set of string with paths to the library dependencies of elf.
        """
        if soname_cache is None:
            soname_cache = SonameCache()

        emit.debug(f"Getting dependencies for {str(self.path)!r}")

        search_paths = [root_path, *content_dirs]
        if base_path is not None:
            search_paths.append(base_path)

        ld_library_paths: List[str] = []
        for path in search_paths:
            ld_library_paths.extend(
                utils.get_common_ld_library_paths(path, arch_triplet))

        libraries = _determine_libraries(path=self.path,
                                         ld_library_paths=ld_library_paths,
                                         arch_triplet=arch_triplet)
        for soname, soname_path in libraries.items():
            if self.arch_tuple is None:
                raise RuntimeError("failed to parse architecture")

            self.dependencies.add(
                _Library(
                    soname=soname,
                    soname_path=Path(soname_path),
                    search_paths=search_paths,
                    base_path=base_path,
                    arch_tuple=self.arch_tuple,
                    soname_cache=soname_cache,
                ))

        # Return the set of dependency paths, minus those found in the base.
        dependencies: Set[str] = set()
        for library in self.dependencies:
            if not library.in_base_snap:
                dependencies.add(str(library.path))
        return dependencies
Esempio n. 12
0
def pack_snap(
    directory: Path,
    *,
    output: Optional[str],
    compression: Optional[str] = None,
    name: Optional[str] = None,
    version: Optional[str] = None,
    target_arch: Optional[str] = None,
) -> str:
    """Pack snap contents with `snap pack`.

    `output` may either be a directory, a file path, or just a file name.
      - directory: write snap to directory with default snap name
      - file path: write snap to specified directory with specified snap name
      - file name: write snap to cwd with specified snap name

    If name, version, and target architecture are not specified, then snap
    will use its default naming convention.

    :param directory: Directory to pack.
    :param output: Snap file name or directory.
    :param compression: Compression type to use, None for defaults.
    :param name: Name of snap project.
    :param version: Version of snap project.
    :param target_arch: Target architecture the snap project is built to.
    """
    emit.debug(f"pack_snap: output={output!r}, compression={compression!r}")

    # TODO remove workaround once LP: #1950465 is fixed
    _verify_snap(directory)

    # create command formatted as `snap pack <options> <snap-dir> <output-dir>`
    command: List[Union[str, Path]] = ["snap", "pack"]
    output_file = _get_filename(output, name, version, target_arch)
    if output_file is not None:
        command.extend(["--filename", output_file])
    if compression is not None:
        command.extend(["--compression", compression])
    command.append(directory)
    command.append(_get_directory(output))

    emit.progress("Creating snap package...")
    emit.debug(f"Pack command: {command}")
    try:
        proc = subprocess.run(command,
                              capture_output=True,
                              check=True,
                              universal_newlines=True)
    except subprocess.CalledProcessError as err:
        msg = f"Cannot pack snap file: {err!s}"
        if err.stderr:
            msg += f" ({err.stderr.strip()!s})"
        raise errors.SnapcraftError(msg)

    snap_filename = Path(str(proc.stdout).partition(":")[2].strip()).name
    return snap_filename
Esempio n. 13
0
def update_project_metadata(
    project: Project,
    *,
    project_vars: Dict[str, str],
    metadata_list: List[ExtractedMetadata],
    assets_dir: Path,
    prime_dir: Path,
) -> None:
    """Set project fields using corresponding adopted entries.

    Fields are validated on assignment by pydantic.

    :param project: The project to update.
    :param project_vars: The variables updated during lifecycle execution.
    :param metadata_list: List containing parsed information from metadata files.

    :raises SnapcraftError: If project update failed.
    """
    _update_project_variables(project, project_vars)

    for metadata in metadata_list:
        # Data specified in the project yaml has precedence over extracted data
        if metadata.title and not project.title:
            project.title = metadata.title

        if metadata.summary and not project.summary:
            project.summary = metadata.summary

        if metadata.description and not project.description:
            project.description = metadata.description

        if metadata.version and not project.version:
            project.version = metadata.version

        if metadata.grade and not project.grade:
            project.grade = metadata.grade  # type: ignore

        emit.debug(f"project icon: {project.icon!r}")
        emit.debug(f"metadata icon: {metadata.icon!r}")

        if not project.icon:
            _update_project_icon(project,
                                 metadata=metadata,
                                 assets_dir=assets_dir)

        _update_project_app_desktop_file(project,
                                         metadata=metadata,
                                         assets_dir=assets_dir,
                                         prime_dir=prime_dir)

    # Fields that must not end empty
    for field in MANDATORY_ADOPTABLE_FIELDS:
        if not getattr(project, field):
            raise errors.SnapcraftError(
                f"Field {field!r} was not adopted from metadata")
Esempio n. 14
0
def _check_output(cmd: List[str], *, extra_env: Dict[str, str]) -> str:
    env = os.environ.copy()
    env.update(extra_env)

    debug_cmd = [f"{k}={v}" for k, v in extra_env.items()]
    debug_cmd += cmd

    emit.debug(f"executing: {' '.join(debug_cmd)}")
    output = subprocess.check_output(cmd, env=env).decode()

    return output
Esempio n. 15
0
def _finalize_icon(
    icon: Optional[str], *, assets_dir: Path, gui_dir: Path, prime_dir: Path
) -> Optional[Path]:
    """Ensure sure icon is properly configured and installed.

    Fetch from a remote URL, if required, and place in the meta/gui
    directory.
    """
    emit.debug(f"finalize icon: {icon!r}")

    # Nothing to do if no icon is configured, search for existing icon.
    if icon is None:
        return _find_icon_file(assets_dir)

    # Extracted appstream icon paths will either:
    # (1) point to a file relative to prime
    # (2) point to a remote http(s) url
    #
    # The 'icon' specified in the snapcraft.yaml has the same
    # constraint as (2) and would have already been validated
    # as existing by the schema.  So we can treat it the same
    # at this point, regardless of the source of the icon.
    parsed_url = urllib.parse.urlparse(icon)
    parsed_path = Path(parsed_url.path)
    icon_ext = parsed_path.suffix[1:]
    target_icon_path = Path(gui_dir, f"icon.{icon_ext}")

    target_icon_path.parent.mkdir(parents=True, exist_ok=True)
    if parsed_url.scheme in ["http", "https"]:
        # Remote - fetch URL and write to target.
        emit.progress(f"Fetching icon from {icon!r}")
        icon_data = requests.get(icon).content
        target_icon_path.write_bytes(icon_data)
    elif parsed_url.scheme == "":
        source_path = Path(
            prime_dir,
            parsed_path.relative_to("/") if parsed_path.is_absolute() else parsed_path,
        )
        if source_path.exists():
            # Local with path relative to prime.
            _copy_file(source_path, target_icon_path)
        elif parsed_path.exists():
            # Local with path relative to project.
            _copy_file(parsed_path, target_icon_path)
        else:
            # No icon found, fall back to searching for existing icon.
            return _find_icon_file(assets_dir)
    else:
        raise RuntimeError(f"Unexpected icon path: {parsed_url!r}")

    return target_icon_path
Esempio n. 16
0
def _verify_snap(directory: Path) -> None:
    emit.debug("pack_snap: check skeleton")
    try:
        subprocess.run(
            ["snap", "pack", "--check-skeleton", directory],
            capture_output=True,
            check=True,
            universal_newlines=True,
        )
    except subprocess.CalledProcessError as err:
        msg = f"Cannot pack snap file: {err!s}"
        if err.stderr:
            msg += f" ({err.stderr.strip()!s})"
        raise errors.SnapcraftError(msg)
Esempio n. 17
0
def _ldd_resolve(soname: str, soname_path: str) -> Tuple[str, str]:
    emit.debug(f"_ldd_resolve: {soname!r} {soname_path!r}")

    # If found, resolve the path components.  We can safely determine that
    # ldd found the match if it returns an absolute path.  For additional
    # safety, check that it exists.  See example ldd output in ldd() below.
    # If not found, ldd should use a string like "not found", but we do not
    # really care what that string is with this approach as it has to start
    # with "/" and point to a valid file.
    if soname_path.startswith("/") and os.path.exists(soname_path):
        abs_path = os.path.abspath(soname_path)
        return soname, abs_path

    # Not found, use the soname.
    return soname, soname
Esempio n. 18
0
def get_launchpad_ppa_key_id(*, ppa: str) -> str:
    """Query Launchpad for PPA's key ID."""
    owner, name = split_ppa_parts(ppa=ppa)
    launchpad = Launchpad.login_anonymously("snapcraft", "production")
    launchpad_url = f"~{owner}/+archive/{name}"

    emit.debug(f"Loading launchpad url: {launchpad_url}")
    try:
        key_id = launchpad.load(launchpad_url).signing_key_fingerprint
    except lazr.restfulclient.errors.NotFound as error:
        raise errors.AptPPAInstallError(ppa,
                                        "not found on launchpad") from error

    emit.debug(f"Retrieved launchpad PPA key ID: {key_id}")

    return key_id
Esempio n. 19
0
def run(command_name: str, parsed_args: "argparse.Namespace") -> None:
    """Run the parts lifecycle.

    :raises SnapcraftError: if the step name is invalid, or the project
        yaml file cannot be loaded.
    :raises LegacyFallback: if the project's base is not core22.
    """
    emit.debug(f"command: {command_name}, arguments: {parsed_args}")

    snap_project = get_snap_project()
    yaml_data = process_yaml(snap_project.project_file)
    start_time = datetime.now()
    build_plan = get_build_plan(yaml_data, parsed_args)

    if parsed_args.provider:
        raise errors.SnapcraftError("Option --provider is not supported.")

    # Register our own plugins and callbacks
    plugins.register()
    callbacks.register_prologue(_set_global_environment)
    callbacks.register_pre_step(_set_step_environment)

    build_count = utils.get_parallel_build_count()

    for build_on, build_for in build_plan:
        emit.verbose(f"Running on {build_on} for {build_for}")
        yaml_data_for_arch = apply_yaml(yaml_data, build_on, build_for)
        parse_info = _extract_parse_info(yaml_data_for_arch)
        _expand_environment(
            yaml_data_for_arch,
            parallel_build_count=build_count,
            target_arch=build_for,
        )
        project = Project.unmarshal(yaml_data_for_arch)

        try:
            _run_command(
                command_name,
                project=project,
                parse_info=parse_info,
                parallel_build_count=build_count,
                assets_dir=snap_project.assets_dir,
                start_time=start_time,
                parsed_args=parsed_args,
            )
        except PermissionError as err:
            raise errors.FilePermissionError(err.filename, reason=err.strerror)
Esempio n. 20
0
def _clean_provider(project: Project,
                    parsed_args: "argparse.Namespace") -> None:
    """Clean the provider environment.

    :param project: The project to clean.
    """
    emit.debug("Clean build provider")
    provider_name = "lxd" if parsed_args.use_lxd else None
    provider = providers.get_provider(provider_name)
    instance_names = provider.clean_project_environments(
        project_name=project.name,
        project_path=Path().absolute(),
        build_on=get_host_architecture(),
        build_for=get_host_architecture(),
    )
    if instance_names:
        emit.message(f"Removed instance: {', '.join(instance_names)}")
    else:
        emit.message("No instances to remove")
Esempio n. 21
0
def _update_project_icon(
    project: Project,
    *,
    metadata: ExtractedMetadata,
    assets_dir: Path,
) -> None:
    """Look for icons files and update project.

    Existing icon in snap/gui/icon.{png,svg} has precedence over extracted data
    """
    icon_files = (f"{assets_dir}/gui/icon.{ext}"
                  for ext in _VALID_ICON_EXTENSIONS)

    for icon_file in icon_files:
        if Path(icon_file).is_file():
            break
    else:
        if metadata.icon:
            project.icon = metadata.icon

    emit.debug(f"updated project icon: {project.icon}")
Esempio n. 22
0
def get_parallel_build_count() -> int:
    """Obtain the number of concurrent jobs to execute.

    Try different strategies to obtain the number of parallel jobs
    to execute. If they fail, assume the safe default of 1. The
    number of concurrent build jobs can be limited by setting the
    environment variable ``SNAPCRAFT_MAX_PARALLEL_BUILD_COUNT``.

    :return: The number of parallel jobs for the current host.
    """
    try:
        build_count = len(os.sched_getaffinity(0))
        emit.debug(f"CPU count (from process affinity): {build_count}")
    except AttributeError:
        # Fall back to multiprocessing.cpu_count()...
        try:
            build_count = multiprocessing.cpu_count()
            emit.debug(f"CPU count (from multiprocessing): {build_count}")
        except NotImplementedError:
            emit.progress(
                "Unable to determine CPU count; disabling parallel builds",
                permanent=True,
            )
            build_count = 1

    try:
        max_count = int(
            os.environ.get("SNAPCRAFT_MAX_PARALLEL_BUILD_COUNT", ""))
        if max_count > 0:
            build_count = min(build_count, max_count)
    except ValueError:
        emit.debug("Invalid SNAPCRAFT_MAX_PARALLEL_BUILD_COUNT value")

    return build_count
Esempio n. 23
0
def capture_logs_from_instance(instance: Executor) -> None:
    """Retrieve logs from instance.

    :param instance: Instance to retrieve logs from.

    :returns: String of logs.
    """
    # Get a temporary file path.
    with tempfile.NamedTemporaryFile(delete=False,
                                     prefix="snapcraft-") as tmp_file:
        local_log_path = pathlib.Path(tmp_file.name)

    instance_log_path = get_managed_environment_log_path()

    try:
        instance.pull_file(source=instance_log_path,
                           destination=local_log_path)
    except FileNotFoundError:
        emit.debug("No logs found in instance.")
        return

    emit.debug("Logs captured from managed instance:")
    with local_log_path.open("rt", encoding="utf8") as logfile:
        for line in logfile:
            emit.debug(":: " + line.rstrip())
    local_log_path.unlink()
Esempio n. 24
0
    def run(self, parsed_args):
        client = store.StoreClientCLI()

        # Account info request to retrieve the snap-id
        account_info = client.get_account_info()
        try:
            snap_id = account_info["snaps"][store.constants.DEFAULT_SERIES][
                parsed_args.name]["snap-id"]
        except KeyError as key_error:
            emit.debug(f"{key_error!r} no found in {account_info!r}")
            raise errors.SnapcraftError(
                f"{parsed_args.name!r} not found or not owned by this account"
            ) from key_error

        client.close(
            snap_id=snap_id,
            channel=parsed_args.channel,
        )

        emit.message(
            f"Channel {parsed_args.channel!r} for {parsed_args.name!r} is now closed"
        )
Esempio n. 25
0
    def install_key_from_keyserver(
            self,
            *,
            key_id: str,
            key_server: str = "keyserver.ubuntu.com") -> None:
        """Install key from specified key server.

        :param key_id: Key ID to install.
        :param key_server: Key server to query.

        :raises: AptGPGKeyInstallError if unable to install key.
        """
        env = {}
        env["LANG"] = "C.UTF-8"

        cmd = [
            "apt-key",
            "--keyring",
            str(self._gpg_keyring),
            "adv",
            "--keyserver",
            key_server,
            "--recv-keys",
            key_id,
        ]

        try:
            emit.debug(f"Executing: {cmd!r}")
            subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                check=True,
                env=env,
            )
        except subprocess.CalledProcessError as error:
            raise errors.AptGPGKeyInstallError(error.output.decode(),
                                               key_id=key_id,
                                               key_server=key_server)
Esempio n. 26
0
def get_elf_files(root: str, file_list: Set[str]) -> FrozenSet[ElfFile]:
    """Return a frozenset of ELF files from file_list prepended with root.

    :param str root: the root directory from where the file_list is generated.
    :param file_list: a list of file in root.
    :returns: a frozentset of ElfFile objects.
    """
    elf_files: Set[ElfFile] = set()

    for part_file in file_list:
        # Filter out object (*.o) files-- we only care about binaries.
        if part_file.endswith(".o"):
            continue

        # No need to crawl links-- the original should be here, too.
        path = Path(root, part_file)
        if os.path.islink(path):
            emit.debug(f"Skipped link {path!r} while finding dependencies")
            continue

        # Ignore if file does not have ELF header.
        if not ElfFile.is_elf(path):
            continue

        try:
            elf_file = ElfFile(path=path)
        except elftools.common.exceptions.ELFError:
            # Ignore invalid ELF files.
            continue
        except errors.CorruptedElfFile as exception:
            # Log if the ELF file seems corrupted
            emit.message(str(exception))
            continue

        # If ELF has dynamic symbols, add it.
        if elf_file.needed:
            elf_files.add(elf_file)

    return frozenset(elf_files)
Esempio n. 27
0
def _update_project_app_desktop_file(project: Project, *,
                                     metadata: ExtractedMetadata,
                                     assets_dir: Path,
                                     prime_dir: Path) -> None:
    """Look for desktop files and update project.

    Existing desktop file snap/gui/<appname>.desktop has precedence over extracted data
    """
    if metadata.common_id and project.apps:
        app_name = None
        for name, data in project.apps.items():
            if data.common_id == metadata.common_id:
                app_name = name
                break

        if not app_name:
            emit.debug(f"no app declares id {metadata.common_id!r}")
            return

        if project.apps[app_name].desktop:
            emit.debug("app {app_name!r} already declares a desktop file")
            return

        emit.debug(
            f"look for desktop file with id {metadata.common_id!r} in app {app_name!r}"
        )

        desktop_file = f"{assets_dir}/gui/{app_name}.desktop"
        if Path(desktop_file).is_file():
            emit.debug(f"use already existing desktop file {desktop_file!r}")
            return

        if metadata.desktop_file_paths:
            for filename in metadata.desktop_file_paths:
                if Path(prime_dir, filename.lstrip("/")).is_file():
                    project.apps[app_name].desktop = filename
                    emit.debug(f"use desktop file {filename!r}")
                    break
Esempio n. 28
0
    def _run_patchelf(self, *, patchelf_args: List[str], elf_file_path: Path) -> None:
        # Run patchelf on a copy of the primed file and replace it
        # after it is successful. This allows us to break the potential
        # hard link created when migrating the file across the steps of
        # the part.
        with tempfile.NamedTemporaryFile() as temp_file:
            shutil.copy2(elf_file_path, temp_file.name)

            cmd = [self._patchelf_cmd] + patchelf_args + [temp_file.name]
            try:
                emit.debug(f"executing: {' '.join(cmd)}")
                subprocess.check_call(cmd)
            # There is no need to catch FileNotFoundError as patchelf should be
            # bundled with snapcraft which means its lack of existence is a
            # "packager" error.
            except subprocess.CalledProcessError as call_error:
                raise PatcherError(
                    elf_file_path, cmd=call_error.cmd, code=call_error.returncode
                ) from call_error

            # We unlink to break the potential hard link
            os.unlink(elf_file_path)
            shutil.copy2(temp_file.name, elf_file_path)
Esempio n. 29
0
    def _install_sources(
        self,
        *,
        architectures: Optional[List[str]] = None,
        components: Optional[List[str]] = None,
        formats: Optional[List[str]] = None,
        name: str,
        suites: List[str],
        url: str,
    ) -> bool:
        """Install sources list configuration.

        Write config to:
        /etc/apt/sources.list.d/snapcraft-<name>.sources

        :returns: True if configuration was changed.
        """
        config = _construct_deb822_source(
            architectures=architectures,
            components=components,
            formats=formats,
            suites=suites,
            url=url,
        )

        if name not in ["default", "default-security"]:
            name = "snapcraft-" + name

        config_path = self._sources_list_d / f"{name}.sources"
        if config_path.exists() and config_path.read_text() == config:
            # Already installed and matches, nothing to do.
            emit.debug(f"Ignoring unchanged sources: {config_path!s}")
            return False

        config_path.write_text(config)
        emit.debug(f"Installed sources: {config_path!s}")
        return True
Esempio n. 30
0
def setup_assets(
    project: Project, *, assets_dir: Path, project_dir: Path, prime_dir: Path
) -> None:
    """Copy assets to the appropriate locations in the snap filesystem.

    :param project: The snap project file.
    :param assets_dir: The directory containing snap project assets.
    :param project_dir: The project root directory.
    :param prime_dir: The directory containing the content to be snapped.
    """
    meta_dir = prime_dir / "meta"
    gui_dir = meta_dir / "gui"
    gui_dir.mkdir(parents=True, exist_ok=True)

    _write_snap_directory(assets_dir=assets_dir, prime_dir=prime_dir, meta_dir=meta_dir)

    if project.hooks:
        for hook_name, hook in project.hooks.items():
            if hook.command_chain:
                _validate_command_chain(
                    hook.command_chain, name=f"hook {hook_name!r}", prime_dir=prime_dir
                )
            ensure_hook(meta_dir / "hooks" / hook_name)

    if project.type == "gadget":
        gadget_yaml = project_dir / "gadget.yaml"
        if not gadget_yaml.exists():
            raise errors.SnapcraftError("gadget.yaml is required for gadget snaps")
        _copy_file(gadget_yaml, meta_dir / "gadget.yaml")

    if project.type == "kernel":
        kernel_yaml = project_dir / "kernel.yaml"
        if kernel_yaml.exists():
            _copy_file(kernel_yaml, meta_dir / "kernel.yaml")

    if not project.apps:
        return

    icon_path = _finalize_icon(
        project.icon, assets_dir=assets_dir, gui_dir=gui_dir, prime_dir=prime_dir
    )
    relative_icon_path: Optional[str] = None

    if icon_path is not None:
        if prime_dir in icon_path.parents:
            icon_path = icon_path.relative_to(prime_dir)
        relative_icon_path = str(icon_path)

    emit.debug(f"relative icon path: {relative_icon_path!r}")

    for app_name, app in project.apps.items():
        _validate_command_chain(
            app.command_chain, name=f"app {app_name!r}", prime_dir=prime_dir
        )

        if app.desktop:
            desktop_file = DesktopFile(
                snap_name=project.name,
                app_name=app_name,
                filename=app.desktop,
                prime_dir=prime_dir,
            )
            desktop_file.write(gui_dir=gui_dir, icon_path=relative_icon_path)