Beispiel #1
0
def _create_bins(package_json, directory):
    bin_entry = package_json.get("bin")
    if not bin_entry:
        return

    bin_dir = os.path.join(directory, "bin")
    os.makedirs(bin_dir, exist_ok=True)

    if type(bin_entry) == dict:
        binaries = bin_entry
    elif type(bin_entry) == str:
        # Support for scoped names of the form of @org/name
        name = package_json["name"]
        binaries = {name[name.find("/") + 1 :]: bin_entry}
    else:
        raise errors.SnapcraftEnvironmentError(
            "The plugin is not prepared to handle bin entries of "
            "type {!r}".format(type(bin_entry))
        )

    for bin_name, bin_path in binaries.items():
        target = os.path.join(bin_dir, bin_name)
        # The binary might be already created from upstream sources.
        if os.path.exists(os.path.join(target)):
            continue
        source = os.path.join("..", bin_path)
        os.symlink(source, target)
        # Make it executable
        os.chmod(os.path.realpath(target), 0o755)
Beispiel #2
0
def _call_function(function_name, args=None):
    if not args:
        args = {}

    data = {"function": function_name, "args": args}

    # We could load the FIFOs in `run` and shove them in the context, but
    # that's too early to error out if these variables aren't defined. Doing it
    # here allows one to run e.g. `snapcraftctl build --help` without needing
    # these variables defined, which is a win for usability.
    try:
        call_fifo = os.environ["SNAPCRAFTCTL_CALL_FIFO"]
        feedback_fifo = os.environ["SNAPCRAFTCTL_FEEDBACK_FIFO"]
    except KeyError as e:
        raise errors.SnapcraftEnvironmentError(
            "{!s} environment variable must be defined. Note that this "
            "utility is only designed for use within a snapcraft.yaml".format(e)
        ) from e

    with open(call_fifo, "w") as f:
        f.write(json.dumps(data))
        f.flush()

    with open(feedback_fifo, "r") as f:
        feedback = f.readline().strip()

    # Any feedback is considered a fatal error.
    if feedback:
        sys.exit(-1)
Beispiel #3
0
def _find_machine(deb_arch):
    for machine in _ARCH_TRANSLATIONS:
        if _ARCH_TRANSLATIONS[machine].get("deb", "") == deb_arch:
            return machine
        elif _ARCH_TRANSLATIONS[machine].get("uts_machine", "") == deb_arch:
            return machine

    raise errors.SnapcraftEnvironmentError(
        "Cannot set machine from deb_arch {!r}".format(deb_arch))
Beispiel #4
0
def get_python2_path(root):
    """Return a valid PYTHONPATH or raise an exception."""
    python_paths = glob.glob(
        os.path.join(root, "usr", "lib", "python2*", "dist-packages"))
    try:
        return python_paths[0]
    except IndexError:
        raise errors.SnapcraftEnvironmentError(
            "PYTHONPATH cannot be set for {!r}".format(root))
Beispiel #5
0
def init():
    """Initialize a snapcraft project."""
    snapcraft_yaml_path = os.path.join("snap", "snapcraft.yaml")

    if os.path.exists(snapcraft_yaml_path):
        raise errors.SnapcraftEnvironmentError(
            "{} already exists!".format(snapcraft_yaml_path))
    elif os.path.exists("snapcraft.yaml"):
        raise errors.SnapcraftEnvironmentError(
            "snapcraft.yaml already exists!")
    elif os.path.exists(".snapcraft.yaml"):
        raise errors.SnapcraftEnvironmentError(
            ".snapcraft.yaml already exists!")
    text = _TEMPLATE_YAML
    with contextlib.suppress(FileExistsError):
        os.mkdir(os.path.dirname(snapcraft_yaml_path))
    with open(snapcraft_yaml_path, mode="w") as f:
        f.write(text)

    return snapcraft_yaml_path
Beispiel #6
0
 def cross_compiler_prefix(self):
     try:
         # cross-compilation of x86 32bit binaries on a x86_64 host is
         # possible by reusing the native toolchain - let Kbuild figure
         # it out by itself and pass down an empty cross-compiler-prefix
         # to start the build
         if self.__platform_arch == "x86_64" and self.__target_machine == "i686":
             return ""
         return self.__machine_info["cross-compiler-prefix"]
     except KeyError:
         raise errors.SnapcraftEnvironmentError(
             "Cross compilation not supported for target arch {!r}".format(
                 self.__target_machine))
Beispiel #7
0
    def _env_dict(self, root):
        env = dict()
        rubydir = os.path.join(root, "lib", "ruby")

        # Patch versions of ruby continue to use the minor version's RUBYLIB,
        # GEM_HOME, and GEM_PATH. Fortunately there should just be one, so we
        # can detect it by globbing instead of trying to determine what the
        # minor version is programmatically
        versions = glob.glob(os.path.join(rubydir, "gems", "*"))

        # Before Ruby has been pulled/installed, no versions will be found.
        # If that's the case, we won't define any Ruby-specific variables yet
        if len(versions) == 1:
            ruby_version = os.path.basename(versions[0])

            rubylib = os.path.join(rubydir, ruby_version)

            # Ruby uses some pretty convoluted rules for determining its
            # arch-specific RUBYLIB. Rather than try and duplicate that logic
            # here, let's just look for a file that we know is in there:
            # rbconfig.rb. There should only be one.
            paths = glob.glob(os.path.join(rubylib, "*", "rbconfig.rb"))
            if len(paths) != 1:
                raise errors.SnapcraftEnvironmentError(
                    "Expected a single rbconfig.rb, but found {}".format(
                        len(paths)))

            env["RUBYLIB"] = "{}:{}".format(rubylib, os.path.dirname(paths[0]))
            env["GEM_HOME"] = os.path.join(rubydir, "gems", ruby_version)
            env["GEM_PATH"] = os.path.join(rubydir, "gems", ruby_version)
        elif len(versions) > 1:
            raise errors.SnapcraftEnvironmentError(
                "Expected a single Ruby version, but found {}".format(
                    len(versions)))

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

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

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

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

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

            link_contents = os.readlink(dynamic_linker_path)
            if os.path.isabs(link_contents):
                dynamic_linker_path = os.path.join(core_path,
                                                   link_contents.lstrip("/"))
            else:
                dynamic_linker_path = os.path.join(
                    os.path.dirname(dynamic_linker_path), link_contents)
Beispiel #9
0
 def _get_target(self) -> str:
     # Cf. rustc --print target-list
     targets = {
         "armhf": "armv7-{}-{}eabihf",
         "arm64": "aarch64-{}-{}",
         "i386": "i686-{}-{}",
         "amd64": "x86_64-{}-{}",
         "ppc64el": "powerpc64le-{}-{}",
         "s390x": "s390x-{}-{}",
     }
     rust_target = targets.get(self.project.deb_arch)
     if not rust_target:
         raise errors.SnapcraftEnvironmentError(
             "{!r} is not supported as a target architecture ".format(
                 self.project.deb_arch))
     return rust_target.format("unknown-linux", "gnu")
Beispiel #10
0
    def build(self):
        super().build()
        self.run(
            ["shards", "build", "--without-development"] +
            self.options.crystal_build_options,
            self.builddir,
        )

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

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

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

        os.makedirs(install_bin_path, exist_ok=True)

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

            elf_dependencies_path = elf_file.load_dependencies(
                root_path=self.installdir,
                core_base_path=common.get_installed_snap_path(
                    self.project._get_build_base()),
                arch_triplet=self.project.arch_triplet,
                content_dirs=self.project._get_provider_content_dirs(),
            )
            for elf_dependency_path in elf_dependencies_path:
                lib_install_path = os.path.join(self.installdir,
                                                elf_dependency_path[1:])
                os.makedirs(os.path.dirname(lib_install_path), exist_ok=True)
                if not os.path.exists(lib_install_path):
                    file_utils.link_or_copy(elf_dependency_path,
                                            lib_install_path,
                                            follow_symlinks=True)
Beispiel #11
0
def stage_runtime_dependencies(
    part_src: str,
    part_install: str,
    part_build: str,
    arch_triplet: str,
    content_dirs: str,
):
    build_path = os.path.join(part_build, "bin")
    install_path = os.path.join(part_install, "bin")

    if not os.path.exists(build_path):
        raise errors.SnapcraftEnvironmentError(
            "No binaries were built. Ensure the shards.yaml contains valid targets."
        )

    bin_paths = (os.path.join(build_path, b) for b in os.listdir(build_path))
    elf_files = (elf.ElfFile(path=b) for b in bin_paths if elf.ElfFile.is_elf(b))
    os.makedirs(install_path, exist_ok=True)

    # convert colon-delimited paths into a set
    if content_dirs == "":
        content_dirs_set = set()
    else:
        content_dirs_set = set(content_dirs.split(":"))

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

        elf_dependencies_path = elf_file.load_dependencies(
            root_path=part_install,
            core_base_path=common.get_installed_snap_path("core20"),
            arch_triplet=arch_triplet,
            content_dirs=content_dirs_set,
        )

        for elf_dependency_path in elf_dependencies_path:
            lib_install_path = os.path.join(part_install, elf_dependency_path[1:])
            os.makedirs(os.path.dirname(lib_install_path), exist_ok=True)
            if not os.path.exists(lib_install_path):
                file_utils.link_or_copy(
                    elf_dependency_path, lib_install_path, follow_symlinks=True
                )
Beispiel #12
0
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-<linker_version>.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_legacy.internal.errors.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 errors.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
Beispiel #13
0
    def pull(self):
        """Copy source into build directory and fetch dependencies.

        Catkin packages can specify their system dependencies in their
        package.xml. In order to support that, the Catkin packages are
        interrogated for their dependencies here. Since `stage-packages` are
        already installed by the time this function is run, the dependencies
        from the package.xml are pulled down explicitly.
        """

        super().pull()

        # There may be nothing contained within the source but a rosinstall
        # file. We need to use it to flesh out the workspace before continuing
        # with the pull.
        if self.options.rosinstall_files or self.options.recursive_rosinstall:
            wstool = _ros.wstool.Wstool(
                self._ros_package_path,
                self._wstool_path,
                self.project,
                self.project._get_build_base(),
            )
            wstool.setup()

            source_path = self.sourcedir
            if self.options.source_subdir:
                source_path = os.path.join(self.sourcedir,
                                           self.options.source_subdir)

            # Recursively handling rosinstall files is a superset of handling
            # individual rosinstall files. If both are specified, the recursive
            # option will cover it.
            if self.options.recursive_rosinstall:
                _recursively_handle_rosinstall_files(wstool, source_path)
            else:
                # The rosinstall files in the YAML are relative to the part's
                # source. However, _handle_rosinstall_files requires absolute
                # paths.
                rosinstall_files = set()
                for rosinstall_file in self.options.rosinstall_files:
                    rosinstall_files.add(
                        os.path.join(source_path, rosinstall_file))

                _handle_rosinstall_files(wstool, rosinstall_files)

        # Make sure the package path exists before continuing. We only care
        # about doing this if there are actually packages to build, which is
        # indicated both by self.catkin_packages being None as well as a
        # non-empty list.
        packages_to_build = (self.catkin_packages is None
                             or len(self.catkin_packages) > 0)
        if packages_to_build and not os.path.exists(self._ros_package_path):
            raise CatkinPackagePathNotFoundError(self._ros_package_path)

        # Validate the underlay. Note that this validation can't happen in
        # __init__ as the underlay will probably only be valid once a
        # dependency has been staged.
        catkin = None
        underlay_build_path = None
        dependency_workspaces = [self.rosdir]
        if self.options.underlay:
            underlay_build_path = self.options.underlay["build-path"]
        if underlay_build_path:
            if not os.path.isdir(underlay_build_path):
                raise errors.SnapcraftEnvironmentError(
                    "Requested underlay ({!r}) does not point to a valid "
                    "directory".format(underlay_build_path))

            if not os.path.isfile(os.path.join(underlay_build_path,
                                               "setup.sh")):
                raise errors.SnapcraftEnvironmentError(
                    "Requested underlay ({!r}) does not contain a "
                    "setup.sh".format(underlay_build_path))

            dependency_workspaces.append(underlay_build_path)
            self._generate_snapcraft_setup_sh(self.installdir,
                                              underlay_build_path)

        # Use catkin_find to discover dependencies already in the underlay
        catkin = _Catkin(self._rosdistro, dependency_workspaces,
                         self._catkin_path, self.project)
        catkin.setup()

        # Use rosdep for dependency detection and resolution
        rosdep = _ros.rosdep.Rosdep(
            ros_distro=self._rosdistro,
            ros_version="1",
            ros_package_path=self._ros_package_path,
            rosdep_path=self._rosdep_path,
            ubuntu_distro=_BASE_TO_UBUNTU_RELEASE_MAP[
                self.project._get_build_base()],
            base=self.project._get_build_base(),
            target_arch=self.project._get_stage_packages_target_arch(),
        )
        rosdep.setup()

        self._setup_dependencies(rosdep, catkin)
Beispiel #14
0
def _get_nodejs_base(node_engine, machine):
    if machine not in _NODEJS_ARCHES:
        raise errors.SnapcraftEnvironmentError(
            "architecture not supported ({})".format(machine)
        )
    return _NODEJS_BASE.format(version=node_engine, arch=_NODEJS_ARCHES[machine])
Beispiel #15
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 errors.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 errors.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)