| 1 | #!/usr/bin/env python
|
|---|
| 2 | # Copyright (C) 2001 Alexander S. Guy <a7r@andern.org>
|
|---|
| 3 | # Andern Research Labs
|
|---|
| 4 | #
|
|---|
| 5 | # This program is free software; you can redistribute it and/or modify
|
|---|
| 6 | # it under the terms of the GNU General Public License as published by
|
|---|
| 7 | # the Free Software Foundation; either version 2, or (at your option)
|
|---|
| 8 | # any later version.
|
|---|
| 9 | #
|
|---|
| 10 | # This program is distributed in the hope that it will be useful,
|
|---|
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|---|
| 13 | # GNU General Public License for more details.
|
|---|
| 14 | #
|
|---|
| 15 | # You should have received a copy of the GNU General Public License
|
|---|
| 16 | # along with this program; if not, write to the Free Software
|
|---|
| 17 | # Foundation, Inc., 59 Temple Place - Suite 330,
|
|---|
| 18 | # Boston, MA 02111-1307, USA. */
|
|---|
| 19 | #
|
|---|
| 20 | # Copyright 2001, Russell Nelson <ipkg.py@russnelson.com>
|
|---|
| 21 | # Added reading in of packages.
|
|---|
| 22 | # Added missing package information fields.
|
|---|
| 23 | # Changed render_control() to __repr__().
|
|---|
| 24 | #
|
|---|
| 25 | # Current Issues:
|
|---|
| 26 | # The API doesn't validate package information fields. It should be
|
|---|
| 27 | # throwing exceptions in the right places.
|
|---|
| 28 | # Executions of tar could silently fail.
|
|---|
| 29 | # Executions of tar *do* fail, and loudly, because you have to specify a full filename,
|
|---|
| 30 | # and tar complains if any files are missing, and the ipkg spec doesn't require
|
|---|
| 31 | # people to say "./control.tar.gz" or "./control" when they package files.
|
|---|
| 32 | # It would be much better to require ./control or disallow ./control (either)
|
|---|
| 33 | # rather than letting people pick. Some freedoms aren't worth their cost.
|
|---|
| 34 |
|
|---|
| 35 | import tempfile
|
|---|
| 36 | import os
|
|---|
| 37 | import sys
|
|---|
| 38 | import glob
|
|---|
| 39 | import md5
|
|---|
| 40 | import re
|
|---|
| 41 | import string
|
|---|
| 42 | import commands
|
|---|
| 43 | from stat import ST_SIZE
|
|---|
| 44 |
|
|---|
| 45 | class Version:
|
|---|
| 46 | """A class for holding parsed package version information."""
|
|---|
| 47 | def __init__(self, epoch, version):
|
|---|
| 48 | self.epoch = epoch
|
|---|
| 49 | self.version = version
|
|---|
| 50 |
|
|---|
| 51 | def _versioncompare(self, ref):
|
|---|
| 52 | selfversion = self.version
|
|---|
| 53 | refversion = ref.version
|
|---|
| 54 | while 1:
|
|---|
| 55 | ## first look for non-numeric version component
|
|---|
| 56 | selfm = re.match('([^0-9]*)(.*)', selfversion)
|
|---|
| 57 | #print 'selfm', selfm.groups()
|
|---|
| 58 | (selfalpha, selfversion) = selfm.groups()
|
|---|
| 59 | refm = re.match('([^0-9]*)(.*)', refversion)
|
|---|
| 60 | #print 'refm', refm.groups()
|
|---|
| 61 | (refalpha, refversion) = refm.groups()
|
|---|
| 62 | if (selfalpha > refalpha):
|
|---|
| 63 | return 1
|
|---|
| 64 | elif (selfalpha < refalpha):
|
|---|
| 65 | return -1
|
|---|
| 66 | ## now look for numeric version component
|
|---|
| 67 | (selfnum, selfversion) = re.match('([0-9]*)(.*)', selfversion).groups()
|
|---|
| 68 | (refnum, refversion) = re.match('([0-9]*)(.*)', refversion).groups()
|
|---|
| 69 | #print 'selfnum', selfnum, selfversion
|
|---|
| 70 | #print 'refnum', refnum, refversion
|
|---|
| 71 | if (selfnum != ''):
|
|---|
| 72 | selfnum = int(selfnum)
|
|---|
| 73 | else:
|
|---|
| 74 | selfnum = -1
|
|---|
| 75 | if (refnum != ''):
|
|---|
| 76 | refnum = int(refnum)
|
|---|
| 77 | else:
|
|---|
| 78 | refnum = -1
|
|---|
| 79 | if (selfnum > refnum):
|
|---|
| 80 | return 1
|
|---|
| 81 | elif (selfnum < refnum):
|
|---|
| 82 | return -1
|
|---|
| 83 | if selfversion == '' and refversion == '':
|
|---|
| 84 | return 0
|
|---|
| 85 |
|
|---|
| 86 | def compare(self, ref):
|
|---|
| 87 | if (self.epoch > ref.epoch):
|
|---|
| 88 | return 1
|
|---|
| 89 | elif (self.epoch < ref.epoch):
|
|---|
| 90 | return -1
|
|---|
| 91 | else:
|
|---|
| 92 | return self._versioncompare(ref)
|
|---|
| 93 |
|
|---|
| 94 | def parse_version(versionstr):
|
|---|
| 95 | epoch = 0
|
|---|
| 96 | # check for epoch
|
|---|
| 97 | m = re.match('([0-9]*):(.*)', versionstr)
|
|---|
| 98 | if m:
|
|---|
| 99 | (epochstr, versionstr) = m.groups()
|
|---|
| 100 | epoch = int(epochstr)
|
|---|
| 101 | return Version(epoch, versionstr)
|
|---|
| 102 |
|
|---|
| 103 | class Package:
|
|---|
| 104 | """A class for creating objects to manipulate (e.g. create) ipkg
|
|---|
| 105 | packages."""
|
|---|
| 106 | def __init__(self, fn=None):
|
|---|
| 107 | self.package = None
|
|---|
| 108 | self.version = 'none'
|
|---|
| 109 | self.parsed_version = None
|
|---|
| 110 | self.architecture = None
|
|---|
| 111 | self.maintainer = None
|
|---|
| 112 | self.source = None
|
|---|
| 113 | self.description = None
|
|---|
| 114 | self.depends = None
|
|---|
| 115 | self.provides = None
|
|---|
| 116 | self.replaces = None
|
|---|
| 117 | self.conflicts = None
|
|---|
| 118 | self.recommends = None
|
|---|
| 119 | self.suggests = None
|
|---|
| 120 | self.section = None
|
|---|
| 121 | self.filename_header = None
|
|---|
| 122 | self.file_list = []
|
|---|
| 123 | self.md5 = None
|
|---|
| 124 | self.size = None
|
|---|
| 125 | self.installed_size = None
|
|---|
| 126 | self.filename = None
|
|---|
| 127 | self.isdeb = 0
|
|---|
| 128 |
|
|---|
| 129 | if fn:
|
|---|
| 130 | # see if it is deb format
|
|---|
| 131 | f = open(fn, "r")
|
|---|
| 132 | magic = f.read(4)
|
|---|
| 133 | f.close()
|
|---|
| 134 | if (magic == "!<ar"):
|
|---|
| 135 | self.isdeb = 1
|
|---|
| 136 |
|
|---|
| 137 | # compute the MD5.
|
|---|
| 138 | f = open(fn, "r")
|
|---|
| 139 | sum = md5.new()
|
|---|
| 140 | while 1:
|
|---|
| 141 | data = f.read(1024)
|
|---|
| 142 | if not data: break
|
|---|
| 143 | sum.update(data)
|
|---|
| 144 | f.close()
|
|---|
| 145 | if sys.version[:1] > '2':
|
|---|
| 146 | # when using Python 2.0 or newer
|
|---|
| 147 | self.md5 = sum.hexdigest()
|
|---|
| 148 | else:
|
|---|
| 149 | self.md5 = string.join(map((lambda x:"%02x" % ord(x)),sum.digest()),'')
|
|---|
| 150 | stat = os.stat(fn)
|
|---|
| 151 | self.size = stat[ST_SIZE]
|
|---|
| 152 | self.filename = os.path.basename(fn)
|
|---|
| 153 | ## sys.stderr.write(" extracting control.tar.gz from %s\n"% (fn,))
|
|---|
| 154 | if self.isdeb:
|
|---|
| 155 | control = os.popen("ar p "+fn+" control.tar.gz | tar --wildcards -xzOf - '*control'","r")
|
|---|
| 156 | else:
|
|---|
| 157 | control = os.popen("tar xfzO "+fn+" '*control.tar.gz' | tar --wildcards -xzOf - '*control'","r")
|
|---|
| 158 | line = control.readline()
|
|---|
| 159 | while 1:
|
|---|
| 160 | if not line: break
|
|---|
| 161 | line = string.rstrip(line)
|
|---|
| 162 | lineparts = re.match(r'([\w-]*?):\s*(.*)', line)
|
|---|
| 163 | if lineparts:
|
|---|
| 164 | name = string.lower(lineparts.group(1))
|
|---|
| 165 | value = lineparts.group(2)
|
|---|
| 166 | while 1:
|
|---|
| 167 | line = control.readline()
|
|---|
| 168 | if not line: break
|
|---|
| 169 | if line[0] != ' ': break
|
|---|
| 170 | line = string.rstrip(line)
|
|---|
| 171 | value = value + '\n' + line
|
|---|
| 172 | # don't allow package to override its own filename
|
|---|
| 173 | if name == "filename":
|
|---|
| 174 | self.filename_header = value
|
|---|
| 175 | else:
|
|---|
| 176 | if self.__dict__.has_key(name):
|
|---|
| 177 | self.__dict__[name] = value
|
|---|
| 178 | else:
|
|---|
| 179 | line = control.readline()
|
|---|
| 180 | control.close()
|
|---|
| 181 | if self.isdeb:
|
|---|
| 182 | data = os.popen("ar p "+fn+" data.tar.gz | tar tfz -","r")
|
|---|
| 183 | else:
|
|---|
| 184 | data = os.popen("tar xfzO "+fn+" '*data.tar.gz' | tar tfz -","r")
|
|---|
| 185 | while 1:
|
|---|
| 186 | line = data.readline()
|
|---|
| 187 | if not line: break
|
|---|
| 188 | self.file_list.append(string.rstrip(line))
|
|---|
| 189 | data.close()
|
|---|
| 190 |
|
|---|
| 191 | self.scratch_dir = None
|
|---|
| 192 | self.file_dir = None
|
|---|
| 193 | self.meta_dir = None
|
|---|
| 194 |
|
|---|
| 195 | def read_control(self, control):
|
|---|
| 196 | import os
|
|---|
| 197 |
|
|---|
| 198 | line = control.readline()
|
|---|
| 199 | while 1:
|
|---|
| 200 | if not line: break
|
|---|
| 201 | line = string.rstrip(line)
|
|---|
| 202 | lineparts = re.match(r'([\w-]*?):\s*(.*)', line)
|
|---|
| 203 | if lineparts:
|
|---|
| 204 | name = string.lower(lineparts.group(1))
|
|---|
| 205 | value = lineparts.group(2)
|
|---|
| 206 | while 1:
|
|---|
| 207 | line = control.readline()
|
|---|
| 208 | if not line: break
|
|---|
| 209 | if line[0] != ' ': break
|
|---|
| 210 | value = value + '\n' + line
|
|---|
| 211 | if name == 'size':
|
|---|
| 212 | self.size = int(value)
|
|---|
| 213 | elif self.__dict__.has_key(name):
|
|---|
| 214 | self.__dict__[name] = value
|
|---|
| 215 | if line[0] == '\n':
|
|---|
| 216 | return # consumes one blank line at end of package descriptoin
|
|---|
| 217 | else:
|
|---|
| 218 | line = control.readline()
|
|---|
| 219 | pass
|
|---|
| 220 | return
|
|---|
| 221 |
|
|---|
| 222 | def _setup_scratch_area(self):
|
|---|
| 223 | self.scratch_dir = "%s/%sipkg" % (tempfile.gettempdir(),
|
|---|
| 224 | tempfile.gettempprefix())
|
|---|
| 225 | self.file_dir = "%s/files" % (self.scratch_dir)
|
|---|
| 226 | self.meta_dir = "%s/meta" % (self.scratch_dir)
|
|---|
| 227 |
|
|---|
| 228 | os.mkdir(self.scratch_dir)
|
|---|
| 229 | os.mkdir(self.file_dir)
|
|---|
| 230 | os.mkdir(self.meta_dir)
|
|---|
| 231 |
|
|---|
| 232 | def set_package(self, package):
|
|---|
| 233 | self.package = package
|
|---|
| 234 |
|
|---|
| 235 | def get_package(self):
|
|---|
| 236 | return self.package
|
|---|
| 237 |
|
|---|
| 238 | def set_version(self, version):
|
|---|
| 239 | self.version = version
|
|---|
| 240 | self.parsed_version = parse_version(version)
|
|---|
| 241 |
|
|---|
| 242 | def get_version(self):
|
|---|
| 243 | return self.version
|
|---|
| 244 |
|
|---|
| 245 | def set_architecture(self, architecture):
|
|---|
| 246 | self.architecture = architecture
|
|---|
| 247 |
|
|---|
| 248 | def get_architecture(self):
|
|---|
| 249 | return self.architecture
|
|---|
| 250 |
|
|---|
| 251 | def set_maintainer(self, maintainer):
|
|---|
| 252 | self.maintainer = maintainer
|
|---|
| 253 |
|
|---|
| 254 | def get_maintainer(self):
|
|---|
| 255 | return self.maintainer
|
|---|
| 256 |
|
|---|
| 257 | def set_source(self, source):
|
|---|
| 258 | self.source = source
|
|---|
| 259 |
|
|---|
| 260 | def get_source(self):
|
|---|
| 261 | return self.source
|
|---|
| 262 |
|
|---|
| 263 | def set_description(self, description):
|
|---|
| 264 | self.description = description
|
|---|
| 265 |
|
|---|
| 266 | def get_description(self):
|
|---|
| 267 | return self.description
|
|---|
| 268 |
|
|---|
| 269 | def set_depends(self, depends):
|
|---|
| 270 | self.depends = depends
|
|---|
| 271 |
|
|---|
| 272 | def get_depends(self, depends):
|
|---|
| 273 | return self.depends
|
|---|
| 274 |
|
|---|
| 275 | def set_provides(self, provides):
|
|---|
| 276 | self.provides = provides
|
|---|
| 277 |
|
|---|
| 278 | def get_provides(self, provides):
|
|---|
| 279 | return self.provides
|
|---|
| 280 |
|
|---|
| 281 | def set_replaces(self, replaces):
|
|---|
| 282 | self.replaces = replaces
|
|---|
| 283 |
|
|---|
| 284 | def get_replaces(self, replaces):
|
|---|
| 285 | return self.replaces
|
|---|
| 286 |
|
|---|
| 287 | def set_conflicts(self, conflicts):
|
|---|
| 288 | self.conflicts = conflicts
|
|---|
| 289 |
|
|---|
| 290 | def get_conflicts(self, conflicts):
|
|---|
| 291 | return self.conflicts
|
|---|
| 292 |
|
|---|
| 293 | def set_suggests(self, suggests):
|
|---|
| 294 | self.suggests = suggests
|
|---|
| 295 |
|
|---|
| 296 | def get_suggests(self, suggests):
|
|---|
| 297 | return self.suggests
|
|---|
| 298 |
|
|---|
| 299 | def set_section(self, section):
|
|---|
| 300 | self.section = section
|
|---|
| 301 |
|
|---|
| 302 | def get_section(self, section):
|
|---|
| 303 | return self.section
|
|---|
| 304 |
|
|---|
| 305 | def get_file_list(self):
|
|---|
| 306 | return self.file_list
|
|---|
| 307 |
|
|---|
| 308 | def write_package(self, dirname):
|
|---|
| 309 | buf = self.render_control()
|
|---|
| 310 | file = open("%s/control" % self.meta_dir, 'w')
|
|---|
| 311 | file.write(buf)
|
|---|
| 312 |
|
|---|
| 313 | self._setup_scratch_area()
|
|---|
| 314 | cmd = "cd %s ; tar cvfz %s/control.tar.gz control" % (self.meta_dir,
|
|---|
| 315 | self.scratch_dir)
|
|---|
| 316 |
|
|---|
| 317 | cmd_out, cmd_in, cmd_err = os.popen3(cmd)
|
|---|
| 318 |
|
|---|
| 319 | while cmd_err.readline() != "":
|
|---|
| 320 | pass
|
|---|
| 321 |
|
|---|
| 322 | cmd_out.close()
|
|---|
| 323 | cmd_in.close()
|
|---|
| 324 | cmd_err.close()
|
|---|
| 325 |
|
|---|
| 326 | bits = "control.tar.gz"
|
|---|
| 327 |
|
|---|
| 328 | if self.file_list:
|
|---|
| 329 | cmd = "cd %s ; tar cvfz %s/data.tar.gz" % (self.file_dir,
|
|---|
| 330 | self.scratch_dir)
|
|---|
| 331 |
|
|---|
| 332 | cmd_out, cmd_in, cmd_err = os.popen3(cmd)
|
|---|
| 333 |
|
|---|
| 334 | while cmd_err.readline() != "":
|
|---|
| 335 | pass
|
|---|
| 336 |
|
|---|
| 337 | cmd_out.close()
|
|---|
| 338 | cmd_in.close()
|
|---|
| 339 | cmd_err.close()
|
|---|
| 340 |
|
|---|
| 341 | bits = bits + " data.tar.gz"
|
|---|
| 342 |
|
|---|
| 343 | file = "%s_%s_%s.ipk" % (self.package, self.version, self.architecture)
|
|---|
| 344 | cmd = "cd %s ; tar cvfz %s/%s %s" % (self.scratch_dir,
|
|---|
| 345 | dirname,
|
|---|
| 346 | file,
|
|---|
| 347 | bits)
|
|---|
| 348 |
|
|---|
| 349 | cmd_out, cmd_in, cmd_err = os.popen3(cmd)
|
|---|
| 350 |
|
|---|
| 351 | while cmd_err.readline() != "":
|
|---|
| 352 | pass
|
|---|
| 353 |
|
|---|
| 354 | cmd_out.close()
|
|---|
| 355 | cmd_in.close()
|
|---|
| 356 | cmd_err.close()
|
|---|
| 357 |
|
|---|
| 358 | def compare_version(self, ref):
|
|---|
| 359 | """Compare package versions of self and ref"""
|
|---|
| 360 | if not self.version:
|
|---|
| 361 | print 'No version for package %s' % self.package
|
|---|
| 362 | if not ref.version:
|
|---|
| 363 | print 'No version for package %s' % ref.package
|
|---|
| 364 | if not self.parsed_version:
|
|---|
| 365 | self.parsed_version = parse_version(self.version)
|
|---|
| 366 | if not ref.parsed_version:
|
|---|
| 367 | ref.parsed_version = parse_version(ref.version)
|
|---|
| 368 | return self.parsed_version.compare(ref.parsed_version)
|
|---|
| 369 |
|
|---|
| 370 | def __repr__(self):
|
|---|
| 371 | out = ""
|
|---|
| 372 |
|
|---|
| 373 | # XXX - Some checks need to be made, and some exceptions
|
|---|
| 374 | # need to be thrown. -- a7r
|
|---|
| 375 |
|
|---|
| 376 | if self.package: out = out + "Package: %s\n" % (self.package)
|
|---|
| 377 | if self.version: out = out + "Version: %s\n" % (self.version)
|
|---|
| 378 | if self.depends: out = out + "Depends: %s\n" % (self.depends)
|
|---|
| 379 | if self.provides: out = out + "Provides: %s\n" % (self.provides)
|
|---|
| 380 | if self.replaces: out = out + "Replaces: %s\n" % (self.replaces)
|
|---|
| 381 | if self.conflicts: out = out + "Conflicts: %s\n" % (self.conflicts)
|
|---|
| 382 | if self.suggests: out = out + "Suggests: %s\n" % (self.suggests)
|
|---|
| 383 | if self.recommends: out = out + "Recommends: %s\n" % (self.recommends)
|
|---|
| 384 | if self.section: out = out + "Section: %s\n" % (self.section)
|
|---|
| 385 | if self.architecture: out = out + "Architecture: %s\n" % (self.architecture)
|
|---|
| 386 | if self.maintainer: out = out + "Maintainer: %s\n" % (self.maintainer)
|
|---|
| 387 | if self.md5: out = out + "MD5Sum: %s\n" % (self.md5)
|
|---|
| 388 | if self.size: out = out + "Size: %d\n" % int(self.size)
|
|---|
| 389 | if self.installed_size: out = out + "InstalledSize: %d\n" % int(self.installed_size)
|
|---|
| 390 | if self.filename: out = out + "Filename: %s\n" % (self.filename)
|
|---|
| 391 | if self.source: out = out + "Source: %s\n" % (self.source)
|
|---|
| 392 | if self.description: out = out + "Description: %s\n" % (self.description)
|
|---|
| 393 | out = out + "\n"
|
|---|
| 394 |
|
|---|
| 395 | return out
|
|---|
| 396 |
|
|---|
| 397 | def __del__(self):
|
|---|
| 398 | # XXX - Why is the `os' module being yanked out before Package objects
|
|---|
| 399 | # are being destroyed? -- a7r
|
|---|
| 400 | pass
|
|---|
| 401 |
|
|---|
| 402 | class Packages:
|
|---|
| 403 | """A currently unimplemented wrapper around the ipkg utility."""
|
|---|
| 404 | def __init__(self):
|
|---|
| 405 | self.packages = {}
|
|---|
| 406 | return
|
|---|
| 407 |
|
|---|
| 408 | def add_package(self, pkg):
|
|---|
| 409 | package = pkg.package
|
|---|
| 410 | arch = pkg.architecture
|
|---|
| 411 | name = ("%s:%s" % (package, arch))
|
|---|
| 412 | if (not self.packages.has_key(name)):
|
|---|
| 413 | self.packages[name] = pkg
|
|---|
| 414 |
|
|---|
| 415 | if pkg.compare_version(self.packages[name]) >= 0:
|
|---|
| 416 | self.packages[name] = pkg
|
|---|
| 417 | return 0
|
|---|
| 418 | else:
|
|---|
| 419 | return 1
|
|---|
| 420 |
|
|---|
| 421 | def read_packages_file(self, fn):
|
|---|
| 422 | f = open(fn, "r")
|
|---|
| 423 | while 1:
|
|---|
| 424 | pkg = Package()
|
|---|
| 425 | pkg.read_control(f)
|
|---|
| 426 | if pkg.get_package():
|
|---|
| 427 | self.add_package(pkg)
|
|---|
| 428 | else:
|
|---|
| 429 | break
|
|---|
| 430 | f.close()
|
|---|
| 431 | return
|
|---|
| 432 |
|
|---|
| 433 | def write_packages_file(self, fn):
|
|---|
| 434 | f = open(fn, "w")
|
|---|
| 435 | names = self.packages.keys()
|
|---|
| 436 | names.sort()
|
|---|
| 437 | for name in names:
|
|---|
| 438 | f.write(self.packages[name].__repr__())
|
|---|
| 439 | return
|
|---|
| 440 |
|
|---|
| 441 | def keys(self):
|
|---|
| 442 | return self.packages.keys()
|
|---|
| 443 |
|
|---|
| 444 | def __getitem__(self, key):
|
|---|
| 445 | return self.packages[key]
|
|---|
| 446 |
|
|---|
| 447 | if __name__ == "__main__":
|
|---|
| 448 | package = Package()
|
|---|
| 449 |
|
|---|
| 450 | package.set_package("FooBar")
|
|---|
| 451 | package.set_version("0.1-fam1")
|
|---|
| 452 | package.set_architecture("arm")
|
|---|
| 453 | package.set_maintainer("Testing <testing@testing.testing>")
|
|---|
| 454 | package.set_depends("libc")
|
|---|
| 455 | package.set_description("A test of the APIs.")
|
|---|
| 456 |
|
|---|
| 457 | print "<"
|
|---|
| 458 | sys.stdout.write(package)
|
|---|
| 459 | print ">"
|
|---|
| 460 |
|
|---|
| 461 | package.write_package("/tmp")
|
|---|
| 462 |
|
|---|