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, etcetera. I have attached several files, two of which I think are particularly in need of review: 1. **package.py** – a class that describes information about a package such as its name, its version, what its dependencies are, etcetera 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, etcetera **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() **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? **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 **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() **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]) **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"