From: epage Date: Wed, 16 Sep 2009 04:24:36 +0000 (+0000) Subject: Mostly improvements to packaging X-Git-Url: http://vcs.maemo.org/git/?a=commitdiff_plain;h=6a2e0155450cd2ce69c55adf120eba981f6d7c6f;p=gc-dialer Mostly improvements to packaging git-svn-id: file:///svnroot/gc-dialer/trunk@441 c39d3808-3fe2-4d86-a59f-b7f623ee9f21 --- diff --git a/Makefile b/Makefile index 58d7ee8..93ae732 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PROGRAM=$(SOURCE_PATH)/$(PROJECT_NAME).py DATA_TYPES=*.ini *.map *.glade *.png DATA=$(foreach type, $(DATA_TYPES), $(shell find $(SOURCE_PATH) -iname "$(type)")) OBJ=$(SOURCE:.py=.pyc) -BUILD_PATH=./build/ +BUILD_PATH=./build TAG_FILE=~/.ctags/$(PROJECT_NAME).tags TODO_FILE=./TODO @@ -37,20 +37,33 @@ debug: $(OBJ) test: $(OBJ) $(UNIT_TEST) -build: $(OBJ) +package: $(OBJ) rm -Rf $(BUILD_PATH) - mkdir $(BUILD_PATH) - cp $(SOURCE_PATH)/constants.py $(BUILD_PATH) - cp $(SOURCE_PATH)/$(PROJECT_NAME).py $(BUILD_PATH) - $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) - $(foreach file, $(SOURCE), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) - $(foreach file, $(OBJ), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) - cp support/$(PROJECT_NAME).desktop $(BUILD_PATH) - cp support/icons/hicolor/26x26/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/26x26-$(PROJECT_NAME).png - cp support/icons/hicolor/64x64/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/64x64-$(PROJECT_NAME).png - cp support/icons/hicolor/scalable/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/scale-$(PROJECT_NAME).png - cp support/builddeb.py $(BUILD_PATH) - cp support/fake_py2deb.py $(BUILD_PATH) + mkdir -p $(BUILD_PATH)/generic + cp $(SOURCE_PATH)/constants.py $(BUILD_PATH)/generic + cp $(SOURCE_PATH)/$(PROJECT_NAME).py $(BUILD_PATH)/generic + $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; ) + $(foreach file, $(SOURCE), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; ) + $(foreach file, $(OBJ), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; ) + cp support/$(PROJECT_NAME).desktop $(BUILD_PATH)/generic + cp support/icons/hicolor/26x26/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/26x26-$(PROJECT_NAME).png + cp support/icons/hicolor/64x64/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/64x64-$(PROJECT_NAME).png + cp support/icons/hicolor/scalable/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/scale-$(PROJECT_NAME).png + cp support/builddeb.py $(BUILD_PATH)/generic + cp support/py2deb.py $(BUILD_PATH)/generic + cp support/fake_py2deb.py $(BUILD_PATH)/generic + mkdir -p $(BUILD_PATH)/chinook + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/chinook + cd $(BUILD_PATH)/chinook ; python builddeb.py chinook + mkdir -p $(BUILD_PATH)/diablo + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/diablo + cd $(BUILD_PATH)/diablo ; python builddeb.py diablo + mkdir -p $(BUILD_PATH)/fremantle + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/fremantle + cd $(BUILD_PATH)/fremantle ; python builddeb.py fremantle + mkdir -p $(BUILD_PATH)/mer + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/mer + cd $(BUILD_PATH)/mer ; python builddeb.py mer lint: $(OBJ) $(foreach file, $(SOURCE), $(LINT) $(file) ; ) diff --git a/src/constants.py b/src/constants.py index 10354bd..51e3803 100644 --- a/src/constants.py +++ b/src/constants.py @@ -3,7 +3,7 @@ import os __pretty_app_name__ = "DialCentral" __app_name__ = "dialcentral" __version__ = "1.0.6" -__build__ = 0 +__build__ = 1 __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".dialcentral") _user_settings_ = "%s/settings.ini" % _data_path_ diff --git a/src/dc_glade.py b/src/dc_glade.py index 2a3d7c9..f2b3f71 100755 --- a/src/dc_glade.py +++ b/src/dc_glade.py @@ -280,6 +280,7 @@ class Dialcentral(object): callbackMapping = { "on_paste": self._on_paste, "on_refresh": self._on_menu_refresh, + "on_rotate": self._on_menu_rotate, "on_clearcookies_clicked": self._on_clearcookies_clicked, "on_notebook_switch_page": self._on_notebook_switch_page, "on_about_activate": self._on_about_activate, @@ -442,7 +443,7 @@ class Dialcentral(object): @note UI Thread """ try: - self._defaultBackendId = int(config.get(constants.__pretty_app_name__, "active")) + self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active") blobs = ( config.get(constants.__pretty_app_name__, "bin_blob_%i" % i) for i in xrange(len(self._credentials)) @@ -455,6 +456,12 @@ class Dialcentral(object): if self._alarmHandler is not None: self._alarmHandler.load_settings(config, "alarm") + + previousOrientation = config.getint(constants.__pretty_app_name__, "orientation") + if previousOrientation == gtk.ORIENTATION_HORIZONTAL: + hildonize.window_to_landscape(self._window) + elif previousOrientation == gtk.ORIENTATION_VERTICAL: + hildonize.window_to_portrait(self._window) except ConfigParser.NoOptionError, e: logging.exception( "Settings file %s is missing section %s" % ( @@ -501,6 +508,7 @@ class Dialcentral(object): """ config.add_section(constants.__pretty_app_name__) config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId)) + config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation()))) for i, value in enumerate(self._credentials): blob = base64.b64encode(value) config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob) @@ -742,6 +750,16 @@ class Dialcentral(object): except Exception, e: self._errorDisplay.push_exception() + def _on_menu_rotate(self, *args): + try: + orientation = gtk_toolbox.get_screen_orientation() + if orientation == gtk.ORIENTATION_HORIZONTAL: + hildonize.window_to_portrait(self._window) + elif orientation == gtk.ORIENTATION_VERTICAL: + hildonize.window_to_landscape(self._window) + except Exception, e: + self._errorDisplay.push_exception() + def _on_paste(self, *args): try: contents = self._clipboard.wait_for_text() diff --git a/src/dialcentral.glade b/src/dialcentral.glade index 34a5326..b64d930 100644 --- a/src/dialcentral.glade +++ b/src/dialcentral.glade @@ -106,6 +106,14 @@ + + + _Rotate + True + True + + + @@ -249,10 +257,10 @@ False False - - - + + + True @@ -275,10 +283,10 @@ False False - - - + + + True @@ -301,10 +309,10 @@ False False - - - + + + True @@ -327,10 +335,10 @@ False False - - - + + + True @@ -355,10 +363,10 @@ False False - - - + + + True @@ -383,11 +391,11 @@ False False - - - - + + + + True @@ -410,10 +418,10 @@ False False - - - + + + True @@ -438,11 +446,11 @@ False False - - - - + + + + True diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index 18c3801..f402ac9 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -6,6 +6,7 @@ import os import errno import sys import time +import itertools import functools import contextlib import logging @@ -16,6 +17,31 @@ import gobject import gtk +def get_screen_orientation(): + width, height = gtk.gdk.get_default_root_window().get_size() + if width < height: + return gtk.ORIENTATION_VERTICAL + else: + return gtk.ORIENTATION_HORIZONTAL + + +def orientation_change_connect(handler, *args): + """ + @param handler(orientation, *args) -> None(?) + """ + initialScreenOrientation = get_screen_orientation() + orientationAndArgs = list(itertools.chain((initialScreenOrientation, ), args)) + + def _on_screen_size_changed(screen): + newScreenOrientation = get_screen_orientation() + if newScreenOrientation != orientationAndArgs[0]: + orientationAndArgs[0] = newScreenOrientation + handler(*orientationAndArgs) + + rootScreen = gtk.gdk.get_default_root_window() + return gtk.connect(rootScreen, "size-changed", _on_screen_size_changed) + + @contextlib.contextmanager def flock(path, timeout=-1): WAIT_FOREVER = -1 diff --git a/src/gv_views.py b/src/gv_views.py index 0ec77c5..1760922 100644 --- a/src/gv_views.py +++ b/src/gv_views.py @@ -886,7 +886,6 @@ class RecentCallsView(object): textrenderer = gtk.CellRendererText() textrenderer.set_property("yalign", 0) - hildonize.set_cell_thumb_selectable(textrenderer) self._numberColumn = gtk.TreeViewColumn("Number") self._numberColumn.pack_start(textrenderer, expand=True) self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX) diff --git a/src/hildonize.py b/src/hildonize.py index 15ddb05..ce80a94 100644 --- a/src/hildonize.py +++ b/src/hildonize.py @@ -2,6 +2,7 @@ import gtk +import dbus class FakeHildonModule(object): @@ -80,6 +81,54 @@ else: if IS_HILDON: + def mark_window_rotatable(window): + raise NotImplementedError("TODO distinguish between Diablo and Fremantle") + # gtk documentation is unclear whether this does a "=" or a "|=" + window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT) +else: + def mark_window_rotatable(window): + pass + + +if IS_HILDON: + def window_to_portrait(window): + raise NotImplementedError("TODO distinguish between Diablo and Fremantle") + # gtk documentation is unclear whether this does a "=" or a "|=" + window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT) + + def window_to_landscape(window): + raise NotImplementedError("TODO distinguish between Diablo and Fremantle") + # gtk documentation is unclear whether this does a "=" or a "&= ~" + window.unset_flags(hildon.HILDON_PORTRAIT_MODE_REQUEST) +else: + def window_to_portrait(window): + pass + + def window_to_landscape(window): + pass + + +def get_device_orientation(): + raise NotImplementedError() + bus = dbus.SystemBus() + try: + rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request") + mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request") + orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation() + except dbus.exception.DBusException: + # catching for documentation purposes that when a system doesn't + # support this, this is what to expect + raise + + if orientation == "": + return gtk.ORIENTATION_HORIZONTAL + elif orientation == "": + return gtk.ORIENTATION_VERTICAL + else: + raise RuntimeError("Unknown orientation: %s" % orientation) + + +if IS_HILDON: def hildonize_password_entry(textEntry): textEntry.set_property('hildon-input-mode', 7 | (1 << 29)) else: diff --git a/support/builddeb.py b/support/builddeb.py index 274712c..5f84a0b 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -17,7 +17,7 @@ __author__ = "Ed Page" __email__ = "eopage@byu.net" __version__ = constants.__version__ __build__ = constants.__build__ -__changelog__ = ''' +__changelog__ = """ 1.0.6 * Fixing some dependencies for Diablo * Fixed error on refreshing tabs when not logged in @@ -25,6 +25,13 @@ __changelog__ = ''' * Fixed Bug #4471 Notification Checkbox Won't Stay Checked (hour roll over error) * Implemented a work around for https://bugs.maemo.org/show_bug.cgi?id=4957 * Fixing a bug where phone numbers in texts wouldn't appear +* Deletes notifications on uninstall +* Fixed category for Fremantle/Diablo +* Fixed needing to manually create "~/.dialcentral" due to earlier logging changes +* Fixed dependencies for fremantle +* Including a vastly improved py2deb +* Tweaked sizes of stuff on recent tab +* Starting some work on rotation support for fremantle 1.0.5 * Contacts Tab remembers the last address book viewed on restart @@ -133,13 +140,18 @@ __changelog__ = ''' * Hold down back to clear number * Standard about dialog * many more smaller fixes -''' +""" -__postinstall__ = '''#!/bin/sh -e +__postinstall__ = """#!/bin/sh -e gtk-update-icon-cache -f /usr/share/icons/hicolor -''' +""" + +__preremove__ = """#!/bin/sh -e + +/usr/lib/dialcentral/alarm_handler.py -d +""" def find_files(path): @@ -168,22 +180,41 @@ def build_package(distribution): except: pass + py2deb.Py2deb.SECTIONS = py2deb.SECTIONS_BY_POLICY[distribution] p = py2deb.Py2deb(__appname__) p.description = __description__ p.author = __author__ p.mail = __email__ p.license = "lgpl" - p.depends = { - "diablo": "python2.5, python2.5-gtk2, python2.5-xml, python2.5-dbus, python2.5-hildon", - "mer": "python2.6, python-gtk2, python-xml, python-glade2, python-dbus", + p.depends = ", ".join([ + "python2.6 | python2.5", + "python-gtk2 | python2.5-gtk2", + "python-xml | python2.5-xml", + ]) + p.depends += { + "chinook": "", + "diablo": "", + "fremantle": ", python-glade2", + "mer": ", python-glade2", + }[distribution] + p.recommends = ", ".join([ + "python-osso | python2.5-osso", + "python-dbus | python2.5-dbus", + "python-hildon | python2.5-hildon", + ]) + p.section = { + "chinook": "communication", + "diablo": "user/network", + "fremantle": "user/network", + "mer": "user/network", }[distribution] - p.section = "user/communication" p.arch = "all" p.urgency = "low" p.distribution = "chinook diablo fremantle mer" p.repository = "extras" p.changelog = __changelog__ p.postinstall = __postinstall__ + p.preremove = __preremove__ p.icon = "26x26-dialcentral.png" p["/usr/bin"] = [ "dialcentral.py" ] for relPath, files in unflatten_files(find_files(".")).iteritems(): @@ -201,8 +232,12 @@ def build_package(distribution): print p print p.generate( - __version__, __build__, changelog=__changelog__, - tar=True, dsc=True, changes=True, build=False, src=True + version="%s-%s" % (__version__, __build__), + changelog=__changelog__, + build=False, + tar=True, + changes=True, + dsc=True, ) print "Building for %s finished" % distribution diff --git a/support/py2deb.py b/support/py2deb.py new file mode 100644 index 0000000..9dc0f70 --- /dev/null +++ b/support/py2deb.py @@ -0,0 +1,951 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +## +## Copyright (C) 2009 manatlan manatlan[at]gmail(dot)com +## +## This program 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; version 2 only. +## +## 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. +## +""" +Known limitations : +- don't sign package (-us -uc) +- no distinctions between author and maintainer(packager) + +depends on : +- dpkg-dev (dpkg-buildpackage) +- alien +- python +- fakeroot + +changelog + - ??? 9/14/2009 (By epage) + - PEP8 + - added recommends + - fixed bug where it couldn't handle the contents of the pre/post scripts being specified + - Added customization based on the targeted policy for sections (Maemo support) + - Added maemo specific tarball, dsc, changes file generation support (including icon support) + - 0.5 05/09/2009 + - pre/post install/remove scripts enabled + - deb package install py2deb in dist-packages for py2.6 + - 0.4 14/10/2008 + - use os.environ USERNAME or USER (debian way) + - install on py 2.(4,5,6) (*FIX* do better here) + +""" + +import os +import hashlib +import sys +import shutil +import time +import string +import StringIO +import stat +import commands +import base64 +from tarfile import TarFile +from glob import glob +from datetime import datetime +import socket # gethostname() +from subprocess import Popen, PIPE + +#~ __version__ = "0.4" +__version__ = "0.5" +__author__ = "manatlan" +__mail__ = "manatlan@gmail.com" + + +PERMS_URW_GRW_OR = stat.S_IRUSR | stat.S_IWUSR | \ + stat.S_IRGRP | stat.S_IWGRP | \ + stat.S_IROTH + +UID_ROOT = 0 +GID_ROOT = 0 + + +def run(cmds): + p = Popen(cmds, shell=False, stdout=PIPE, stderr=PIPE) + time.sleep(0.01) # to avoid "IOError: [Errno 4] Interrupted system call" + out = string.join(p.stdout.readlines()).strip() + outerr = string.join(p.stderr.readlines()).strip() + return out + + +def deb2rpm(file): + txt=run(['alien', '-r', file]) + return txt.split(" generated")[0] + + +def py2src(TEMP, name): + l=glob("%(TEMP)s/%(name)s*.tar.gz"%locals()) + if len(l) != 1: + raise Py2debException("don't find source package tar.gz") + + tar = os.path.basename(l[0]) + shutil.move(l[0], tar) + + return tar + + +def md5sum(filename): + f = open(filename, "r") + try: + return hashlib.md5(f.read()).hexdigest() + finally: + f.close() + + +class Py2changes(object): + + def __init__(self, ChangedBy, description, changes, files, category, repository, **kwargs): + self.options = kwargs # TODO: Is order important? + self.description = description + self.changes=changes + self.files=files + self.category=category + self.repository=repository + self.ChangedBy=ChangedBy + + def getContent(self): + content = ["%s: %s" % (k, v) + for k,v in self.options.iteritems()] + + if self.description: + description=self.description.replace("\n","\n ") + content.append('Description: ') + content.append(' %s' % description) + if self.changes: + changes=self.changes.replace("\n","\n ") + content.append('Changes: ') + content.append(' %s' % changes) + if self.ChangedBy: + content.append("Changed-By: %s" % self.ChangedBy) + + content.append('Files:') + + for onefile in self.files: + md5 = md5sum(onefile) + size = os.stat(onefile).st_size.__str__() + content.append(' ' + md5 + ' ' + size + ' ' + self.category +' '+self.repository+' '+os.path.basename(onefile)) + + return "\n".join(content) + "\n\n" + + +def py2changes(params): + changescontent = Py2changes( + "%(author)s <%(mail)s>" % params, + "%(description)s" % params, + "%(changelog)s" % params, + ( + "%(TEMP)s/%(name)s_%(version)s.tar.gz" % params, + "%(TEMP)s/%(name)s_%(version)s.dsc" % params, + ), + "%(section)s" % params, + "", #"%(repository)s" % params, + Format='1.7', + Date=time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()), + Source="%(name)s" % params, + Architecture="%(arch)s" % params, + Version="%(version)s" % params, + Distribution="", #"%(distribution)s" % params, + Urgency="", #"%(urgency)s" % params, + Maintainer="%(author)s <%(mail)s>" % params + ) + f = open("%(TEMP)s/%(name)s_%(version)s.changes" % params,"wb") + f.write(changescontent.getContent()) + f.close() + + fileHandle = open('/tmp/py2deb2.tmp', 'w') + fileHandle.write('#!/bin/sh\n') + fileHandle.write("cd " +os.getcwd()+ "\n") + fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.changes\n" % params) + fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.changes.asc %(TEMP)s/%(name)s_%(version)s.changes\n" % params) + fileHandle.write('\nexit') + fileHandle.close() + commands.getoutput("chmod 777 /tmp/py2deb2.tmp") + commands.getoutput("/tmp/py2deb2.tmp") + + ret = [] + + l=glob("%(TEMP)s/%(name)s*.tar.gz" % params) + if len(l)!=1: + raise Py2debException("don't find source package tar.gz") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + l=glob("%(TEMP)s/%(name)s*.dsc" % params) + if len(l)!=1: + raise Py2debException("don't find source package dsc") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + l=glob("%(TEMP)s/%(name)s*.changes" % params) + if len(l)!=1: + raise Py2debException("don't find source package changes") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + return ret + + +class Py2dsc(object): + + def __init__(self, StandardsVersion, BuildDepends, files, **kwargs): + self.options = kwargs # TODO: Is order important? + self.StandardsVersion = StandardsVersion + self.BuildDepends=BuildDepends + self.files=files + + @property + def content(self): + content = ["%s: %s" % (k, v) + for k,v in self.options.iteritems()] + + if self.BuildDepends: + content.append("Build-Depends: %s" % self.BuildDepends) + if self.StandardsVersion: + content.append("Standards-Version: %s" % self.StandardsVersion) + + content.append('Files:') + + for onefile in self.files: + print onefile + md5 = md5sum(onefile) + size = os.stat(onefile).st_size.__str__() + content.append(' '+md5 + ' ' + size +' '+os.path.basename(onefile)) + + return "\n".join(content)+"\n\n" + + +def py2dsc(TEMP, name, version, depends, author, mail, arch): + dsccontent = Py2dsc("%(version)s" % locals(), + "%(depends)s" % locals(), + ("%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals(),), + Format='1.0', + Source="%(name)s" % locals(), + Version="%(version)s" % locals(), + Maintainer="%(author)s <%(mail)s>" % locals(), + Architecture="%(arch)s" % locals(), + ) + + filename = "%(TEMP)s/%(name)s_%(version)s.dsc" % locals() + + f = open(filename, "wb") + try: + f.write(dsccontent.content) + finally: + f.close() + + fileHandle = open('/tmp/py2deb.tmp', 'w') + try: + fileHandle.write('#!/bin/sh\n') + fileHandle.write("cd " + os.getcwd() + "\n") + fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.dsc\n" % locals()) + fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.dsc.asc %(filename)s\n" % locals()) + fileHandle.write('\nexit') + fileHandle.close() + finally: + f.close() + + commands.getoutput("chmod 777 /tmp/py2deb.tmp") + commands.getoutput("/tmp/py2deb.tmp") + + return filename + + +class Py2tar(object): + + def __init__(self, dataDirectoryPath): + self._dataDirectoryPath = dataDirectoryPath + + def packed(self): + return self._getSourcesFiles() + + def _getSourcesFiles(self): + directoryPath = self._dataDirectoryPath + + outputFileObj = StringIO.StringIO() # TODO: Do more transparently? + + tarOutput = TarFile.open('sources', + mode = "w:gz", + fileobj = outputFileObj) + + # Note: We can't use this because we need to fiddle permissions: + # tarOutput.add(directoryPath, arcname = "") + + for root, dirs, files in os.walk(directoryPath): + archiveRoot = root[len(directoryPath):] + + tarinfo = tarOutput.gettarinfo(root, archiveRoot) + # TODO: Make configurable? + tarinfo.uid = UID_ROOT + tarinfo.gid = GID_ROOT + tarinfo.uname = "" + tarinfo.gname = "" + tarOutput.addfile(tarinfo) + + for f in files: + tarinfo = tarOutput.gettarinfo(os.path.join(root, f), + os.path.join(archiveRoot, f)) + tarinfo.uid = UID_ROOT + tarinfo.gid = GID_ROOT + tarinfo.uname = "" + tarinfo.gname = "" + tarOutput.addfile(tarinfo, file(os.path.join(root, f))) + + tarOutput.close() + + data_tar_gz = outputFileObj.getvalue() + + return data_tar_gz + + +def py2tar(DEST, TEMP, name, version): + tarcontent = Py2tar("%(DEST)s" % locals()) + filename = "%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals() + f = open(filename, "wb") + try: + f.write(tarcontent.packed()) + finally: + f.close() + return filename + + +class Py2debException(Exception): + pass + + +SECTIONS_BY_POLICY = { + # http://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections + "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", + # http://maemo.org/forrest-images/pdf/maemo-policy.pdf + "chinook": "accessories, communication, games, multimedia, office, other, programming, support, themes, tools", + # http://wiki.maemo.org/Task:Package_categories + "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", + # http://wiki.maemo.org/Task:Fremantle_application_categories + "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", + # http://wiki.maemo.org/Task:Fremantle_application_categories + "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", +} + + +class Py2deb(object): + """ + heavily based on technic described here : + http://wiki.showmedo.com/index.php?title=LinuxJensMakingDeb + """ + ## STATICS + clear=False # clear build folder after py2debianization + + SECTIONS = SECTIONS_BY_POLICY["debian"] + + #http://www.debian.org/doc/debian-policy/footnotes.html#f69 + 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(" ") + + # license terms taken from dh_make + LICENSES=["gpl", "lgpl", "bsd", "artistic"] + + def __setitem__(self, path, files): + + if not type(files)==list: + raise Py2debException("value of key path '%s' is not a list"%path) + if not files: + raise Py2debException("value of key path '%s' should'nt be empty"%path) + if not path.startswith("/"): + raise Py2debException("key path '%s' malformed (don't start with '/')"%path) + if path.endswith("/"): + raise Py2debException("key path '%s' malformed (shouldn't ends with '/')"%path) + + nfiles=[] + for file in files: + + if ".." in file: + raise Py2debException("file '%s' contains '..', please avoid that!"%file) + + + if "|" in file: + if file.count("|")!=1: + raise Py2debException("file '%s' is incorrect (more than one pipe)"%file) + + file, nfile = file.split("|") + else: + nfile=file # same localisation + + if os.path.isdir(file): + raise Py2debException("file '%s' is a folder, and py2deb refuse folders !"%file) + + if not os.path.isfile(file): + raise Py2debException("file '%s' doesn't exist"%file) + + if file.startswith("/"): # if an absolute file is defined + if file==nfile: # and not renamed (pipe trick) + nfile=os.path.basename(file) # it's simply copied to 'path' + + nfiles.append((file, nfile)) + + nfiles.sort(lambda a, b: cmp(a[1], b[1])) #sort according new name (nfile) + + self.__files[path]=nfiles + + def __delitem__(self, k): + del self.__files[k] + + def __init__(self, + name, + description="no description", + license="gpl", + depends="", + section="utils", + arch="all", + + url="", + author = None, + mail = None, + + preinstall = None, + postinstall = None, + preremove = None, + postremove = None + ): + + if author is None: + author = ("USERNAME" in os.environ) and os.environ["USERNAME"] or None + if author is None: + author = ("USER" in os.environ) and os.environ["USER"] or "unknown" + + if mail is None: + mail = author+"@"+socket.gethostname() + + self.name = name + self.description = description + self.license = license + self.depends = depends + self.recommends = "" + self.section = section + self.arch = arch + self.url = url + self.author = author + self.mail = mail + self.icon = "" + + self.preinstall = preinstall + self.postinstall = postinstall + self.preremove = preremove + self.postremove = postremove + + self.__files={} + + def __repr__(self): + name = self.name + license = self.license + description = self.description + depends = self.depends + recommends = self.recommends + section = self.section + arch = self.arch + url = self.url + author = self.author + mail = self.mail + + preinstall = self.preinstall + postinstall = self.postinstall + preremove = self.preremove + postremove = self.postremove + + paths=self.__files.keys() + paths.sort() + files=[] + for path in paths: + for file, nfile in self.__files[path]: + #~ rfile=os.path.normpath(os.path.join(path, nfile)) + rfile=os.path.join(path, nfile) + if nfile==file: + files.append(rfile) + else: + files.append(rfile + " (%s)"%file) + + files.sort() + files = "\n".join(files) + + + lscripts = [ preinstall and "preinst", + postinstall and "postinst", + preremove and "prerm", + postremove and "postrm", + ] + scripts = lscripts and ", ".join([i for i in lscripts if i]) or "None" + return """ +---------------------------------------------------------------------- +NAME : %(name)s +---------------------------------------------------------------------- +LICENSE : %(license)s +URL : %(url)s +AUTHOR : %(author)s +MAIL : %(mail)s +---------------------------------------------------------------------- +DEPENDS : %(depends)s +RECOMMENDS : %(recommends)s +ARCH : %(arch)s +SECTION : %(section)s +---------------------------------------------------------------------- +DESCRIPTION : +%(description)s +---------------------------------------------------------------------- +SCRIPTS : %(scripts)s +---------------------------------------------------------------------- +FILES : +%(files)s +""" % locals() + + def generate(self, version, changelog="", rpm=False, src=False, build=True, tar=False, changes=False, dsc=False): + """ generate a deb of version 'version', with or without 'changelog', with or without a rpm + (in the current folder) + return a list of generated files + """ + if not sum([len(i) for i in self.__files.values()])>0: + raise Py2debException("no files are defined") + + if not changelog: + changelog="* no changelog" + + name = self.name + description = self.description + license = self.license + depends = self.depends + recommends = self.recommends + section = self.section + arch = self.arch + url = self.url + author = self.author + mail = self.mail + files = self.__files + preinstall = self.preinstall + postinstall = self.postinstall + preremove = self.preremove + postremove = self.postremove + + if section not in Py2deb.SECTIONS: + raise Py2debException("section '%s' is unknown (%s)" % (section, str(Py2deb.SECTIONS))) + + if arch not in Py2deb.ARCHS: + raise Py2debException("arch '%s' is unknown (%s)"% (arch, str(Py2deb.ARCHS))) + + if license not in Py2deb.LICENSES: + raise Py2debException("License '%s' is unknown (%s)" % (license, str(Py2deb.LICENSES))) + + # create dates (buildDate, buildDateYear) + d=datetime.now() + buildDate=d.strftime("%a, %d %b %Y %H:%M:%S +0000") + buildDateYear=str(d.year) + + + #clean description (add a space before each next lines) + description=description.replace("\r", "").strip() + description = "\n ".join(description.split("\n")) + + #clean changelog (add 2 spaces before each next lines) + changelog=changelog.replace("\r", "").strip() + changelog = "\n ".join(changelog.split("\n")) + + + TEMP = ".py2deb_build_folder" + DEST = os.path.join(TEMP, name) + DEBIAN = os.path.join(DEST, "debian") + + # let's start the process + try: + shutil.rmtree(TEMP) + except: + pass + + os.makedirs(DEBIAN) + try: + rules=[] + dirs=[] + for path in files: + for ofile, nfile in files[path]: + if os.path.isfile(ofile): + # it's a file + + if ofile.startswith("/"): # if absolute path + # we need to change dest + dest=os.path.join(DEST, nfile) + else: + dest=os.path.join(DEST, ofile) + + # copy file to be packaged + destDir = os.path.dirname(dest) + if not os.path.isdir(destDir): + os.makedirs(destDir) + + shutil.copy2(ofile, dest) + + ndir = os.path.join(path, os.path.dirname(nfile)) + nname = os.path.basename(nfile) + + # make a line RULES to be sure the destination folder is created + # and one for copying the file + fpath = "/".join(["$(CURDIR)", "debian", name+ndir]) + rules.append('mkdir -p "%s"' % fpath) + rules.append('cp -a "%s" "%s"' % (ofile, os.path.join(fpath, nname))) + + # append a dir + dirs.append(ndir) + + else: + raise Py2debException("unknown file '' "%ofile) # shouldn't be raised (because controlled before) + + # make rules right + rules= "\n\t".join(rules) + "\n" + + # make dirs right + dirs= [i[1:] for i in set(dirs)] + dirs.sort() + + #========================================================================== + # CREATE debian/dirs + #========================================================================== + open(os.path.join(DEBIAN, "dirs"), "w").write("\n".join(dirs)) + + #========================================================================== + # CREATE debian/changelog + #========================================================================== + clog="""%(name)s (%(version)s) stable; urgency=low + + %(changelog)s + + -- %(author)s <%(mail)s> %(buildDate)s +""" % locals() + + open(os.path.join(DEBIAN, "changelog"), "w").write(clog) + + #========================================================================== + #Create pre/post install/remove + #========================================================================== + def mkscript(name, dest): + if name and name.strip()!="": + if os.path.isfile(name): # it's a file + content = file(name).read() + else: # it's a script + content = name + open(os.path.join(DEBIAN, dest), "w").write(content) + + mkscript(preinstall, "preinst") + mkscript(postinstall, "postinst") + mkscript(preremove, "prerm") + mkscript(postremove, "postrm") + + + #========================================================================== + # CREATE debian/compat + #========================================================================== + open(os.path.join(DEBIAN, "compat"), "w").write("5\n") + + #========================================================================== + # CREATE debian/control + #========================================================================== + txt="""Source: %(name)s +Section: %(section)s +Priority: extra +Maintainer: %(author)s <%(mail)s> +Build-Depends: debhelper (>= 5) +Standards-Version: 3.7.2 + +Package: %(name)s +Architecture: %(arch)s +Depends: %(depends)s +Recommends: %(recommends)s +Description: %(description)s""" % locals() + if self.icon: + f = open(self.icon, "rb") + try: + rawIcon = f.read() + finally: + f.close() + uueIcon = base64.b64encode(rawIcon) + uueIconLines = [] + for i, c in enumerate(uueIcon): + if i % 60 == 0: + uueIconLines.append("") + uueIconLines[-1] += c + uueIconLines[0:0] = ("XB-Maemo-Icon-26:", ) + txt = "\n".join((txt, "\n ".join(uueIconLines), "")) + open(os.path.join(DEBIAN, "control"), "w").write(txt) + + #========================================================================== + # CREATE debian/copyright + #========================================================================== + copy={} + copy["gpl"]=""" + This package 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 package 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 package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. +""" + copy["lgpl"]=""" + This package is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This package 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU Lesser General +Public License can be found in `/usr/share/common-licenses/LGPL'. +""" + copy["bsd"]=""" + Redistribution and use in source and binary forms, with or without + modification, are permitted under the terms of the BSD License. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +On Debian systems, the complete text of the BSD License can be +found in `/usr/share/common-licenses/BSD'. +""" + copy["artistic"]=""" + This program is free software; you can redistribute it and/or modify it + under the terms of the "Artistic License" which comes with Debian. + + THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES + OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +On Debian systems, the complete text of the Artistic License +can be found in `/usr/share/common-licenses/Artistic'. +""" + + txtLicense = copy[license] + pv=__version__ + txt="""This package was py2debianized(%(pv)s) by %(author)s <%(mail)s> on +%(buildDate)s. + +It was downloaded from %(url)s + +Upstream Author: %(author)s <%(mail)s> + +Copyright: %(buildDateYear)s by %(author)s + +License: + +%(txtLicense)s + +The Debian packaging is (C) %(buildDateYear)s, %(author)s <%(mail)s> and +is licensed under the GPL, see above. + + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +""" % locals() + open(os.path.join(DEBIAN, "copyright"), "w").write(txt) + + #========================================================================== + # CREATE debian/rules + #========================================================================== + txt="""#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # ====================================================== + #$(MAKE) DESTDIR="$(CURDIR)/debian/%(name)s" install + mkdir -p "$(CURDIR)/debian/%(name)s" + + %(rules)s + # ====================================================== + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs debian/changelog + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure +""" % locals() + open(os.path.join(DEBIAN, "rules"), "w").write(txt) + os.chmod(os.path.join(DEBIAN, "rules"), 0755) + + ########################################################################### + ########################################################################### + ########################################################################### + + generatedFiles = [] + + if build: + #http://www.debian.org/doc/manuals/maint-guide/ch-build.fr.html + ret=os.system('cd "%(DEST)s"; dpkg-buildpackage -tc -rfakeroot -us -uc' % locals()) + if ret != 0: + raise Py2debException("buildpackage failed (see output)") + + l=glob("%(TEMP)s/%(name)s*.deb"%locals()) + if len(l) != 1: + raise Py2debException("didn't find builded deb") + + tdeb = l[0] + deb = os.path.basename(tdeb) + shutil.move(tdeb, deb) + + generatedFiles = [deb, ] + + if rpm: + rpmFilename = deb2rpm(deb) + generatedFiles.append(rpmFilename) + + if src: + tarFilename = py2src(TEMP, name) + generatedFiles.append(tarFilename) + + if tar: + tarFilename = py2tar(DEST, TEMP, name, version) + generatedFiles.append(tarFilename) + + if dsc: + dscFilename = py2dsc(TEMP, name, version, depends, author, mail, arch) + generatedFiles.append(dscFilename) + + if changes: + changesFilenames = py2changes(locals()) + generatedFiles.extend(changesFilenames) + + return generatedFiles + + #~ except Exception,m: + #~ raise Py2debException("build error :"+str(m)) + + finally: + if Py2deb.clear: + shutil.rmtree(TEMP) + + +if __name__ == "__main__": + try: + os.chdir(os.path.dirname(sys.argv[0])) + except: + pass + + p=Py2deb("python-py2deb") + p.description="Generate simple deb(/rpm/tgz) from python (2.4, 2.5 and 2.6)" + p.url = "http://www.manatlan.com/page/py2deb" + p.author=__author__ + p.mail=__mail__ + p.depends = "dpkg-dev, fakeroot, alien, python" + p.section="python" + p["/usr/lib/python2.6/dist-packages"] = ["py2deb.py", ] + p["/usr/lib/python2.5/site-packages"] = ["py2deb.py", ] + p["/usr/lib/python2.4/site-packages"] = ["py2deb.py", ] + #~ p.postinstall = "s.py" + #~ p.preinstall = "s.py" + #~ p.postremove = "s.py" + #~ p.preremove = "s.py" + print p + print p.generate(__version__, changelog = __doc__, src=True)