Beispiel #1
0
    def _inject_snapcraft(self):
        if common.is_snap():
            # Because of https://bugs.launchpad.net/snappy/+bug/1628289
            self._container_run(['apt-get', 'install', 'squashfuse', '-y'])

            # Push core snap into container
            self._inject_snap('core')
            self._inject_snap('snapcraft')
        else:
            self._container_run(['apt-get', 'install', 'snapcraft', '-y'])
Beispiel #2
0
def setup_dirs() -> None:
    """
    Ensure that snapcraft.common plugindir is setup correctly
    and support running out of a development snapshot
    """
    from snapcraft.internal import common

    topdir = os.path.abspath(os.path.join(__file__, "..", "..", ".."))

    # Only change the default if we are running from a checkout or from the
    # snap, or in Windows.
    if os.path.exists(os.path.join(topdir, "setup.py")):
        common.set_plugindir(os.path.join(topdir, "snapcraft", "plugins"))
        common.set_schemadir(os.path.join(topdir, "schema"))
        common.set_extensionsdir(os.path.join(topdir, "extensions"))
        common.set_keyringsdir(os.path.join(topdir, "keyrings"))

    # The default paths are relative to sys.prefix, which works well for
    # Snapcraft as a deb or in a venv. However, the Python plugin installs
    # packages into $SNAP/ as a prefix, while Python itself is contained in
    # $SNAP/usr/. As a result, using sys.prefix (which is '/usr') to find these
    # files won't work in the snap.
    elif common.is_snap():
        snap_path = os.environ.get("SNAP")
        if snap_path is None:
            # Shouldn't happen, but it is certainly an error if it does.
            raise RuntimeError("SNAP not defined, but SNAP_NAME is?")

        parent_dir = os.path.join(snap_path, "share", "snapcraft")
        common.set_plugindir(os.path.join(parent_dir, "plugins"))
        common.set_schemadir(os.path.join(parent_dir, "schema"))
        common.set_extensionsdir(os.path.join(parent_dir, "extensions"))
        common.set_keyringsdir(os.path.join(parent_dir, "keyrings"))
        common.set_legacy_snapcraft_dir(
            os.path.join(snap_path, "legacy_snapcraft"))

    elif sys.platform == "win32":
        common.set_plugindir(os.path.join(topdir, "snapcraft", "plugins"))

        data_dir = _find_windows_data_dir(topdir)
        common.set_schemadir(os.path.join(data_dir, "schema"))
        common.set_extensionsdir(os.path.join(data_dir, "extensions"))
        common.set_keyringsdir(os.path.join(data_dir, "keyrings"))

    else:
        # Make sure required data directories exist in the default locations.
        # Plugins and legacy snapcraft directory are not required.
        for d in [
                common.get_schemadir(),
                common.get_extensionsdir(),
                common.get_keyringsdir(),
        ]:
            if not os.path.exists(d):
                raise snapcraft.internal.errors.SnapcraftDataDirectoryMissingError(
                )
Beispiel #3
0
    def _setup_apt(self, cache_dir):
        # Do not install recommends
        apt.apt_pkg.config.set("Apt::Install-Recommends", "False")

        # Methods and solvers dir for when in the SNAP
        if common.is_snap():
            snap_dir = os.getenv("SNAP")
            apt_dir = os.path.join(snap_dir, "usr", "lib", "apt")
            apt.apt_pkg.config.set("Dir", apt_dir)
            # yes apt is broken like that we need to append os.path.sep
            methods_dir = os.path.join(apt_dir, "methods")
            apt.apt_pkg.config.set("Dir::Bin::methods", methods_dir + os.path.sep)
            solvers_dir = os.path.join(apt_dir, "solvers")
            apt.apt_pkg.config.set("Dir::Bin::solvers::", solvers_dir + os.path.sep)
            apt_key_path = os.path.join(snap_dir, "usr", "bin", "apt-key")
            apt.apt_pkg.config.set("Dir::Bin::apt-key", apt_key_path)
            gpgv_path = os.path.join(snap_dir, "usr", "bin", "gpgv")
            apt.apt_pkg.config.set("Apt::Key::gpgvcommand", gpgv_path)
            apt.apt_pkg.config.set("Dir::Etc::Trusted", "/etc/apt/trusted.gpg")
            apt.apt_pkg.config.set("Dir::Etc::TrustedParts", "/etc/apt/trusted.gpg.d/")

        # Make sure we always use the system GPG configuration, even with
        # apt.Cache(rootdir).
        for key in "Dir::Etc::Trusted", "Dir::Etc::TrustedParts":
            apt.apt_pkg.config.set(key, apt.apt_pkg.config.find_file(key))

        # Clear up apt's Post-Invoke-Success as we are not running
        # on the system.
        apt.apt_pkg.config.clear("APT::Update::Post-Invoke-Success")

        self.progress = apt.progress.text.AcquireProgress()
        if is_dumb_terminal():
            # Make output more suitable for logging.
            self.progress.pulse = lambda owner: True
            self.progress._width = 0

        sources_list_file = os.path.join(cache_dir, "etc", "apt", "sources.list")

        os.makedirs(os.path.dirname(sources_list_file), exist_ok=True)
        with open(sources_list_file, "w") as f:
            f.write(self._collected_sources_list())

        # dpkg also needs to be in the rootdir in order to support multiarch
        # (apt calls dpkg --print-foreign-architectures).
        dpkg_path = shutil.which("dpkg")
        if dpkg_path:
            # Symlink it into place
            destination = os.path.join(cache_dir, dpkg_path[1:])
            if not os.path.exists(destination):
                os.makedirs(os.path.dirname(destination), exist_ok=True)
                os.symlink(dpkg_path, destination)
        else:
            logger.warning("Cannot find 'dpkg' command needed to support multiarch")

        return self._create_cache(cache_dir, sources_list_file)
Beispiel #4
0
    def _inject_snapcraft(self):
        if common.is_snap():
            # Because of https://bugs.launchpad.net/snappy/+bug/1628289
            self._container_run(['apt-get', 'install', 'squashfuse', '-y'])

            with tempfile.TemporaryDirectory(
                    prefix='snapcraft', dir=self._lxd_common_dir) as tmp_dir:
                self._inject_snap('core', tmp_dir)
                self._inject_snap('snapcraft', tmp_dir)
        else:
            self._container_run(['apt-get', 'install', 'snapcraft', '-y'])
Beispiel #5
0
def _get_env():
    env = ""
    if common.is_snap():
        # Since the snap is classic, there is no $PATH pointing into the snap, which
        # means snapcraftctl won't be found. We can't use aliases since they don't
        # persist into subshells. However, we know that snapcraftctl lives in its own
        # directory, so adding that to the PATH should have no ill side effects.
        env += 'export PATH="$PATH:$SNAP/bin/scriptlet-bin"\n'
    env += common.assemble_env()

    return env
Beispiel #6
0
def _get_env():
    env = ""
    if common.is_snap():
        # Since the snap is classic, $SNAP/bin is not on the $PATH.
        # Let's set an alias to make sure it's found (but only if it
        # exists).
        snapcraftctl_path = os.path.join(os.getenv("SNAP"), "bin", "snapcraftctl")
        if os.path.exists(snapcraftctl_path):
            env += 'alias snapcraftctl="$SNAP/bin/snapcraftctl"\n'
    env += common.assemble_env()

    return env
Beispiel #7
0
def _patchelf_install_required(project_options) -> bool:
    is_xenial = False
    with contextlib.suppress(os_release.errors.OsReleaseCodenameError):
        release_codename = os_release.OsRelease().version_codename()
        is_xenial = release_codename == 'xenial'

    is_snap = common.is_snap()
    is_environment = os.getenv('SNAPCRAFT_NO_PATCHELF')
    is_arch_missing_xenial = project_options.deb_arch in ('armhf', 's390x')

    return not (is_snap or is_environment or
                (is_arch_missing_xenial and is_xenial))
def _get_env():
    env = ""
    if common.is_snap():
        # Since the snap is classic, $SNAP/bin is not on the $PATH.
        # Let's set an alias to make sure it's found (but only if it
        # exists).
        snapcraftctl_path = os.path.join(os.getenv("SNAP"), "bin",
                                         "snapcraftctl")
        if os.path.exists(snapcraftctl_path):
            env += 'alias snapcraftctl="$SNAP/bin/snapcraftctl"\n'
    env += common.assemble_env()

    return env
Beispiel #9
0
 def _inject_snapcraft(self, *, new_container: bool):
     if common.is_snap():
         with tempfile.TemporaryDirectory(
                 prefix="snapcraft", dir=self._lxd_common_dir) as tmp_dir:
             # Wait for any on-going refreshes to finish.
             # If there are no changes an error will be returned.
             with contextlib.suppress(errors.ContainerRunError):
                 self._container_run(
                     ["snap", "watch", "--last=auto-refresh"])
             self._inject_snap("core", tmp_dir)
             self._inject_snap("snapcraft", tmp_dir)
     elif new_container:
         self._container_run(["apt-get", "install", "snapcraft", "-y"])
 def _inject_snapcraft(self, *, new_container: bool):
     if common.is_snap():
         with tempfile.TemporaryDirectory(
                 prefix='snapcraft', dir=self._lxd_common_dir) as tmp_dir:
             # Wait for any on-going refreshes to finish.
             # If there are no changes an error will be returned.
             with contextlib.suppress(ContainerRunError):
                 self._container_run(
                     ['snap', 'watch', '--last=auto-refresh'])
             self._inject_snap('core', tmp_dir)
             self._inject_snap('snapcraft', tmp_dir)
     elif new_container:
         self._container_run(['apt-get', 'install', 'snapcraft', '-y'])
Beispiel #11
0
 def _inject_snapcraft(self, *, new_container: bool):
     if common.is_snap():
         with tempfile.TemporaryDirectory(
             prefix="snapcraft", dir=self._lxd_common_dir
         ) as tmp_dir:
             # Wait for any on-going refreshes to finish.
             # If there are no changes an error will be returned.
             with contextlib.suppress(errors.ContainerRunError):
                 self._container_run(["snap", "watch", "--last=auto-refresh"])
             self._inject_snap("core", tmp_dir)
             self._inject_snap("snapcraft", tmp_dir)
     elif new_container:
         self._container_run(["apt-get", "install", "snapcraft", "-y"])
Beispiel #12
0
def run_legacy_snapcraft(argv=sys.argv[1:]) -> None:
    if not common.is_snap():
        raise errors.SnapcraftEnvironmentError(
            "Legacy mode not supported in this installation. "
            "Install snapcraft from https://snapcraft.io/snapcraft and try again."
        )

    legacy_python = os.path.join(common.get_legacy_snapcraft_dir(), "usr",
                                 "bin", "python3")
    legacy_snapcraft = os.path.join(common.get_legacy_snapcraft_dir(), "bin",
                                    "snapcraft")

    cmd = [legacy_python, legacy_snapcraft] + argv
    logging.debug("Running legacy snapcraft with: {}".format(cmd))
    os.execv(legacy_python, cmd)
Beispiel #13
0
    def _inject_snapcraft(self):
        if common.is_snap():
            # Because of https://bugs.launchpad.net/snappy/+bug/1628289
            self._container_run(['apt-get', 'install', 'squashfuse', '-y'])

            # Use a temporary folder the 'lxd' snap can access
            self._tmp = os.path.expanduser(
                os.path.join('~', 'snap', 'lxd', 'common', 'snapcraft.tmp'))
            os.makedirs(self._tmp, exist_ok=True)

            # Push core snap into container
            self._inject_snap('core')
            self._inject_snap('snapcraft')
        else:
            self._container_run(['apt-get', 'install', 'snapcraft', '-y'])
    def _setup_snapcraft(self) -> None:
        self._save_info(
            data={
                "base": self.project._get_build_base(),
                "created-by-snapcraft-version": snapcraft._get_version(),
            }
        )

        registry_filepath = os.path.join(
            self.provider_project_dir, "snap-registry.yaml"
        )

        # We do not want to inject from the host if not running from the snap
        # or if the provider cannot handle snap mounts.
        # This latter problem should go away when API for retrieving snaps
        # through snapd is generally available.
        if self._get_is_snap_injection_capable():
            inject_from_host = common.is_snap()
        else:
            inject_from_host = False

        snap_injector = SnapInjector(
            registry_filepath=registry_filepath,
            snap_arch=self.project.deb_arch,
            runner=self._run,
            file_pusher=self._push_file,
            inject_from_host=inject_from_host,
        )

        # Note that snap injection order is important.
        # If the build base is core, we do not need to inject snapd.
        # Check for None as this build can be driven from a non snappy enabled
        # system, so we may find ourselves in a situation where the base is not
        # set like on OSX or Windows.
        build_base = self.project._get_build_base()
        if build_base is not None and build_base != "core":
            snap_injector.add(snap_name="snapd")

        # Prevent injecting core18 twice.
        if build_base is not None and build_base != "core18":
            snap_injector.add(snap_name=build_base)

        # Inject snapcraft
        snap_injector.add(snap_name="core18")
        snap_injector.add(snap_name="snapcraft")

        snap_injector.apply()
Beispiel #15
0
    def __init__(self, *, dynamic_linker: str) -> None:
        """Create a Patcher instance.

        :param str dynamic_linker: the path to the dynamic linker to set the
                                   elf file to.
        """
        self._dynamic_linker = dynamic_linker

        # If we are running from the snap we want to use the patchelf
        # bundled there as it would have the capabilty of working
        # anywhere given the fixed ld it would have.
        # If not found, resort to whatever is on the system brought
        # in by packaging dependencies.
        if common.is_snap():
            snap_dir = os.getenv('SNAP')
            self._patchelf_cmd = os.path.join(snap_dir, 'bin', 'patchelf')
        else:
            self._patchelf_cmd = 'patchelf'
Beispiel #16
0
    def __init__(self, project_options=None):
        if project_options is None:
            project_options = snapcraft.ProjectOptions()

        self.build_snaps = set()
        self.build_tools = []
        self._project_options = project_options

        self.snapcraft_yaml_path = get_snapcraft_yaml()
        snapcraft_yaml = _snapcraft_yaml_load(self.snapcraft_yaml_path)

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

        snapcraft_yaml = self._process_remote_parts(snapcraft_yaml)
        snapcraft_yaml = self._expand_filesets(snapcraft_yaml)

        # both confinement type and build quality are optionals
        _ensure_confinement_default(snapcraft_yaml, self._validator.schema)
        _ensure_grade_default(snapcraft_yaml, self._validator.schema)

        self.data = self._expand_env(snapcraft_yaml)
        self._ensure_no_duplicate_app_aliases()

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

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

        # Install patchelf to enable patching in classic
        if self.data['confinement'] == 'classic' and not common.is_snap():
            self.build_tools.add('patchelf')

        self.parts = PartsConfig(parts=self.data,
                                 project_options=self._project_options,
                                 validator=self._validator,
                                 build_snaps=self.build_snaps,
                                 build_tools=self.build_tools,
                                 snapcraft_yaml=self.snapcraft_yaml_path)

        if 'architectures' not in self.data:
            self.data['architectures'] = [self._project_options.deb_arch]
Beispiel #17
0
def setup_dirs() -> None:
    """
    Ensure that snapcraft.common plugindir is setup correctly
    and support running out of a development snapshot
    """
    from snapcraft.internal import common

    topdir = os.path.abspath(os.path.join(__file__, "..", "..", ".."))

    # Only change the default if we are running from a checkout or from the
    # snap, or in Windows.
    if os.path.exists(os.path.join(topdir, "setup.py")):
        common.set_plugindir(os.path.join(topdir, "snapcraft", "plugins"))
        common.set_schemadir(os.path.join(topdir, "schema"))
        common.set_extensionsdir(os.path.join(topdir, "extensions"))
        common.set_keyringsdir(os.path.join(topdir, "keyrings"))

    # The default paths are relative to sys.prefix, which works well for
    # Snapcraft as a deb or in a venv. However, the Python plugin installs
    # packages into $SNAP/ as a prefix, while Python itself is contained in
    # $SNAP/usr/. As a result, using sys.prefix (which is '/usr') to find these
    # files won't work in the snap.
    elif common.is_snap():
        parent_dir = os.path.join(os.environ.get("SNAP"), "share", "snapcraft")
        common.set_plugindir(os.path.join(parent_dir, "plugins"))
        common.set_schemadir(os.path.join(parent_dir, "schema"))
        common.set_extensionsdir(os.path.join(parent_dir, "extensions"))
        common.set_keyringsdir(os.path.join(parent_dir, "keyrings"))
        common.set_legacy_snapcraft_dir(
            os.path.join(os.environ.get("SNAP"), "legacy_snapcraft")
        )

    elif sys.platform == "win32":
        common.set_plugindir(os.path.join(topdir, "snapcraft", "plugins"))

        data_dir = _find_windows_data_dir(topdir)
        common.set_schemadir(os.path.join(data_dir, "schema"))
        common.set_extensionsdir(os.path.join(data_dir, "extensions"))
        common.set_keyringsdir(os.path.join(data_dir, "keyrings"))

    else:
        raise snapcraft.internal.errors.SnapcraftDataDirectoryMissingError()
Beispiel #18
0
    def _setup_snapcraft(self) -> None:
        self._save_info(
            data={
                "base": self.project._get_build_base(),
                "created-by-snapcraft-version": snapcraft._get_version(),
                "host-project-directory": self.project._project_dir,
            }
        )

        registry_filepath = os.path.join(
            self.provider_project_dir, "snap-registry.yaml"
        )

        # We do not want to inject from the host if not running from the snap
        # or if the provider cannot handle snap mounts.
        # This latter problem should go away when API for retrieving snaps
        # through snapd is generally available.
        if self._get_is_snap_injection_capable():
            inject_from_host = common.is_snap()
        else:
            inject_from_host = False

        snap_injector = SnapInjector(
            registry_filepath=registry_filepath,
            runner=self._run,
            file_pusher=self._push_file,
            inject_from_host=inject_from_host,
        )

        snap_injector.add(snap_name="snapd")

        # Prevent injecting core20 twice (Snapcraft's base).
        build_base = self.project._get_build_base()
        if build_base != "core20":
            snap_injector.add(snap_name=build_base)

        # Inject snapcraft and its base.
        snap_injector.add(snap_name="core20")
        snap_injector.add(snap_name="snapcraft")

        snap_injector.apply()
Beispiel #19
0
    def __init__(self,
                 *,
                 dynamic_linker: str,
                 root_path: str,
                 preferred_patchelf_path=None) -> None:
        """Create a Patcher instance.

        :param str dynamic_linker: the path to the dynamic linker to set the
                                   elf file to.
        :param str root_path: the base path for the snap to determine
                              if use of $ORIGIN is possible.
        :param str preferred_patchelf_path: patch the necessary elf_files with
                                        this patchelf.
        """
        self._dynamic_linker = dynamic_linker
        self._root_path = root_path

        # We will first fallback to the preferred_patchelf_path,
        # if that is not found we will look for the snap and finally,
        # if we are running from the snap we want to use the patchelf
        # bundled there as it would have the capability of working
        # anywhere given the fixed ld it would have.
        # If not found, resort to whatever is on the system brought
        # in by packaging dependencies.
        # The docker conditional will work if the docker image has the
        # snaps unpacked in the corresponding locations.
        if preferred_patchelf_path:
            self._patchelf_cmd = preferred_patchelf_path
        # We use the full path here as the path may not be set on
        # build systems where the path is recently created and added
        # to the environment
        elif os.path.exists('/snap/bin/patchelf'):
            self._patchelf_cmd = '/snap/bin/patchelf'
        elif common.is_snap():
            snap_dir = os.getenv('SNAP')
            self._patchelf_cmd = os.path.join(snap_dir, 'bin', 'patchelf')
        elif (common.is_docker_instance()
              and os.path.exists('/snap/snapcraft/current/bin/patchelf')):
            self._patchelf_cmd = '/snap/snapcraft/current/bin/patchelf'
        else:
            self._patchelf_cmd = 'patchelf'
Beispiel #20
0
    def setup_snapcraft(self) -> None:
        self._disable_and_wait_for_refreshes()
        self.echoer.info("Setting up snapcraft in {!r}".format(self.instance_name))

        # Add the store assertion, common to all snaps.
        self._inject_assertions(
            [["account-key", "public-key-sha3-384={}".format(_STORE_ASSERTION_KEY)]]
        )

        # TODO make mounting requirement smarter and depend on is_installed
        if common.is_snap():
            # Make the snaps available to the provider
            self._mount_snaps_directory()

        # Now install the snapcraft required base/core.
        self.echoer.info("Setting up core")
        self._install_snap("core")

        # And finally install snapcraft itself.
        self.echoer.info("Setting up snapcraft")
        self._install_snap("snapcraft")
Beispiel #21
0
def get_tool_path(command_name):
    """Return the path to the given command

    By default this utilizes the PATH, but if Snapcraft is running out of the
    snap or out of Docker, it ensures it's using the one in the snap, not the
    host.

    :return: Path to command
    :rtype: str
    """
    path = command_name

    if common.is_snap():
        path = _command_path_in_root(os.getenv("SNAP"), command_name)
    elif common.is_docker_instance():
        path = _command_path_in_root(
            os.path.join(os.sep, "snap", "snapcraft", "current"), command_name)

    if path:
        return path
    else:
        return command_name
Beispiel #22
0
    def _configure_apt(self):
        # Do not install recommends.
        apt.apt_pkg.config.set("Apt::Install-Recommends", "False")

        # Ensure repos are provided by trusted third-parties.
        apt.apt_pkg.config.set("Acquire::AllowInsecureRepositories", "False")

        # Methods and solvers dir for when in the SNAP.
        snap_dir = os.getenv("SNAP")
        if common.is_snap() and snap_dir and os.path.exists(snap_dir):
            apt_dir = os.path.join(snap_dir, "usr", "lib", "apt")
            apt.apt_pkg.config.set("Dir", apt_dir)
            # yes apt is broken like that we need to append os.path.sep
            methods_dir = os.path.join(apt_dir, "methods")
            apt.apt_pkg.config.set("Dir::Bin::methods",
                                   methods_dir + os.path.sep)
            solvers_dir = os.path.join(apt_dir, "solvers")
            apt.apt_pkg.config.set("Dir::Bin::solvers::",
                                   solvers_dir + os.path.sep)
            apt_key_path = os.path.join(snap_dir, "usr", "bin", "apt-key")
            apt.apt_pkg.config.set("Dir::Bin::apt-key", apt_key_path)
            gpgv_path = os.path.join(snap_dir, "usr", "bin", "gpgv")
            apt.apt_pkg.config.set("Apt::Key::gpgvcommand", gpgv_path)

        apt.apt_pkg.config.set("Dir::Etc::Trusted", "/etc/apt/trusted.gpg")
        apt.apt_pkg.config.set("Dir::Etc::TrustedParts",
                               "/etc/apt/trusted.gpg.d/")
        apt.apt_pkg.config.set("Dir::State", "/var/lib/apt")

        # Clear up apt's Post-Invoke-Success as we are not running
        # on the system.
        apt.apt_pkg.config.clear("APT::Update::Post-Invoke-Success")

        self.progress = apt.progress.text.AcquireProgress()
        if is_dumb_terminal():
            # Make output more suitable for logging.
            self.progress.pulse = lambda owner: True
            self.progress._width = 0
Beispiel #23
0
def get_snap_tool_path(command_name: str) -> str:
    """Return the path command found in the snap.

    If snapcraft is not running as a snap, shutil.which() is used
    to resolve the command using PATH.

    :param command_name: the name of the command to resolve a path for.
    :raises ToolMissingError: if command_name was not found.
    :return: Path to command
    """
    if common.is_snap():
        snap_path = os.getenv("SNAP")
        if snap_path is None:
            raise RuntimeError("SNAP not defined, but SNAP_NAME is?")

        command_path = _find_command_path_in_root(snap_path, command_name)
    else:
        command_path = shutil.which(command_name)

    if command_path is None:
        raise errors.ToolMissingError(command_name=command_name)

    return command_path
Beispiel #24
0
    def setup_snapcraft(self) -> None:
        self._disable_and_wait_for_refreshes()
        self.echoer.info("Setting up snapcraft in {!r}".format(
            self.instance_name))

        # Add the store assertion, common to all snaps.
        self._inject_assertions([[
            "account-key",
            "public-key-sha3-384={}".format(_STORE_ASSERTION_KEY)
        ]])

        # TODO make mounting requirement smarter and depend on is_installed
        if common.is_snap():
            # Make the snaps available to the provider
            self._mount_snaps_directory()

        # Now install the snapcraft required base/core.
        self.echoer.info("Setting up core")
        self._install_snap("core")

        # And finally install snapcraft itself.
        self.echoer.info("Setting up snapcraft")
        self._install_snap("snapcraft")
Beispiel #25
0
def get_tool_path(command_name):
    """Return the path to the given command

    By default this utilizes the PATH, but if Snapcraft is running out of the
    snap or out of Docker, it ensures it's using the one in the snap, not the
    host.

    :return: Path to command
    :rtype: str
    """
    path = command_name

    if common.is_snap():
        path = _command_path_in_root(os.getenv("SNAP"), command_name)
    elif common.is_docker_instance():
        path = _command_path_in_root(
            os.path.join(os.sep, "snap", "snapcraft", "current"), command_name
        )

    if path:
        return path
    else:
        return command_name
Beispiel #26
0
    def _setup_snapcraft(self) -> None:
        self._save_info(base=self.project.info.base)

        registry_filepath = os.path.join(
            self.provider_project_dir, "snap-registry.yaml"
        )

        # We do not want to inject from the host if not running from the snap
        # or if the provider cannot handle snap mounts.
        # This latter problem should go away when API for retrieving snaps
        # through snapd is generally available.
        if self._get_is_snap_injection_capable():
            inject_from_host = common.is_snap()
        else:
            inject_from_host = False

        snap_injector = SnapInjector(
            snap_dir=self._SNAPS_MOUNTPOINT,
            registry_filepath=registry_filepath,
            snap_arch=self.project.deb_arch,
            runner=self._run,
            snap_dir_mounter=self._mount_snaps_directory,
            snap_dir_unmounter=self._unmount_snaps_directory,
            file_pusher=self._push_file,
            inject_from_host=inject_from_host,
        )
        # Inject snapcraft
        snap_injector.add(snap_name="core")
        snap_injector.add(snap_name="snapcraft")

        # This build can be driven from a non snappy enabled system, so we may
        # find ourself in a situation where the base is not set like on OSX or
        # Windows.
        if self.project.info.base is not None:
            snap_injector.add(snap_name=self.project.info.base)

        snap_injector.apply()
Beispiel #27
0
def setup_dirs():
    """
    Ensure that snapcraft.common plugindir is setup correctly
    and support running out of a development snapshot
    """
    from snapcraft.internal import common
    topdir = os.path.abspath(os.path.join(__file__, '..', '..', '..'))
    # Only change the default if we are running from a checkout or from the
    # snap.
    if os.path.exists(os.path.join(topdir, 'setup.py')):
        common.set_plugindir(os.path.join(topdir, 'snapcraft', 'plugins'))
        common.set_schemadir(os.path.join(topdir, 'schema'))
        common.set_librariesdir(os.path.join(topdir, 'libraries'))

    # The default paths are relative to sys.prefix, which works well for
    # Snapcraft as a deb or in a venv. However, the Python plugin installs
    # packages into $SNAP/ as a prefix, while Python itself is contained in
    # $SNAP/usr/. As a result, using sys.prefix (which is '/usr') to find these
    # files won't work in the snap.
    elif common.is_snap():
        parent_dir = os.path.join(os.environ.get('SNAP'), 'share', 'snapcraft')
        common.set_plugindir(os.path.join(parent_dir, 'plugins'))
        common.set_schemadir(os.path.join(parent_dir, 'schema'))
        common.set_librariesdir(os.path.join(parent_dir, 'libraries'))
def get_tool_path(command_name: str) -> str:
    """Return the path to the given command

    Return a path to command_name, if Snapcraft is running out of the snap
    or in legacy mode (snap or sources), it ensures it is using the one in
    the snap, not the host.
    If a path cannot be resolved, ToolMissingError is raised.

    : param str command_name: the name of the command to resolve a path for.
    :raises ToolMissingError: if command_name cannot be resolved to a path.
    :return: Path to command
    :rtype: str
    """
    if common.is_snap():
        command_path = _command_path_in_root(os.getenv("SNAP"), command_name)
    else:
        command_path = shutil.which(command_name)

    # shutil.which will return None if it cannot find command_name but
    # _command_path_in_root will return an empty string.
    if not command_path:
        raise ToolMissingError(command_name=command_name)

    return command_path
Beispiel #29
0
    def _run_scriptlet(
        self,
        scriptlet_name: str,
        scriptlet: str,
        workdir: str,
        env_generator: Callable[..., str] = common.assemble_env,
    ) -> None:
        if common.is_snap():
            # Since the snap is classic, there is no $PATH pointing into the snap, which
            # means snapcraftctl won't be found. We can't use aliases since they don't
            # persist into subshells. However, we know that snapcraftctl lives in its own
            # directory, so adding that to the PATH should have no ill side effects.
            snapcraftctl_env = 'export PATH="$PATH:$SNAP/bin/scriptlet-bin"\n'
        else:
            snapcraftctl_env = ""

        with tempfile.TemporaryDirectory(dir=self._partdir) as tempdir:
            call_fifo = _NonBlockingRWFifo(os.path.join(tempdir, "function_call"))
            feedback_fifo = _NonBlockingRWFifo(os.path.join(tempdir, "call_feedback"))

            # snapcraftctl only works consistently if it's using the exact same
            # interpreter as that used by snapcraft itself, thus the definition
            # of SNAPCRAFT_INTERPRETER.
            script = textwrap.dedent(
                """\
                set -e
                export SNAPCRAFTCTL_CALL_FIFO={call_fifo}
                export SNAPCRAFTCTL_FEEDBACK_FIFO={feedback_fifo}
                export SNAPCRAFT_INTERPRETER={interpreter}
                {snapcraftctl_env}
                {env}
                {scriptlet}"""
            ).format(
                interpreter=sys.executable,
                call_fifo=call_fifo.path,
                feedback_fifo=feedback_fifo.path,
                scriptlet=scriptlet,
                snapcraftctl_env=snapcraftctl_env,
                env=env_generator(),
            )

            with tempfile.TemporaryFile(mode="w+") as script_file:
                print(script, file=script_file)
                script_file.flush()
                script_file.seek(0)

                process = subprocess.Popen(["/bin/sh"], stdin=script_file, cwd=workdir)

            status = None
            try:
                while status is None:
                    function_call = call_fifo.read()
                    if function_call:
                        # Handle the function and let caller know that function
                        # call has been handled (must contain at least a
                        # newline, anything beyond is considered an error by
                        # snapcraftctl)
                        feedback_fifo.write(
                            "{}\n".format(
                                self._handle_builtin_function(
                                    scriptlet_name, function_call.strip()
                                )
                            )
                        )
                    status = process.poll()

                    # Don't loop TOO busily
                    time.sleep(0.1)
            finally:
                call_fifo.close()
                feedback_fifo.close()

            if status:
                raise errors.ScriptletRunError(
                    scriptlet_name=scriptlet_name, code=status
                )
Beispiel #30
0
# from http://stackoverflow.com/a/21048064 Wed Jun 22 16:05:34 UTC 2016
_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG


def dict_representer(dumper, data):
    return dumper.represent_dict(data.items())


def dict_constructor(loader, node):
    # Necessary in order to make yaml merge tags work
    loader.flatten_mapping(node)
    return OrderedDict(loader.construct_pairs(node))


def str_presenter(dumper, data):
    if len(data.splitlines()) > 1:  # check for multiline string
        return dumper.represent_scalar('tag:yaml.org,2002:str',
                                       data,
                                       style='|')
    return dumper.represent_scalar('tag:yaml.org,2002:str', data)


yaml.add_representer(str, str_presenter)
yaml.add_representer(OrderedDict, dict_representer)
yaml.add_constructor(_mapping_tag, dict_constructor)

from snapcraft.internal import common as _common  # noqa
if _common.is_snap():
    snap = _os.environ.get('SNAP')
    _common.set_schemadir(_os.path.join(snap, 'share', 'snapcraft', 'schema'))
Beispiel #31
0
    def _setup_apt(self, cache_dir):
        # Do not install recommends
        apt.apt_pkg.config.set("Apt::Install-Recommends", "False")

        # Ensure repos are provided by trusted third-parties
        apt.apt_pkg.config.set("Acquire::AllowInsecureRepositories", "False")

        # Methods and solvers dir for when in the SNAP
        if common.is_snap():
            snap_dir = os.getenv("SNAP")
            apt_dir = os.path.join(snap_dir, "usr", "lib", "apt")
            apt.apt_pkg.config.set("Dir", apt_dir)
            # yes apt is broken like that we need to append os.path.sep
            methods_dir = os.path.join(apt_dir, "methods")
            apt.apt_pkg.config.set("Dir::Bin::methods",
                                   methods_dir + os.path.sep)
            solvers_dir = os.path.join(apt_dir, "solvers")
            apt.apt_pkg.config.set("Dir::Bin::solvers::",
                                   solvers_dir + os.path.sep)
            apt_key_path = os.path.join(snap_dir, "usr", "bin", "apt-key")
            apt.apt_pkg.config.set("Dir::Bin::apt-key", apt_key_path)
            gpgv_path = os.path.join(snap_dir, "usr", "bin", "gpgv")
            apt.apt_pkg.config.set("Apt::Key::gpgvcommand", gpgv_path)
            apt.apt_pkg.config.set("Dir::Etc::Trusted", "/etc/apt/trusted.gpg")
            apt.apt_pkg.config.set("Dir::Etc::TrustedParts",
                                   "/etc/apt/trusted.gpg.d/")

        # Make sure we always use the system GPG configuration, even with
        # apt.Cache(rootdir). However, we also want to be able to add keys to it
        # without root, so symlink back to the system's, but maintain our own.
        # We'll leave Trusted alone and just fiddle with TrustedParts (Trusted is the
        # one modified by apt-key, so add-apt-repository should still work).
        apt.apt_pkg.config.set(
            "Dir::Etc::Trusted",
            apt.apt_pkg.config.find_file("Dir::Etc::Trusted"))
        apt_config_path = os.path.join(cache_dir, "etc", "apt", "apt.conf")
        trusted_parts_path = apt.apt_pkg.config.find_file(
            "Dir::Etc::TrustedParts")
        if not trusted_parts_path.startswith(cache_dir):
            cached_trusted_parts = os.path.join(cache_dir,
                                                trusted_parts_path.lstrip("/"))
            with contextlib.suppress(FileNotFoundError):
                shutil.rmtree(cached_trusted_parts)
            os.makedirs(cached_trusted_parts)
            for trusted_part in os.scandir(trusted_parts_path):
                os.symlink(
                    os.path.join(trusted_parts_path, trusted_part.name),
                    os.path.join(cached_trusted_parts, trusted_part.name),
                )

            apt.apt_pkg.config.set("Dir::Etc::TrustedParts",
                                   cached_trusted_parts)

            # The above config is all that is needed on bionic, but xenial
            # requires this configuration file
            os.makedirs(os.path.dirname(apt_config_path), exist_ok=True)
            with open(apt_config_path, "w") as f:
                f.write("Dir::Etc::TrustedParts {};\n".format(
                    cached_trusted_parts))

            # Now copy in any requested keyrings
            for keyring in self._keyrings:
                shutil.copy2(keyring, cached_trusted_parts)

        # Clear up apt's Post-Invoke-Success as we are not running
        # on the system.
        apt.apt_pkg.config.clear("APT::Update::Post-Invoke-Success")

        self.progress = apt.progress.text.AcquireProgress()
        if is_dumb_terminal():
            # Make output more suitable for logging.
            self.progress.pulse = lambda owner: True
            self.progress._width = 0

        sources_list_file = os.path.join(cache_dir, "etc", "apt",
                                         "sources.list")

        os.makedirs(os.path.dirname(sources_list_file), exist_ok=True)
        with open(sources_list_file, "w") as f:
            f.write(self._collected_sources_list())

        # dpkg also needs to be in the rootdir in order to support multiarch
        # (apt calls dpkg --print-foreign-architectures).
        dpkg_path = shutil.which("dpkg")
        if dpkg_path:
            # Symlink it into place
            destination = os.path.join(cache_dir, dpkg_path[1:])
            if not os.path.exists(destination):
                os.makedirs(os.path.dirname(destination), exist_ok=True)
                os.symlink(dpkg_path, destination)
        else:
            logger.warning(
                "Cannot find 'dpkg' command needed to support multiarch")

        old_apt_config = os.environ.get("APT_CONFIG")
        try:
            # Only xenial needs this. If snapcraft changes to core18, this
            # variable is unnecessary.
            os.environ["APT_CONFIG"] = apt_config_path
            return self._create_cache(cache_dir, sources_list_file)
        finally:
            if old_apt_config is None:
                del os.environ["APT_CONFIG"]
            else:
                os.environ["APT_CONFIG"] = old_apt_config
Beispiel #32
0
    def _setup_apt(self, cache_dir):
        # Do not install recommends
        apt.apt_pkg.config.set('Apt::Install-Recommends', 'False')

        # Methods and solvers dir for when in the SNAP
        if common.is_snap():
            snap_dir = os.getenv('SNAP')
            apt_dir = os.path.join(snap_dir, 'apt')
            apt.apt_pkg.config.set('Dir', apt_dir)
            # yes apt is broken like that we need to append os.path.sep
            apt.apt_pkg.config.set('Dir::Bin::methods',
                                   apt_dir + os.path.sep)
            apt.apt_pkg.config.set('Dir::Bin::solvers::',
                                   apt_dir + os.path.sep)
            apt_key_path = os.path.join(apt_dir, 'apt-key')
            apt.apt_pkg.config.set('Dir::Bin::apt-key', apt_key_path)
            gpgv_path = os.path.join(snap_dir, 'bin', 'gpgv')
            apt.apt_pkg.config.set('Apt::Key::gpgvcommand', gpgv_path)
            apt.apt_pkg.config.set('Dir::Etc::Trusted',
                                   '/etc/apt/trusted.gpg')
            apt.apt_pkg.config.set('Dir::Etc::TrustedParts',
                                   '/etc/apt/trusted.gpg.d/')

        # Make sure we always use the system GPG configuration, even with
        # apt.Cache(rootdir).
        for key in 'Dir::Etc::Trusted', 'Dir::Etc::TrustedParts':
            apt.apt_pkg.config.set(key, apt.apt_pkg.config.find_file(key))

        # Clear up apt's Post-Invoke-Success as we are not running
        # on the system.
        apt.apt_pkg.config.clear('APT::Update::Post-Invoke-Success')

        self.progress = apt.progress.text.AcquireProgress()
        if is_dumb_terminal():
            # Make output more suitable for logging.
            self.progress.pulse = lambda owner: True
            self.progress._width = 0

        sources_list_file = os.path.join(
            cache_dir, 'etc', 'apt', 'sources.list')

        os.makedirs(os.path.dirname(sources_list_file), exist_ok=True)
        with open(sources_list_file, 'w') as f:
            f.write(self._collected_sources_list())

        # dpkg also needs to be in the rootdir in order to support multiarch
        # (apt calls dpkg --print-foreign-architectures).
        dpkg_path = shutil.which('dpkg')
        if dpkg_path:
            # Symlink it into place
            destination = os.path.join(cache_dir, dpkg_path[1:])
            if not os.path.exists(destination):
                os.makedirs(os.path.dirname(destination), exist_ok=True)
                os.symlink(dpkg_path, destination)
        else:
            logger.warning(
                "Cannot find 'dpkg' command needed to support multiarch")

        apt_cache = apt.Cache(rootdir=cache_dir, memonly=True)
        apt_cache.update(fetch_progress=self.progress,
                         sources_list=sources_list_file)

        return apt_cache
Beispiel #33
0
    def _setup_apt(self, cache_dir):
        # Do not install recommends
        apt.apt_pkg.config.set('Apt::Install-Recommends', 'False')

        # Methods and solvers dir for when in the SNAP
        if common.is_snap():
            snap_dir = os.getenv('SNAP')
            apt_dir = os.path.join(snap_dir, 'apt')
            apt.apt_pkg.config.set('Dir', apt_dir)
            # yes apt is broken like that we need to append os.path.sep
            apt.apt_pkg.config.set('Dir::Bin::methods',
                                   apt_dir + os.path.sep)
            apt.apt_pkg.config.set('Dir::Bin::solvers::',
                                   apt_dir + os.path.sep)
            apt_key_path = os.path.join(apt_dir, 'apt-key')
            apt.apt_pkg.config.set('Dir::Bin::apt-key', apt_key_path)
            gpgv_path = os.path.join(snap_dir, 'bin', 'gpgv')
            apt.apt_pkg.config.set('Apt::Key::gpgvcommand', gpgv_path)
            apt.apt_pkg.config.set('Dir::Etc::Trusted',
                                   '/etc/apt/trusted.gpg')
            apt.apt_pkg.config.set('Dir::Etc::TrustedParts',
                                   '/etc/apt/trusted.gpg.d/')

        # Make sure we always use the system GPG configuration, even with
        # apt.Cache(rootdir).
        for key in 'Dir::Etc::Trusted', 'Dir::Etc::TrustedParts':
            apt.apt_pkg.config.set(key, apt.apt_pkg.config.find_file(key))

        # Clear up apt's Post-Invoke-Success as we are not running
        # on the system.
        apt.apt_pkg.config.clear('APT::Update::Post-Invoke-Success')

        self.progress = apt.progress.text.AcquireProgress()
        if is_dumb_terminal():
            # Make output more suitable for logging.
            self.progress.pulse = lambda owner: True
            self.progress._width = 0

        sources_list_file = os.path.join(
            cache_dir, 'etc', 'apt', 'sources.list')

        os.makedirs(os.path.dirname(sources_list_file), exist_ok=True)
        with open(sources_list_file, 'w') as f:
            f.write(self._collected_sources_list())

        # dpkg also needs to be in the rootdir in order to support multiarch
        # (apt calls dpkg --print-foreign-architectures).
        dpkg_path = shutil.which('dpkg')
        if dpkg_path:
            # Symlink it into place
            destination = os.path.join(cache_dir, dpkg_path[1:])
            if not os.path.exists(destination):
                os.makedirs(os.path.dirname(destination), exist_ok=True)
                os.symlink(dpkg_path, destination)
        else:
            logger.warning(
                "Cannot find 'dpkg' command needed to support multiarch")

        apt_cache = apt.Cache(rootdir=cache_dir, memonly=True)
        apt_cache.update(fetch_progress=self.progress,
                         sources_list=sources_list_file)

        return apt_cache
Beispiel #34
0
def _requires_patchelf_snap(confinement: str) -> bool:
    is_snap = common.is_snap()
    is_docker_instance = common.is_docker_instance()
    is_classic = confinement == 'classic'

    return (is_classic and not is_snap and not is_docker_instance)