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: