Code

svn repository setup
[roundup.git] / setup.py
1 #! /usr/bin/env python
2 #
3 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
4 # This module is free software, and you may redistribute it and/or modify
5 # under the same terms as Python, so long as this copyright message and
6 # disclaimer are retained in their original form.
7 #
8 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
9 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
10 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
11 # POSSIBILITY OF SUCH DAMAGE.
12 #
13 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
14 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
15 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
16 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
18 #
19 # $Id: setup.py,v 1.105 2008-09-01 01:58:32 richard Exp $
21 from distutils.core import setup, Extension
22 from distutils.util import get_platform
23 from distutils.file_util import write_file
24 from distutils.command.bdist_rpm import bdist_rpm
25 from distutils.command.build import build
26 from distutils.command.build_scripts import build_scripts
27 from distutils.command.build_py import build_py
29 import sys, os, string
30 from glob import glob
32 # patch distutils if it can't cope with the "classifiers" keyword
33 from distutils.dist import DistributionMetadata
34 if not hasattr(DistributionMetadata, 'classifiers'):
35     DistributionMetadata.classifiers = None
36     DistributionMetadata.download_url = None
38 from roundup import msgfmt
40 #############################################################################
41 ### Build script files
42 #############################################################################
44 class build_scripts_create(build_scripts):
45     """ Overload the build_scripts command and create the scripts
46         from scratch, depending on the target platform.
48         You have to define the name of your package in an inherited
49         class (due to the delayed instantiation of command classes
50         in distutils, this cannot be passed to __init__).
52         The scripts are created in an uniform scheme: they start the
53         run() function in the module
55             <packagename>.scripts.<mangled_scriptname>
57         The mangling of script names replaces '-' and '/' characters
58         with '-' and '.', so that they are valid module paths.
60         If the target platform is win32, create .bat files instead of
61         *nix shell scripts.  Target platform is set to "win32" if main
62         command is 'bdist_wininst' or if the command is 'bdist' and
63         it has the list of formats (from command line or config file)
64         and the first item on that list is wininst.  Otherwise
65         target platform is set to current (build) platform.
66     """
67     package_name = None
69     def initialize_options(self):
70         build_scripts.initialize_options(self)
71         self.script_preamble = None
72         self.target_platform = None
73         self.python_executable = None
75     def finalize_options(self):
76         build_scripts.finalize_options(self)
77         cmdopt=self.distribution.command_options
79         # find the target platform
80         if self.target_platform:
81             # TODO? allow explicit setting from command line
82             target = self.target_platform
83         if cmdopt.has_key("bdist_wininst"):
84             target = "win32"
85         elif cmdopt.get("bdist", {}).has_key("formats"):
86             formats = cmdopt["bdist"]["formats"][1].split(",")
87             if formats[0] == "wininst":
88                 target = "win32"
89             else:
90                 target = sys.platform
91             if len(formats) > 1:
92                 self.warn(
93                     "Scripts are built for %s only (requested formats: %s)"
94                     % (target, ",".join(formats)))
95         else:
96             # default to current platform
97             target = sys.platform
98         self.target_platfom = target
100         # for native builds, use current python executable path;
101         # for cross-platform builds, use default executable name
102         if self.python_executable:
103             # TODO? allow command-line option
104             pass
105         if target == sys.platform:
106             self.python_executable = os.path.normpath(sys.executable)
107         else:
108             self.python_executable = "python"
110         # for windows builds, add ".bat" extension
111         if target == "win32":
112             # *nix-like scripts may be useful also on win32 (cygwin)
113             # to build both script versions, use:
114             #self.scripts = list(self.scripts) + [script + ".bat"
115             #    for script in self.scripts]
116             self.scripts = [script + ".bat" for script in self.scripts]
118         # tweak python path for installations outside main python library
119         if cmdopt.get("install", {}).has_key("prefix"):
120             prefix = os.path.expanduser(cmdopt['install']['prefix'][1])
121             version = '%d.%d'%sys.version_info[:2]
122             self.script_preamble = '''
123 import sys
124 sys.path.insert(1, "%s/lib/python%s/site-packages")
125 '''%(prefix, version)
126         else:
127             self.script_preamble = ''
129     def copy_scripts(self):
130         """ Create each script listed in 'self.scripts'
131         """
132         if not self.package_name:
133             raise Exception("You have to inherit build_scripts_create and"
134                 " provide a package name")
136         to_module = string.maketrans('-/', '_.')
138         self.mkpath(self.build_dir)
139         for script in self.scripts:
140             outfile = os.path.join(self.build_dir, os.path.basename(script))
142             #if not self.force and not newer(script, outfile):
143             #    self.announce("not copying %s (up-to-date)" % script)
144             #    continue
146             if self.dry_run:
147                 self.announce("would create %s" % outfile)
148                 continue
150             module = os.path.splitext(os.path.basename(script))[0]
151             module = string.translate(module, to_module)
152             script_vars = {
153                 'python': self.python_executable,
154                 'package': self.package_name,
155                 'module': module,
156                 'prefix': self.script_preamble,
157             }
159             self.announce("creating %s" % outfile)
160             file = open(outfile, 'w')
162             try:
163                 # could just check self.target_platform,
164                 # but looking at the script extension
165                 # makes it possible to build both *nix-like
166                 # and windows-like scripts on win32.
167                 # may be useful for cygwin.
168                 if os.path.splitext(outfile)[1] == ".bat":
169                     file.write('@echo off\n'
170                         'if NOT "%%_4ver%%" == "" "%(python)s" -c "from %(package)s.scripts.%(module)s import run; run()" %%$\n'
171                         'if     "%%_4ver%%" == "" "%(python)s" -c "from %(package)s.scripts.%(module)s import run; run()" %%*\n'
172                         % script_vars)
173                 else:
174                     file.write('#! %(python)s\n%(prefix)s'
175                         'from %(package)s.scripts.%(module)s import run\n'
176                         'run()\n'
177                         % script_vars)
178             finally:
179                 file.close()
180                 os.chmod(outfile, 0755)
183 class build_scripts_roundup(build_scripts_create):
184     package_name = 'roundup'
187 def scriptname(path):
188     """ Helper for building a list of script names from a list of
189         module files.
190     """
191     script = os.path.splitext(os.path.basename(path))[0]
192     script = string.replace(script, '_', '-')
193     return script
195 ### Build Roundup
197 def list_message_files(suffix=".po"):
198     """Return list of all found message files and their intallation paths"""
199     _files = glob("locale/*" + suffix)
200     _list = []
201     for _file in _files:
202         # basename (without extension) is a locale name
203         _locale = os.path.splitext(os.path.basename(_file))[0]
204         _list.append((_file, os.path.join(
205             "share", "locale", _locale, "LC_MESSAGES", "roundup.mo")))
206     return _list
208 def check_manifest():
209     """Check that the files listed in the MANIFEST are present when the
210     source is unpacked.
211     """
212     try:
213         f = open('MANIFEST')
214     except:
215         print '\n*** SOURCE WARNING: The MANIFEST file is missing!'
216         return
217     try:
218         manifest = [l.strip() for l in f.readlines()]
219     finally:
220         f.close()
221     err = [line for line in manifest if not os.path.exists(line)]
222     err.sort()
223     # ignore auto-generated files
224     if err == ['roundup-admin', 'roundup-demo', 'roundup-gettext',
225             'roundup-mailgw', 'roundup-server']:
226         err = []
227     if err:
228         n = len(manifest)
229         print '\n*** SOURCE WARNING: There are files missing (%d/%d found)!'%(
230             n-len(err), n)
231         print 'Missing:', '\nMissing: '.join(err)
234 class build_py_roundup(build_py):
236     def find_modules(self):
237         # Files listed in py_modules are in the toplevel directory
238         # of the source distribution.
239         modules = []
240         for module in self.py_modules:
241             path = string.split(module, '.')
242             package = string.join(path[0:-1], '.')
243             module_base = path[-1]
244             module_file = module_base + '.py'
245             if self.check_module(module, module_file):
246                 modules.append((package, module_base, module_file))
247         return modules
250 class build_roundup(build):
252     def build_message_files(self):
253         """For each locale/*.po, build .mo file in target locale directory"""
254         for (_src, _dst) in list_message_files():
255             _build_dst = os.path.join("build", _dst)
256             self.mkpath(os.path.dirname(_build_dst))
257             self.announce("Compiling %s -> %s" % (_src, _build_dst))
258             msgfmt.make(_src, _build_dst)
260     def run(self):
261         check_manifest()
262         self.build_message_files()
263         build.run(self)
265 class bdist_rpm_roundup(bdist_rpm):
267     def finalize_options(self):
268         bdist_rpm.finalize_options(self)
269         if self.install_script:
270             # install script is overridden.  skip default
271             return
272         # install script option must be file name.
273         # create the file in rpm build directory.
274         install_script = os.path.join(self.rpm_base, "install.sh")
275         self.mkpath(self.rpm_base)
276         self.execute(write_file, (install_script, [
277                 ("%s setup.py install --root=$RPM_BUILD_ROOT "
278                     "--record=ROUNDUP_FILES") % self.python,
279                 # allow any additional extension for man pages
280                 # (rpm may compress them to .gz or .bz2)
281                 # man page here is any file
282                 # with single-character extension
283                 # in man directory
284                 "sed -e 's,\(/man/.*\..\)$,\\1*,' "
285                     "<ROUNDUP_FILES >INSTALLED_FILES",
286             ]), "writing '%s'" % install_script)
287         self.install_script = install_script
289 #############################################################################
290 ### Main setup stuff
291 #############################################################################
293 def main():
294     # build list of scripts from their implementation modules
295     roundup_scripts = map(scriptname, glob('roundup/scripts/[!_]*.py'))
297     # template munching
298     packagelist = [
299         'roundup',
300         'roundup.cgi',
301         'roundup.cgi.PageTemplates',
302         'roundup.cgi.TAL',
303         'roundup.cgi.ZTUtils',
304         'roundup.backends',
305         'roundup.scripts',
306     ]
307     installdatafiles = [
308         ('share/roundup/cgi-bin', ['frontends/roundup.cgi']),
309     ]
310     py_modules = ['roundup.demo',]
312     # install man pages on POSIX platforms
313     if os.name == 'posix':
314         installdatafiles.append(('man/man1', ['doc/roundup-admin.1',
315             'doc/roundup-mailgw.1', 'doc/roundup-server.1',
316             'doc/roundup-demo.1']))
318     # add the templates to the data files lists
319     from roundup.init import listTemplates
320     templates = [t['path'] for t in listTemplates('templates').values()]
321     for tdir in templates:
322         # scan for data files
323         for idir in '. detectors extensions html'.split():
324             idir = os.path.join(tdir, idir)
325             if not os.path.isdir(idir):
326                 continue
327             tfiles = []
328             for f in os.listdir(idir):
329                 if f.startswith('.'):
330                     continue
331                 ifile = os.path.join(idir, f)
332                 if os.path.isfile(ifile):
333                     tfiles.append(ifile)
334             installdatafiles.append(
335                 (os.path.join('share', 'roundup', idir), tfiles)
336             )
338     # add message files
339     for (_dist_file, _mo_file) in list_message_files():
340         installdatafiles.append((os.path.dirname(_mo_file),
341             [os.path.join("build", _mo_file)]))
343     # perform the setup action
344     from roundup import __version__
345     setup_args = {
346         'name': "roundup",
347         'version': __version__,
348         'description': "A simple-to-use and -install issue-tracking system"
349             " with command-line, web and e-mail interfaces. Highly"
350             " customisable.",
351         'long_description':
352 '''In this release
353 ===============
355 1.4.6 is a bugfix release:
357 - Fix bug introduced in 1.4.5 in RDBMS full-text indexing
358 - Make URL matching code less matchy
360 If you're upgrading from an older version of Roundup you *must* follow
361 the "Software Upgrade" guidelines given in the maintenance documentation.
363 Roundup requires python 2.3 or later for correct operation.
365 To give Roundup a try, just download (see below), unpack and run::
367     roundup-demo
369 Documentation is available at the website:
370      http://roundup.sourceforge.net/
371 Mailing lists - the place to ask questions:
372      http://sourceforge.net/mail/?group_id=31577
374 About Roundup
375 =============
377 Roundup is a simple-to-use and -install issue-tracking system with
378 command-line, web and e-mail interfaces. It is based on the winning design
379 from Ka-Ping Yee in the Software Carpentry "Track" design competition.
381 Note: Ping is not responsible for this project. The contact for this
382 project is richard@users.sourceforge.net.
384 Roundup manages a number of issues (with flexible properties such as
385 "description", "priority", and so on) and provides the ability to:
387 (a) submit new issues,
388 (b) find and edit existing issues, and
389 (c) discuss issues with other participants.
391 The system will facilitate communication among the participants by managing
392 discussions and notifying interested parties when issues are edited. One of
393 the major design goals for Roundup that it be simple to get going. Roundup
394 is therefore usable "out of the box" with any python 2.3+ installation. It
395 doesn't even need to be "installed" to be operational, though a
396 disutils-based install script is provided.
398 It comes with two issue tracker templates (a classic bug/feature tracker and
399 a minimal skeleton) and five database back-ends (anydbm, sqlite, metakit,
400 mysql and postgresql).
401 ''',
402         'author': "Richard Jones",
403         'author_email': "richard@users.sourceforge.net",
404         'url': 'http://roundup.sourceforge.net/',
405         'packages': packagelist,
406         'classifiers': [
407             'Development Status :: 5 - Production/Stable',
408             'Environment :: Console',
409             'Environment :: Web Environment',
410             'Intended Audience :: End Users/Desktop',
411             'Intended Audience :: Developers',
412             'Intended Audience :: System Administrators',
413             'License :: OSI Approved :: Python Software Foundation License',
414             'Operating System :: MacOS :: MacOS X',
415             'Operating System :: Microsoft :: Windows',
416             'Operating System :: POSIX',
417             'Programming Language :: Python',
418             'Topic :: Communications :: Email',
419             'Topic :: Office/Business',
420             'Topic :: Software Development :: Bug Tracking',
421         ],
423         # Override certain command classes with our own ones
424         'cmdclass': {
425             'build_scripts': build_scripts_roundup,
426             'build_py': build_py_roundup,
427             'build': build_roundup,
428             'bdist_rpm': bdist_rpm_roundup,
429         },
430         'scripts': roundup_scripts,
432         'data_files':  installdatafiles
433     }
434     if sys.version_info[:2] > (2, 2):
435        setup_args['py_modules'] = py_modules
437     setup(**setup_args)
439 if __name__ == '__main__':
440     main()
442 # vim: set filetype=python sts=4 sw=4 et si :