Skip to main content
6 of 10
Added additional files that might be useful for understanding the question

Package management system

A short time ago, I discovered the LinuxFromScratch project. After getting a system up and working (after much struggling), I realized that if I wanted to continue using LFS, some sort of package management would be quite nice. Of course I could have installed Pacman, apt-get, rpm, etc. like any sane person, perhaps. Instead, I was interested in creating my own simple 'package management' system that would keep track of files that belonged to a certain package, etc. I have attached two files:

  1. package.py – a class that describes information about a package such as its name, its version, what its dependencies are, etc
  2. fakeroot.py – this file is in charge of installing all of a package's files to the filesystem from a fakeroot, adding records of the installed files to a table in a database called Files, etc.

Here is package.py:

import io_crate, os.path, sqlite3, core_regex, datetime, io_output

class Package:
    crate_extension = '.crate'
    database_location = 'proto.db'

    def __init__(self, name, verbosity = 0, fp = '/home/duncan/Documents/fakeroot', rp = '/home/duncan/Documents/install', ap = '/usr/src/archive/', cp = '/home/duncan/Documents/package/'):
        # Setup database stuff
        self.connection = sqlite3.connect(self.database_location)
        self.connection.text_factory = str
        self.db_cursor = self.connection.cursor()
        
        # Setup path and name
        self.name = name
        self.fakeroot_path = os.path.join(fp, self.name)
        self.root = rp
        self.archive_path = ap
        self.crate_path = os.path.join(cp, self.name) + self.crate_extension
        
        # Setup description taken from .crate file
        crate_contents = io_crate.read_crate(self.crate_path)
        self.description = crate_contents[0][1]
        self.homepage = crate_contents[1][1]
        self.optional_deps = crate_contents[2][1]
        self.recommended_deps = crate_contents[3][1]
        self.required_deps = crate_contents[4][1]
        
        self.verbosity = verbosity

    def add_to_db(self):
        """Adds self.name to the package database."""
        if self.is_in_db():
            return 0
        else:
            # no need to try..except this because is_in_db.
            self.db_cursor.execute('INSERT INTO Packages VALUES(?, ?);', (self.name, datetime.datetime.today()))
            io_output.vbprint(self.verbosity, '{} added to the databased'.format(self.name))
            return 1
        
    def remove_from_db(self):
        """Removes self from the database of packages."""
        if self.is_in_db():
            self.db_cursor.execute('DELETE FROM Packages WHERE Package=?;', (self.name,))
            io_output.vbprint(self.verbosity, '{} removed from database'.format(self.name))
            return 1
        return 0
    
    def is_in_db(self):
        """Checks if the name of self is contained in the packages database."""
        try:
            self.db_cursor.execute('SELECT * FROM Packages WHERE Package=?;', (self.name,))
        except:
            print 'Couldn\'t read the database at {}'.format(self.database_location)

        if not self.db_cursor.fetchone():
            return 0
        return 1
    
    def __del__(self):
        self.connection.commit()
        self.connection.close()

and here is fakeroot.py:

import os, md5_gen, shutil, io_output

class Fakeroot():
    def __init__(self, package):
        self.dirs = [] # A list based on the files in the fakeroot.
        self.files = [] # A list of all the directories to be created in package.root.
        self.links = [] # A list of links from the fakeroot
        self.package = package
        
        for root, dirs, files in os.walk(package.fakeroot_path):
            for f in files:
                new_dir = os.path.normpath(os.path.join(package.root, root[len(package.fakeroot_path) + 1:len(root)]))
                src = os.path.join(root, f)
                dest = os.path.join(new_dir, f) 
                
                if (os.path.islink(src)):
                    self.links.append([root, new_dir, f])
                else:
                    self.files.append([src, dest])
            for d in dirs:
                self.dirs.append(os.path.join(package.root, root[len(package.fakeroot_path) + 1: len(root)], d))

    def create_dirs(self):
    # Go through self.dirs and check to see if a directory exists. If it does not, create it.
        for d in self.dirs:
            if not os.path.exists(d): # If the directory does not exist, run the equivalent of
                os.makedirs(d) # mkdir -p on it.
                io_output.vbprint(self.package.verbosity, 'DD {}'.format(d))
            else:
                io_output.vbprint(self.package.verbosity, 'UU {}'.format(d))
                continue

    def remove_dirs(self):
        for d in reversed(self.dirs): # remove the directories that are the highest in the tree first.
            if os.path.isdir(d): # If the directory exists
                if not os.listdir(d): # and is empty...
                    os.rmdir(d) # remove it.
                    io_output.vbprint(self.package.verbosity, '-- {}'.format(d))
                else: # If it is not empty.
                    io_output.vbprint(self.package.verbosity, 'UU {}'.format(d))
            else: # If it does not exist.
                io_output.vbprint(self.package.verbosity, '?? {}'.format(d))

    def copy_files(self):
        for f in self.files:
            if os.path.exists(f[1]): # If the file exists, show that it is being used.
                print 'Overwrite {}???'.format(f[1])
                # TODO
                # TODO: "Code" for overwiting stuff goes here.
                # TODO: If yes, copy the file and add it to the DB.
                # TODO: Perhaps an option to overwrite all files could be useful?
                # TODO
                continue
            else: # If it does not exist,
                try: # try...
                    shutil.copy2(f[0], f[1]) # copying it!
                    self.add_to_db(f)
                    io_output.vbprint(self.package.verbosity, '++ {}'.format(f[1]))
                except:
                    io_output.vbprint(self.package.verbosity, 'Failed to copy a file...rolling back changes.')
                    #self.remove_files()
                    break

    def remove_files(self):
        for f in self.files:
            if os.path.exists(f[1]):
                os.remove(f[1])
                self.remove_from_db(f)
                io_output.vbprint(self.package.verbosity, '-- {}'.format(f[1]))
            else:
                io_output.vbprint(self.package.verbosity, '?? {}'.format(f[1]))

    def create_links(self):
        for l in self.links:
            try:
                if not os.path.exists(os.path.join(l[1], l[2])):
                    linkto = os.path.join(l[1], os.readlink(os.path.join(l[0], l[2])))
                    os.symlink(linkto, os.path.join(l[1], l[2]))
                    io_output.vbprint(self.package.verbosity, 'LL {}'.format(l[2]))
                else:
                    print 'Overwrite existing link {}??'.format(l[2])
                    # TODO: See above todo for more info. ^^^^
            except:
                print 'Couldn\'t find the specified fakeroot!'
                break
                
    def remove_links(self):
        for l in self.links:
            try:
                os.remove(os.path.join(l[1], l[2]))
                io_output.vbprint(self.package.verbosity, '-- {}.'.format(l))
            except:
                raise OSError('\nFailed to remove the link `{}`'.format(os.path.join(l[1], l[2])))

    def add_to_db(self, f):
        """Returns 0 if the db can't be read, 1 if the file is added."""    
        try:
            self.package.db_cursor.execute('INSERT INTO Files VALUES(?, ?, ?, ?);', (f[1], md5_gen.generate(f[1]), 
                                                    os.path.getsize(f[1]),
                                                    self.package.name))
            return 1
        except:
            return 0

    def remove_from_db(self, f):
        self.package.db_cursor.execute('DELETE FROM Files WHERE Path=?;', (f[1],))

In regard to these two files, I feel that my design is somewhat crappy. In particular, the design of the Package and Fakeroot objects seems bad. Additionally, the many similar methods in fakeroot.py for copying files / removing files seem somewhat redundant. What do you think?

Here is io_output.py:

def vbprint(verb_l, message):
    """Take a level of verboseness, `verb_l`, and a message to be printed. If verb_l > 0, the message is printed."""
    if verb_l != 0:
        print message

and here is md5_gen.py (the idea for this code was found by searching Google for something like md5 python:

import hashlib

def generate(filename):
    """Return the md5 hash of filename.

    Keyword arguments:
    filename -- The path and name of the file to calculate the SHA1 hash of
    """
    # Returns the md5 hash of the given file.
    md5 = hashlib.md5()

    with open(filename, 'rb') as file:
        while True:
            block = file.read(2**10)
            if not block:
                break
            md5.update(block)
        return md5.hexdigest()

Here's io_crate.py -- used to get information from files about packages:

from re import split

def read_crate(package):
    """Opens a crate file, reads the variables, and returns them as a sorted list of tuples.

    Keyword arguments:
    package -- A Package object, as defined in package.py
    """
    # Returns 0 if the crate file cannot be read, 1 on success.
    try:
        crate_text = open(package).read()
    except IOError:
        print 'Failed to read the crate file {}.'.format(package.crate_path)
        return 0

    processed = split("=.*?\"|\".*?\n", crate_text)

    lastline = ""
    final = []

    for line in processed:
        if lastline.isupper():
            if '_DEP' in lastline:
                final.append((lastline, split(' ', line)))
            else:
                final.append((lastline, line))
        lastline = line
    return sorted(final, key=lambda pos: pos[0])

and finally...whew...here is a sample .crate file:

DESCRIPTION="The most foo of all bars."
HOMEPAGE="http://www.foo.it/"   
REQUIRED_DEP="foobar-11-2"
RECOMMENDED_DEP="foobus-1.7"    
OPTIONAL_DEP="foon-7.6a"