Avoid extra space at the end of SMS (bug #869).
[wammu.git] / setup.py
blob1ede37bb3dac9abf3b3c1e6f1825b371ba6935d5
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 # vim: expandtab sw=4 ts=4 sts=4:
4 '''
5 Wammu - Phone manager
6 Setup script for installation using distutils
7 '''
8 __author__ = 'Michal Čihař'
9 __email__ = 'michal@cihar.com'
10 __license__ = '''
11 Copyright © 2003 - 2010 Michal Čihař
13 This program is free software; you can redistribute it and/or modify it
14 under the terms of the GNU General Public License version 2 as published by
15 the Free Software Foundation.
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
20 more details.
22 You should have received a copy of the GNU General Public License along with
23 this program; if not, write to the Free Software Foundation, Inc.,
24 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 '''
27 import distutils
28 import distutils.command.build
29 import distutils.command.build_scripts
30 import distutils.command.clean
31 import distutils.command.install
32 import distutils.command.install_data
33 from stat import ST_MODE
34 from wammu_setup import msgfmt
35 import sys
36 import glob
37 import Wammu
38 import os.path
39 import os
40 import re
41 # Optional support for py2exe
42 try:
43 import py2exe
44 HAVE_PY2EXE = True
45 except:
46 HAVE_PY2EXE = False
48 # some defines
49 PYTHONGAMMU_REQUIRED = (0, 24)
50 WXPYTHON_REQUIRED = (2, 6, 2, 0)
52 # check if Python is called on the first line with this expression
53 first_line_re = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
55 class build_scripts_wammu(distutils.command.build_scripts.build_scripts, object):
56 '''
57 This is mostly distutils copy, it just renames script according
58 to platform (.pyw for Windows, without extension for others)
59 '''
60 def copy_scripts (self):
61 """Copy each script listed in 'self.scripts'; if it's marked as a
62 Python script in the Unix way (first line matches 'first_line_re',
63 ie. starts with "\#!" and contains "python"), then adjust the first
64 line to refer to the current Python interpreter as we copy.
65 """
66 self.mkpath(self.build_dir)
67 outfiles = []
68 for script in self.scripts:
69 adjust = 0
70 script = distutils.util.convert_path(script)
71 outfile = os.path.join(self.build_dir, os.path.splitext(os.path.basename(script))[0])
72 if sys.platform == 'win32':
73 outfile += os.extsep + 'pyw'
74 outfiles.append(outfile)
76 if not self.force and not distutils.dep_util.newer(script, outfile):
77 distutils.log.debug("not copying %s (up-to-date)", script)
78 continue
80 # Always open the file, but ignore failures in dry-run mode --
81 # that way, we'll get accurate feedback if we can read the
82 # script.
83 try:
84 f = open(script, "r")
85 except IOError:
86 if not self.dry_run:
87 raise
88 f = None
89 else:
90 first_line = f.readline()
91 if not first_line:
92 self.warn("%s is an empty file (skipping)" % script)
93 continue
95 match = first_line_re.match(first_line)
96 if match:
97 adjust = 1
98 post_interp = match.group(1) or ''
100 if adjust:
101 distutils.log.info("copying and adjusting %s -> %s", script,
102 self.build_dir)
103 if not self.dry_run:
104 outf = open(outfile, "w")
105 if not distutils.sysconfig.python_build:
106 outf.write("#!%s%s\n" %
107 (os.path.normpath(sys.executable),
108 post_interp))
109 else:
110 outf.write("#!%s%s\n" %
111 (os.path.join(
112 distutils.sysconfig.get_config_var("BINDIR"),
113 "python" + distutils.sysconfig.get_config_var("EXE")),
114 post_interp))
115 outf.writelines(f.readlines())
116 outf.close()
117 if f:
118 f.close()
119 else:
120 f.close()
121 self.copy_file(script, outfile)
123 if os.name == 'posix':
124 for file in outfiles:
125 if self.dry_run:
126 distutils.log.info("changing mode of %s", file)
127 else:
128 oldmode = os.stat(file)[ST_MODE] & 07777
129 newmode = (oldmode | 0555) & 07777
130 if newmode != oldmode:
131 distutils.log.info("changing mode of %s from %o to %o",
132 file, oldmode, newmode)
133 os.chmod(file, newmode)
135 # copy_scripts ()
137 def list_message_files(package = 'wammu', suffix = '.po'):
139 Return list of all found message files and their installation paths.
141 _files = glob.glob('locale/*/' + package + suffix)
142 _list = []
143 for _file in _files:
144 # basename (without extension) is a locale name
145 _locale = os.path.basename(os.path.dirname(_file))
146 _list.append((_locale, _file, os.path.join(
147 'share', 'locale', _locale, 'LC_MESSAGES', '%s.mo' % package)))
148 return _list
150 class build_wammu(distutils.command.build.build, object):
152 Custom build command with locales support.
155 def build_message_files (self):
157 For each locale/*.po, build .mo file in target locale directory.
159 As a side effect we build wammu.desktop file with updated
160 translations here.
162 desktop_translations = {}
163 for (_locale, _src, _dst) in list_message_files():
164 _build_dst = os.path.join(self.build_base, _dst)
165 destdir = os.path.dirname(_build_dst)
166 if not os.path.exists(destdir):
167 self.mkpath(destdir)
168 distutils.log.info('compiling %s -> %s' % (_src, _build_dst))
169 msgfmt.make(_src, _build_dst)
170 desktop_translations[_locale] = msgfmt.DESKTOP_TRANSLATIONS
172 desktop = os.path.join(self.build_base, 'wammu.desktop')
173 in_desktop = file('wammu.desktop.in', 'r')
174 out_desktop = file(desktop, 'w')
175 for line in in_desktop:
176 if line.startswith('_Name'):
177 out_desktop.write('Name=%s\n' % msgfmt.DESKTOP_NAME)
178 for loc in desktop_translations.keys():
179 if desktop_translations[loc].has_key('Name'):
180 out_desktop.write('Name[%s]=%s\n' % (loc, desktop_translations[loc]['Name']))
181 elif line.startswith('_GenericName'):
182 out_desktop.write('GenericName=%s\n' % msgfmt.DESKTOP_GENERIC_NAME)
183 for loc in desktop_translations.keys():
184 if desktop_translations[loc].has_key('GenericName'):
185 out_desktop.write('GenericName[%s]=%s\n' % (loc, desktop_translations[loc]['GenericName']))
186 elif line.startswith('_Comment'):
187 out_desktop.write('Comment=%s\n' % msgfmt.DESKTOP_COMMENT)
188 for loc in desktop_translations.keys():
189 if desktop_translations[loc].has_key('Comment'):
190 out_desktop.write('Comment[%s]=%s\n' % (loc, desktop_translations[loc]['Comment']))
191 else:
192 out_desktop.write(line)
195 def check_requirements(self):
196 print 'Checking for python-gammu ...',
197 try:
198 import gammu
199 version = gammu.Version()
200 print 'found version %s using Gammu %s ...' % (version[1], version[0]),
202 pygver = tuple(map(int, version[1].split('.')))
203 if pygver < PYTHONGAMMU_REQUIRED:
204 print 'too old!'
205 print 'You need python-gammu at least %s!' % '.'.join(map(str, PYTHONGAMMU_REQUIRED))
206 print 'You can get it from <http://wammu.eu/python-gammu/>'
207 else:
208 print 'OK'
209 except ImportError, message:
210 print
211 print 'Could not import python-gammu!'
212 print 'You can get it from <http://wammu.eu/python-gammu/>'
213 print 'Import failed with following error: %s' % message
215 print 'Checking for wxPython ...',
216 try:
217 import wx
218 print 'found version %s ...' % wx.VERSION_STRING,
219 if wx.VERSION < WXPYTHON_REQUIRED:
220 print 'too old!'
221 print 'You need at least wxPython %s!' % '.'.join(map(str, WXPYTHON_REQUIRED))
222 print 'You can get it from <http://www.wxpython.org>'
223 elif not wx.USE_UNICODE:
224 print 'not unicode!'
225 print 'You need at least wxPython %s with unicode enabled!' % '.'.join(map(str, WXPYTHON_REQUIRED))
226 print 'You can get it from <http://www.wxpython.org>'
227 else:
228 print 'OK'
229 except ImportError:
230 print
231 print 'You need wxPython!'
232 print 'You can get it from <http://www.wxpython.org>'
234 print 'Checking for Bluetooth stack ...',
235 try:
236 import bluetooth
237 print 'OK'
238 except ImportError:
239 print
240 print 'WARNING: PyBluez not found, without it you can not search for bluetooth devices'
241 print 'PyBluez can be downloaded from <http://org.csail.mit.edu/pybluez/>'
243 print 'Checking for xml stack ...',
244 try:
245 import xml
246 print 'OK'
247 except ImportError:
248 print
249 print 'python-xml not found!'
251 if sys.platform == 'win32':
252 print 'Checking for PyWin32 ...',
253 try:
254 import win32file, win32com, win32api
255 print 'found'
256 except ImportError:
257 print 'not found!'
258 print 'This module is now needed for Windows!'
259 print 'PyWin32 can be downloaded from <https://sourceforge.net/projects/pywin32/>'
260 sys.exit(1)
262 def run (self):
263 self.build_message_files()
264 self.check_requirements()
265 super(build_wammu, self).run()
267 class clean_wammu(distutils.command.clean.clean, object):
269 Custom clean command.
272 def run (self):
273 if self.all:
274 # remove share directory
275 directory = os.path.join(self.build_base, 'share')
276 if os.path.exists(directory):
277 distutils.dir_util.remove_tree(directory, dry_run=self.dry_run)
278 else:
279 distutils.log.warn('\'%s\' does not exist -- can\'t clean it',
280 directory)
281 super(clean_wammu, self).run()
283 class install_data_wammu(distutils.command.install_data.install_data, object):
285 Install locales in addition to regullar data.
288 def run (self):
290 Install also .mo files.
292 # add .mo files to data files
293 for (_locale, _src, _dst) in list_message_files():
294 _build_dst = os.path.join('build', _dst)
295 item = [os.path.dirname(_dst), [_build_dst]]
296 self.data_files.append(item)
298 # desktop file
299 if sys.platform != 'win32':
300 self.data_files.append((os.path.join('share','applications'), [os.path.join('build', 'wammu.desktop')]))
302 # install data files
303 super(install_data_wammu, self).run()
305 py2exepackages = ['Wammu']
306 if sys.version_info >= (2, 5):
307 # Email module changed a lot in python 2.5 and we can not yet use new API
308 py2exepackages.append('email')
309 py2exepackages.append('email.mime')
311 # ModuleFinder can't handle runtime changes to __path__, but win32com uses them
312 try:
313 # if this doesn't work, try import modulefinder
314 import py2exe.mf as modulefinder
315 import win32com
316 for p in win32com.__path__[1:]:
317 modulefinder.AddPackagePath("win32com", p)
318 for extra in ["win32com.shell"]: #,"win32com.mapi"
319 __import__(extra)
320 m = sys.modules[extra]
321 for p in m.__path__[1:]:
322 modulefinder.AddPackagePath(extra, p)
323 except ImportError:
324 # no build path setup, no worries.
325 pass
327 addparams = {}
329 if HAVE_PY2EXE:
330 addparams['windows'] = [
332 'script': 'wammu.py',
333 'icon_resources': [(1, 'icon/wammu.ico')],
336 addparams['zipfile'] = 'shared.lib'
338 data_files = [
339 (os.path.join('share','Wammu','images','icons'), glob.glob('images/icons/*.png')),
340 (os.path.join('share','Wammu','images','misc'), glob.glob('images/misc/*.png')),
343 data_files.append((os.path.join('share','pixmaps'), [
344 'icon/wammu.png',
345 'icon/wammu.xpm',
346 'icon/wammu.ico',
347 'icon/wammu.svg',
349 data_files.append((os.path.join('share','man','man1'), ['wammu.1', 'wammu-configure.1']))
350 data_files.append((os.path.join('share','man','cs','man1'), ['man/cs/wammu.1', 'man/cs/wammu-configure.1']))
352 distutils.core.setup(name="wammu",
353 version = Wammu.__version__,
354 description = "Wammu Mobile Phone Manager",
355 long_description = "Phone manager built on top of python-gammu. Supports many phones.",
356 author = u"Michal Cihar",
357 author_email = "michal@cihar.com",
358 maintainer = u"Michal Cihar",
359 maintainer_email = "michal@cihar.com",
360 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME'],
361 keywords = ['mobile', 'phone', 'SMS', 'contact', 'gammu', 'calendar', 'todo'],
362 url = "http://wammu.eu/wammu/",
363 download_url = 'http://wammu.eu/download/wammu/',
364 license = "GPL",
365 classifiers = [
366 'Development Status :: 5 - Production/Stable',
367 'Environment :: Win32 (MS Windows)',
368 'Environment :: X11 Applications :: GTK',
369 'Intended Audience :: End Users/Desktop',
370 'License :: OSI Approved :: GNU General Public License (GPL)',
371 'Operating System :: Microsoft :: Windows :: Windows 95/98/2000',
372 'Operating System :: Microsoft :: Windows :: Windows NT/2000',
373 'Operating System :: POSIX',
374 'Operating System :: Unix',
375 'Programming Language :: Python',
376 'Topic :: Communications :: Telephony',
377 'Topic :: Office/Business :: Scheduling',
378 'Topic :: Utilities',
379 'Natural Language :: English',
380 'Natural Language :: Afrikaans',
381 'Natural Language :: Catalan',
382 'Natural Language :: Czech',
383 'Natural Language :: German',
384 'Natural Language :: Greek',
385 'Natural Language :: Spanish',
386 # 'Natural Language :: Estonian',
387 'Natural Language :: Finnish',
388 'Natural Language :: French',
389 # 'Natural Language :: Galician',
390 'Natural Language :: Hebrew',
391 'Natural Language :: Hungarian',
392 'Natural Language :: Indonesian',
393 'Natural Language :: Italian',
394 'Natural Language :: Korean',
395 'Natural Language :: Dutch',
396 'Natural Language :: Polish',
397 'Natural Language :: Portuguese (Brazilian)',
398 'Natural Language :: Russian',
399 'Natural Language :: Slovak',
400 'Natural Language :: Swedish',
401 'Natural Language :: Chinese (Simplified)',
402 'Natural Language :: Chinese (Traditional)',
404 packages = ['Wammu'],
405 scripts = ['wammu.py', 'wammu-configure.py'],
406 data_files = data_files,
407 # Override certain command classes with our own ones
408 cmdclass = {
409 'build': build_wammu,
410 'build_scripts': build_scripts_wammu,
411 'clean': clean_wammu,
412 'install_data': install_data_wammu,
414 # py2exe options
415 options = {'py2exe': {
416 'optimize': 2,
417 'packages': py2exepackages,
419 **addparams