Update.
[wammu.git] / setup.py
blobd67d09d61e78e80aaac897bcb9fcbde7d52d1c50
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 - 2008 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 # used for passing state for skiping dependency check
49 skip_dependencies = False
51 # some defines
52 PYTHONGAMMU_REQUIRED = (0, 24)
53 WXPYTHON_REQUIRED = (2, 6, 2, 0)
55 # check if Python is called on the first line with this expression
56 first_line_re = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
58 class build_scripts_wammu(distutils.command.build_scripts.build_scripts, object):
59 '''
60 This is mostly distutils copy, it just renames script according
61 to platform (.pyw for Windows, without extension for others)
62 '''
63 def copy_scripts (self):
64 """Copy each script listed in 'self.scripts'; if it's marked as a
65 Python script in the Unix way (first line matches 'first_line_re',
66 ie. starts with "\#!" and contains "python"), then adjust the first
67 line to refer to the current Python interpreter as we copy.
68 """
69 self.mkpath(self.build_dir)
70 outfiles = []
71 for script in self.scripts:
72 adjust = 0
73 script = distutils.util.convert_path(script)
74 outfile = os.path.join(self.build_dir, os.path.splitext(os.path.basename(script))[0])
75 if sys.platform == 'win32':
76 outfile += os.extsep + 'pyw'
77 outfiles.append(outfile)
79 if not self.force and not distutils.dep_util.newer(script, outfile):
80 distutils.log.debug("not copying %s (up-to-date)", script)
81 continue
83 # Always open the file, but ignore failures in dry-run mode --
84 # that way, we'll get accurate feedback if we can read the
85 # script.
86 try:
87 f = open(script, "r")
88 except IOError:
89 if not self.dry_run:
90 raise
91 f = None
92 else:
93 first_line = f.readline()
94 if not first_line:
95 self.warn("%s is an empty file (skipping)" % script)
96 continue
98 match = first_line_re.match(first_line)
99 if match:
100 adjust = 1
101 post_interp = match.group(1) or ''
103 if adjust:
104 distutils.log.info("copying and adjusting %s -> %s", script,
105 self.build_dir)
106 if not self.dry_run:
107 outf = open(outfile, "w")
108 if not distutils.sysconfig.python_build:
109 outf.write("#!%s%s\n" %
110 (os.path.normpath(sys.executable),
111 post_interp))
112 else:
113 outf.write("#!%s%s\n" %
114 (os.path.join(
115 distutils.sysconfig.get_config_var("BINDIR"),
116 "python" + distutils.sysconfig.get_config_var("EXE")),
117 post_interp))
118 outf.writelines(f.readlines())
119 outf.close()
120 if f:
121 f.close()
122 else:
123 f.close()
124 self.copy_file(script, outfile)
126 if os.name == 'posix':
127 for file in outfiles:
128 if self.dry_run:
129 distutils.log.info("changing mode of %s", file)
130 else:
131 oldmode = os.stat(file)[ST_MODE] & 07777
132 newmode = (oldmode | 0555) & 07777
133 if newmode != oldmode:
134 distutils.log.info("changing mode of %s from %o to %o",
135 file, oldmode, newmode)
136 os.chmod(file, newmode)
138 # copy_scripts ()
140 def list_message_files(package = 'wammu', suffix = '.po'):
142 Return list of all found message files and their installation paths.
144 _files = glob.glob('locale/*/' + package + suffix)
145 _list = []
146 for _file in _files:
147 # basename (without extension) is a locale name
148 _locale = os.path.basename(os.path.dirname(_file))
149 _list.append((_locale, _file, os.path.join(
150 'share', 'locale', _locale, 'LC_MESSAGES', '%s.mo' % package)))
151 return _list
153 class build_wammu(distutils.command.build.build, object):
155 Custom build command with locales support.
157 user_options = distutils.command.build.build.user_options + [('skip-deps', 's', 'skip checking for dependencies')]
158 boolean_options = distutils.command.build.build.boolean_options + ['skip-deps']
160 def initialize_options(self):
161 global skip_dependencies
162 super(build_wammu, self).initialize_options()
163 self.skip_deps = skip_dependencies
165 def finalize_options(self):
166 global skip_dependencies
167 super(build_wammu, self).finalize_options()
168 if self.skip_deps:
169 skip_dependencies = self.skip_deps
171 def build_message_files (self):
173 For each locale/*.po, build .mo file in target locale directory.
175 As a side effect we build wammu.desktop file with updated
176 translations here.
178 desktop_translations = {}
179 for (_locale, _src, _dst) in list_message_files():
180 _build_dst = os.path.join('build', _dst)
181 destdir = os.path.dirname(_build_dst)
182 if not os.path.exists(destdir):
183 self.mkpath(destdir)
184 distutils.log.info('compiling %s -> %s' % (_src, _build_dst))
185 msgfmt.make(_src, _build_dst)
186 desktop_translations[_locale] = msgfmt.DESKTOP_TRANSLATIONS
188 desktop = os.path.join('build', 'wammu.desktop')
189 in_desktop = file('wammu.desktop.in', 'r')
190 out_desktop = file(desktop, 'w')
191 for line in in_desktop:
192 if line.startswith('_Name'):
193 out_desktop.write('Name=%s\n' % msgfmt.DESKTOP_NAME)
194 for loc in desktop_translations.keys():
195 if desktop_translations[loc].has_key('Name'):
196 out_desktop.write('Name[%s]=%s\n' % (loc, desktop_translations[loc]['Name']))
197 elif line.startswith('_GenericName'):
198 out_desktop.write('GenericName=%s\n' % msgfmt.DESKTOP_GENERIC_NAME)
199 for loc in desktop_translations.keys():
200 if desktop_translations[loc].has_key('GenericName'):
201 out_desktop.write('GenericName[%s]=%s\n' % (loc, desktop_translations[loc]['GenericName']))
202 elif line.startswith('_Comment'):
203 out_desktop.write('Comment=%s\n' % msgfmt.DESKTOP_COMMENT)
204 for loc in desktop_translations.keys():
205 if desktop_translations[loc].has_key('Comment'):
206 out_desktop.write('Comment[%s]=%s\n' % (loc, desktop_translations[loc]['Comment']))
207 else:
208 out_desktop.write(line)
211 def check_requirements(self):
212 if os.getenv('SKIPGAMMUCHECK') == 'yes':
213 print 'Skipping Gammu check, expecting you know what you are doing!'
214 else:
215 print 'Checking for python-gammu ...',
216 try:
217 import gammu
218 version = gammu.Version()
219 print 'found version %s using Gammu %s ...' % (version[1], version[0]),
221 pygver = tuple(map(int, version[1].split('.')))
222 if pygver < PYTHONGAMMU_REQUIRED:
223 print 'too old!'
224 print 'You need python-gammu at least %s!' % '.'.join(map(str, PYTHONGAMMU_REQUIRED))
225 print 'You can get it from <http://cihar.com/gammu/python/>'
226 sys.exit(1)
227 print 'OK'
228 except ImportError, message:
229 print 'Could not import python-gammu!'
230 print 'You can get it from <http://cihar.com/gammu/python/>'
231 print 'Import failed with following error: %s' % message
232 sys.exit(1)
234 if os.getenv('SKIPWXCHECK') == 'yes':
235 print 'Skipping wxPython check, expecting you know what you are doing!'
236 else:
237 print 'Checking for wxPython ...',
238 try:
239 import wx
240 print 'found version %s ...' % wx.VERSION_STRING,
241 if wx.VERSION < WXPYTHON_REQUIRED:
242 print 'too old!'
243 print 'You need at least wxPython %s!' % '.'.join(map(str, WXPYTHON_REQUIRED))
244 print 'You can get it from <http://www.wxpython.org>'
245 sys.exit(1)
246 if not wx.USE_UNICODE:
247 print 'not unicode!'
248 print 'You need at least wxPython %s with unicode enabled!' % '.'.join(map(str, WXPYTHON_REQUIRED))
249 print 'You can get it from <http://www.wxpython.org>'
250 sys.exit(1)
251 print 'OK'
252 except ImportError:
253 print 'You need wxPython!'
254 print 'You can get it from <http://www.wxpython.org>'
255 sys.exit(1)
257 print 'Checking for Bluetooth stack ...',
258 try:
259 import bluetooth
260 print 'PyBluez found'
261 except ImportError:
262 try:
263 import gnomebt.controller
264 print 'GNOME Bluetooth found'
265 print 'WARNING: GNOME Bluetooth support is limited, consider installing PyBluez'
266 except ImportError:
267 print 'WARNING: neither GNOME Bluetooth nor PyBluez found, without those you can not search for bluetooth devices'
268 print 'PyBluez can be downloaded from <http://org.csail.mit.edu/pybluez/>'
270 if sys.platform == 'win32':
271 print 'Checking for PyWin32 ...',
272 try:
273 import win32file, win32com, win32api
274 print 'found'
275 except ImportError:
276 print 'not found!'
277 print 'This module is now needed for Windows!'
278 print 'PyWin32 can be downloaded from <https://sourceforge.net/projects/pywin32/>'
279 sys.exit(1)
281 def run (self):
282 global skip_dependencies
283 self.build_message_files()
284 if not skip_dependencies:
285 self.check_requirements()
286 super(build_wammu, self).run()
288 class clean_wammu(distutils.command.clean.clean, object):
290 Custom clean command.
293 def run (self):
294 if self.all:
295 # remove share directory
296 directory = os.path.join('build', 'share')
297 if os.path.exists(directory):
298 distutils.dir_util.remove_tree(directory, dry_run=self.dry_run)
299 else:
300 distutils.log.warn('\'%s\' does not exist -- can\'t clean it',
301 directory)
302 super(clean_wammu, self).run()
304 class install_wammu(distutils.command.install.install, object):
306 Install wrapper to support option for skipping deps
309 user_options = distutils.command.install.install.user_options + [('skip-deps', 's', 'skip checking for dependencies')]
310 boolean_options = distutils.command.install.install.boolean_options + ['skip-deps']
312 def initialize_options(self):
313 global skip_dependencies
314 super(install_wammu, self).initialize_options()
315 self.skip_deps = skip_dependencies
317 def finalize_options(self):
318 global skip_dependencies
319 super(install_wammu, self).finalize_options()
320 if self.skip_deps:
321 skip_dependencies = self.skip_deps
323 class install_data_wammu(distutils.command.install_data.install_data, object):
325 Install locales in addition to regullar data.
328 def run (self):
330 Install also .mo files.
332 # add .mo files to data files
333 for (_locale, _src, _dst) in list_message_files():
334 _build_dst = os.path.join('build', _dst)
335 item = [os.path.dirname(_dst), [_build_dst]]
336 self.data_files.append(item)
338 # install data files
339 super(install_data_wammu, self).run()
341 py2exepackages = ['Wammu']
342 if sys.version_info >= (2, 5):
343 # Email module changed a lot in python 2.5 and we can not yet use new API
344 py2exepackages.append('email')
345 py2exepackages.append('email.mime')
347 # ModuleFinder can't handle runtime changes to __path__, but win32com uses them
348 try:
349 # if this doesn't work, try import modulefinder
350 import py2exe.mf as modulefinder
351 import win32com
352 for p in win32com.__path__[1:]:
353 modulefinder.AddPackagePath("win32com", p)
354 for extra in ["win32com.shell"]: #,"win32com.mapi"
355 __import__(extra)
356 m = sys.modules[extra]
357 for p in m.__path__[1:]:
358 modulefinder.AddPackagePath(extra, p)
359 except ImportError:
360 # no build path setup, no worries.
361 pass
363 addparams = {}
365 if HAVE_PY2EXE:
366 addparams['windows'] = [
368 'script': 'wammu.py',
369 'icon_resources': [(1, 'icon/wammu.ico')],
372 addparams['zipfile'] = 'shared.lib'
374 distutils.core.setup(name="wammu",
375 version = Wammu.__version__,
376 description = "Wammu Mobile Phone Manager",
377 long_description = "Phone manager built on top of python-gammu. Supports many phones.",
378 author = u"Michal Cihar",
379 author_email = "michal@cihar.com",
380 maintainer = u"Michal Cihar",
381 maintainer_email = "michal@cihar.com",
382 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME'],
383 keywords = ['mobile', 'phone', 'SMS', 'contact', 'gammu', 'calendar', 'todo'],
384 url = "http://wammu.eu/",
385 download_url = 'http://wammu.eu/download/',
386 license = "GPL",
387 classifiers = [
388 'Development Status :: 5 - Production/Stable',
389 'Environment :: Win32 (MS Windows)',
390 'Environment :: X11 Applications :: GTK',
391 'Intended Audience :: End Users/Desktop',
392 'License :: OSI Approved :: GNU General Public License (GPL)',
393 'Operating System :: Microsoft :: Windows :: Windows 95/98/2000',
394 'Operating System :: Microsoft :: Windows :: Windows NT/2000',
395 'Operating System :: POSIX',
396 'Operating System :: Unix',
397 'Programming Language :: Python',
398 'Topic :: Communications :: Telephony',
399 'Topic :: Office/Business :: Scheduling',
400 'Topic :: Utilities',
401 'Natural Language :: Afrikaans',
402 'Natural Language :: English',
403 'Natural Language :: Catalan',
404 'Natural Language :: Chinese (Simplified)',
405 'Natural Language :: Czech',
406 'Natural Language :: Dutch',
407 # 'Natural Language :: Estonian',
408 'Natural Language :: Finnish',
409 'Natural Language :: French',
410 'Natural Language :: German',
411 'Natural Language :: Hungarian',
412 'Natural Language :: Italian',
413 'Natural Language :: Korean',
414 'Natural Language :: Polish',
415 'Natural Language :: Portuguese (Brazilian)',
416 'Natural Language :: Russian',
417 'Natural Language :: Slovak',
418 'Natural Language :: Spanish',
419 'Natural Language :: Swedish',
421 packages = ['Wammu', 'Wammu.wxcomp'],
422 scripts = ['wammu.py', 'wammu-configure.py'],
423 data_files = [
424 (os.path.join('share','Wammu','images','icons'), glob.glob('images/icons/*.png')),
425 (os.path.join('share','Wammu','images','misc'), glob.glob('images/misc/*.png')),
426 (os.path.join('share','applications'), ['build/wammu.desktop']),
427 (os.path.join('share','pixmaps'), [
428 'icon/wammu.png',
429 'icon/wammu.xpm',
430 'icon/wammu.ico',
431 'icon/wammu.svg',
433 (os.path.join('share','man','man1'), ['wammu.1', 'wammu-configure.1'])
435 # Override certain command classes with our own ones
436 cmdclass = {
437 'build': build_wammu,
438 'build_scripts': build_scripts_wammu,
439 'clean': clean_wammu,
440 'install': install_wammu,
441 'install_data': install_data_wammu,
443 # py2exe options
444 options = {'py2exe': {
445 'optimize': 2,
446 'packages': py2exepackages,
448 **addparams