Skip to content
This repository has been archived by the owner on May 19, 2022. It is now read-only.

depends = "$auto" shouldn't recursively add dependencies #170

Open
H-M-H opened this issue Apr 14, 2021 · 6 comments
Open

depends = "$auto" shouldn't recursively add dependencies #170

H-M-H opened this issue Apr 14, 2021 · 6 comments

Comments

@H-M-H
Copy link

H-M-H commented Apr 14, 2021

As of now not only direct dependencies are added but also dependencies of dependencies and so on. Consider the following example:

> objdump -p weylus
...
Dynamic Section:
...
  NEEDED               libgobject-2.0.so.0
...

One of the direct dependencies is libgobject-2.0.so.0, which in turn depends directly on either libffi.so.7 or libffi.so.6, depending on the system I build my binary with. But libffi is not a direct dependency of my binary. That means my binary runs fine no matter if libffi.so.7 or libffi.so.6 is installed (as long as libgobject is available).
So cargo-deb places some unnecessary constraints that reduce compatibility across distributions/versions. In my specific case a program that I built on Debian Stretch can not be installed on Ubuntu Focal because Ubuntu switched from libffi.so.6 to libffi.so.7 and the former is not available anymore (and not needed as libgobject points to the latter).

The cause is that ldd is used to gather information on dependencies:

let output = Command::new("ldd")

But ldd always resolves direct and indirect dependencies, that's why I propose to replace ldd with something like objdump -p or readelf -d.

@kornelski
Copy link
Collaborator

Interesting! Would you be able to implement this?

There's also a request for better debug info. So maybe it'd be worth adding elf parser in Rust to handle all of this properly, without needing to spawn shell tools.

@H-M-H
Copy link
Author

H-M-H commented Apr 15, 2021

Alright, I have had a look at xmas-elf and unfortunately the documentation is all but existent. Nonetheless I could get it working after some guess work. The following function reads a binary file and returns all directly required libraries:

fn extract_needed_libs<P: AsRef<std::path::Path>>(
    binary_path: P,
) -> Result<std::collections::HashSet<String>, Box<dyn std::error::Error>> {
    use xmas_elf::{
        dynamic::Tag,
        header,
        sections::{sanity_check, SectionData, ShType},
        ElfFile,
    };

    let mut libs = std::collections::HashSet::new();

    // read binary to memory
    use std::fs::File;
    use std::io::Read;
    let mut f = File::open(binary_path)?;
    let mut buf = Vec::new();
    assert!(f.read_to_end(&mut buf)? > 0);
    let elf_file = ElfFile::new(&buf)?;
    header::sanity_check(&elf_file)?;

    let mut sect_iter = elf_file.section_iter();
    // Skip the first (dummy) section
    sect_iter.next();

    // iterate over sections
    for sect in sect_iter {
        // skip things we do not care about
        if sect.get_type() != Ok(ShType::Dynamic) {
            continue;
        }
        sanity_check(sect, &elf_file)?;

        // find entries for needed dynamic libs
        if let Ok(SectionData::Dynamic64(dyns)) = sect.get_data(&elf_file) {
            for d in dyns.iter() {
                if let Ok(Tag::Needed) = d.get_tag() {
                    // lookup the string containing the name of the library
                    libs.insert(elf_file.get_dyn_string(d.get_val()? as u32)?.to_string());
                }
            }
        }
        // same procedure for 32bit
        else if let Ok(SectionData::Dynamic32(dyns)) = sect.get_data(&elf_file) {
            for d in dyns.iter() {
                if let Ok(Tag::Needed) = d.get_tag() {
                    libs.insert(elf_file.get_dyn_string(d.get_val()? as u32)?.to_string());
                }
            }
        }
    }
    Ok(libs)
}

Something to note is that the libraries returned are just their names and not the resolved paths pointing to the actual location of these libraries on the system. So calling ldd on the binary or ldconfig -p to get the file paths is still required.

@H-M-H
Copy link
Author

H-M-H commented Apr 15, 2021

I just found an even better solution, there is: https://manpages.debian.org/testing/dpkg-dev/dpkg-shlibdeps.1.en.html which handles this correctly.

H-M-H added a commit to H-M-H/Weylus that referenced this issue Apr 15, 2021
@AlexTMjugador
Copy link

AlexTMjugador commented Aug 2, 2021

Hey @H-M-H - would you mind elaborating on how to use the dpkg-shlibdeps program to generate this correct dependency information? Thanks!

AlexTMjugador added a commit to ComunidadAylas/PackSquash that referenced this issue Aug 2, 2021
Automatic dependency detection, while useful to get started, sometimes
pulls unnecessary transitive dependencies that sometimes impose too
strict requirements on implementation details that may change
unexpectedly.

Let's curate the list of dependencies that cargo-deb generates so
everything works as it should. This also fixes dependencies of cross
compiled binaries not being detected correctly, probably because
cargo-deb didn't know where to find the ldd binary of the
cross-compilation toolchain.

Related issue: mmstick/cargo-deb#170
@H-M-H
Copy link
Author

H-M-H commented Aug 3, 2021

dpkg-shlibdeps unfortunately assumes a debian package path structure, which actually is not required. Assuming you have your binary my_program located in some folder, you have to create the subfolder debian and add the file control to it (may be empty, still works). Then run dpkg-shlibdeps my_program -O, which will print the dependency information to stdout.

@AlexTMjugador
Copy link

Indeed, that Debian package path structure was confusing me, and I didn't have the time to read loads of documentation or source code to find out. Thanks.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants