contrib/qa/ipkg-diff:
authorHolger Freyther <zecke@selfish.org>
Wed, 3 May 2006 20:58:49 +0000 (20:58 +0000)
committerOpenEmbedded Project <openembedded-devel@lists.openembedded.org>
Wed, 3 May 2006 20:58:49 +0000 (20:58 +0000)
    Add a work in progress ipkg-diff utilitiy to show
    differences between two directory of ipk packages files.
    Currently it does nothing.

contrib/qa/.mtn2git_empty [new file with mode: 0644]
contrib/qa/ipkg-diff/.mtn2git_empty [new file with mode: 0644]
contrib/qa/ipkg-diff/README [new file with mode: 0644]
contrib/qa/ipkg-diff/ipkg-diff [new file with mode: 0644]
contrib/qa/ipkg-diff/ipkg.py [new file with mode: 0644]

diff --git a/contrib/qa/.mtn2git_empty b/contrib/qa/.mtn2git_empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/contrib/qa/ipkg-diff/.mtn2git_empty b/contrib/qa/ipkg-diff/.mtn2git_empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/contrib/qa/ipkg-diff/README b/contrib/qa/ipkg-diff/README
new file mode 100644 (file)
index 0000000..7ddc441
--- /dev/null
@@ -0,0 +1,12 @@
+A Small utility to diff two directories of Itsy Packages.
+
+This utility will take two directories hopefully containing
+Itsy Packages (ipk) and compare the ipks. It will find deleted
+and new packages but will also compare packages with the same
+name. And check file types, sizes, control files. And one time
+it will be able to find upgrades and scan them as well.
+
+(this script is meant to be hacky, hacky, hacky and to do the
+ job)
+
+The only pattern applied is a Anti-Pattern: Time pressure
diff --git a/contrib/qa/ipkg-diff/ipkg-diff b/contrib/qa/ipkg-diff/ipkg-diff
new file mode 100644 (file)
index 0000000..d09ec4e
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# License of this file:
+#  Copy, Modify, Change, Sell it at your will.
+#
+# There is absolutely no warranty with this file
+#
+# (C) Copyright 2006 Holger Hans Peter Freyther
+
+
+
+def check_dir(name, error):
+    import os, sys
+    import stat
+    if not os.path.isdir(name):
+        print error
+        sys.exit(-1)
+
+if __name__ == "__main__":
+    import os,sys
+    if len(sys.argv) != 3:
+        print "ipkg-diff OLD NEW"
+        sys.exit(-2)
+
+    check_dir(sys.argv[1], "Source Directory does not exist")
+    check_dir(sys.argv[2], "Dest Directory does not exists")
diff --git a/contrib/qa/ipkg-diff/ipkg.py b/contrib/qa/ipkg-diff/ipkg.py
new file mode 100644 (file)
index 0000000..02aab22
--- /dev/null
@@ -0,0 +1,462 @@
+#!/usr/bin/env python
+#   Copyright (C) 2001 Alexander S. Guy <a7r@andern.org>
+#                      Andern Research Labs
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2, or (at your option)
+#   any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 59 Temple Place - Suite 330,
+#   Boston, MA 02111-1307, USA.  */
+#
+#   Copyright 2001, Russell Nelson <ipkg.py@russnelson.com>
+#   Added reading in of packages.
+#   Added missing package information fields.
+#   Changed render_control() to __repr__().
+#
+# Current Issues:
+#    The API doesn't validate package information fields.  It should be
+#        throwing exceptions in the right places.
+#    Executions of tar could silently fail.
+#    Executions of tar *do* fail, and loudly, because you have to specify a full filename,
+#        and tar complains if any files are missing, and the ipkg spec doesn't require
+#        people to say "./control.tar.gz" or "./control" when they package files.
+#        It would be much better to require ./control or disallow ./control (either)
+#        rather than letting people pick.  Some freedoms aren't worth their cost.
+
+import tempfile
+import os
+import sys
+import glob
+import md5
+import re
+import string
+import commands
+from stat import ST_SIZE
+
+class Version:
+    """A class for holding parsed package version information."""
+    def __init__(self, epoch, version):
+        self.epoch = epoch
+        self.version = version
+
+    def _versioncompare(self, ref):
+        selfversion = self.version
+        refversion = ref.version
+        while 1:
+            ## first look for non-numeric version component
+            selfm = re.match('([^0-9]*)(.*)', selfversion)
+            #print 'selfm', selfm.groups()
+            (selfalpha, selfversion) = selfm.groups()
+            refm = re.match('([^0-9]*)(.*)', refversion)
+            #print 'refm', refm.groups()
+            (refalpha, refversion) = refm.groups()
+            if (selfalpha > refalpha):
+                return 1
+            elif (selfalpha < refalpha):
+                return -1
+            ## now look for numeric version component
+            (selfnum, selfversion) = re.match('([0-9]*)(.*)', selfversion).groups()
+            (refnum, refversion) = re.match('([0-9]*)(.*)', refversion).groups()
+            #print 'selfnum', selfnum, selfversion
+            #print 'refnum', refnum, refversion
+            if (selfnum != ''):
+                selfnum = int(selfnum)
+            else:
+                selfnum = -1
+            if (refnum != ''):
+                refnum = int(refnum)
+            else:
+                refnum = -1
+            if (selfnum > refnum):
+                return 1
+            elif (selfnum < refnum):
+                return -1
+            if selfversion == '' and refversion == '':
+                return 0
+
+    def compare(self, ref):
+        if (self.epoch > ref.epoch):
+            return 1
+        elif (self.epoch < ref.epoch):
+            return -1
+        else:
+            return self._versioncompare(ref)
+
+def parse_version(versionstr):
+    epoch = 0
+    # check for epoch
+    m = re.match('([0-9]*):(.*)', versionstr)
+    if m:
+        (epochstr, versionstr) = m.groups()
+        epoch = int(epochstr)
+    return Version(epoch, versionstr)
+
+class Package:
+    """A class for creating objects to manipulate (e.g. create) ipkg
+       packages."""
+    def __init__(self, fn=None):
+       self.package = None
+       self.version = 'none'
+       self.parsed_version = None
+       self.architecture = None
+       self.maintainer = None
+       self.source = None
+       self.description = None
+       self.depends = None
+       self.provides = None
+       self.replaces = None
+       self.conflicts = None
+        self.recommends = None
+       self.suggests = None
+       self.section = None
+        self.filename_header = None
+       self.file_list = []
+        self.md5 = None
+        self.size = None
+        self.installed_size = None
+        self.filename = None
+        self.isdeb = 0
+
+       if fn:
+            # see if it is deb format
+            f = open(fn, "r")
+            magic = f.read(4)
+            f.close()
+            if (magic == "!<ar"):
+                self.isdeb = 1
+
+            # compute the MD5.
+            f = open(fn, "r")
+            sum = md5.new()
+            while 1:
+                data = f.read(1024)
+                if not data: break
+                sum.update(data)
+            f.close()
+            if sys.version[:1] > '2':
+                # when using Python 2.0 or newer
+                self.md5 = sum.hexdigest() 
+            else:
+                self.md5 = string.join(map((lambda x:"%02x" % ord(x)),sum.digest()),'')
+            stat = os.stat(fn)
+            self.size = stat[ST_SIZE]
+            self.filename = os.path.basename(fn)
+           ## sys.stderr.write("  extracting control.tar.gz from %s\n"% (fn,)) 
+            if self.isdeb:
+                control = os.popen("ar p "+fn+" control.tar.gz | tar xfzO - '*control'","r")
+            else:
+                control = os.popen("tar xfzO "+fn+" '*control.tar.gz' | tar xfzO - '*control'","r")
+            line = control.readline()
+            while 1:
+                if not line: break
+                line = string.rstrip(line)
+                lineparts = re.match(r'([\w-]*?):\s*(.*)', line)
+               if lineparts:
+                    name = string.lower(lineparts.group(1))
+                   value = lineparts.group(2)
+                   while 1:
+                       line = control.readline()
+                       if not line: break
+                       if line[0] != ' ': break
+                        line = string.rstrip(line)
+                       value = value + '\n' + line
+                    # don't allow package to override its own filename
+                    if name == "filename":
+                        self.filename_header = value
+                    else:
+                        if self.__dict__.has_key(name):
+                            self.__dict__[name] = value
+                else:
+                    line = control.readline()
+            control.close()
+            if self.isdeb:
+                data = os.popen("ar p "+fn+" data.tar.gz | tar tfz -","r")
+            else:
+                data = os.popen("tar xfzO "+fn+" '*data.tar.gz' | tar tfz -","r")
+            while 1:
+                line = data.readline()
+                if not line: break
+                self.file_list.append(string.rstrip(line))
+            data.close()
+
+       self.scratch_dir = None
+       self.file_dir = None
+       self.meta_dir = None
+
+    def read_control(self, control):
+        import os
+
+        line = control.readline()
+        while 1:
+            if not line: break
+            line = string.rstrip(line)
+            lineparts = re.match(r'([\w-]*?):\s*(.*)', line)
+            if lineparts:
+                name = string.lower(lineparts.group(1))
+                value = lineparts.group(2)
+                while 1:
+                    line = control.readline()
+                    if not line: break
+                    if line[0] != ' ': break
+                    value = value + '\n' + line
+                if name == 'size':
+                    self.size = int(value)
+                elif self.__dict__.has_key(name):
+                    self.__dict__[name] = value
+                if line[0] == '\n':
+                    return # consumes one blank line at end of package descriptoin
+            else:
+                line = control.readline()
+                pass
+        return    
+
+    def _setup_scratch_area(self):
+       self.scratch_dir = "%s/%sipkg" % (tempfile.gettempdir(),
+                                          tempfile.gettempprefix())
+       self.file_dir = "%s/files" % (self.scratch_dir)
+       self.meta_dir = "%s/meta" % (self.scratch_dir)
+
+       os.mkdir(self.scratch_dir)
+       os.mkdir(self.file_dir)
+       os.mkdir(self.meta_dir)
+
+    def set_package(self, package):
+       self.package = package
+
+    def get_package(self):
+       return self.package
+               
+    def set_version(self, version):
+       self.version = version
+        self.parsed_version = parse_version(version)
+
+    def get_version(self):
+       return self.version
+
+    def set_architecture(self, architecture):
+       self.architecture = architecture
+
+    def get_architecture(self):
+       return self.architecture
+
+    def set_maintainer(self, maintainer):
+       self.maintainer = maintainer
+
+    def get_maintainer(self):
+       return self.maintainer
+
+    def set_source(self, source):
+       self.source = source
+
+    def get_source(self):
+       return self.source
+
+    def set_description(self, description):
+       self.description = description
+
+    def get_description(self):
+       return self.description
+
+    def set_depends(self, depends):
+       self.depends = depends
+
+    def get_depends(self, depends):
+       return self.depends
+
+    def set_provides(self, provides):
+       self.provides = provides
+
+    def get_provides(self, provides):
+       return self.provides
+
+    def set_replaces(self, replaces):
+       self.replaces = replaces
+
+    def get_replaces(self, replaces):
+       return self.replaces
+
+    def set_conflicts(self, conflicts):
+       self.conflicts = conflicts
+
+    def get_conflicts(self, conflicts):
+       return self.conflicts
+
+    def set_suggests(self, suggests):
+       self.suggests = suggests
+
+    def get_suggests(self, suggests):
+       return self.suggests
+
+    def set_section(self, section):
+       self.section = section
+
+    def get_section(self, section):
+       return self.section
+
+    def get_file_list(self):
+       return self.file_list
+
+    def write_package(self, dirname):
+        buf = self.render_control()
+       file = open("%s/control" % self.meta_dir, 'w')
+       file.write(buf)
+
+       self._setup_scratch_area()
+       cmd = "cd %s ; tar cvfz %s/control.tar.gz control" % (self.meta_dir,
+                                                             self.scratch_dir)
+
+       cmd_out, cmd_in, cmd_err = os.popen3(cmd)
+       
+       while cmd_err.readline() != "":
+           pass
+
+       cmd_out.close()
+       cmd_in.close()
+       cmd_err.close()
+
+       bits = "control.tar.gz"
+
+       if self.file_list:
+               cmd = "cd %s ; tar cvfz %s/data.tar.gz" % (self.file_dir,
+                                                          self.scratch_dir)
+
+               cmd_out, cmd_in, cmd_err = os.popen3(cmd)
+
+               while cmd_err.readline() != "":
+                   pass
+
+               cmd_out.close()
+               cmd_in.close()
+               cmd_err.close()
+
+               bits = bits + " data.tar.gz"
+
+       file = "%s_%s_%s.ipk" % (self.package, self.version, self.architecture)
+       cmd = "cd %s ; tar cvfz %s/%s %s" % (self.scratch_dir,
+                                            dirname,
+                                            file,
+                                            bits)
+
+       cmd_out, cmd_in, cmd_err = os.popen3(cmd)
+
+       while cmd_err.readline() != "":
+           pass
+
+       cmd_out.close()
+       cmd_in.close()
+       cmd_err.close()
+
+    def compare_version(self, ref):
+        """Compare package versions of self and ref"""
+        if not self.version:
+            print 'No version for package %s' % self.package
+        if not ref.version:
+            print 'No version for package %s' % ref.package
+        if not self.parsed_version:
+            self.parsed_version = parse_version(self.version)
+        if not ref.parsed_version:
+            ref.parsed_version = parse_version(ref.version)
+        return self.parsed_version.compare(ref.parsed_version)
+
+    def __repr__(self):
+       out = ""
+
+       # XXX - Some checks need to be made, and some exceptions
+       #       need to be thrown. -- a7r
+
+        if self.package: out = out + "Package: %s\n" % (self.package)
+        if self.version: out = out + "Version: %s\n" % (self.version)
+        if self.depends: out = out + "Depends: %s\n" % (self.depends)
+        if self.provides: out = out + "Provides: %s\n" % (self.provides)
+        if self.replaces: out = out + "Replaces: %s\n" % (self.replaces)
+        if self.conflicts: out = out + "Conflicts: %s\n" % (self.conflicts)
+        if self.suggests: out = out + "Suggests: %s\n" % (self.suggests)
+        if self.recommends: out = out + "Recommends: %s\n" % (self.recommends)
+        if self.section: out = out + "Section: %s\n" % (self.section)
+        if self.architecture: out = out + "Architecture: %s\n" % (self.architecture)
+        if self.maintainer: out = out + "Maintainer: %s\n" % (self.maintainer)
+        if self.md5: out = out + "MD5Sum: %s\n" % (self.md5)
+        if self.size: out = out + "Size: %d\n" % int(self.size)
+        if self.installed_size: out = out + "InstalledSize: %d\n" % int(self.installed_size)
+        if self.filename: out = out + "Filename: %s\n" % (self.filename)
+        if self.source: out = out + "Source: %s\n" % (self.source)
+        if self.description: out = out + "Description: %s\n" % (self.description)
+       out = out + "\n"
+
+       return out
+
+    def __del__(self):
+       # XXX - Why is the `os' module being yanked out before Package objects
+       #       are being destroyed?  -- a7r
+        pass
+
+class Packages:
+    """A currently unimplemented wrapper around the ipkg utility."""
+    def __init__(self):
+        self.packages = {}
+        return
+
+    def add_package(self, pkg):
+        package = pkg.package
+        arch = pkg.architecture
+        name = ("%s:%s" % (package, arch))
+        if (not self.packages.has_key(name)):
+            self.packages[name] = pkg
+        
+        if pkg.compare_version(self.packages[name]) >= 0:
+            self.packages[name] = pkg
+            return 0
+        else:
+            return 1
+
+    def read_packages_file(self, fn):
+        f = open(fn, "r")
+        while 1:
+            pkg = Package()
+            pkg.read_control(f)
+            if pkg.get_package():
+                self.add_package(pkg)
+            else:
+                break
+        f.close()    
+        return
+
+    def write_packages_file(self, fn):
+        f = open(fn, "w")
+        names = self.packages.keys()
+        names.sort()
+        for name in names:
+            f.write(self.packages[name].__repr__())
+        return    
+
+    def keys(self):
+        return self.packages.keys()
+
+    def __getitem__(self, key):
+        return self.packages[key]
+
+if __name__ == "__main__":
+    package = Package()
+
+    package.set_package("FooBar")
+    package.set_version("0.1-fam1")
+    package.set_architecture("arm")
+    package.set_maintainer("Testing <testing@testing.testing>")
+    package.set_depends("libc")
+    package.set_description("A test of the APIs.")
+
+    print "<"
+    sys.stdout.write(package)
+    print ">"
+
+    package.write_package("/tmp")
+