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 :