首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >包装管理系统

包装管理系统
EN

Code Review用户
提问于 2015-11-20 16:09:30
回答 1查看 128关注 0票数 4

不久前,我发现了LinuxFromScratch项目。在系统启动和工作之后(经过了很大的挣扎),我意识到如果我想继续使用LFS,某种类型的包管理将是非常好的。当然,我可以安装Pacman,apt,rpm等,就像任何理智的人一样。相反,我对创建自己的简单的“包管理”系统很感兴趣,这个系统可以跟踪属于某个包的文件,等等。

我已附上数份档案,其中两份特别需要检讨:

  1. package.py -描述有关包的信息的类,例如它的名称、版本、依赖项是什么等等。
  2. fakeroot.py -此文件负责从fakeroot将包的所有文件安装到文件系统,将已安装文件的记录添加到名为Files等数据库中的表中。

package.py

代码语言:javascript
复制
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

代码语言:javascript
复制
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],))

对于这两个文件,我觉得我的设计有点拙劣。特别是,PackageFakeroot对象的设计似乎很糟糕。此外,fakeroot.py中用于复制文件/删除文件的许多类似方法似乎有些多余。你认为如何?

io_output.py

代码语言:javascript
复制
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 (这段代码的想法是通过搜索Google寻找类似于"md5 python“的东西找到的):

代码语言:javascript
复制
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 --用于从有关包的文件中获取信息:

代码语言:javascript
复制
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])

示例.crate文件:

DESCRIPTION=是“所有酒吧中最愚蠢的。”HOMEPAGE="http://www.foo.it/“REQUIRED_DEP="foobar-11-2”RECOMMENDED_DEP="foobus-1.7“”OPTIONAL_DEP=“foon-7.6a

EN

回答 1

Code Review用户

回答已采纳

发布于 2015-11-20 19:38:02

可能出现的问题

  1. 不要使用精确的路径。我没有一个名为“duncan”的用户,也不希望这样做。相反,请使用~/Documents/fakeroot。但是,我认为这是/tmp的重点,因为您应该希望在之后从系统中清除这些文件。
  2. 我的Arch安装不附带SQL预安装,所以我非常怀疑LFS会安装它(当然您可以安装它,但这正是包管理器的目的)。这是一个包装经理海事组织的糟糕的设计。
  3. verbosityio_output可以用logging代替。
  4. io_crate应该添加到package.py中,而不是作为一个导入。
  5. except应该始终有你要防范的东西!如果您将^C从程序中删除,当它在尝试中时,它将继续执行。打印“无法读取数据库.”。它可以预防sys.exit
  6. read_crate不应该抑制IOError。当您期望数组作为输出时,所实现的只是一个索引错误。还有令人困惑的回溯。
  7. 您应该始终close一个文件。一个简单的方法是使用with。打开(包)为f: f.read()
  8. 您应该逐行读取*.crate,以使处理更容易。打开(包)为f: for行在f:.
  9. 对于正确的内容,您应该使用正确的撇号。'=.*?"|".*?\n'更容易阅读。
  10. 您应该改变for line in processed的工作方式。这是没有意义的,因为你不一行行,你去关键字,价值,关键字,价值,.
  11. 您应该从read_crate返回一个字典,因为这样它就更加可靠和可扩展。
  12. md5_sum.py应该添加到fakeroot.py中。如果package.py也应该这么做的话,这也是值得商榷的。这是个小程序..。
  13. md5_sum.py看上去不错。但md5不是sha1。
  14. 我不喜欢你的Fakeroot可能只是我,但self.dirs是可怕的记忆饥饿。每个索引都有package.root + root[...],然后它的名称。我用发电机就行了。
  15. 让我们不要对每个文件执行os.path.join(package.root, root[...])。把它存储在一个变量中。
  16. 如果您为self.dirs制作了一个生成器,则不需要使用reversed。相反,将topdown=False传递给os.walk
  17. 由于您没有说明如何使用该程序,我假设您所做的事情如下: root =Fakeroot(.)root.create_dirs() root.copy_files() root.create_links()相反,root.remove_links() root.remove_files() root.remove_files(),我建议您创建一个purge函数。并在__init__或其他单个函数中执行“create”内容。
  18. 你的评论大多是毫无意义的。# If it does not exist,try: # try...都很糟糕。如果您询问任何程序员,以及相当多的非程序员,他们就会明白if not os.path.exists()意味着路径确实不存在。
  19. 要获得最好的支持,很难阅读代码,将代码行限制为79个字符。如图所示,你需要在CR上滚动,一些代码从我的编辑器.

额外积分

简化了read_crate

代码语言:javascript
复制
def read_crate(path):
    with open(path) as f:
        return {
            key: val
            for key, val in (
                line.split('=', 1)
                for line in f
            )
        }

这导致了在Package中的简单使用。

代码语言:javascript
复制
self.crate = io_crate.read_crate(self.crate_path)
self.description = self.crate['DESCRIPTION']

Making purge.

这应该是一个简单的反向os.walk。您不需要做更多的函数,因为它们将是1/2行。在算法中更容易读懂。

代码语言:javascript
复制
def purge(self):
    for root, dirs, files in os.walk(package.root, topdown=False):
        for name in files:
            path = os.path.join(root, name)
            try:
                os.remove(path)
            except ...:
                logging.warn('-- ' + path)
            else:
                logging.info('-- ' + path)
                if not os.path.islink(src):
                    self.remove_from_db(path)
        for name in dirs:
            path = os.path.join(root, name)
            try:
                os.rmdir(path)
            except ...:
                logging.warn('-- ' + path)
            else:
                logging.info('-- ' + path)

注意使用python的记录器!这是个不错的工具。您甚至可以使用它将日志保存到文件中!

复制树

这与purge正好相反,所以,我建议您这样做。但是循环的第一个应该是package.root + root[...]。比如:

代码语言:javascript
复制
fakeroot_len = len(package.fakeroot_path) + 1
for root, dirs, files in os.walk(package.fakeroot_path):
    fakeroot = os.path.join(package.root, root[fakeroot_len:len(root)])
    for name in files:
        ...
    for name in dirs:
        ...
票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/111335

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档