mmstick / cargo-deb

A cargo subcommand that generates Debian packages from information in Cargo.toml
615 stars 50 forks source link

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

Open H-M-H opened 3 years ago

H-M-H commented 3 years ago

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: https://github.com/mmstick/cargo-deb/blob/ac4b338be0cbae5222e5f2598eb3928f518eefc2/src/dependencies.rs#L11 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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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.

AlexTMjugador commented 3 years ago

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

H-M-H commented 3 years ago

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 commented 3 years ago

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.