Exemple #1
0
    def ensure_provider(cls):
        error_message = None  # type: Optional[str]
        prompt_installable = False

        if sys.platform != "linux":
            error_message = "LXD is not supported on this platform"
        else:
            try:
                if not repo.snaps.SnapPackage.is_snap_installed("lxd"):
                    error_message = (
                        "The LXD snap is required to continue: snap install lxd"
                    )
                    prompt_installable = True
            except repo.errors.SnapdConnectionError:
                error_message = (
                    "snap support is required to continue: "
                    "https://docs.snapcraft.io/installing-snapd/6735")

        if error_message is not None:
            raise errors.ProviderNotFound(
                provider=cls._get_provider_name(),
                prompt_installable=prompt_installable,
                error_message=error_message,
            )

        # If we reach this point, it means the lxd snap is properly setup.
        # Now is the time for additional sanity checks to ensure the provider
        # will work.
        try:
            # TODO: add support for more distributions. Maybe refactor a bit so that Repo behaves
            # similar to a build provider.
            if repo.Repo.is_package_installed(
                    "lxd") or repo.Repo.is_package_installed("lxd-client"):
                raise SnapcraftEnvironmentError((
                    "The {!r} provider does not support having the 'lxd' or "
                    "'lxd-client' deb packages installed. To completely migrate "
                    "to the LXD snap run 'lxd.migrate' and try again.").format(
                        cls._get_provider_name()))
        except repo.errors.NoNativeBackendError:
            pass
def get_linker_version_from_file(linker_file: str) -> str:
    """Returns the version of the linker from linker_file.

    linker_file must be of the format ld-(?P<linker_version>[\d.]+).so$

    :param str linker_file: a valid file path or basename representing
                            the linker from libc6 or related.
    :returns: the version extracted from the linker file.
    :rtype: string
    :raises snapcraft.internal.errors.SnapcraftEnvironmentError:
       if linker_file is not of the expected format.
    """
    m = re.search(r"ld-(?P<linker_version>[\d.]+).so$", linker_file)
    if not m:
        # This is a programmatic error, we don't want to be friendly
        # about this.
        raise SnapcraftEnvironmentError(
            "The format for the linker should be of the of the form "
            "<root>/ld-<X>.<Y>.so. {!r} does not match that format. "
            "Ensure you are targeting an appropriate base".format(linker_file))
    linker_version = m.group("linker_version")

    return linker_version
Exemple #3
0
    def __init__(self, project: project.Project) -> None:
        self.build_snaps: Set[str] = set()
        self.project = project

        # raw_snapcraft_yaml is read only, create a new copy
        snapcraft_yaml = apply_extensions(project.info.get_raw_snapcraft())

        self.validator = Validator(snapcraft_yaml)
        self.validator.validate()

        snapcraft_yaml = self._expand_filesets(snapcraft_yaml)

        self.data = self._expand_env(snapcraft_yaml)

        self.data["architectures"] = _process_architectures(
            self.data.get("architectures"), project.deb_arch)

        self._ensure_no_duplicate_app_aliases()

        grammar_processor = grammar_processing.GlobalGrammarProcessor(
            properties=self.data, project=project)

        keys_path = project._get_keys_path()
        if any([
                package_repo.install(keys_path=keys_path)
                for package_repo in project._snap_meta.package_repositories
        ]):
            repo.Repo.refresh_build_packages()

        self.build_tools = grammar_processor.get_build_packages()
        self.build_tools |= set(project.additional_build_packages)

        # If version: git is used we want to add "git" to build-packages
        if self.data.get("version") == "git":
            self.build_tools.add("git")

        # XXX: Resetting snap_meta due to above mangling of data.
        # Convergence to operating on snap_meta will remove this requirement...
        project._snap_meta = Snap.from_dict(self.data)

        # Always add the base for building for non os and base snaps
        if project.info.base is None and project.info.type in ("app",
                                                               "gadget"):
            raise SnapcraftEnvironmentError(
                "A base is required for snaps of type {!r}.".format(
                    project.info.type))
        if project.info.base is not None:
            # If the base is already installed by other means, skip its installation.
            # But, we should always add it when in a docker environment so
            # the creator of said docker image is aware that it is required.
            if common.is_process_container(
            ) or not repo.snaps.SnapPackage.is_snap_installed(
                    project.info.base):
                self.build_snaps.add(project.info.base)

        self.parts = PartsConfig(
            parts=self.data,
            project=project,
            validator=self.validator,
            build_snaps=self.build_snaps,
            build_tools=self.build_tools,
        )
Exemple #4
0
def link_or_copy_tree(
    source_tree: str,
    destination_tree: str,
    ignore: Callable[[str, List[str]], List[str]] = None,
    copy_function: Callable[..., None] = link_or_copy,
) -> None:
    """Copy a source tree into a destination, hard-linking if possible.

    :param str source_tree: Source directory to be copied.
    :param str destination_tree: Destination directory. If this directory
                                 already exists, the files in `source_tree`
                                 will take precedence.
    :param callable ignore: If given, called with two params, source dir and
                            dir contents, for every dir copied. Should return
                            list of contents to NOT copy.
    :param callable copy_function: Callable that actually copies.
    """

    if not os.path.isdir(source_tree):
        raise SnapcraftEnvironmentError(
            "{!r} is not a directory".format(source_tree))

    if not os.path.isdir(destination_tree) and (
            os.path.exists(destination_tree)
            or os.path.islink(destination_tree)):
        raise SnapcraftEnvironmentError(
            "Cannot overwrite non-directory {!r} with directory "
            "{!r}".format(destination_tree, source_tree))

    create_similar_directory(source_tree, destination_tree)

    destination_basename = os.path.basename(destination_tree)

    for root, directories, files in os.walk(source_tree, topdown=True):
        ignored: Set[str] = set()
        if ignore is not None:
            ignored = set(ignore(root, directories + files))

        # Don't recurse into destination tree if it's a subdirectory of the
        # source tree.
        if os.path.relpath(destination_tree, root) == destination_basename:
            ignored.add(destination_basename)

        if ignored:
            # Prune our search appropriately given an ignore list, i.e. don't
            # walk into directories that are ignored.
            directories[:] = [d for d in directories if d not in ignored]

        for directory in directories:
            source = os.path.join(root, directory)
            # os.walk doesn't by default follow symlinks (which is good), but
            # it includes symlinks that are pointing to directories in the
            # directories list. We want to treat it as a file, here.
            if os.path.islink(source):
                files.append(directory)
                continue

            destination = os.path.join(destination_tree,
                                       os.path.relpath(source, source_tree))

            create_similar_directory(source, destination)

        for file_name in set(files) - ignored:
            source = os.path.join(root, file_name)
            destination = os.path.join(destination_tree,
                                       os.path.relpath(source, source_tree))

            copy_function(source, destination)
Exemple #5
0
def remote_build(
    recover: bool,
    status: bool,
    build_on: str,
    launchpad_accept_public_upload: bool,
    launchpad_timeout: int,
    package_all_sources: bool,
    echoer=echo,
) -> None:
    """Dispatch a snap for remote build.

    Command remote-build sends the current project to be built remotely. After the build
    is complete, packages for each architecture are retrieved and will be available in
    the local filesystem.

    If not specified in the snapcraft.yaml file, the list of architectures to build
    can be set using the --build-on option. If both are specified, an error will occur.

    Interrupted remote builds can be resumed using the --recover option, followed by
    the build number informed when the remote build was originally dispatched. The
    current state of the remote build for each architecture can be checked using the
    --status option.

    \b
    Examples:
        snapcraft remote-build
        snapcraft remote-build --build-on=amd64
        snapcraft remote-build --build-on=amd64,arm64,armhf,i386,ppc64el,s390x
        snapcraft remote-build --recover
        snapcraft remote-build --status
    """
    if os.getenv("SUDO_USER") and os.geteuid() == 0:
        raise SnapcraftEnvironmentError("'sudo' cannot be used with remote-build")

    if not launchpad_accept_public_upload:
        raise errors.AcceptPublicUploadError()

    echo.warning(
        "snapcraft remote-build is experimental and is subject to change - use with caution."
    )

    project = get_project()

    # TODO: use project.is_legacy() when available.
    try:
        project._get_build_base()
    except RuntimeError:
        raise errors.BaseRequiredError()

    # Use a hash of current working directory to distinguish between other
    # potential project builds occurring in parallel elsewhere.
    project_hash = project._get_project_directory_hash()
    build_id = f"snapcraft-{project.info.name}-{project_hash}"
    architectures = _determine_architectures(project, build_on)

    # Calculate timeout timestamp, if specified.
    if launchpad_timeout > 0:
        deadline = int(time.time()) + launchpad_timeout
    else:
        deadline = 0

    lp = LaunchpadClient(
        project=project,
        build_id=build_id,
        architectures=architectures,
        deadline=deadline,
    )

    if status:
        _print_status(lp)
        return

    if lp.has_outstanding_build():
        echo.info("Found previously started build.")
        _print_status(lp)

        # If recovery specified, monitor build and exit.
        if recover or echo.confirm("Do you wish to recover this build?", default=True):
            _monitor_build(lp)
            return

        # Otherwise clean running build before we start a new one.
        _clean_build(lp)

    _start_build(
        lp=lp,
        project=project,
        build_id=build_id,
        package_all_sources=package_all_sources,
    )

    _monitor_build(lp)