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:
- package.py – a class that describes information about a package such as its name, its version, what its dependencies are, etc
- 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"