Example #1
0
    def test_prime_state_with_dependencies(self, mock_migrate_files,
                                           mock_load_dependencies,
                                           mock_get_symbols):
        mock_load_dependencies.return_value = {
            "/foo/bar/baz",
            "{}/lib1/installed".format(self.handler.part_install_dir),
            "{}/lib2/staged".format(self.handler._project.stage_dir),
            "{}/lib3/primed".format(self.handler._project.prime_dir),
        }
        self.get_elf_files_mock.return_value = frozenset([
            elf.ElfFile(path=os.path.join(self.handler._project.prime_dir,
                                          "bin", "1")),
            elf.ElfFile(path=os.path.join(self.handler._project.prime_dir,
                                          "bin", "2")),
        ])
        self.assertRaises(errors.NoLatestStepError, self.handler.latest_step)
        self.assertThat(self.handler.next_step(), Equals(steps.PULL))

        bindir = os.path.join(self.handler.part_install_dir, "bin")
        os.makedirs(bindir)
        open(os.path.join(bindir, "1"), "w").close()
        open(os.path.join(bindir, "2"), "w").close()

        self.handler.mark_done(steps.BUILD)
        self.handler.stage()

        # Resetting for test clarity
        mock_migrate_files.reset_mock()

        self.handler.prime()

        self.assertThat(self.handler.latest_step(), Equals(steps.PRIME))
        self.assertRaises(errors.NoNextStepError, self.handler.next_step)
        self.get_elf_files_mock.assert_called_once_with(
            self.handler._project.prime_dir, {"bin/1", "bin/2"})
        mock_migrate_files.assert_has_calls([
            call(
                {"bin/1", "bin/2"},
                {"bin"},
                self.handler._project.stage_dir,
                self.handler._project.prime_dir,
            )
        ])

        state = self.handler.get_prime_state()

        self.assertTrue(type(state) is states.PrimeState)
        self.assertTrue(type(state.files) is set)
        self.assertTrue(type(state.directories) is set)
        self.assertTrue(type(state.properties) is OrderedDict)
        self.assertThat(len(state.files), Equals(2))
        self.assertThat(state.dependency_paths, Equals({"lib3"}))
        self.assertTrue("bin/1" in state.files)
        self.assertTrue("bin/2" in state.files)
        self.assertThat(len(state.directories), Equals(1))
        self.assertTrue("bin" in state.directories)
        self.assertTrue("prime" in state.properties)
        self.assertThat(state.properties["prime"], Equals(["*"]))
        self.assertTrue(type(state.project_options) is OrderedDict)
        self.assertThat(len(state.project_options), Equals(0))
Example #2
0
    def test_prime_state_missing_libraries(self, mock_migrate_files,
                                           mock_load_dependencies,
                                           mock_get_symbols):
        self.handler = self.load_part("test_part")

        self.get_elf_files_mock.return_value = frozenset([
            elf.ElfFile(path=os.path.join(self.handler._project.prime_dir,
                                          "bin", "file"))
        ])
        # Pretend we found a system dependency, as well as a part and stage
        # dependency.
        mock_load_dependencies.return_value = set([
            "/foo/bar/baz",
            "{}/lib1/installed".format(self.handler.part_install_dir),
            "{}/lib2/staged".format(self.handler._project.stage_dir),
            "{}/lib3/primed".format(self.handler._project.prime_dir),
        ])

        self.assertRaises(errors.NoLatestStepError, self.handler.latest_step)
        self.assertThat(self.handler.next_step(), Equals(steps.PULL))

        bindir = os.path.join(self.handler.part_install_dir, "bin")
        os.makedirs(bindir)
        open(os.path.join(bindir, "file"), "w").close()

        self.handler.mark_done(steps.BUILD)
        self.handler.stage()
        mock_migrate_files.reset_mock()
        self.handler.prime()

        self.assertThat(self.handler.latest_step(), Equals(steps.PRIME))
        self.assertRaises(errors.NoNextStepError, self.handler.next_step)
        self.get_elf_files_mock.assert_called_once_with(
            self.handler._project.prime_dir, {"bin/file"})
        # Verify that only the part's files were migrated-- not the system
        # dependency.
        mock_migrate_files.assert_called_once_with(
            {"bin/file"},
            {"bin"},
            self.handler._project.stage_dir,
            self.handler._project.prime_dir,
        )

        state = self.handler.get_prime_state()

        # Verify that only the primed paths were captured.
        # The rest should be considered missing.
        self.assertThat(state.dependency_paths, Equals({"lib3"}))
Example #3
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)
Example #4
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
                )
Example #5
0
    def test_bin_echo(self):
        # Try parsing a file without the pyelftools logic mocked out
        elf_file = elf.ElfFile(path="/bin/ls")

        self.assertThat(elf_file.path, Equals("/bin/ls"))

        # The arch attribute will be a tuple of three strings
        self.assertTrue(isinstance(elf_file.arch, tuple))
        self.assertThat(len(elf_file.arch), Equals(3))
        self.assertThat(elf_file.arch[0], StartsWith("ELFCLASS"))
        self.assertThat(elf_file.arch[1], StartsWith("ELFDATA"))
        self.assertThat(elf_file.arch[2], StartsWith("EM_"))

        # We expect Python to be a dynamic linked executable with an
        # ELF interpreter.
        self.assertTrue(isinstance(elf_file.interp, str))
        self.assertThat(elf_file.interp, NotEquals(""))

        # Python is not a shared library, so has no soname or defined versions
        self.assertThat(elf_file.soname, Equals(""))
        self.assertThat(elf_file.versions, Equals(set()))

        # We expect that Python will be linked to libc
        for lib in elf_file.needed.values():
            if lib.name.startswith("libc.so"):
                break
        else:
            self.fail("Expected to find libc in needed library list")

        self.assertTrue(isinstance(lib.name, str))
        for version in lib.versions:
            self.assertTrue(isinstance(version, str),
                            "expected {!r} to be a string".format(version))

        # GCC adds a build ID to executables
        self.assertThat(elf_file.build_id, NotEquals(""))

        # If the Python interpreter is distro packaged, it probably
        # doesn't have debug info, but we don't know for sure.
        # Instead just check that it is a boolean.
        self.assertTrue(isinstance(elf_file.has_debug_info, bool))

        # Ensure type is detered as executable.
        self.assertThat(elf_file.elf_type, Equals("ET_DYN"))
Example #6
0
    def test_prime_state_with_shadowed_dependencies(self, mock_migrate_files,
                                                    mock_load_dependencies,
                                                    mock_get_symbols):
        self.get_elf_files_mock.return_value = frozenset(
            [elf.ElfFile(path="bin/1")])
        mock_load_dependencies.return_value = {
            f"{self.handler._project.prime_dir}/foo/bar/baz"
        }

        self.assertRaises(errors.NoLatestStepError, self.handler.latest_step)
        self.assertThat(self.handler.next_step(), Equals(steps.PULL))

        bindir = os.path.join(self.handler.part_install_dir, "bin")
        foobardir = os.path.join(self.handler.part_install_dir, "foo", "bar")
        os.makedirs(bindir)
        os.makedirs(foobardir)

        # Make a "binary" as well as a "library" at the same path as the one on
        # the system
        open(os.path.join(bindir, "1"), "w").close()
        open(os.path.join(foobardir, "baz"), "w").close()

        self.handler.mark_done(steps.BUILD)
        self.handler.stage()
        mock_migrate_files.reset_mock()
        self.handler.prime()

        self.assertThat(self.handler.latest_step(), Equals(steps.PRIME))
        self.assertRaises(errors.NoNextStepError, self.handler.next_step)
        self.get_elf_files_mock.assert_called_once_with(
            self.handler._project.prime_dir, {"bin/1", "foo/bar/baz"})
        mock_migrate_files.assert_called_once_with(
            {"bin/1", "foo/bar/baz"},
            {"bin", "foo", "foo/bar"},
            self.handler._project.stage_dir,
            self.handler._project.prime_dir,
        )

        state = self.handler.get_prime_state()

        self.assertTrue(type(state) is states.PrimeState)
        self.assertThat(state.dependency_paths, Equals({"foo/bar"}))
Example #7
0
    def _build(self, *, package: str = "") -> None:
        build_cmd = ["go", "build"]

        if self.options.go_buildtags:
            build_cmd.extend(
                ["-tags={}".format(",".join(self.options.go_buildtags))])

        relink_cmd = build_cmd + ["-ldflags", "-linkmode=external"]

        if self._is_using_go_mod(self.builddir) and not package:
            work_dir = self.builddir
            build_type_args = ["-o"]

            # go build ./... is not supported in go 1.11 or 1.12.
            # This will only install the main module.
            if self._get_parsed_go_version() < parse_version(
                    _GO_MOD_ENV_FLAG_REQUIRED_GO_VERSION):
                build_type_args.append(
                    os.path.join(self._install_bin_dir, self._get_module()))
            else:
                build_type_args.extend([self._install_bin_dir, "./..."])
        else:
            work_dir = self._install_bin_dir
            build_type_args = [package]

        pre_build_files = os.listdir(self._install_bin_dir)
        self._run(build_cmd + build_type_args, cwd=work_dir)
        post_build_files = os.listdir(self._install_bin_dir)

        new_files = set(post_build_files) - set(pre_build_files)

        if len(new_files) == 0:
            logger.warning(f"no binaries found from {build_cmd!r}")

        for new_file in new_files:
            binary_path = os.path.join(self._install_bin_dir, new_file)

            # Relink with system linker if executable is dynamic in order to be
            # able to set rpath later on. This workaround can be removed after
            # https://github.com/NixOS/patchelf/issues/146 is fixed.
            if self._is_classic and elf.ElfFile(path=binary_path).is_dynamic:
                self._run(relink_cmd + build_type_args, cwd=work_dir)
Example #8
0
    def _setUp(self):
        super()._setUp()

        self.core_base_path = self.useFixture(fixtures.TempDir()).path

        binaries_path = os.path.join(get_snapcraft_path(), "tests", "bin", "elf")

        new_binaries_path = self.useFixture(fixtures.TempDir()).path
        current_path = os.environ.get("PATH")
        new_path = "{}:{}".format(new_binaries_path, current_path)
        self.useFixture(fixtures.EnvironmentVariable("PATH", new_path))

        # Copy strip
        for f in ["strip", "execstack"]:
            shutil.copy(
                os.path.join(binaries_path, f), os.path.join(new_binaries_path, f)
            )
            os.chmod(os.path.join(new_binaries_path, f), 0o755)

        # Some values in ldd need to be set with core_path
        with open(os.path.join(binaries_path, "ldd")) as rf:
            with open(os.path.join(new_binaries_path, "ldd"), "w") as wf:
                for line in rf.readlines():
                    wf.write(line.replace("{CORE_PATH}", self.core_base_path))
        os.chmod(os.path.join(new_binaries_path, "ldd"), 0o755)

        # Some values in ldd need to be set with core_path
        self.patchelf_path = os.path.join(new_binaries_path, "patchelf")
        with open(os.path.join(binaries_path, "patchelf")) as rf:
            with open(self.patchelf_path, "w") as wf:
                for line in rf.readlines():
                    wf.write(line.replace("{VERSION}", self._patchelf_version))
        os.chmod(os.path.join(new_binaries_path, "patchelf"), 0o755)

        patcher = mock.patch.object(
            elf.ElfFile,
            "_extract_attributes",
            new_callable=lambda: _fake_elffile_extract_attributes,
        )
        patcher.start()
        self.addCleanup(patcher.stop)

        self._elf_files = {
            "fake_elf-2.26": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-2.26")
            ),
            "fake_elf-2.23": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-2.23")
            ),
            "fake_elf-1.1": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-1.1")
            ),
            "fake_elf-static": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-static")
            ),
            "fake_elf-shared-object": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-shared-object")
            ),
            "fake_elf-with-host-libraries": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-with-host-libraries")
            ),
            "fake_elf-bad-ldd": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-bad-ldd")
            ),
            "fake_elf-bad-patchelf": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-bad-patchelf")
            ),
            "fake_elf-with-core-libs": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-with-core-libs")
            ),
            "fake_elf-with-missing-libs": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-with-missing-libs")
            ),
            "fake_elf-with-execstack": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-with-execstack")
            ),
            "fake_elf-with-bad-execstack": elf.ElfFile(
                path=os.path.join(self.root_path, "fake_elf-with-bad-execstack")
            ),
            "libc.so.6": elf.ElfFile(path=os.path.join(self.root_path, "libc.so.6")),
            "libssl.so.1.0.0": elf.ElfFile(
                path=os.path.join(self.root_path, "libssl.so.1.0.0")
            ),
        }

        for elf_file in self._elf_files.values():
            with open(elf_file.path, "wb") as f:
                f.write(b"\x7fELF")
                if elf_file.path.endswith("fake_elf-bad-patchelf"):
                    f.write(b"nointerpreter")

        self.root_libraries = {
            "foo.so.1": os.path.join(self.root_path, "foo.so.1"),
            "moo.so.2": os.path.join(self.root_path, "non-standard", "moo.so.2"),
        }

        barsnap_elf = os.path.join(self.core_base_path, "barsnap.so.2")
        elf_list = [*self.root_libraries.values(), barsnap_elf]

        for root_library in elf_list:
            os.makedirs(os.path.dirname(root_library), exist_ok=True)
            with open(root_library, "wb") as f:
                f.write(b"\x7fELF")