source: ipk/patch/src/ipkg-utils-050831/ipkg.py@ 4800

Last change on this file since 4800 was 4800, checked in by Captain, 15 years ago

[ipk] fix TAR BUG

File size: 14.1 KB
Line 
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
35import tempfile
36import os
37import sys
38import glob
39import md5
40import re
41import string
42import commands
43from stat import ST_SIZE
44
45class 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
94def 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
103class 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
402class 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
447if __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
Note: See TracBrowser for help on using the repository browser.