[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

dpkg-source 2.0



Hello,

I've been working on a replacement of dpkg-source in Python for a while,
and I just learned that there is apparently another implementation in
progress.  We should merge.  I'm attaching all the code I have here
(which is very alpha, but not too far from working), along with a design
document.





* Support for arbitrary compression (.Z, .gz, .bz2) and archive types
  (.tar, .zip)

* In non-native packages, the debian/ directory is just a tarball.
  This solves the problem of adding new binary files, etc.

* The directory debian/patches is searched for patches to apply after
  the source is unpackaged.  Patches can be xdeltas or just regular
  patches.

* There will be an optional file patch-control.xml which specifies the
  dependencies among different patches, which upstream sources they
  apply to, and other attributes such as allowed fuzziness and patch
  levels (for text patches).

* To support multiple upstream sources, there will be a new file
  debian/source-control.xml.  It can specify things such as what
  directory name the various upstream sources unpack into, in order to
  recognize it.
  
  

#!/usr/bin/python
# 
# ArchiveMachine

# A class for handling archiving/unarchiving files
# e.g. .tar.gz, .zip, .tar.bz2...
#
# Copyright 2002 Colin Walters <walters@gnu.org>
#
# This file 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 of the License, 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.

import re, os, sys

# FIXME FIXME
def shell_quote_filename(filename):
   return filename

class CompressionInability(Exception):
   def __init__(self):
      Exception.__init__(self)

class CompressionMachine:
   _compression_table = { '.gz' : ("gzip %i",  'gzip -dc %i'),
                          '.bz2' : ("bzip2 %i", 'bunzip2 -dc %i'),
                          '.Z' : (-1, "gunzip %i", 'gzip -dc %i') }

   _re_table = {}

   def __init__(self):
      for comprtype in self._compression_table.keys():
         self._re_table[comprtype] = re.compile(re.escape(comprtype) + "$")

   def _lookup_cmd(self, filename, type):
      for comprtype in self._compression_table.keys():
         typere = self._re_table[comprtype]
         if typere.search(filename):
            print "using compression type " + comprtype
            comprcmds = self._compression_table[comprtype]
            if comprcmds[type] == -1:
               raise CompressionInability()
            return (comprtype, comprcmds[type]) 
      return ('', None)

   def lookup_maybe_compressed(self, filename):
      for comprtype in self._compression_table.keys():
         name = filename + comprtype
         if (os.access(name, os.R_OK)):
            return name

   def get_uncompression_type(self, filename):
      cmd = self._lookup_cmd(filename, 1)
      return cmd[0]

   def get_uncompression_pipe_command(self, filename):
      cmd = self._lookup_cmd(filename, 1)
      if cmd[0] == '':
         return ('', None)
      return (re.sub(re.escape(cmd[0]) + "$", '', filename), re.sub('%i', shell_quote_filename(filename), cmd[1]))

   def get_compression_command(self, inputfilename, outputfilename):
      cmd = self._lookup_cmd(filename, 0)
      if cmd == '':
         return ""
      return (cmd[0], re.sub('%i', shell_quote_filename(inputfilename), cmd[1]))

class UnknownArchive:
   def get_unarchive_stdin_command(self, outdir):
      return None

   def get_archive_command(self, filename):
      return None

   def _substitute_unarchive_command(self, command, filename, outdir):
      return re.sub('%o', shell_quote_filename(outdir), re.sub('%i', shell_quote_filename(filename), command))

   def _substitute_unarchive_stdin_command(self, command, outdir):
      return re.sub('%o', shell_quote_filename(outdir), command)

   def _substitute_archive_command(self, command, filename, suffix):
      return re.sub('%o', shell_quote_filename(shell_quote_filename(filename) + suffix), re.sub('%i', shell_quote_filename(filename), command))

class TarArchive(UnknownArchive):
   def get_unarchive_stdin_command(self, outdir):
      return self._substitute_unarchive_stdin_command("tar -C %o -xf -", outdir)

   def get_archive_command(self, filename):
      return self._substitute_unarchive_command("tar -cf %o %i", filename, ".tar")
      
class ZipArchive(UnknownArchive):
   def get_unarchive_stdin_command(self, outdir):
      return self._substitute_unarchive_stdin_command("tar -C %o -xf -", outdir)

   def get_archive_command(self, filename):
      return self._substitute_unarchive_command("zip %o %i", filename, ".zip")

def start_process(name, args, stdin):
   fd = stdin.fileno()
   pid = os.fork()
   if pid == 0:
      os.dup2(stdin.fileno(),0)
      os.execv(name, args)
   else:
      return os.wait()

class ArchiveMachine:
   _archive_table = { '.tar' : TarArchive,
                      '.zip' : ZipArchive }
   _comprmachine = CompressionMachine()

   def unarchive(self, filename, outdir):
      (newfilename, comprcmd) = self._comprmachine.get_uncompression_pipe_command(filename)
      print "comprsuffix: %s comprcmd: %s" % (comprsuffix, comprcmd)
      for archivetype in self._archive_table.keys():
         # remove the suffix
         print "newfilename: %s " % (newfilename,)
         restr = re.escape(archivetype) + "$"
         typere = re.compile(restr)
         if typere.search(newfilename):
            archive = self._archive_table[archivetype]()
            cmd = archive.get_unarchive_stdin_command(outdir)
            print "comprcmd: %s\ncommand: %s " % (comprcmd, cmd)
            if comprcmd is None:
               input = open(filename)
            else:
               input = os.popen(comprcmd, 'r')
            return start_process("/bin/sh", ["/bin/sh", "-c", cmd], input)
      return None
    
   def archive(self, filename, outname):
       comprcmd = None
#       for comprtype in self._compression_table.keys():
#          restr = re.escape(comprtype) + "$"
#          typere = re.compile(restr)
#          if typere.search(filename):
#             comprcmds = self._compression_table[comprtype]
#             comprfilename = filename + comprtype
#             cmd = re.sub('%i', shell_quote_filename(filename), comprcmds[1]) + " && " + re.sub('%i', shell_quote_filename(uncomprfilename), archivecmds[1])
#             return re.sub('%o', shell_quote_filename(outdir), cmd)
         
#       if comprs is None:
#          return None
#       # FIXME: NEED TO SHELL QUOTE ARGUMENTS
#       return re.sub('%i', filename, re.sub('%o', outname, comprs[0]))

if __name__=="__main__":
   import stat, shutil
   uncompr = 1
   run = 0
   if '-c' in sys.argv:
      uncompr = 0
   if '-r' in sys.argv:
      run = 1
   if len(sys.argv) == 0:
      os.exit(1)

   arch = ArchiveMachine()

   if uncompr:
      if os.access("dpkg-source.tmp-nest", os.F_OK):
         shutil.rmtree("dpkg-source.tmp-nest")
      os.mkdir("dpkg-source.tmp-nest")
      print arch.unarchive(sys.argv[1], "dpkg-source.tmp-nest")
   else:
      print arch.archive(sys.argv[2], sys.argv[1])
#!/usr/bin/python
 
# DebianPatch, DebianPatchMachine
# A class representing an applicable patch (text patch, xdelta,
# rdiff...), as well as a patch machine which takes a set of patches
# and can apply them, respecting their dependencies.

# Copyright 2002 Colin Walters <walters@gnu.org>

# This file 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 of the License, 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.

import os, re, string
from xml.dom import ext
from xml.dom.ext.reader import PyExpat

from ArchiveMachine import CompressionMachine

class ArchitectureSet:
	_dpkg_archtable_filename = '/usr/share/dpkg/archtable'
	_all_arches = _get_all_known_architectures()

	_current_arch = _canonicalize_arch(os.popen('dpkg --print-architecture', 'r').readline())

	def get_current_arch():
		return self._current_arch

	def _canonicalize_arch(arch):
		if string.find(arch, '-') == -1:
			return ('linux', arch)
		else:
			return string.split(arch, '-',1)

	def _stringify_arch(arch):
		return string.join(arch, '-')

	def get_arch_strings(self):
		return map(_stringify_arch, self._arches)

	def get_all_known_architectures(self):
		return self._all_arches
			
	def _get_all_known_architectures(self):
		known_arches = []
		if (os.access(self._dpkg_archtable_filename, os.R_OK)):
			f = open(self._dpkg_archtable_filename)
			line = f.readline()
			while line != '':
				if line != '\n' and line[0] != '#':
					arch = _canonicalize_arch(line.split()[1])
					if not arch in known_arches:
						known_arches.append(arch)
		else:
			stdout = os.popen("dpkg-architecture --help", 'r')
			# Whee.  This is gross.
			archre = re.compile('^Known Debian Architectures are(.+)$')
			line = stdout.readline()
			while line != '':
				if archre.search(line):
					known_arches = map(_canonicalize_arch, string.split(line, ', '))
					break
		return known_arches

	def __init__(self, initset=None):
		if initset is None:
			self._arches = self._all_arches
		else:
			self._arches = map(_canonicialize_arch, initset)

	def exclude_arch(self, arch):
		self._arches.remove(_canonicalize_arch(arch))

	def exclude_processor(self, proc):
		self._arches = filter(lambda arch, proc=proc: arch[1] == proc)

	def exclude_kernel(self, kernel):
		self._arches = filter(lambda arch, proc=proc: arch[0] == kernel)

	def include_arch(self, arch):
		newarch = _canonicalize_arch(arch)
		if not arch in self._arches:
			self._arches.append(newarch)

	def include_processor(self, proc):
		for arch in map(lambda arch,proc=proc: (arch[0], proc), self._all_arches):
			self.include_arch(arch)
		
	def include_kernel(self, kernel):
		for arch in map(lambda arch,kernel=kernel: (kernel, arch[1]), self._all_arches):
			self.include_arch(arch)

	def is_arch_included(self, arch):
		return arch in self._arches
	
class PatchFailure(Exception):
	def __init__(self, value):
		self._value = value

class DebianPatch:
	def __init__(self, type, filename, source, target=None, depends=[], priority=0, archset=None, verbose=0):
		self._type = type
		self._filename = filename
		self._source = source
		self._target = target
		self._depends = depends
		self._priority = priority
		if archset is None:
			self._archset = ArchitectureSet()
		else:
			self._archset = archset
		self._verbose = verbose

	def apply(self):
		if self._applied:
			return
		if not self.archset.is_arch_included(ArchitectureSet.get_current_arch()):
			return
		return self.apply_impl()

	def is_applied(self, val):
		return self._applied

	def set_applied(self, val):
		self._applied = val

	def get_filename(self):
		return self._filename

	def set_depends(sef, deps):
		self._depends = deps

	def set_priority(sef, pri):
		self._pri = pri

	def set_verbose(sef, verbose):
		self._verbose = verbose

class XDelta(DebianPatch):
	def apply_impl(self):
		cmd = "xdelta patch " + self._filename
		if not self._target is None:
			tmpname = self._target + ".tmp"
			cmd = cmd + (" %s %s.dpkg-new" % (self._target, tmpname)
		# FIXME should shell quote filenames
		(stdin,output) = os.popen4(cmd, 'r')
		out = output.read()
		status = output.close()
		if (status != 0):
			raise PatchFailure(out)
		else:
			if self._verbose:
				print out
		os.rename(tmpname, target)

class TextPatch(DebianPatch):
	_fuzziness = 2
	_level = 0

	def apply_impl(self):
		(stdin,output) = os.popen4("patch -s -t -F %s -N -V never -g0 -b -p%s -Z -z .dpkg-orig %s" % (self.fuzziness, self._level, _self.filename), 'r')
		out = output.read()
		status = output.close()
		if (status != 0):
			raise PatchFailure(out)
		else:
			if self._verbose:
				print out

	def set_fuzziness(self, fuzz):
		self._fuzziness = fuzz
		
	def set_level(self, level):
		self._level = level


class DebianPatchMachine:
	_patchcontrolname = 'patch-control.xml'
	_version = 1.0

	_type_filenamesuffix_table = {'.patch' : TextPatch,
				      '.diff' : TextPatch,
				      '.xdelta' : XDelta}

	_type_table = {'text-patch' : TextPatch,
		       'patch' : TextPatch,
		       'xdelta' : XDelta }

	_priorityre = re.compile('^\\d+')

	_known_architectues = get_known_architectures()

	_comprmachine = CompressionMachine()
	_patches = {}

	def _type_from_filename(self, filename):
		for type in self._type_filenamesuffix_table:
			typere = re.compile(re.escape(type) + '$')
			if typere.search(filename):
				return self._typetable[type]
		return None
				
	def _add_patch(self, name, patchnode):
		try:
			stat = os.stat(name)
		except OSError, e:

		deferred = []

		type = None
		filename = name
		source = None
		target = None
		depends = []
		archset = ArchitectureSet()

		priority = 0
		match = self._priorityre.search(name)
		if not match is None:
			priority = int(match.group(0))

		archincludes = []
		archexcludes = []

		# try to determine the patch type
		for patchattrnodes in patchnode.childNodes:
			if elt.type == xml.dom.Node.ELEMENT_NODE && elt.localName == 'type':
				type = elt.childNodes[0].data
		if type is None:
			type = self._type_from_filename(name)
		if type is None:
			abort("No type known for patch %s" % (name,))
		if not self._type_table.has_key(type):
			abort("Unknown type %s for patch %s" % (type, filename))
		
		for patchattrnodes in patchnode.childNodes:
			if elt.type == xml.dom.Node.ELEMENT_NODE:
				name = elt.localName
				value = elt.childNodes[0].data
				if name == 'depends':
					depends.append(value)
				elif name == 'priority':
					priority = int(value)
				elif name == 'arch-exclude':
					archset.exclude_arch(value)
				elif name == 'arch-include':
					archset.include_arch(value)
				elif name == 'kernel-include':
					archset.include_kernel(value)
				elif name == 'kernel-exclude':
					archset.exclude_kernel(value)
				elif name == 'processor-include':
					archset.include_processor(value)
				elif name == 'processor-exclude':
					archset.exclude_processor(value)
				elif name == 'type':
					continue
				else:
					deferred.append(elt)

		patch = self._type_table[type]()
		for attrnode in deferred:
			name = attrnode.localName
			value = attrnode.childNodes[0].data
			if type == 'text-patch':
				if name == 'level':
					patch.set_level(int(value))
					continue
				elif name == 'fuzziness':
					patch.set_fuzziness(int(value))
					continue
			abort("Unknown attribute %s for patch %s" % (name, filename))

		self._patches[filename] = patch
					
		
	def __init__(self, directory, verbose=0):
		self._directory = directory
		self._verbose = verbose
		if os.access(os.path.join(directory, self._patchcontrolname, os.R_OK)):
			root = document.documentElement
			version = root.getAttribute('version')
			if version == '':
				inform("No version specified in patch-control.xml, assuming 1.0")
			else:
				if apt_pkg.VersionCompare(version, self._version) == -1:
					abort("Don't know how to handle patch-control.xml version %s" % (version,))
			patchroot = None 
			for elt in root.childNodes:
				if elt.type == xml.dom.Node.ELEMENT_NODE && elt.localName == u'patch-control':
					patchroot = elt
					break
			if patchroot is None:
				abort("Can't find root in patch-control.xml")
			for patchnode in patchroot.childNodes:
				if patchnode.type == xml.dom.Node.ELEMENT_NODE && elt.localName == u'patch':
					patchname = patchnode.getAttribute('name')
					if patchname == '':
						abort("Patch with no name in patch-control.xml")
					self._add_patch(os.path.join(self._directory, patchname))
				else:
					abort("Unknown entry %s in patch-control.xml" % (elt.localName,))
					
				
#!/usr/bin/python
# -*- coding: utf-8 -*-

# SignedFile offers a subset of file object operations, and is
# designed to transparently handle files with PGP signatures.

# Copyright © 2002 Colin Walters <walters@gnu.org>
#
# This file 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 of the License, 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.

import re,string

class SignedFile:
    _stream = None
    _eof = 0
    _signed = 0
    _signature = None
    _signatureversion = None
    _initline = None
    def __init__(self, stream):
        self._stream = stream
        line = stream.readline()
        if (line == "-----BEGIN PGP SIGNED MESSAGE-----\n"):
            self._signed = 1
            while (1):
                line = stream.readline()
                if (len(line) == 0 or line == '\n'):
                    break
        else:
            self._initline = line

    def readline(self):
        if self._eof:
            return ''
        if self._initline:
            line = self._initline
            self._initline = None
        else:
            line = self._stream.readline()
        if not self._signed:
            return line
        elif line == "-----BEGIN PGP SIGNATURE-----\n":
            self._eof = 1
            self._signature = []
            self._signatureversion = self._stream.readline()
            self._stream.readline()  # skip blank line
            while 1:
                line = self._stream.readline()
                if len(line) == 0 or line == "-----END PGP SIGNATURE-----\n":
                    break
                self._signature.append(line)
            self._signature = string.join
            return ''
        return line
            
    def readlines(self):
        ret = []
        while 1:
            line = self.readline()
            if (line != ''):
                ret.append(line)
            else:
                break
        return ret

    def close(self):
        self._stream.close()

    def getSigned(self):
        return self._signed

    def getSignature(self):
        return self._signature

    def getSignatureVersion(self):
        return self._signatureversion
            
if __name__=="__main__":
	import sys
        if len(sys.argv) == 0:
            print "Need one file as an argument"
            sys.exit(1)
        filename = sys.argv[1]
	f=SignedFile(open(filename))
        if f.getSigned():
            print "**** SIGNED ****"
        else:
            print "**** NOT SIGNED ****"
	lines=f.readlines()
	print lines
        if not f.getSigned():
            assert(len(lines) == len(actuallines))
        else:
            print "Signature: %s" % (f.getSignature())

#! /usr/bin/python
 
# XMLControlFile.py

# An XML file which has a few characteristics like a version and a
# single non-comment toplevel node.

# Copyright 2002 Colin Walters <walters@gnu.org>

# This file 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 of the License, 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.

import os, re, string, apt_pkg

class XMLControlFileException(Exception):
	def __init__(self, value):
		self._value = value

class XMLControlFile:
	_reader = PyExpat.Reader()

	# This sets self._xml_document, which subclasses should use
	def __init__(self, filename):
		self._xml_document = self._reader.fromUri(filename)
		realrootnode = None
		for node in self._xml_document.documentElement.childNodes:
			if node.nodeType == xml.dom.Node.ELEMENT_NODE && node.localName == self._xml_rootnode_name:
				realrootnode = node
				break
		if realrootnode is None:
			raise XMLControlFileException('No root node named %s found' % (self._xml_rootnode_name))
		check_version(rootnode.getAttribute('version'))

	def check_version(self, version):
		if version != '' or apt_pkg.VersionCompare(version, self._xml_version) == -1:
			raise XMLControlFileException("Don't know how to handle version %s" % (version,))
#! /usr/bin/python
# 
# dpkg-source.py
# Unpack and repack Debian source packages, version 1 and 2
#
# Copyright 2002 Colin Walters <walters@gnu.org>
#
# This file 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 of the License, 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.

import os, re, sys, string, types, errno, stat
import md5
import apt_pkg

from xml.dom import ext
from xml.dom.ext.reader import PyExpat

import DpkgVarlist
from DpkgUtils import *
from DpkgControl import *
from DpkgOptions import Options

from ArchiveMachine import CompressionMachine, ArchiveMachine

def usage():
	print """Debian dpkg-source $version.
Copyright (C) 2002 Colin Walters <walters@gnu.org>.  Derived in part
from dpkg-source.pl written by Ian Jackson and Klee Dienes.

This is free software; see the GNU General Public Licence version 2 or
later for copying conditions.  There is NO warranty.

Usage:
  dpkg-source -x <filename>.dsc
  dpkg-source -b <directory> [<orig-directory>|<orig-targz>|\'\']
  dpkg-source -b1 <directory> [<orig-directory>|<orig-targz>|\'\']
  dpkg-source -p <directory>
Build options:
  --control=file          get control info from this file
  --changelog=file        get per-version info from this file
  --changelog-format=fmt  force change log format
  --substvarsfile=file    read variables here, not debian/substvars
  -V<name>=<value>        set a substitution variable
  -D<field>=<value>       override or add a .dsc field and value
  -U<field>               remove a field
  -W                      Turn certain errors into warnings
  -E                      When -W is enabled, -E disables it
  --include-source=type   How to include the original source distribution
     auto: auto select orig source
     unpackkeep: use packed orig source (unpack & keep)
     unpackremove: use packed orig source (unpack & remove)
     packkeep: use unpacked orig source (pack & keep)
     packremove: use unpacked orig source (pack & remove)
  --no-patches            there is no diff, do main tarfile only
  --allow-source-overwrite Packing up original source may overwrite files
  --same-source           trust packed & unpacked orig src are same
  -i[<regexp>]            filter out files to ignore diffs of
                            (See documentation for default value)
  -I<filename>            filter out files when building tarballs
Extract options:
  --keep-source-unpacked  leave orig source packed in current dir (default)
  --sn                    do not copy original source to current dir
  --unpack-orig-source    unpack original source tree too
  --verify-only           Just check the integrity of files in the .dsc
  --no-patches            Do not apply any patches
Patch application options:
  --no-xdelta             Do not apply xdelta patches
  --no-textpatch          Do not apply normal (text) patches
  --patch-exclude=regexp  Do not apply patches matching regexp
  --no-arch-patches       Do not apply architecture-dependent patches
  --ignore-patch-failure  Continue even if some patches fail to apply
General options:
  -h                      print this message
  --compressor=name       default compression program to use
  --uncompressor=name     default uncompression program to use
"""

class DscFile(DpkgParagraph):
	def __init__(self, filename):
		DpkgParagraph.__init__(self)
		f = SignedFile(open(filename))
		self.load(f)
		f.close()

	def setFiles(self, fileslist):
		out = ""
		for file in fileslist:
			f = open(file)
			out = out + ("%s %s %s\n" % (md5.new(open(file).read()).digest(), os.stat(file)[stat.ST_SIZE], os.path.basename(file)))
		self['files'] = out

	def getFiles(self):
		out = []
		try:
			files = self['files']
		except KeyError:
			return []
		lineregexp = re.compile("^([0-9a-f]{32})[ \t]+(\d+)[ \t]+([0-9a-zA-Z][-+:.,=0-9a-zA-Z_]+)$")
		for line in files:
			if line == '':
				continue
			match = lineregexp.match(line)
			if (match is None):
				abort("Couldn't parse file entry in Files: of .dsc")
			out.append((match.group(1), match.group(2), match.group(3)))
		return out

class ChangeLogFile(DpkgParagraph):
	def load(self, filename, format=None, since=None):
		args = ["-l%s" % (filename,)]
		if not format is None:
			args.append("-F%s" % (format,))
		if not since is None:
			args.append("-v%s" % (since,))
			
		(proc, stdin, stdout) = apply(sys.popen3, "dpkg-parsechangelog", args)
		DpkgControl.load(self, stdin)

	def getSource(self):
		return self.getField("Source")

	def getVersion(self):
		return self.getField("Version")



class SourceControl(XMLControlFile):
	# needed by XMLControlFile
	_xml_version = "1.0"
	_xml_rootnode_name = 'source-control'

	_sources = {}

	def __init__(self, filename):
		XMLControlFile.__init__(self, filename)

		root = self._xml_document.documentElement

		sourcecount = 0
		for sourcenode in root.childNodes:
			if node.type == node.ELEMENT_NODE:
				sourcecount = sourcecount + 1

		is_multisource = sourcecount > 1

		for sourcenode in root.childNodes:
			if node.type != node.ELEMENT_NODE:
				continue
			name = sourcenode.getAttribute('Name')
			if name == '':
				abort("No name specified for source in source description")
			defaultre = '^' + re.escape(name + '-')
			source = {'name' : name,
				  'dir-regexp' : defaultre
				  'archive-regexp' : defaultre,
				  'unpack-style': 'normal'}
			
			for node in sourcenode.childNodes:
				if node.type != node.ELEMENT_NODE:
					continue
				if node.localName in ('name',
						      'dir-regexp',
						      'archive-regexp',
						      'version',
						      'unpack-style',
						      'archive-format',
						      'compression-format'):
					source[attribute] = node.data
				else:
					abort("Unknown element %s in source %s " % (node.localName, name))
			self._sources[name] = source

def parse_options(args):
	todo=[]
	try:
		while args:
			(opt,args)=(args[0], args[1:])

			# Check if we encountered the magic end-of-options flag
			if HandleArgOption("admindir", "-a", "--admindir", opt, args): continue
			elif HandleNoArgOption("verbose", "-v", "--verbose", opt): continue
			elif opt=='-h' or opt=="--help":
				usage()
				sys.exit(0)
			elif opt[:2]=="-e":
				opt=opt[2:]
			elif opt[0]=='-':
				abort("Illegal option " + opt)
			else:
				todo.append(opt)

	except IndexError:
		abort("Option %s needs an argument" % opt)
	
	return todo

def get_file_md5sum(filename):
	f = open(filename)
	md5sum = md5.new()
	buf = f.read(8192)
	while buf != '':
		md5sum.update(buf)
		buf = f.read(8192)
	return md5sum.hexdigest()

def parse_versions(pkgname, fullversion):
	debianversion = re.sub('^[0-9]+:', '', fullversion)
	upstreamver = re.sub('-[^-]*$', '', debianversion)
	upstream_prefix = pkgname + '_' + upstreamver
	full_prefix = pkgname + '_' + debianversion 
	upstream_dir = pkgname + '-' + upstreamver 
	return (upstreamver, debianversion, upstream_prefix, full_prefix, upstream_dir)

class DebianSourceArchive:
	_comprmachine = CompressionMachine()
	_archivemachine = ArchiveMachine()

	def __init__(self, dscfile):
		try:
			self._name = dscfile['source']
		except KeyError:
			abort("No Source field in .dsc")
		try:
			fullversion = dscfile['version']
		except KeyError:
			abort("No Version field in .dsc")
		(self._upstreamver, self._debianversion, self._upstream_prefix, self._full_prefix, self._upstream_dir) = parse_versions(self._name, fullversion)
		print "upstreamver: %s\ndebianversion: %s\nupstream_prefix: %s\nfull_prefix: %s\nupstream_dir: %s" % (self._upstreamver, self._debianversion, self._upstream_prefix, self._full_prefix, self._upstream_dir)

		dscfiles = dscfile.getFiles()
		self._dscfiles = dscfiles
		if len(dscfiles) == 0:
			abort("No Files field in .dsc")
		self._files = map(lambda x: x[2], dscfiles)

		self._update_files()
		isnative = self._is_native()

		self._origarchive = None
		if isnative:
			retext = re.escape(self._upstream_prefix)
		else:
			retext = re.escape(self._upstream_prefix + ".orig")
		self._origarchive_regexp = re.compile('^' + retext)

	def _check_orig_archive(self):
		for filename in self._files:
			if self._origarchive_regexp.search(filename):
				self._origarchive = filename
				print "%s is orig tarfile" % (filename,)
		if self._origarchive is None:
			abort("No original archive found in .dsc")

	def verify_integrity(self):
		for (md5sum, size, filename) in self._dscfiles:
			self._verify_dsc_file_integrity(filename, int(size), md5sum)

	def _verify_dsc_file_integrity(self, filename, expected_size, expected_md5sum):
		try:
			statbuf = os.stat(filename)
			if not stat.S_ISREG(statbuf[stat.ST_MODE]):
				abort("%s is not a regular file" % (filename,))
			size = statbuf[stat.ST_SIZE]
		except OSError, e:
			abort("Can't stat %s: %s" % (filename,e.strerror))
		if size != expected_size:
			abort("File size for %s does not match that specified in .dsc" % (filename,))
		if (get_file_md5sum(filename) != expected_md5sum):
			abort("md5sum for %s does not match that specified in .dsc" % (filename,))
		print "verified md5sum %s and size %s for %s" % (expected_md5sum, expected_size, filename)

class DebianSourceArchiveVersion2(DebianSourceArchive):
	def __init__(self, dscfile):
		DebianSourceArchive.__init__(self, dscfile)
		self._check_orig_archive()

	# called by the DebianSourceArchive constructor to update
	# internal stuff in subclasses like this one
	def _update_files(self):
		self._debianarchive = None
		self._debianarchive_regexp = re.compile('^' + re.escape(self._full_prefix + ".debian")) 
		for filename in self._files:
			if self._debianarchive_regexp.search(filename):
				self._debianarchive = filename
				print "%s is debian archive" % (filename,)

	def _is_native(self):
		return self._debianarchive is None

	def _extract_upstream(self):
		

	def extract(self):
		

class DebianSourceArchiveVersion1(DebianSourceArchive):
	def __init__(self, dscfile):
		DebianSourceArchive.__init__(self, dscfile)
		self._check_orig_archive()

	# called by the DebianSourceArchive constructor to update
	# internal stuff in subclasses like this one
	def _update_files(self):
		self._debiandiff = None
		self._debiandiff_regexp = re.compile('^' + re.escape(self._full_prefix + ".diff.gz") + '$') 
		for filename in self._files:
			if debiandiff_regexp.search(filename):
				self._debiandiff = filename
				print "%s is debian diff" % (filename,)
	
		if debiandiff is None:
			inform("No debian diff found in .dsc, package is Debian-native")

	def _is_native(self):
		return self._debiandiff is None

def main():
	todo=parse_options(sys.argv[1:])

	if not todo:
		abort("Need at least one .dsc file as argument")

	apt_pkg.init()

	Options['verbose'] = VERB_DEBUG
		
	dscfile = DscFile(todo[0])
	if apt_pkg.VersionCompare(dscfile['format'], '2.0') >= 0:
		inform("Debian source archive version: 2")
		archive = DebianSourceArchiveVersion2(dscfile)
	else:
		inform("Debian source archive version: 1")
		archive = DebianSourceArchiveVersion1(dscfile)

	archive.verify_integrity()
		
		
		
# 		(newfilename, uncomprcmd) = comprmachine.get_uncompression_pipe_command(filename)
# 		print "uncompressing %s with %s" % (filename, uncomprcmd)
# 		if uncomprcmd is None:
# 			f = open(filename)
# 		else:
# 			f = os.popen(uncomprcmd, 'r')
	sys.exit(0)


if __name__=="__main__":
	main()


# Local Variables:
# compile-command: "python dpkg-source.py"
# End:
<?xml version="1.0"?>

<!-- An example patch-control.xml file -->

<patch-control version="1.0">
  <!-- The 30 prefix implies <priority>30</priority>, and the
       .patch implies <type>text-patch</type> -->
  <patch name="30move-binaries.patch">
    <!-- The next two patches must be applied for this one to be applied. -->
    <depends>15move-directories.patch</depends>
    <depends>10setup-makefile.patch</depends>
    <!-- This patch is not needed on hurd-i386 (e.g. libexec),
         although in that case we would actually want just a
         kernel-exclude... -->
    <arch-exclude>hurd-i386</arch-exclude>
    <!-- This patch is against the "foo" upstream source distribution.
	 This is useful for packages with multiple upstream sources.  If
	 not present, dpkg-source will attempt to autodetect it. -->
    <source>foo</source>
    <!-- These next two are text-patch specific options; "fuzziness"
         gives the patch fuzziness (e.g. -F 2), and "prefix" gives the
         patch prefix level (e.g. -p0).  The defaults are "0" and "1",
         respectively. -->
    <fuzziness>1</fuzziness> 
    <level>0</level> 
  </patch>
  <patch name="60fix-database.xdelta">
    <!-- This patch is applied to the "data/star-database.db" file,
         relative to the upstream source distribution. -->
    <target>data/star-database.db</target>
    <depends>10setup-makefile.patch</depends>
    <!-- Suppose this only affects linux and the hurd, but not on
         powerpc or sparc -->
    <kernel-include>linux</kernel-include>
    <kernel-include>hurd</kernel-include>
    <processor-exclude>powerpc</processor-exclude>
    <processor-exclude>sparc</processor-exclude>
  </patch>
</patch-control>
<?xml version="1.0"?>

<source-control version="1.0">
  <source name="dpkg-source2">
    <dir-regexp>^dpkg-source2-</dir-regexp>
    <archive-regexp>^dpkg-source2-.+\.tar\.bz2</dir-regexp>
    <archive-format>tar</archive-format>
    <compression-format>bz2</compression-format>
  </source>
  <source name="foo">
    <dir-regexp>^foo$</dir-regexp>
    <version>1.0</version>
    <!-- This means that the upstream source doesn't unpack
         into <name>-<version>, so we explicitly specify a version.  -->
    <unpack-style>toplevel</unpack-style>
    <!-- we also accept "braindamaged" as an alias for "toplevel" -->
  </source>
</source-control>

Reply to: