Baseline for work is Dialcentral 1.0.6-10
[theonering] / support / py2deb.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 ##
4 ##    Copyright (C) 2009 manatlan manatlan[at]gmail(dot)com
5 ##
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published
8 ## by the Free Software Foundation; version 2 only.
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 """
16 Known limitations :
17 - don't sign package (-us -uc)
18 - no distinctions between author and maintainer(packager)
19
20 depends on :
21 - dpkg-dev (dpkg-buildpackage)
22 - alien
23 - python
24 - fakeroot
25
26 changelog
27  - ??? 9/14/2009 (By epage)
28     - PEP8
29     - added recommends
30     - fixed bug where it couldn't handle the contents of the pre/post scripts being specified
31     - Added customization based on the targeted policy for sections (Maemo support)
32     - Added maemo specific tarball, dsc, changes file generation support (including icon support)
33  - 0.5 05/09/2009
34     - pre/post install/remove scripts enabled
35     - deb package install py2deb in dist-packages for py2.6
36  - 0.4 14/10/2008
37     - use os.environ USERNAME or USER (debian way)
38     - install on py 2.(4,5,6) (*FIX* do better here)
39
40 """
41
42 import os
43 import hashlib
44 import sys
45 import shutil
46 import time
47 import string
48 import StringIO
49 import stat
50 import commands
51 import base64
52 from tarfile import TarFile
53 from glob import glob
54 from datetime import datetime
55 import socket # gethostname()
56 from subprocess import Popen, PIPE
57
58 #~ __version__ = "0.4"
59 __version__ = "0.5"
60 __author__ = "manatlan"
61 __mail__ = "manatlan@gmail.com"
62
63
64 PERMS_URW_GRW_OR = stat.S_IRUSR | stat.S_IWUSR | \
65                    stat.S_IRGRP | stat.S_IWGRP | \
66                    stat.S_IROTH
67
68 UID_ROOT = 0
69 GID_ROOT = 0
70
71
72 def run(cmds):
73     p = Popen(cmds, shell=False, stdout=PIPE, stderr=PIPE)
74     time.sleep(0.01)    # to avoid "IOError: [Errno 4] Interrupted system call"
75     out = string.join(p.stdout.readlines()).strip()
76     outerr = string.join(p.stderr.readlines()).strip()
77     return out
78
79
80 def deb2rpm(file):
81     txt=run(['alien', '-r', file])
82     return txt.split(" generated")[0]
83
84
85 def py2src(TEMP, name):
86     l=glob("%(TEMP)s/%(name)s*.tar.gz"%locals())
87     if len(l) != 1:
88         raise Py2debException("don't find source package tar.gz")
89
90     tar = os.path.basename(l[0])
91     shutil.move(l[0], tar)
92
93     return tar
94
95
96 def md5sum(filename):
97     f = open(filename, "r")
98     try:
99         return hashlib.md5(f.read()).hexdigest()
100     finally:
101         f.close()
102
103
104 class Py2changes(object):
105
106     def __init__(self, ChangedBy, description, changes, files, category, repository, **kwargs):
107       self.options = kwargs # TODO: Is order important?
108       self.description = description
109       self.changes=changes
110       self.files=files
111       self.category=category
112       self.repository=repository
113       self.ChangedBy=ChangedBy
114
115     def getContent(self):
116         content = ["%s: %s" % (k, v)
117                    for k,v in self.options.iteritems()]
118
119         if self.description:
120             description=self.description.replace("\n","\n ")
121             content.append('Description: ')
122             content.append(' %s' % description)
123         if self.changes:
124             changes=self.changes.replace("\n","\n ")
125             content.append('Changes: ')
126             content.append(' %s' % changes)
127         if self.ChangedBy:
128             content.append("Changed-By: %s" % self.ChangedBy)
129
130         content.append('Files:')
131
132         for onefile in self.files:
133             md5 = md5sum(onefile)
134             size = os.stat(onefile).st_size.__str__()
135             content.append(' ' + md5 + ' ' + size + ' ' + self.category +' '+self.repository+' '+os.path.basename(onefile))
136
137         return "\n".join(content) + "\n\n"
138
139
140 def py2changes(params):
141     changescontent = Py2changes(
142                     "%(author)s <%(mail)s>" % params,
143                     "%(description)s" % params,
144                     "%(changelog)s" % params,
145                     (
146                            "%(TEMP)s/%(name)s_%(version)s.tar.gz" % params,
147                            "%(TEMP)s/%(name)s_%(version)s.dsc" % params,
148                     ),
149                     "%(section)s" % params,
150                     "", #"%(repository)s" % params,
151                     Format='1.7',
152                     Date=time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()),
153                     Source="%(name)s" % params,
154                     Architecture="%(arch)s" % params,
155                     Version="%(version)s" % params,
156                     Distribution="", #"%(distribution)s" % params,
157                     Urgency="", #"%(urgency)s" % params,
158                     Maintainer="%(author)s <%(mail)s>" % params
159                     )
160     f = open("%(TEMP)s/%(name)s_%(version)s.changes" % params,"wb")
161     f.write(changescontent.getContent())
162     f.close()
163
164     fileHandle = open('/tmp/py2deb2.tmp', 'w')
165     fileHandle.write('#!/bin/sh\n')
166     fileHandle.write("cd " +os.getcwd()+ "\n")
167     fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.changes\n" % params)
168     fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.changes.asc %(TEMP)s/%(name)s_%(version)s.changes\n" % params)
169     fileHandle.write('\nexit')
170     fileHandle.close()
171     commands.getoutput("chmod 777 /tmp/py2deb2.tmp")
172     commands.getoutput("/tmp/py2deb2.tmp")
173
174     ret = []
175
176     l=glob("%(TEMP)s/%(name)s*.tar.gz" % params)
177     if len(l)!=1:
178         raise Py2debException("don't find source package tar.gz")
179     tar = os.path.basename(l[0])
180     shutil.move(l[0],tar)
181     ret.append(tar)
182
183     l=glob("%(TEMP)s/%(name)s*.dsc" % params)
184     if len(l)!=1:
185         raise Py2debException("don't find source package dsc")
186     tar = os.path.basename(l[0])
187     shutil.move(l[0],tar)
188     ret.append(tar)
189
190     l=glob("%(TEMP)s/%(name)s*.changes" % params)
191     if len(l)!=1:
192         raise Py2debException("don't find source package changes")
193     tar = os.path.basename(l[0])
194     shutil.move(l[0],tar)
195     ret.append(tar)
196
197     return ret
198
199
200 class Py2dsc(object):
201
202     def __init__(self, StandardsVersion, BuildDepends, files, **kwargs):
203       self.options = kwargs # TODO: Is order important?
204       self.StandardsVersion = StandardsVersion
205       self.BuildDepends=BuildDepends
206       self.files=files
207
208     @property
209     def content(self):
210         content = ["%s: %s" % (k, v)
211                    for k,v in self.options.iteritems()]
212
213         if self.BuildDepends:
214             content.append("Build-Depends: %s" % self.BuildDepends)
215         if self.StandardsVersion:
216             content.append("Standards-Version: %s" % self.StandardsVersion)
217
218         content.append('Files:')
219
220         for onefile in self.files:
221             print onefile
222             md5 = md5sum(onefile)
223             size = os.stat(onefile).st_size.__str__()
224             content.append(' '+md5 + ' ' + size +' '+os.path.basename(onefile))
225
226         return "\n".join(content)+"\n\n"
227
228
229 def py2dsc(TEMP, name, version, depends, author, mail, arch):
230     dsccontent = Py2dsc("%(version)s" % locals(),
231              "%(depends)s" % locals(),
232              ("%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals(),),
233              Format='1.0',
234              Source="%(name)s" % locals(),
235              Version="%(version)s" % locals(),
236              Maintainer="%(author)s <%(mail)s>" % locals(),
237              Architecture="%(arch)s" % locals(),
238     )
239
240     filename = "%(TEMP)s/%(name)s_%(version)s.dsc" % locals()
241
242     f = open(filename, "wb")
243     try:
244         f.write(dsccontent.content)
245     finally:
246         f.close()
247
248     fileHandle = open('/tmp/py2deb.tmp', 'w')
249     try:
250         fileHandle.write('#!/bin/sh\n')
251         fileHandle.write("cd " + os.getcwd() + "\n")
252         fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.dsc\n" % locals())
253         fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.dsc.asc %(filename)s\n" % locals())
254         fileHandle.write('\nexit')
255         fileHandle.close()
256     finally:
257         f.close()
258
259     commands.getoutput("chmod 777 /tmp/py2deb.tmp")
260     commands.getoutput("/tmp/py2deb.tmp")
261
262     return filename
263
264
265 class Py2tar(object):
266
267     def __init__(self, dataDirectoryPath):
268         self._dataDirectoryPath = dataDirectoryPath
269
270     def packed(self):
271         return self._getSourcesFiles()
272
273     def _getSourcesFiles(self):
274         directoryPath = self._dataDirectoryPath
275
276         outputFileObj = StringIO.StringIO() # TODO: Do more transparently?
277
278         tarOutput = TarFile.open('sources',
279                                  mode = "w:gz",
280                                  fileobj = outputFileObj)
281
282         # Note: We can't use this because we need to fiddle permissions:
283         #       tarOutput.add(directoryPath, arcname = "")
284
285         for root, dirs, files in os.walk(directoryPath):
286             archiveRoot = root[len(directoryPath):]
287
288             tarinfo = tarOutput.gettarinfo(root, archiveRoot)
289             # TODO: Make configurable?
290             tarinfo.uid = UID_ROOT
291             tarinfo.gid = GID_ROOT
292             tarinfo.uname = ""
293             tarinfo.gname = ""
294             tarOutput.addfile(tarinfo)
295
296             for f in  files:
297                 tarinfo = tarOutput.gettarinfo(os.path.join(root, f),
298                                                os.path.join(archiveRoot, f))
299                 tarinfo.uid = UID_ROOT
300                 tarinfo.gid = GID_ROOT
301                 tarinfo.uname = ""
302                 tarinfo.gname = ""
303                 tarOutput.addfile(tarinfo, file(os.path.join(root, f)))
304
305         tarOutput.close()
306
307         data_tar_gz = outputFileObj.getvalue()
308
309         return data_tar_gz
310
311
312 def py2tar(DEST, TEMP, name, version):
313     tarcontent = Py2tar("%(DEST)s" % locals())
314     filename = "%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals()
315     f = open(filename, "wb")
316     try:
317         f.write(tarcontent.packed())
318     finally:
319         f.close()
320     return filename
321
322
323 class Py2debException(Exception):
324     pass
325
326
327 SECTIONS_BY_POLICY = {
328     # http://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections
329     "debian": "admin, base, comm, contrib, devel, doc, editors, electronics, embedded, games, gnome, graphics, hamradio, interpreters, kde, libs, libdevel, mail, math, misc, net, news, non-free, oldlibs, otherosfs, perl, python, science, shells, sound, tex, text, utils, web, x11",
330     # http://maemo.org/forrest-images/pdf/maemo-policy.pdf
331     "chinook": "accessories, communication, games, multimedia, office, other, programming, support, themes, tools",
332     # http://wiki.maemo.org/Task:Package_categories
333     "diablo": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities",
334     # http://wiki.maemo.org/Task:Fremantle_application_categories
335     "mer": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities",
336     # http://wiki.maemo.org/Task:Fremantle_application_categories
337     "fremantle": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities",
338 }
339
340
341 class Py2deb(object):
342     """
343     heavily based on technic described here :
344     http://wiki.showmedo.com/index.php?title=LinuxJensMakingDeb
345     """
346     ## STATICS
347     clear=False  # clear build folder after py2debianization
348
349     SECTIONS = SECTIONS_BY_POLICY["debian"]
350
351     #http://www.debian.org/doc/debian-policy/footnotes.html#f69
352     ARCHS="all i386 ia64 alpha amd64 armeb arm hppa m32r m68k mips mipsel powerpc ppc64 s390 s390x sh3 sh3eb sh4 sh4eb sparc darwin-i386 darwin-ia64 darwin-alpha darwin-amd64 darwin-armeb darwin-arm darwin-hppa darwin-m32r darwin-m68k darwin-mips darwin-mipsel darwin-powerpc darwin-ppc64 darwin-s390 darwin-s390x darwin-sh3 darwin-sh3eb darwin-sh4 darwin-sh4eb darwin-sparc freebsd-i386 freebsd-ia64 freebsd-alpha freebsd-amd64 freebsd-armeb freebsd-arm freebsd-hppa freebsd-m32r freebsd-m68k freebsd-mips freebsd-mipsel freebsd-powerpc freebsd-ppc64 freebsd-s390 freebsd-s390x freebsd-sh3 freebsd-sh3eb freebsd-sh4 freebsd-sh4eb freebsd-sparc kfreebsd-i386 kfreebsd-ia64 kfreebsd-alpha kfreebsd-amd64 kfreebsd-armeb kfreebsd-arm kfreebsd-hppa kfreebsd-m32r kfreebsd-m68k kfreebsd-mips kfreebsd-mipsel kfreebsd-powerpc kfreebsd-ppc64 kfreebsd-s390 kfreebsd-s390x kfreebsd-sh3 kfreebsd-sh3eb kfreebsd-sh4 kfreebsd-sh4eb kfreebsd-sparc knetbsd-i386 knetbsd-ia64 knetbsd-alpha knetbsd-amd64 knetbsd-armeb knetbsd-arm knetbsd-hppa knetbsd-m32r knetbsd-m68k knetbsd-mips knetbsd-mipsel knetbsd-powerpc knetbsd-ppc64 knetbsd-s390 knetbsd-s390x knetbsd-sh3 knetbsd-sh3eb knetbsd-sh4 knetbsd-sh4eb knetbsd-sparc netbsd-i386 netbsd-ia64 netbsd-alpha netbsd-amd64 netbsd-armeb netbsd-arm netbsd-hppa netbsd-m32r netbsd-m68k netbsd-mips netbsd-mipsel netbsd-powerpc netbsd-ppc64 netbsd-s390 netbsd-s390x netbsd-sh3 netbsd-sh3eb netbsd-sh4 netbsd-sh4eb netbsd-sparc openbsd-i386 openbsd-ia64 openbsd-alpha openbsd-amd64 openbsd-armeb openbsd-arm openbsd-hppa openbsd-m32r openbsd-m68k openbsd-mips openbsd-mipsel openbsd-powerpc openbsd-ppc64 openbsd-s390 openbsd-s390x openbsd-sh3 openbsd-sh3eb openbsd-sh4 openbsd-sh4eb openbsd-sparc hurd-i386 hurd-ia64 hurd-alpha hurd-amd64 hurd-armeb hurd-arm hurd-hppa hurd-m32r hurd-m68k hurd-mips hurd-mipsel hurd-powerpc hurd-ppc64 hurd-s390 hurd-s390x hurd-sh3 hurd-sh3eb hurd-sh4 hurd-sh4eb hurd-sparc".split(" ")
353
354     # license terms taken from dh_make
355     LICENSES=["gpl", "lgpl", "bsd", "artistic"]
356
357     def __setitem__(self, path, files):
358
359         if not type(files)==list:
360             raise Py2debException("value of key path '%s' is not a list"%path)
361         if not files:
362             raise Py2debException("value of key path '%s' should'nt be empty"%path)
363         if not path.startswith("/"):
364             raise Py2debException("key path '%s' malformed (don't start with '/')"%path)
365         if path.endswith("/"):
366             raise Py2debException("key path '%s' malformed (shouldn't ends with '/')"%path)
367
368         nfiles=[]
369         for file in files:
370
371             if ".." in file:
372                 raise Py2debException("file '%s' contains '..', please avoid that!"%file)
373
374
375             if "|" in file:
376                 if file.count("|")!=1:
377                     raise Py2debException("file '%s' is incorrect (more than one pipe)"%file)
378
379                 file, nfile = file.split("|")
380             else:
381                 nfile=file  # same localisation
382
383             if os.path.isdir(file):
384                 raise Py2debException("file '%s' is a folder, and py2deb refuse folders !"%file)
385
386             if not os.path.isfile(file):
387                 raise Py2debException("file '%s' doesn't exist"%file)
388
389             if file.startswith("/"):    # if an absolute file is defined
390                 if file==nfile:         # and not renamed (pipe trick)
391                     nfile=os.path.basename(file)   # it's simply copied to 'path'
392
393             nfiles.append((file, nfile))
394
395         nfiles.sort(lambda a, b: cmp(a[1], b[1]))    #sort according new name (nfile)
396
397         self.__files[path]=nfiles
398
399     def __delitem__(self, k):
400         del self.__files[k]
401
402     def __init__(self,
403                     name,
404                     description="no description",
405                     license="gpl",
406                     depends="",
407                     section="utils",
408                     arch="all",
409
410                     url="",
411                     author = None,
412                     mail = None,
413
414                     preinstall = None,
415                     postinstall = None,
416                     preremove = None,
417                     postremove = None
418                 ):
419
420         if author is None:
421             author = ("USERNAME" in os.environ) and os.environ["USERNAME"] or None
422             if author is None:
423                 author = ("USER" in os.environ) and os.environ["USER"] or "unknown"
424
425         if mail is None:
426             mail = author+"@"+socket.gethostname()
427
428         self.name = name
429         self.description = description
430         self.upgradeDescription = ""
431         self.license = license
432         self.depends = depends
433         self.recommends = ""
434         self.section = section
435         self.arch = arch
436         self.url = url
437         self.author = author
438         self.mail = mail
439         self.icon = ""
440
441         self.preinstall = preinstall
442         self.postinstall = postinstall
443         self.preremove = preremove
444         self.postremove = postremove
445
446         self.__files={}
447
448     def __repr__(self):
449         name = self.name
450         license = self.license
451         description = self.description
452         depends = self.depends
453         recommends = self.recommends
454         section = self.section
455         arch = self.arch
456         url = self.url
457         author = self.author
458         mail = self.mail
459
460         preinstall = self.preinstall
461         postinstall = self.postinstall
462         preremove = self.preremove
463         postremove = self.postremove
464
465         paths=self.__files.keys()
466         paths.sort()
467         files=[]
468         for path in paths:
469             for file, nfile in self.__files[path]:
470                 #~ rfile=os.path.normpath(os.path.join(path, nfile))
471                 rfile=os.path.join(path, nfile)
472                 if nfile==file:
473                     files.append(rfile)
474                 else:
475                     files.append(rfile + " (%s)"%file)
476
477         files.sort()
478         files = "\n".join(files)
479
480
481         lscripts = [    preinstall and "preinst",
482                         postinstall and "postinst",
483                         preremove and "prerm",
484                         postremove and "postrm",
485                     ]
486         scripts = lscripts and ", ".join([i for i in lscripts if i]) or "None"
487         return """
488 ----------------------------------------------------------------------
489 NAME        : %(name)s
490 ----------------------------------------------------------------------
491 LICENSE     : %(license)s
492 URL         : %(url)s
493 AUTHOR      : %(author)s
494 MAIL        : %(mail)s
495 ----------------------------------------------------------------------
496 DEPENDS     : %(depends)s
497 RECOMMENDS  : %(recommends)s
498 ARCH        : %(arch)s
499 SECTION     : %(section)s
500 ----------------------------------------------------------------------
501 DESCRIPTION :
502 %(description)s
503 ----------------------------------------------------------------------
504 SCRIPTS : %(scripts)s
505 ----------------------------------------------------------------------
506 FILES :
507 %(files)s
508 """ % locals()
509
510     def generate(self, version, changelog="", rpm=False, src=False, build=True, tar=False, changes=False, dsc=False):
511         """ generate a deb of version 'version', with or without 'changelog', with or without a rpm
512             (in the current folder)
513             return a list of generated files
514         """
515         if not sum([len(i) for i in self.__files.values()])>0:
516             raise Py2debException("no files are defined")
517
518         if not changelog:
519             changelog="* no changelog"
520
521         name = self.name
522         description = self.description
523         license = self.license
524         depends = self.depends
525         recommends = self.recommends
526         section = self.section
527         arch = self.arch
528         url = self.url
529         author = self.author
530         mail = self.mail
531         files = self.__files
532         preinstall = self.preinstall
533         postinstall = self.postinstall
534         preremove = self.preremove
535         postremove = self.postremove
536
537         if section not in Py2deb.SECTIONS:
538             raise Py2debException("section '%s' is unknown (%s)" % (section, str(Py2deb.SECTIONS)))
539
540         if arch not in Py2deb.ARCHS:
541             raise Py2debException("arch '%s' is unknown (%s)"% (arch, str(Py2deb.ARCHS)))
542
543         if license not in Py2deb.LICENSES:
544             raise Py2debException("License '%s' is unknown (%s)" % (license, str(Py2deb.LICENSES)))
545
546         # create dates (buildDate, buildDateYear)
547         d=datetime.now()
548         buildDate=d.strftime("%a, %d %b %Y %H:%M:%S +0000")
549         buildDateYear=str(d.year)
550
551
552         #clean description (add a space before each next lines)
553         description=description.replace("\r", "").strip()
554         description = "\n ".join(description.split("\n"))
555
556         #clean changelog (add 2 spaces before each next lines)
557         changelog=changelog.replace("\r", "").strip()
558         changelog = "\n  ".join(changelog.split("\n"))
559
560
561         TEMP = ".py2deb_build_folder"
562         DEST = os.path.join(TEMP, name)
563         DEBIAN = os.path.join(DEST, "debian")
564
565         # let's start the process
566         try:
567             shutil.rmtree(TEMP)
568         except:
569             pass
570
571         os.makedirs(DEBIAN)
572         try:
573             rules=[]
574             dirs=[]
575             for path in files:
576                 for ofile, nfile in files[path]:
577                     if os.path.isfile(ofile):
578                         # it's a file
579
580                         if ofile.startswith("/"): # if absolute path
581                             # we need to change dest
582                             dest=os.path.join(DEST, nfile)
583                         else:
584                             dest=os.path.join(DEST, ofile)
585
586                         # copy file to be packaged
587                         destDir = os.path.dirname(dest)
588                         if not os.path.isdir(destDir):
589                             os.makedirs(destDir)
590
591                         shutil.copy2(ofile, dest)
592
593                         ndir = os.path.join(path, os.path.dirname(nfile))
594                         nname = os.path.basename(nfile)
595
596                         # make a line RULES to be sure the destination folder is created
597                         # and one for copying the file
598                         fpath = "/".join(["$(CURDIR)", "debian", name+ndir])
599                         rules.append('mkdir -p "%s"' % fpath)
600                         rules.append('cp -a "%s" "%s"' % (ofile, os.path.join(fpath, nname)))
601
602                         # append a dir
603                         dirs.append(ndir)
604
605                     else:
606                         raise Py2debException("unknown file '' "%ofile) # shouldn't be raised (because controlled before)
607
608             # make rules right
609             rules= "\n\t".join(rules) + "\n"
610
611             # make dirs right
612             dirs= [i[1:] for i in set(dirs)]
613             dirs.sort()
614
615             #==========================================================================
616             # CREATE debian/dirs
617             #==========================================================================
618             open(os.path.join(DEBIAN, "dirs"), "w").write("\n".join(dirs))
619
620             #==========================================================================
621             # CREATE debian/changelog
622             #==========================================================================
623             clog="""%(name)s (%(version)s) stable; urgency=low
624
625   %(changelog)s
626
627  -- %(author)s <%(mail)s>  %(buildDate)s
628 """ % locals()
629
630             open(os.path.join(DEBIAN, "changelog"), "w").write(clog)
631
632             #==========================================================================
633             #Create pre/post install/remove
634             #==========================================================================
635             def mkscript(name, dest):
636                 if name and name.strip()!="":
637                     if os.path.isfile(name):    # it's a file
638                         content = file(name).read()
639                     else:   # it's a script
640                         content = name
641                     open(os.path.join(DEBIAN, dest), "w").write(content)
642
643             mkscript(preinstall, "preinst")
644             mkscript(postinstall, "postinst")
645             mkscript(preremove, "prerm")
646             mkscript(postremove, "postrm")
647
648
649             #==========================================================================
650             # CREATE debian/compat
651             #==========================================================================
652             open(os.path.join(DEBIAN, "compat"), "w").write("5\n")
653
654             #==========================================================================
655             # CREATE debian/control
656             #==========================================================================
657             txt="""Source: %(name)s
658 Section: %(section)s
659 Priority: extra
660 Maintainer: %(author)s <%(mail)s>
661 Build-Depends: debhelper (>= 5)
662 Standards-Version: 3.7.2
663
664 Package: %(name)s
665 Architecture: %(arch)s
666 Depends: %(depends)s
667 Recommends: %(recommends)s
668 Description: %(description)s""" % locals()
669             if self.upgradeDescription:
670                 upgradeDescription = "XB-Maemo-Upgrade-Description: %s" % self.upgradeDescription.strip()
671                 txt = "%s\n%s" % (txt, "\n  ".join(upgradeDescription.split("\n")))
672             if self.icon:
673                 f = open(self.icon, "rb")
674                 try:
675                     rawIcon = f.read()
676                 finally:
677                     f.close()
678                 uueIcon = base64.b64encode(rawIcon)
679                 uueIconLines = []
680                 for i, c in enumerate(uueIcon):
681                     if i % 60 == 0:
682                         uueIconLines.append("")
683                     uueIconLines[-1] += c
684                 uueIconLines[0:0] = ("XB-Maemo-Icon-26:", )
685                 txt = "\n".join((txt, "\n  ".join(uueIconLines), ""))
686             open(os.path.join(DEBIAN, "control"), "w").write(txt)
687
688             #==========================================================================
689             # CREATE debian/copyright
690             #==========================================================================
691             copy={}
692             copy["gpl"]="""
693     This package is free software; you can redistribute it and/or modify
694     it under the terms of the GNU General Public License as published by
695     the Free Software Foundation; either version 2 of the License, or
696     (at your option) any later version.
697
698     This package is distributed in the hope that it will be useful,
699     but WITHOUT ANY WARRANTY; without even the implied warranty of
700     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
701     GNU General Public License for more details.
702
703     You should have received a copy of the GNU General Public License
704     along with this package; if not, write to the Free Software
705     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
706
707 On Debian systems, the complete text of the GNU General
708 Public License can be found in `/usr/share/common-licenses/GPL'.
709 """
710             copy["lgpl"]="""
711     This package is free software; you can redistribute it and/or
712     modify it under the terms of the GNU Lesser General Public
713     License as published by the Free Software Foundation; either
714     version 2 of the License, or (at your option) any later version.
715
716     This package is distributed in the hope that it will be useful,
717     but WITHOUT ANY WARRANTY; without even the implied warranty of
718     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
719     Lesser General Public License for more details.
720
721     You should have received a copy of the GNU Lesser General Public
722     License along with this package; if not, write to the Free Software
723     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
724
725 On Debian systems, the complete text of the GNU Lesser General
726 Public License can be found in `/usr/share/common-licenses/LGPL'.
727 """
728             copy["bsd"]="""
729     Redistribution and use in source and binary forms, with or without
730     modification, are permitted under the terms of the BSD License.
731
732     THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
733     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
734     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
735     ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
736     FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
737     DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
738     OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
739     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
740     LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
741     OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
742     SUCH DAMAGE.
743
744 On Debian systems, the complete text of the BSD License can be
745 found in `/usr/share/common-licenses/BSD'.
746 """
747             copy["artistic"]="""
748     This program is free software; you can redistribute it and/or modify it
749     under the terms of the "Artistic License" which comes with Debian.
750
751     THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
752     WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES
753     OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
754
755 On Debian systems, the complete text of the Artistic License
756 can be found in `/usr/share/common-licenses/Artistic'.
757 """
758
759             txtLicense = copy[license]
760             pv=__version__
761             txt="""This package was py2debianized(%(pv)s) by %(author)s <%(mail)s> on
762 %(buildDate)s.
763
764 It was downloaded from %(url)s
765
766 Upstream Author: %(author)s <%(mail)s>
767
768 Copyright: %(buildDateYear)s by %(author)s
769
770 License:
771
772 %(txtLicense)s
773
774 The Debian packaging is (C) %(buildDateYear)s, %(author)s <%(mail)s> and
775 is licensed under the GPL, see above.
776
777
778 # Please also look if there are files or directories which have a
779 # different copyright/license attached and list them here.
780 """ % locals()
781             open(os.path.join(DEBIAN, "copyright"), "w").write(txt)
782
783             #==========================================================================
784             # CREATE debian/rules
785             #==========================================================================
786             txt="""#!/usr/bin/make -f
787 # -*- makefile -*-
788 # Sample debian/rules that uses debhelper.
789 # This file was originally written by Joey Hess and Craig Small.
790 # As a special exception, when this file is copied by dh-make into a
791 # dh-make output file, you may use that output file without restriction.
792 # This special exception was added by Craig Small in version 0.37 of dh-make.
793
794 # Uncomment this to turn on verbose mode.
795 #export DH_VERBOSE=1
796
797
798
799
800 CFLAGS = -Wall -g
801
802 ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
803         CFLAGS += -O0
804 else
805         CFLAGS += -O2
806 endif
807
808 configure: configure-stamp
809 configure-stamp:
810         dh_testdir
811         # Add here commands to configure the package.
812
813         touch configure-stamp
814
815
816 build: build-stamp
817
818 build-stamp: configure-stamp
819         dh_testdir
820         touch build-stamp
821
822 clean:
823         dh_testdir
824         dh_testroot
825         rm -f build-stamp configure-stamp
826         dh_clean
827
828 install: build
829         dh_testdir
830         dh_testroot
831         dh_clean -k
832         dh_installdirs
833
834         # ======================================================
835         #$(MAKE) DESTDIR="$(CURDIR)/debian/%(name)s" install
836         mkdir -p "$(CURDIR)/debian/%(name)s"
837
838         %(rules)s
839         # ======================================================
840
841 # Build architecture-independent files here.
842 binary-indep: build install
843 # We have nothing to do by default.
844
845 # Build architecture-dependent files here.
846 binary-arch: build install
847         dh_testdir
848         dh_testroot
849         dh_installchangelogs debian/changelog
850         dh_installdocs
851         dh_installexamples
852 #       dh_install
853 #       dh_installmenu
854 #       dh_installdebconf
855 #       dh_installlogrotate
856 #       dh_installemacsen
857 #       dh_installpam
858 #       dh_installmime
859 #       dh_python
860 #       dh_installinit
861 #       dh_installcron
862 #       dh_installinfo
863         dh_installman
864         dh_link
865         dh_strip
866         dh_compress
867         dh_fixperms
868 #       dh_perl
869 #       dh_makeshlibs
870         dh_installdeb
871         dh_shlibdeps
872         dh_gencontrol
873         dh_md5sums
874         dh_builddeb
875
876 binary: binary-indep binary-arch
877 .PHONY: build clean binary-indep binary-arch binary install configure
878 """ % locals()
879             open(os.path.join(DEBIAN, "rules"), "w").write(txt)
880             os.chmod(os.path.join(DEBIAN, "rules"), 0755)
881
882             ###########################################################################
883             ###########################################################################
884             ###########################################################################
885
886             generatedFiles = []
887
888             if build:
889                 #http://www.debian.org/doc/manuals/maint-guide/ch-build.fr.html
890                 ret=os.system('cd "%(DEST)s"; dpkg-buildpackage -tc -rfakeroot -us -uc' % locals())
891                 if ret != 0:
892                     raise Py2debException("buildpackage failed (see output)")
893
894                 l=glob("%(TEMP)s/%(name)s*.deb"%locals())
895                 if len(l) != 1:
896                     raise Py2debException("didn't find builded deb")
897
898                 tdeb = l[0]
899                 deb = os.path.basename(tdeb)
900                 shutil.move(tdeb, deb)
901
902                 generatedFiles = [deb, ]
903
904                 if rpm:
905                     rpmFilename = deb2rpm(deb)
906                     generatedFiles.append(rpmFilename)
907
908                 if src:
909                     tarFilename = py2src(TEMP, name)
910                     generatedFiles.append(tarFilename)
911
912             if tar:
913                 tarFilename = py2tar(DEST, TEMP, name, version)
914                 generatedFiles.append(tarFilename)
915
916             if dsc:
917                 dscFilename = py2dsc(TEMP, name, version, depends, author, mail, arch)
918                 generatedFiles.append(dscFilename)
919
920             if changes:
921                 changesFilenames = py2changes(locals())
922                 generatedFiles.extend(changesFilenames)
923
924             return generatedFiles
925
926         #~ except Exception,m:
927             #~ raise Py2debException("build error :"+str(m))
928
929         finally:
930             if Py2deb.clear:
931                 shutil.rmtree(TEMP)
932
933
934 if __name__ == "__main__":
935     try:
936         os.chdir(os.path.dirname(sys.argv[0]))
937     except:
938         pass
939
940     p=Py2deb("python-py2deb")
941     p.description="Generate simple deb(/rpm/tgz) from python (2.4, 2.5 and 2.6)"
942     p.url = "http://www.manatlan.com/page/py2deb"
943     p.author=__author__
944     p.mail=__mail__
945     p.depends = "dpkg-dev, fakeroot, alien, python"
946     p.section="python"
947     p["/usr/lib/python2.6/dist-packages"] = ["py2deb.py", ]
948     p["/usr/lib/python2.5/site-packages"] = ["py2deb.py", ]
949     p["/usr/lib/python2.4/site-packages"] = ["py2deb.py", ]
950     #~ p.postinstall = "s.py"
951     #~ p.preinstall = "s.py"
952     #~ p.postremove = "s.py"
953     #~ p.preremove = "s.py"
954     print p
955     print p.generate(__version__, changelog = __doc__, src=True)