22

To support offline installation of multiple .deb files, I'm using:

sudo dpkg -i dependencies/*.deb

I've noticed some packages fail using this method because of the order they installed

For example, this is what happens when I install mariadb-server-5.5 and its dependencies:

these are the files

this is the error

How can I Install *.deb files while respecting their dependencies?

  • I'm trying to avoid setting up a local repo, since it's harder to maintain.
  • As a workaround, I run command dpkg -i *.deb twice.
d a i s y
  • 5,511
  • 1
    Have you tried GDebi? It's a tad more intelligent than plain dpkg concerning dependency management. You can simulate the resulting actions with the --apt-line flag. – David Foerster Dec 10 '15 at 11:48
  • This is an old question but hopefully you can get back to the community on how you did this? I'd be interested in whether you tried 1) Just running the installation twice (second pass should be OK?) or 2) Any other alternative, like apt-get install -f – pzkpfw Mar 06 '17 at 06:05
  • 1
    @pzkpfw currently just running the installation twice. Planning to write a python script to sort the dependencies in topological order. Will update as soon as I'll deploy it – Jossef Harush Kadouri Mar 06 '17 at 06:15
  • If it ain't broke don't fix it I guess :) – pzkpfw Mar 06 '17 at 08:33

3 Answers3

8

Topological sort (via script)

The command dpkg -i packages/*.deb is problematic - does not properly respect the order packages should be installed (even if you supply all required packages).

Custom script to rule them all

Under the assumption your debian distro has python installed (mine is ubuntu 14.04 LTS and comes with python27)

Aside the offline .deb packages directory, provide a script that;

  • Extract metadata and Topological sorts all candidate packages
  • Uses dpkg -i to install the sorted packages in proper order they should be installed

For example, execute this command to install all pre-collected offline packages

sudo python install.py
  • Your directory structure should look like this

    enter image description here


install.py

#!/usr/bin/env python

import os
import re
import subprocess
import logging

import sys

rootLogger = logging.getLogger()
rootLogger.setLevel(logging.INFO)
consoleHandler = logging.StreamHandler(sys.stdout)
consoleHandler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
rootLogger.addHandler(consoleHandler)

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))


class TopologicalSort(object):
    def __init__(self, dependency_map):
        self._dependency_map = dependency_map
        self._already_processed = set()

    def _get_dependencies(self, item, root=None):

        if not root:
            root = item

        elif root == item:
            logging.warn("circular dependency detected in '{}'".format(item))
            raise StopIteration()

        dependencies = self._dependency_map.get(item, [])
        for dependency in dependencies:

            if dependency in self._already_processed:
                continue

            self._already_processed.add(dependency)

            for sub_dependency in self._get_dependencies(dependency, root=root):
                yield sub_dependency

            yield dependency

    def sort(self):
        # Reduction, connect all nodes to a dummy node and re-calculate
        special_package_id = 'topological-sort-special-node'
        self._dependency_map[special_package_id] = self._dependency_map.keys()
        sorted_dependencies = self._get_dependencies(special_package_id)
        sorted_dependencies = list(sorted_dependencies)
        del self._dependency_map[special_package_id]

        # Remove "noise" dependencies (only referenced, not declared)
        sorted_dependencies = filter(lambda x: x in self._dependency_map, sorted_dependencies)
        return sorted_dependencies


class DebianPackage(object):
    def __init__(self, file_path):
        metadata = subprocess.check_output('dpkg -I {}'.format(file_path), shell=True)
        metadata = metadata.replace('\n ', '\n')
        self._metadata = metadata
        self.id = self._get('Package')
        self.dependencies = list(self._get_dependencies())
        self.file_path = file_path

    def _get_dependencies(self):
        dependencies = self._get('Depends') + ',' + self._get('Pre-Depends')
        dependencies = re.split(r',|\|', dependencies)
        dependencies = map(lambda x: re.sub(r'\(.*\)|:any', '', x).strip(), dependencies)
        dependencies = filter(lambda x: x, dependencies)
        dependencies = set(dependencies)
        for dependency in dependencies:
            yield dependency

    def _get(self, key):
        search = re.search(r'\n{key}:(.*)\n[A-Z]'.format(key=key), self._metadata)
        return search.group(1).strip() if search else ''


def sort_debian_packages(directory_path):
    file_names = os.listdir(directory_path)
    debian_packages = {}
    dependency_map = {}
    for file_name in file_names:

        file_path = os.path.join(directory_path, file_name)

        if not os.path.isfile(file_path):
            continue

        debian_package = DebianPackage(file_path)
        debian_packages[debian_package.id] = debian_package
        dependency_map[debian_package.id] = debian_package.dependencies

    sorted_dependencies = TopologicalSort(dependency_map).sort()
    sorted_dependencies = map(lambda package_id: debian_packages[package_id].file_path, sorted_dependencies)
    return sorted_dependencies


def main():
    # ------------------
    # Sort the packages using topological sort

    packages_dir_path = os.path.join(SCRIPT_DIR, 'packages')
    logging.info('sorting packages in "{}" using topological sort ...'.format(packages_dir_path))
    sorted_packages = sort_debian_packages(packages_dir_path)

    # ------------------
    # Install the packages in the sorted order

    for index, package_file_path in enumerate(sorted_packages):
        command = 'dpkg -i {}'.format(package_file_path)
        logging.info('executing "{}" ...'.format(command))
        subprocess.check_call(command, shell=True)


if __name__ == '__main__':

    if os.geteuid() != 0:
        logging.error('must be run as root')
        sys.exit(1)

    try:
        main()
    except:
        logging.error('failed to install packages', exc_info=True)
        sys.exit(1)
  • Probably this script has become old and needs an update? Here's the message I get: metadata = metadata.replace('\n ', '\n') TypeError: a bytes-like object is required, not 'str' – aderchox Aug 09 '20 at 10:50
  • 1
    @aderchox - in python3. to fix, you need to add metadata.decode().replace(... – Jossef Harush Kadouri Aug 09 '20 at 11:05
  • Ah thanks, but to my surprise I found an easier way for apt sudo apt install ./* && sudo apt --fix-broken install ./*. – aderchox Aug 09 '20 at 11:10
6

You can try with -R and install options:

sudo dpkg -R --install dependencies/

-R Recursive handle all regular files matching pattern *.deb found at specific directories and all of its subdirectories

mauek unak
  • 218
  • 3
  • 12
1

I'm not a dpkg expert, but its --force-depends flag seems to work.

I'd make sure all the dependencies are fulfilled first using dkpg-deb -I <package.deb> or better yet, recursively for every .deb you have in the current folder:

ls -1 -d "$PWD"/* | xargs --max-lines=1 dpkg-deb -I | grep --only-matching --perl-regexp "(?<=Depends: ).*" | sed -z 's/, /\n/g' | sort | uniq -u

If all these dependencies are contained in the pwd or already installed on the system, then simply install all .debs in the pwd with:

dpkg --force-depends -i ./*

To read more about the force flag:

man dpkg --force-help