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))
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"}))
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)
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 )
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"))
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"}))
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)
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")