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 $
22 from roundup.dist.command.build_doc import build_doc
23 from distutils.core import setup, Extension
24 from distutils.util import get_platform
25 from distutils.file_util import write_file
26 from distutils.command.bdist_rpm import bdist_rpm
27 from distutils.command.build import build
28 from distutils.command.build_scripts import build_scripts
29 from distutils.command.build_py import build_py
31 import sys, os, string
32 from glob import glob
34 # patch distutils if it can't cope with the "classifiers" keyword
35 from distutils.dist import DistributionMetadata
36 if not hasattr(DistributionMetadata, 'classifiers'):
37 DistributionMetadata.classifiers = None
38 DistributionMetadata.download_url = None
40 from roundup import msgfmt
42 def include(d, e):
43 """Generate a pair of (directory, file-list) for installation.
45 'd' -- A directory
47 'e' -- A glob pattern"""
49 return (d, [f for f in glob('%s/%s'%(d, e)) if os.path.isfile(f)])
51 #############################################################################
52 ### Build script files
53 #############################################################################
55 class build_scripts_create(build_scripts):
56 """ Overload the build_scripts command and create the scripts
57 from scratch, depending on the target platform.
59 You have to define the name of your package in an inherited
60 class (due to the delayed instantiation of command classes
61 in distutils, this cannot be passed to __init__).
63 The scripts are created in an uniform scheme: they start the
64 run() function in the module
66 <packagename>.scripts.<mangled_scriptname>
68 The mangling of script names replaces '-' and '/' characters
69 with '-' and '.', so that they are valid module paths.
71 If the target platform is win32, create .bat files instead of
72 *nix shell scripts. Target platform is set to "win32" if main
73 command is 'bdist_wininst' or if the command is 'bdist' and
74 it has the list of formats (from command line or config file)
75 and the first item on that list is wininst. Otherwise
76 target platform is set to current (build) platform.
77 """
78 package_name = None
80 def initialize_options(self):
81 build_scripts.initialize_options(self)
82 self.script_preamble = None
83 self.target_platform = None
84 self.python_executable = None
86 def finalize_options(self):
87 build_scripts.finalize_options(self)
88 cmdopt=self.distribution.command_options
90 # find the target platform
91 if self.target_platform:
92 # TODO? allow explicit setting from command line
93 target = self.target_platform
94 if cmdopt.has_key("bdist_wininst"):
95 target = "win32"
96 elif cmdopt.get("bdist", {}).has_key("formats"):
97 formats = cmdopt["bdist"]["formats"][1].split(",")
98 if formats[0] == "wininst":
99 target = "win32"
100 else:
101 target = sys.platform
102 if len(formats) > 1:
103 self.warn(
104 "Scripts are built for %s only (requested formats: %s)"
105 % (target, ",".join(formats)))
106 else:
107 # default to current platform
108 target = sys.platform
109 self.target_platfom = target
111 # for native builds, use current python executable path;
112 # for cross-platform builds, use default executable name
113 if self.python_executable:
114 # TODO? allow command-line option
115 pass
116 if target == sys.platform:
117 self.python_executable = os.path.normpath(sys.executable)
118 else:
119 self.python_executable = "python"
121 # for windows builds, add ".bat" extension
122 if target == "win32":
123 # *nix-like scripts may be useful also on win32 (cygwin)
124 # to build both script versions, use:
125 #self.scripts = list(self.scripts) + [script + ".bat"
126 # for script in self.scripts]
127 self.scripts = [script + ".bat" for script in self.scripts]
129 # tweak python path for installations outside main python library
130 if cmdopt.get("install", {}).has_key("prefix"):
131 prefix = os.path.expanduser(cmdopt['install']['prefix'][1])
132 version = '%d.%d'%sys.version_info[:2]
133 self.script_preamble = '''
134 import sys
135 sys.path.insert(1, "%s/lib/python%s/site-packages")
136 '''%(prefix, version)
137 else:
138 self.script_preamble = ''
140 def copy_scripts(self):
141 """ Create each script listed in 'self.scripts'
142 """
143 if not self.package_name:
144 raise Exception("You have to inherit build_scripts_create and"
145 " provide a package name")
147 to_module = string.maketrans('-/', '_.')
149 self.mkpath(self.build_dir)
150 for script in self.scripts:
151 outfile = os.path.join(self.build_dir, os.path.basename(script))
153 #if not self.force and not newer(script, outfile):
154 # self.announce("not copying %s (up-to-date)" % script)
155 # continue
157 if self.dry_run:
158 self.announce("would create %s" % outfile)
159 continue
161 module = os.path.splitext(os.path.basename(script))[0]
162 module = string.translate(module, to_module)
163 script_vars = {
164 'python': self.python_executable,
165 'package': self.package_name,
166 'module': module,
167 'prefix': self.script_preamble,
168 }
170 self.announce("creating %s" % outfile)
171 file = open(outfile, 'w')
173 try:
174 # could just check self.target_platform,
175 # but looking at the script extension
176 # makes it possible to build both *nix-like
177 # and windows-like scripts on win32.
178 # may be useful for cygwin.
179 if os.path.splitext(outfile)[1] == ".bat":
180 file.write('@echo off\n'
181 'if NOT "%%_4ver%%" == "" "%(python)s" -c "from %(package)s.scripts.%(module)s import run; run()" %%$\n'
182 'if "%%_4ver%%" == "" "%(python)s" -c "from %(package)s.scripts.%(module)s import run; run()" %%*\n'
183 % script_vars)
184 else:
185 file.write('#! %(python)s\n%(prefix)s'
186 'from %(package)s.scripts.%(module)s import run\n'
187 'run()\n'
188 % script_vars)
189 finally:
190 file.close()
191 os.chmod(outfile, 0755)
194 class build_scripts_roundup(build_scripts_create):
195 package_name = 'roundup'
198 def scriptname(path):
199 """ Helper for building a list of script names from a list of
200 module files.
201 """
202 script = os.path.splitext(os.path.basename(path))[0]
203 script = string.replace(script, '_', '-')
204 return script
206 ### Build Roundup
208 def list_message_files(suffix=".po"):
209 """Return list of all found message files and their intallation paths"""
210 _files = glob("locale/*" + suffix)
211 _list = []
212 for _file in _files:
213 # basename (without extension) is a locale name
214 _locale = os.path.splitext(os.path.basename(_file))[0]
215 _list.append((_file, os.path.join(
216 "share", "locale", _locale, "LC_MESSAGES", "roundup.mo")))
217 return _list
219 def check_manifest():
220 """Check that the files listed in the MANIFEST are present when the
221 source is unpacked.
222 """
223 try:
224 f = open('MANIFEST')
225 except:
226 print '\n*** SOURCE WARNING: The MANIFEST file is missing!'
227 return
228 try:
229 manifest = [l.strip() for l in f.readlines()]
230 finally:
231 f.close()
232 err = [line for line in manifest if not os.path.exists(line)]
233 err.sort()
234 # ignore auto-generated files
235 if err == ['roundup-admin', 'roundup-demo', 'roundup-gettext',
236 'roundup-mailgw', 'roundup-server']:
237 err = []
238 if err:
239 n = len(manifest)
240 print '\n*** SOURCE WARNING: There are files missing (%d/%d found)!'%(
241 n-len(err), n)
242 print 'Missing:', '\nMissing: '.join(err)
245 class build_py_roundup(build_py):
247 def find_modules(self):
248 # Files listed in py_modules are in the toplevel directory
249 # of the source distribution.
250 modules = []
251 for module in self.py_modules:
252 path = string.split(module, '.')
253 package = string.join(path[0:-1], '.')
254 module_base = path[-1]
255 module_file = module_base + '.py'
256 if self.check_module(module, module_file):
257 modules.append((package, module_base, module_file))
258 return modules
261 class build_roundup(build):
263 def build_message_files(self):
264 """For each locale/*.po, build .mo file in target locale directory"""
265 for (_src, _dst) in list_message_files():
266 _build_dst = os.path.join("build", _dst)
267 self.mkpath(os.path.dirname(_build_dst))
268 self.announce("Compiling %s -> %s" % (_src, _build_dst))
269 msgfmt.make(_src, _build_dst)
271 def run(self):
272 check_manifest()
273 self.build_message_files()
274 build.run(self)
276 class bdist_rpm_roundup(bdist_rpm):
278 def finalize_options(self):
279 bdist_rpm.finalize_options(self)
280 if self.install_script:
281 # install script is overridden. skip default
282 return
283 # install script option must be file name.
284 # create the file in rpm build directory.
285 install_script = os.path.join(self.rpm_base, "install.sh")
286 self.mkpath(self.rpm_base)
287 self.execute(write_file, (install_script, [
288 ("%s setup.py install --root=$RPM_BUILD_ROOT "
289 "--record=ROUNDUP_FILES") % self.python,
290 # allow any additional extension for man pages
291 # (rpm may compress them to .gz or .bz2)
292 # man page here is any file
293 # with single-character extension
294 # in man directory
295 "sed -e 's,\(/man/.*\..\)$,\\1*,' "
296 "<ROUNDUP_FILES >INSTALLED_FILES",
297 ]), "writing '%s'" % install_script)
298 self.install_script = install_script
300 #############################################################################
301 ### Main setup stuff
302 #############################################################################
304 def main():
305 # build list of scripts from their implementation modules
306 roundup_scripts = map(scriptname, glob('roundup/scripts/[!_]*.py'))
308 # template munching
309 packagelist = [
310 'roundup',
311 'roundup.cgi',
312 'roundup.cgi.PageTemplates',
313 'roundup.cgi.TAL',
314 'roundup.cgi.ZTUtils',
315 'roundup.backends',
316 'roundup.scripts',
317 ]
318 installdatafiles = [
319 ('share/roundup/cgi-bin', ['frontends/roundup.cgi']),
320 ]
321 py_modules = ['roundup.demo',]
323 # install man pages on POSIX platforms
324 if os.name == 'posix':
325 installdatafiles.append(('man/man1', ['doc/roundup-admin.1',
326 'doc/roundup-mailgw.1', 'doc/roundup-server.1',
327 'doc/roundup-demo.1']))
329 # add the templates to the data files lists
330 from roundup.init import listTemplates
331 templates = [t['path'] for t in listTemplates('templates').values()]
332 for tdir in templates:
333 # scan for data files
334 for idir in '. detectors extensions html'.split():
335 idir = os.path.join(tdir, idir)
336 if not os.path.isdir(idir):
337 continue
338 tfiles = []
339 for f in os.listdir(idir):
340 if f.startswith('.'):
341 continue
342 ifile = os.path.join(idir, f)
343 if os.path.isfile(ifile):
344 tfiles.append(ifile)
345 installdatafiles.append(
346 (os.path.join('share', 'roundup', idir), tfiles)
347 )
349 # add message files
350 for (_dist_file, _mo_file) in list_message_files():
351 installdatafiles.append((os.path.dirname(_mo_file),
352 [os.path.join("build", _mo_file)]))
354 # add docs
355 installdatafiles.append(include(os.path.join('share', 'doc', 'roundup', 'html'), '*'))
357 # perform the setup action
358 from roundup import __version__
359 setup_args = {
360 'name': "roundup",
361 'version': __version__,
362 'description': "A simple-to-use and -install issue-tracking system"
363 " with command-line, web and e-mail interfaces. Highly"
364 " customisable.",
365 'long_description':
366 '''In this release
367 ===============
369 1.4.6 is a bugfix release:
371 - Fix bug introduced in 1.4.5 in RDBMS full-text indexing
372 - Make URL matching code less matchy
374 If you're upgrading from an older version of Roundup you *must* follow
375 the "Software Upgrade" guidelines given in the maintenance documentation.
377 Roundup requires python 2.3 or later for correct operation.
379 To give Roundup a try, just download (see below), unpack and run::
381 roundup-demo
383 Documentation is available at the website:
384 http://roundup.sourceforge.net/
385 Mailing lists - the place to ask questions:
386 http://sourceforge.net/mail/?group_id=31577
388 About Roundup
389 =============
391 Roundup is a simple-to-use and -install issue-tracking system with
392 command-line, web and e-mail interfaces. It is based on the winning design
393 from Ka-Ping Yee in the Software Carpentry "Track" design competition.
395 Note: Ping is not responsible for this project. The contact for this
396 project is richard@users.sourceforge.net.
398 Roundup manages a number of issues (with flexible properties such as
399 "description", "priority", and so on) and provides the ability to:
401 (a) submit new issues,
402 (b) find and edit existing issues, and
403 (c) discuss issues with other participants.
405 The system will facilitate communication among the participants by managing
406 discussions and notifying interested parties when issues are edited. One of
407 the major design goals for Roundup that it be simple to get going. Roundup
408 is therefore usable "out of the box" with any python 2.3+ installation. It
409 doesn't even need to be "installed" to be operational, though a
410 disutils-based install script is provided.
412 It comes with two issue tracker templates (a classic bug/feature tracker and
413 a minimal skeleton) and five database back-ends (anydbm, sqlite, metakit,
414 mysql and postgresql).
415 ''',
416 'author': "Richard Jones",
417 'author_email': "richard@users.sourceforge.net",
418 'url': 'http://roundup.sourceforge.net/',
419 'packages': packagelist,
420 'classifiers': [
421 'Development Status :: 5 - Production/Stable',
422 'Environment :: Console',
423 'Environment :: Web Environment',
424 'Intended Audience :: End Users/Desktop',
425 'Intended Audience :: Developers',
426 'Intended Audience :: System Administrators',
427 'License :: OSI Approved :: Python Software Foundation License',
428 'Operating System :: MacOS :: MacOS X',
429 'Operating System :: Microsoft :: Windows',
430 'Operating System :: POSIX',
431 'Programming Language :: Python',
432 'Topic :: Communications :: Email',
433 'Topic :: Office/Business',
434 'Topic :: Software Development :: Bug Tracking',
435 ],
437 # Override certain command classes with our own ones
438 'cmdclass': {
439 'build_doc': build_doc,
440 'build_scripts': build_scripts_roundup,
441 'build_py': build_py_roundup,
442 'build': build_roundup,
443 'bdist_rpm': bdist_rpm_roundup,
444 },
445 'scripts': roundup_scripts,
447 'data_files': installdatafiles
448 }
449 if sys.version_info[:2] > (2, 2):
450 setup_args['py_modules'] = py_modules
452 setup(**setup_args)
454 if __name__ == '__main__':
455 main()
457 # vim: set filetype=python sts=4 sw=4 et si :