##// END OF EJS Templates
setup: user-friendly error message if Python headers are missing
Nicolas Dumazet -
r12649:6c0e1aee default
parent child Browse files
Show More
@@ -1,343 +1,347
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # This is the mercurial setup script.
3 # This is the mercurial setup script.
4 #
4 #
5 # 'python setup.py install', or
5 # 'python setup.py install', or
6 # 'python setup.py --help' for more options
6 # 'python setup.py --help' for more options
7
7
8 import sys
8 import sys
9 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
9 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
10 raise SystemExit("Mercurial requires Python 2.4 or later.")
10 raise SystemExit("Mercurial requires Python 2.4 or later.")
11
11
12 if sys.version_info[0] >= 3:
12 if sys.version_info[0] >= 3:
13 def b(s):
13 def b(s):
14 '''A helper function to emulate 2.6+ bytes literals using string
14 '''A helper function to emulate 2.6+ bytes literals using string
15 literals.'''
15 literals.'''
16 return s.encode('latin1')
16 return s.encode('latin1')
17 else:
17 else:
18 def b(s):
18 def b(s):
19 '''A helper function to emulate 2.6+ bytes literals using string
19 '''A helper function to emulate 2.6+ bytes literals using string
20 literals.'''
20 literals.'''
21 return s
21 return s
22
22
23 # Solaris Python packaging brain damage
23 # Solaris Python packaging brain damage
24 try:
24 try:
25 import hashlib
25 import hashlib
26 sha = hashlib.sha1()
26 sha = hashlib.sha1()
27 except:
27 except:
28 try:
28 try:
29 import sha
29 import sha
30 except:
30 except:
31 raise SystemExit(
31 raise SystemExit(
32 "Couldn't import standard hashlib (incomplete Python install).")
32 "Couldn't import standard hashlib (incomplete Python install).")
33
33
34 try:
34 try:
35 import zlib
35 import zlib
36 except:
36 except:
37 raise SystemExit(
37 raise SystemExit(
38 "Couldn't import standard zlib (incomplete Python install).")
38 "Couldn't import standard zlib (incomplete Python install).")
39
39
40 try:
40 try:
41 import bz2
41 import bz2
42 except:
42 except:
43 raise SystemExit(
43 raise SystemExit(
44 "Couldn't import standard bz2 (incomplete Python install).")
44 "Couldn't import standard bz2 (incomplete Python install).")
45
45
46 import os, subprocess, time
46 import os, subprocess, time
47 import shutil
47 import shutil
48 import tempfile
48 import tempfile
49 from distutils import log
49 from distutils import log
50 from distutils.core import setup, Extension
50 from distutils.core import setup, Extension
51 from distutils.dist import Distribution
51 from distutils.dist import Distribution
52 from distutils.command.build import build
52 from distutils.command.build import build
53 from distutils.command.build_ext import build_ext
53 from distutils.command.build_ext import build_ext
54 from distutils.command.build_py import build_py
54 from distutils.command.build_py import build_py
55 from distutils.spawn import spawn, find_executable
55 from distutils.spawn import spawn, find_executable
56 from distutils.ccompiler import new_compiler
56 from distutils.ccompiler import new_compiler
57 from distutils.errors import CCompilerError
57 from distutils.errors import CCompilerError
58 from distutils.sysconfig import get_python_inc
58
59
59 scripts = ['hg']
60 scripts = ['hg']
60 if os.name == 'nt':
61 if os.name == 'nt':
61 scripts.append('contrib/win32/hg.bat')
62 scripts.append('contrib/win32/hg.bat')
62
63
63 # simplified version of distutils.ccompiler.CCompiler.has_function
64 # simplified version of distutils.ccompiler.CCompiler.has_function
64 # that actually removes its temporary files.
65 # that actually removes its temporary files.
65 def hasfunction(cc, funcname):
66 def hasfunction(cc, funcname):
66 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
67 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
67 devnull = oldstderr = None
68 devnull = oldstderr = None
68 try:
69 try:
69 try:
70 try:
70 fname = os.path.join(tmpdir, 'funcname.c')
71 fname = os.path.join(tmpdir, 'funcname.c')
71 f = open(fname, 'w')
72 f = open(fname, 'w')
72 f.write('int main(void) {\n')
73 f.write('int main(void) {\n')
73 f.write(' %s();\n' % funcname)
74 f.write(' %s();\n' % funcname)
74 f.write('}\n')
75 f.write('}\n')
75 f.close()
76 f.close()
76 # Redirect stderr to /dev/null to hide any error messages
77 # Redirect stderr to /dev/null to hide any error messages
77 # from the compiler.
78 # from the compiler.
78 # This will have to be changed if we ever have to check
79 # This will have to be changed if we ever have to check
79 # for a function on Windows.
80 # for a function on Windows.
80 devnull = open('/dev/null', 'w')
81 devnull = open('/dev/null', 'w')
81 oldstderr = os.dup(sys.stderr.fileno())
82 oldstderr = os.dup(sys.stderr.fileno())
82 os.dup2(devnull.fileno(), sys.stderr.fileno())
83 os.dup2(devnull.fileno(), sys.stderr.fileno())
83 objects = cc.compile([fname], output_dir=tmpdir)
84 objects = cc.compile([fname], output_dir=tmpdir)
84 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
85 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
85 except:
86 except:
86 return False
87 return False
87 return True
88 return True
88 finally:
89 finally:
89 if oldstderr is not None:
90 if oldstderr is not None:
90 os.dup2(oldstderr, sys.stderr.fileno())
91 os.dup2(oldstderr, sys.stderr.fileno())
91 if devnull is not None:
92 if devnull is not None:
92 devnull.close()
93 devnull.close()
93 shutil.rmtree(tmpdir)
94 shutil.rmtree(tmpdir)
94
95
95 # py2exe needs to be installed to work
96 # py2exe needs to be installed to work
96 try:
97 try:
97 import py2exe
98 import py2exe
98 py2exeloaded = True
99 py2exeloaded = True
99
100
100 # Help py2exe to find win32com.shell
101 # Help py2exe to find win32com.shell
101 try:
102 try:
102 import modulefinder
103 import modulefinder
103 import win32com
104 import win32com
104 for p in win32com.__path__[1:]: # Take the path to win32comext
105 for p in win32com.__path__[1:]: # Take the path to win32comext
105 modulefinder.AddPackagePath("win32com", p)
106 modulefinder.AddPackagePath("win32com", p)
106 pn = "win32com.shell"
107 pn = "win32com.shell"
107 __import__(pn)
108 __import__(pn)
108 m = sys.modules[pn]
109 m = sys.modules[pn]
109 for p in m.__path__[1:]:
110 for p in m.__path__[1:]:
110 modulefinder.AddPackagePath(pn, p)
111 modulefinder.AddPackagePath(pn, p)
111 except ImportError:
112 except ImportError:
112 pass
113 pass
113
114
114 except ImportError:
115 except ImportError:
115 py2exeloaded = False
116 py2exeloaded = False
116 pass
117 pass
117
118
118 def runcmd(cmd, env):
119 def runcmd(cmd, env):
119 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
120 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
120 stderr=subprocess.PIPE, env=env)
121 stderr=subprocess.PIPE, env=env)
121 out, err = p.communicate()
122 out, err = p.communicate()
122 # If root is executing setup.py, but the repository is owned by
123 # If root is executing setup.py, but the repository is owned by
123 # another user (as in "sudo python setup.py install") we will get
124 # another user (as in "sudo python setup.py install") we will get
124 # trust warnings since the .hg/hgrc file is untrusted. That is
125 # trust warnings since the .hg/hgrc file is untrusted. That is
125 # fine, we don't want to load it anyway. Python may warn about
126 # fine, we don't want to load it anyway. Python may warn about
126 # a missing __init__.py in mercurial/locale, we also ignore that.
127 # a missing __init__.py in mercurial/locale, we also ignore that.
127 err = [e for e in err.splitlines()
128 err = [e for e in err.splitlines()
128 if not e.startswith(b('Not trusting file')) \
129 if not e.startswith(b('Not trusting file')) \
129 and not e.startswith(b('warning: Not importing'))]
130 and not e.startswith(b('warning: Not importing'))]
130 if err:
131 if err:
131 return ''
132 return ''
132 return out
133 return out
133
134
134 version = ''
135 version = ''
135
136
136 if os.path.isdir('.hg'):
137 if os.path.isdir('.hg'):
137 # Execute hg out of this directory with a custom environment which
138 # Execute hg out of this directory with a custom environment which
138 # includes the pure Python modules in mercurial/pure. We also take
139 # includes the pure Python modules in mercurial/pure. We also take
139 # care to not use any hgrc files and do no localization.
140 # care to not use any hgrc files and do no localization.
140 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
141 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
141 env = {'PYTHONPATH': os.pathsep.join(pypath),
142 env = {'PYTHONPATH': os.pathsep.join(pypath),
142 'HGRCPATH': '',
143 'HGRCPATH': '',
143 'LANGUAGE': 'C'}
144 'LANGUAGE': 'C'}
144 if 'LD_LIBRARY_PATH' in os.environ:
145 if 'LD_LIBRARY_PATH' in os.environ:
145 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
146 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
146 if 'SystemRoot' in os.environ:
147 if 'SystemRoot' in os.environ:
147 # Copy SystemRoot into the custom environment for Python 2.6
148 # Copy SystemRoot into the custom environment for Python 2.6
148 # under Windows. Otherwise, the subprocess will fail with
149 # under Windows. Otherwise, the subprocess will fail with
149 # error 0xc0150004. See: http://bugs.python.org/issue3440
150 # error 0xc0150004. See: http://bugs.python.org/issue3440
150 env['SystemRoot'] = os.environ['SystemRoot']
151 env['SystemRoot'] = os.environ['SystemRoot']
151 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
152 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
152 l = runcmd(cmd, env).split()
153 l = runcmd(cmd, env).split()
153 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
154 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
154 l.pop()
155 l.pop()
155 if len(l) > 1: # tag found
156 if len(l) > 1: # tag found
156 version = l[-1]
157 version = l[-1]
157 if l[0].endswith('+'): # propagate the dirty status to the tag
158 if l[0].endswith('+'): # propagate the dirty status to the tag
158 version += '+'
159 version += '+'
159 elif len(l) == 1: # no tag found
160 elif len(l) == 1: # no tag found
160 cmd = [sys.executable, 'hg', 'parents', '--template',
161 cmd = [sys.executable, 'hg', 'parents', '--template',
161 '{latesttag}+{latesttagdistance}-']
162 '{latesttag}+{latesttagdistance}-']
162 version = runcmd(cmd, env) + l[0]
163 version = runcmd(cmd, env) + l[0]
163 if version.endswith('+'):
164 if version.endswith('+'):
164 version += time.strftime('%Y%m%d')
165 version += time.strftime('%Y%m%d')
165 elif os.path.exists('.hg_archival.txt'):
166 elif os.path.exists('.hg_archival.txt'):
166 kw = dict([[t.strip() for t in l.split(':', 1)]
167 kw = dict([[t.strip() for t in l.split(':', 1)]
167 for l in open('.hg_archival.txt')])
168 for l in open('.hg_archival.txt')])
168 if 'tag' in kw:
169 if 'tag' in kw:
169 version = kw['tag']
170 version = kw['tag']
170 elif 'latesttag' in kw:
171 elif 'latesttag' in kw:
171 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
172 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
172 else:
173 else:
173 version = kw.get('node', '')[:12]
174 version = kw.get('node', '')[:12]
174
175
175 if version:
176 if version:
176 f = open("mercurial/__version__.py", "w")
177 f = open("mercurial/__version__.py", "w")
177 f.write('# this file is autogenerated by setup.py\n')
178 f.write('# this file is autogenerated by setup.py\n')
178 f.write('version = "%s"\n' % version)
179 f.write('version = "%s"\n' % version)
179 f.close()
180 f.close()
180
181
181
182
182 try:
183 try:
183 from mercurial import __version__
184 from mercurial import __version__
184 version = __version__.version
185 version = __version__.version
185 except ImportError:
186 except ImportError:
186 version = 'unknown'
187 version = 'unknown'
187
188
188 class hgbuildmo(build):
189 class hgbuildmo(build):
189
190
190 description = "build translations (.mo files)"
191 description = "build translations (.mo files)"
191
192
192 def run(self):
193 def run(self):
193 if not find_executable('msgfmt'):
194 if not find_executable('msgfmt'):
194 self.warn("could not find msgfmt executable, no translations "
195 self.warn("could not find msgfmt executable, no translations "
195 "will be built")
196 "will be built")
196 return
197 return
197
198
198 podir = 'i18n'
199 podir = 'i18n'
199 if not os.path.isdir(podir):
200 if not os.path.isdir(podir):
200 self.warn("could not find %s/ directory" % podir)
201 self.warn("could not find %s/ directory" % podir)
201 return
202 return
202
203
203 join = os.path.join
204 join = os.path.join
204 for po in os.listdir(podir):
205 for po in os.listdir(podir):
205 if not po.endswith('.po'):
206 if not po.endswith('.po'):
206 continue
207 continue
207 pofile = join(podir, po)
208 pofile = join(podir, po)
208 modir = join('locale', po[:-3], 'LC_MESSAGES')
209 modir = join('locale', po[:-3], 'LC_MESSAGES')
209 mofile = join(modir, 'hg.mo')
210 mofile = join(modir, 'hg.mo')
210 mobuildfile = join('mercurial', mofile)
211 mobuildfile = join('mercurial', mofile)
211 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
212 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
212 if sys.platform != 'sunos5':
213 if sys.platform != 'sunos5':
213 # msgfmt on Solaris does not know about -c
214 # msgfmt on Solaris does not know about -c
214 cmd.append('-c')
215 cmd.append('-c')
215 self.mkpath(join('mercurial', modir))
216 self.mkpath(join('mercurial', modir))
216 self.make_file([pofile], mobuildfile, spawn, (cmd,))
217 self.make_file([pofile], mobuildfile, spawn, (cmd,))
217
218
218 # Insert hgbuildmo first so that files in mercurial/locale/ are found
219 # Insert hgbuildmo first so that files in mercurial/locale/ are found
219 # when build_py is run next.
220 # when build_py is run next.
220 build.sub_commands.insert(0, ('build_mo', None))
221 build.sub_commands.insert(0, ('build_mo', None))
221
222
222 Distribution.pure = 0
223 Distribution.pure = 0
223 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
224 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
224 "code instead of C extensions"))
225 "code instead of C extensions"))
225
226
226 class hgbuildext(build_ext):
227 class hgbuildext(build_ext):
227
228
228 def build_extension(self, ext):
229 def build_extension(self, ext):
229 try:
230 try:
230 build_ext.build_extension(self, ext)
231 build_ext.build_extension(self, ext)
231 except CCompilerError:
232 except CCompilerError:
232 if not getattr(ext, 'optional', False):
233 if not getattr(ext, 'optional', False):
233 raise
234 raise
234 log.warn("Failed to build optional extension '%s' (skipping)",
235 log.warn("Failed to build optional extension '%s' (skipping)",
235 ext.name)
236 ext.name)
236
237
237 class hgbuildpy(build_py):
238 class hgbuildpy(build_py):
238
239
239 def finalize_options(self):
240 def finalize_options(self):
240 build_py.finalize_options(self)
241 build_py.finalize_options(self)
241
242
242 if self.distribution.pure:
243 if self.distribution.pure:
243 if self.py_modules is None:
244 if self.py_modules is None:
244 self.py_modules = []
245 self.py_modules = []
245 for ext in self.distribution.ext_modules:
246 for ext in self.distribution.ext_modules:
246 if ext.name.startswith("mercurial."):
247 if ext.name.startswith("mercurial."):
247 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
248 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
248 self.distribution.ext_modules = []
249 self.distribution.ext_modules = []
250 else:
251 if not os.path.exists(os.path.join(get_python_inc(), 'Python.h')):
252 raise SystemExit("Python headers are required to build Mercurial")
249
253
250 def find_modules(self):
254 def find_modules(self):
251 modules = build_py.find_modules(self)
255 modules = build_py.find_modules(self)
252 for module in modules:
256 for module in modules:
253 if module[0] == "mercurial.pure":
257 if module[0] == "mercurial.pure":
254 if module[1] != "__init__":
258 if module[1] != "__init__":
255 yield ("mercurial", module[1], module[2])
259 yield ("mercurial", module[1], module[2])
256 else:
260 else:
257 yield module
261 yield module
258
262
259 cmdclass = {'build_mo': hgbuildmo,
263 cmdclass = {'build_mo': hgbuildmo,
260 'build_ext': hgbuildext,
264 'build_ext': hgbuildext,
261 'build_py': hgbuildpy}
265 'build_py': hgbuildpy}
262
266
263 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
267 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
264 'hgext.highlight', 'hgext.zeroconf']
268 'hgext.highlight', 'hgext.zeroconf']
265
269
266 pymodules = []
270 pymodules = []
267
271
268 extmodules = [
272 extmodules = [
269 Extension('mercurial.base85', ['mercurial/base85.c']),
273 Extension('mercurial.base85', ['mercurial/base85.c']),
270 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
274 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
271 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
275 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
272 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
276 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
273 Extension('mercurial.parsers', ['mercurial/parsers.c']),
277 Extension('mercurial.parsers', ['mercurial/parsers.c']),
274 ]
278 ]
275
279
276 # disable osutil.c under windows + python 2.4 (issue1364)
280 # disable osutil.c under windows + python 2.4 (issue1364)
277 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
281 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
278 pymodules.append('mercurial.pure.osutil')
282 pymodules.append('mercurial.pure.osutil')
279 else:
283 else:
280 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
284 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
281
285
282 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
286 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
283 # The inotify extension is only usable with Linux 2.6 kernels.
287 # The inotify extension is only usable with Linux 2.6 kernels.
284 # You also need a reasonably recent C library.
288 # You also need a reasonably recent C library.
285 # In any case, if it fails to build the error will be skipped ('optional').
289 # In any case, if it fails to build the error will be skipped ('optional').
286 cc = new_compiler()
290 cc = new_compiler()
287 if hasfunction(cc, 'inotify_add_watch'):
291 if hasfunction(cc, 'inotify_add_watch'):
288 inotify = Extension('hgext.inotify.linux._inotify',
292 inotify = Extension('hgext.inotify.linux._inotify',
289 ['hgext/inotify/linux/_inotify.c'],
293 ['hgext/inotify/linux/_inotify.c'],
290 ['mercurial'])
294 ['mercurial'])
291 inotify.optional = True
295 inotify.optional = True
292 extmodules.append(inotify)
296 extmodules.append(inotify)
293 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
297 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
294
298
295 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
299 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
296 'help/*.txt']}
300 'help/*.txt']}
297
301
298 def ordinarypath(p):
302 def ordinarypath(p):
299 return p and p[0] != '.' and p[-1] != '~'
303 return p and p[0] != '.' and p[-1] != '~'
300
304
301 for root in ('templates',):
305 for root in ('templates',):
302 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
306 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
303 curdir = curdir.split(os.sep, 1)[1]
307 curdir = curdir.split(os.sep, 1)[1]
304 dirs[:] = filter(ordinarypath, dirs)
308 dirs[:] = filter(ordinarypath, dirs)
305 for f in filter(ordinarypath, files):
309 for f in filter(ordinarypath, files):
306 f = os.path.join(curdir, f)
310 f = os.path.join(curdir, f)
307 packagedata['mercurial'].append(f)
311 packagedata['mercurial'].append(f)
308
312
309 datafiles = []
313 datafiles = []
310 setupversion = version
314 setupversion = version
311 extra = {}
315 extra = {}
312
316
313 if py2exeloaded:
317 if py2exeloaded:
314 extra['console'] = [
318 extra['console'] = [
315 {'script':'hg',
319 {'script':'hg',
316 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
320 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
317 'product_version':version}]
321 'product_version':version}]
318
322
319 if os.name == 'nt':
323 if os.name == 'nt':
320 # Windows binary file versions for exe/dll files must have the
324 # Windows binary file versions for exe/dll files must have the
321 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
325 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
322 setupversion = version.split('+', 1)[0]
326 setupversion = version.split('+', 1)[0]
323
327
324 setup(name='mercurial',
328 setup(name='mercurial',
325 version=setupversion,
329 version=setupversion,
326 author='Matt Mackall',
330 author='Matt Mackall',
327 author_email='mpm@selenic.com',
331 author_email='mpm@selenic.com',
328 url='http://mercurial.selenic.com/',
332 url='http://mercurial.selenic.com/',
329 description='Scalable distributed SCM',
333 description='Scalable distributed SCM',
330 license='GNU GPLv2+',
334 license='GNU GPLv2+',
331 scripts=scripts,
335 scripts=scripts,
332 packages=packages,
336 packages=packages,
333 py_modules=pymodules,
337 py_modules=pymodules,
334 ext_modules=extmodules,
338 ext_modules=extmodules,
335 data_files=datafiles,
339 data_files=datafiles,
336 package_data=packagedata,
340 package_data=packagedata,
337 cmdclass=cmdclass,
341 cmdclass=cmdclass,
338 options=dict(py2exe=dict(packages=['hgext', 'email']),
342 options=dict(py2exe=dict(packages=['hgext', 'email']),
339 bdist_mpkg=dict(zipdist=True,
343 bdist_mpkg=dict(zipdist=True,
340 license='COPYING',
344 license='COPYING',
341 readme='contrib/macosx/Readme.html',
345 readme='contrib/macosx/Readme.html',
342 welcome='contrib/macosx/Welcome.html')),
346 welcome='contrib/macosx/Welcome.html')),
343 **extra)
347 **extra)
General Comments 0
You need to be logged in to leave comments. Login now