##// END OF EJS Templates
cleanup: make sure we always access members of imported modules...
Mads Kiilerich -
r22198:77142de4 default
parent child Browse files
Show More
@@ -1,95 +1,96 b''
1 # An example WSGI script for IIS/isapi-wsgi to export multiple hgweb repos
1 # An example WSGI script for IIS/isapi-wsgi to export multiple hgweb repos
2 # Copyright 2010 Sune Foldager <cryo@cyanite.org>
2 # Copyright 2010 Sune Foldager <cryo@cyanite.org>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6 #
6 #
7 # Requirements:
7 # Requirements:
8 # - Python 2.6
8 # - Python 2.6
9 # - PyWin32 build 214 or newer
9 # - PyWin32 build 214 or newer
10 # - Mercurial installed from source (python setup.py install)
10 # - Mercurial installed from source (python setup.py install)
11 # - IIS 7
11 # - IIS 7
12 #
12 #
13 # Earlier versions will in general work as well, but the PyWin32 version is
13 # Earlier versions will in general work as well, but the PyWin32 version is
14 # necessary for win32traceutil to work correctly.
14 # necessary for win32traceutil to work correctly.
15 #
15 #
16 #
16 #
17 # Installation and use:
17 # Installation and use:
18 #
18 #
19 # - Download the isapi-wsgi source and run python setup.py install:
19 # - Download the isapi-wsgi source and run python setup.py install:
20 # http://code.google.com/p/isapi-wsgi/
20 # http://code.google.com/p/isapi-wsgi/
21 #
21 #
22 # - Run this script (i.e. python hgwebdir_wsgi.py) to get a shim dll. The
22 # - Run this script (i.e. python hgwebdir_wsgi.py) to get a shim dll. The
23 # shim is identical for all scripts, so you can just copy and rename one
23 # shim is identical for all scripts, so you can just copy and rename one
24 # from an earlier run, if you wish.
24 # from an earlier run, if you wish.
25 #
25 #
26 # - Setup an IIS application where your hgwebdir is to be served from.
26 # - Setup an IIS application where your hgwebdir is to be served from.
27 # On 64-bit systems, make sure it's assigned a 32-bit app pool.
27 # On 64-bit systems, make sure it's assigned a 32-bit app pool.
28 #
28 #
29 # - In the application, setup a wildcard script handler mapping of type
29 # - In the application, setup a wildcard script handler mapping of type
30 # IsapiModule with the shim dll as its executable. This file MUST reside
30 # IsapiModule with the shim dll as its executable. This file MUST reside
31 # in the same directory as the shim. Remove all other handlers, if you wish.
31 # in the same directory as the shim. Remove all other handlers, if you wish.
32 #
32 #
33 # - Make sure the ISAPI and CGI restrictions (configured globally on the
33 # - Make sure the ISAPI and CGI restrictions (configured globally on the
34 # web server) includes the shim dll, to allow it to run.
34 # web server) includes the shim dll, to allow it to run.
35 #
35 #
36 # - Adjust the configuration variables below to match your needs.
36 # - Adjust the configuration variables below to match your needs.
37 #
37 #
38
38
39 # Configuration file location
39 # Configuration file location
40 hgweb_config = r'c:\src\iis\hg\hgweb.config'
40 hgweb_config = r'c:\src\iis\hg\hgweb.config'
41
41
42 # Global settings for IIS path translation
42 # Global settings for IIS path translation
43 path_strip = 0 # Strip this many path elements off (when using url rewrite)
43 path_strip = 0 # Strip this many path elements off (when using url rewrite)
44 path_prefix = 1 # This many path elements are prefixes (depends on the
44 path_prefix = 1 # This many path elements are prefixes (depends on the
45 # virtual path of the IIS application).
45 # virtual path of the IIS application).
46
46
47 import sys
47 import sys
48
48
49 # Adjust python path if this is not a system-wide install
49 # Adjust python path if this is not a system-wide install
50 #sys.path.insert(0, r'c:\path\to\python\lib')
50 #sys.path.insert(0, r'c:\path\to\python\lib')
51
51
52 # Enable tracing. Run 'python -m win32traceutil' to debug
52 # Enable tracing. Run 'python -m win32traceutil' to debug
53 if getattr(sys, 'isapidllhandle', None) is not None:
53 if getattr(sys, 'isapidllhandle', None) is not None:
54 import win32traceutil
54 import win32traceutil
55 win32traceutil.SetupForPrint # silence unused import warning
55
56
56 # To serve pages in local charset instead of UTF-8, remove the two lines below
57 # To serve pages in local charset instead of UTF-8, remove the two lines below
57 import os
58 import os
58 os.environ['HGENCODING'] = 'UTF-8'
59 os.environ['HGENCODING'] = 'UTF-8'
59
60
60
61
61 import isapi_wsgi
62 import isapi_wsgi
62 from mercurial import demandimport; demandimport.enable()
63 from mercurial import demandimport; demandimport.enable()
63 from mercurial.hgweb.hgwebdir_mod import hgwebdir
64 from mercurial.hgweb.hgwebdir_mod import hgwebdir
64
65
65 # Example tweak: Replace isapi_wsgi's handler to provide better error message
66 # Example tweak: Replace isapi_wsgi's handler to provide better error message
66 # Other stuff could also be done here, like logging errors etc.
67 # Other stuff could also be done here, like logging errors etc.
67 class WsgiHandler(isapi_wsgi.IsapiWsgiHandler):
68 class WsgiHandler(isapi_wsgi.IsapiWsgiHandler):
68 error_status = '500 Internal Server Error' # less silly error message
69 error_status = '500 Internal Server Error' # less silly error message
69
70
70 isapi_wsgi.IsapiWsgiHandler = WsgiHandler
71 isapi_wsgi.IsapiWsgiHandler = WsgiHandler
71
72
72 # Only create the hgwebdir instance once
73 # Only create the hgwebdir instance once
73 application = hgwebdir(hgweb_config)
74 application = hgwebdir(hgweb_config)
74
75
75 def handler(environ, start_response):
76 def handler(environ, start_response):
76
77
77 # Translate IIS's weird URLs
78 # Translate IIS's weird URLs
78 url = environ['SCRIPT_NAME'] + environ['PATH_INFO']
79 url = environ['SCRIPT_NAME'] + environ['PATH_INFO']
79 paths = url[1:].split('/')[path_strip:]
80 paths = url[1:].split('/')[path_strip:]
80 script_name = '/' + '/'.join(paths[:path_prefix])
81 script_name = '/' + '/'.join(paths[:path_prefix])
81 path_info = '/'.join(paths[path_prefix:])
82 path_info = '/'.join(paths[path_prefix:])
82 if path_info:
83 if path_info:
83 path_info = '/' + path_info
84 path_info = '/' + path_info
84 environ['SCRIPT_NAME'] = script_name
85 environ['SCRIPT_NAME'] = script_name
85 environ['PATH_INFO'] = path_info
86 environ['PATH_INFO'] = path_info
86
87
87 return application(environ, start_response)
88 return application(environ, start_response)
88
89
89 def __ExtensionFactory__():
90 def __ExtensionFactory__():
90 return isapi_wsgi.ISAPISimpleHandler(handler)
91 return isapi_wsgi.ISAPISimpleHandler(handler)
91
92
92 if __name__=='__main__':
93 if __name__=='__main__':
93 from isapi.install import *
94 from isapi.install import ISAPIParameters, HandleCommandLine
94 params = ISAPIParameters()
95 params = ISAPIParameters()
95 HandleCommandLine(params)
96 HandleCommandLine(params)
@@ -1,588 +1,592 b''
1 #
1 #
2 # This is the mercurial setup script.
2 # This is the mercurial setup script.
3 #
3 #
4 # 'python setup.py install', or
4 # 'python setup.py install', or
5 # 'python setup.py --help' for more options
5 # 'python setup.py --help' for more options
6
6
7 import sys, platform
7 import sys, platform
8 if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'):
8 if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'):
9 raise SystemExit("Mercurial requires Python 2.4 or later.")
9 raise SystemExit("Mercurial requires Python 2.4 or later.")
10
10
11 if sys.version_info[0] >= 3:
11 if sys.version_info[0] >= 3:
12 def b(s):
12 def b(s):
13 '''A helper function to emulate 2.6+ bytes literals using string
13 '''A helper function to emulate 2.6+ bytes literals using string
14 literals.'''
14 literals.'''
15 return s.encode('latin1')
15 return s.encode('latin1')
16 printf = eval('print')
16 printf = eval('print')
17 libdir_escape = 'unicode_escape'
17 libdir_escape = 'unicode_escape'
18 else:
18 else:
19 libdir_escape = 'string_escape'
19 libdir_escape = 'string_escape'
20 def b(s):
20 def b(s):
21 '''A helper function to emulate 2.6+ bytes literals using string
21 '''A helper function to emulate 2.6+ bytes literals using string
22 literals.'''
22 literals.'''
23 return s
23 return s
24 def printf(*args, **kwargs):
24 def printf(*args, **kwargs):
25 f = kwargs.get('file', sys.stdout)
25 f = kwargs.get('file', sys.stdout)
26 end = kwargs.get('end', '\n')
26 end = kwargs.get('end', '\n')
27 f.write(b(' ').join(args) + end)
27 f.write(b(' ').join(args) + end)
28
28
29 # Solaris Python packaging brain damage
29 # Solaris Python packaging brain damage
30 try:
30 try:
31 import hashlib
31 import hashlib
32 sha = hashlib.sha1()
32 sha = hashlib.sha1()
33 except ImportError:
33 except ImportError:
34 try:
34 try:
35 import sha
35 import sha
36 sha.sha # silence unused import warning
36 except ImportError:
37 except ImportError:
37 raise SystemExit(
38 raise SystemExit(
38 "Couldn't import standard hashlib (incomplete Python install).")
39 "Couldn't import standard hashlib (incomplete Python install).")
39
40
40 try:
41 try:
41 import zlib
42 import zlib
43 zlib.compressobj # silence unused import warning
42 except ImportError:
44 except ImportError:
43 raise SystemExit(
45 raise SystemExit(
44 "Couldn't import standard zlib (incomplete Python install).")
46 "Couldn't import standard zlib (incomplete Python install).")
45
47
46 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
48 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
47 isironpython = False
49 isironpython = False
48 try:
50 try:
49 isironpython = (platform.python_implementation()
51 isironpython = (platform.python_implementation()
50 .lower().find("ironpython") != -1)
52 .lower().find("ironpython") != -1)
51 except AttributeError:
53 except AttributeError:
52 pass
54 pass
53
55
54 if isironpython:
56 if isironpython:
55 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
57 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
56 else:
58 else:
57 try:
59 try:
58 import bz2
60 import bz2
61 bz2.BZ2Compressor # silence unused import warning
59 except ImportError:
62 except ImportError:
60 raise SystemExit(
63 raise SystemExit(
61 "Couldn't import standard bz2 (incomplete Python install).")
64 "Couldn't import standard bz2 (incomplete Python install).")
62
65
63 import os, subprocess, time
66 import os, subprocess, time
64 import re
67 import re
65 import shutil
68 import shutil
66 import tempfile
69 import tempfile
67 from distutils import log
70 from distutils import log
68 from distutils.core import setup, Command, Extension
71 from distutils.core import setup, Command, Extension
69 from distutils.dist import Distribution
72 from distutils.dist import Distribution
70 from distutils.command.build import build
73 from distutils.command.build import build
71 from distutils.command.build_ext import build_ext
74 from distutils.command.build_ext import build_ext
72 from distutils.command.build_py import build_py
75 from distutils.command.build_py import build_py
73 from distutils.command.install_scripts import install_scripts
76 from distutils.command.install_scripts import install_scripts
74 from distutils.spawn import spawn, find_executable
77 from distutils.spawn import spawn, find_executable
75 from distutils import cygwinccompiler
78 from distutils import cygwinccompiler
76 from distutils.errors import CCompilerError, DistutilsExecError
79 from distutils.errors import CCompilerError, DistutilsExecError
77 from distutils.sysconfig import get_python_inc, get_config_var
80 from distutils.sysconfig import get_python_inc, get_config_var
78 from distutils.version import StrictVersion
81 from distutils.version import StrictVersion
79
82
80 convert2to3 = '--c2to3' in sys.argv
83 convert2to3 = '--c2to3' in sys.argv
81 if convert2to3:
84 if convert2to3:
82 try:
85 try:
83 from distutils.command.build_py import build_py_2to3 as build_py
86 from distutils.command.build_py import build_py_2to3 as build_py
84 from lib2to3.refactor import get_fixers_from_package as getfixers
87 from lib2to3.refactor import get_fixers_from_package as getfixers
85 except ImportError:
88 except ImportError:
86 if sys.version_info[0] < 3:
89 if sys.version_info[0] < 3:
87 raise SystemExit("--c2to3 is only compatible with python3.")
90 raise SystemExit("--c2to3 is only compatible with python3.")
88 raise
91 raise
89 sys.path.append('contrib')
92 sys.path.append('contrib')
90 elif sys.version_info[0] >= 3:
93 elif sys.version_info[0] >= 3:
91 raise SystemExit("setup.py with python3 needs --c2to3 (experimental)")
94 raise SystemExit("setup.py with python3 needs --c2to3 (experimental)")
92
95
93 scripts = ['hg']
96 scripts = ['hg']
94 if os.name == 'nt':
97 if os.name == 'nt':
95 scripts.append('contrib/win32/hg.bat')
98 scripts.append('contrib/win32/hg.bat')
96
99
97 # simplified version of distutils.ccompiler.CCompiler.has_function
100 # simplified version of distutils.ccompiler.CCompiler.has_function
98 # that actually removes its temporary files.
101 # that actually removes its temporary files.
99 def hasfunction(cc, funcname):
102 def hasfunction(cc, funcname):
100 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
103 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
101 devnull = oldstderr = None
104 devnull = oldstderr = None
102 try:
105 try:
103 try:
106 try:
104 fname = os.path.join(tmpdir, 'funcname.c')
107 fname = os.path.join(tmpdir, 'funcname.c')
105 f = open(fname, 'w')
108 f = open(fname, 'w')
106 f.write('int main(void) {\n')
109 f.write('int main(void) {\n')
107 f.write(' %s();\n' % funcname)
110 f.write(' %s();\n' % funcname)
108 f.write('}\n')
111 f.write('}\n')
109 f.close()
112 f.close()
110 # Redirect stderr to /dev/null to hide any error messages
113 # Redirect stderr to /dev/null to hide any error messages
111 # from the compiler.
114 # from the compiler.
112 # This will have to be changed if we ever have to check
115 # This will have to be changed if we ever have to check
113 # for a function on Windows.
116 # for a function on Windows.
114 devnull = open('/dev/null', 'w')
117 devnull = open('/dev/null', 'w')
115 oldstderr = os.dup(sys.stderr.fileno())
118 oldstderr = os.dup(sys.stderr.fileno())
116 os.dup2(devnull.fileno(), sys.stderr.fileno())
119 os.dup2(devnull.fileno(), sys.stderr.fileno())
117 objects = cc.compile([fname], output_dir=tmpdir)
120 objects = cc.compile([fname], output_dir=tmpdir)
118 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
121 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
119 except Exception:
122 except Exception:
120 return False
123 return False
121 return True
124 return True
122 finally:
125 finally:
123 if oldstderr is not None:
126 if oldstderr is not None:
124 os.dup2(oldstderr, sys.stderr.fileno())
127 os.dup2(oldstderr, sys.stderr.fileno())
125 if devnull is not None:
128 if devnull is not None:
126 devnull.close()
129 devnull.close()
127 shutil.rmtree(tmpdir)
130 shutil.rmtree(tmpdir)
128
131
129 # py2exe needs to be installed to work
132 # py2exe needs to be installed to work
130 try:
133 try:
131 import py2exe
134 import py2exe
135 py2exe.Distribution # silence unused import warning
132 py2exeloaded = True
136 py2exeloaded = True
133 # import py2exe's patched Distribution class
137 # import py2exe's patched Distribution class
134 from distutils.core import Distribution
138 from distutils.core import Distribution
135 except ImportError:
139 except ImportError:
136 py2exeloaded = False
140 py2exeloaded = False
137
141
138 def runcmd(cmd, env):
142 def runcmd(cmd, env):
139 if sys.platform == 'plan9':
143 if sys.platform == 'plan9':
140 # subprocess kludge to work around issues in half-baked Python
144 # subprocess kludge to work around issues in half-baked Python
141 # ports, notably bichued/python:
145 # ports, notably bichued/python:
142 _, out, err = os.popen3(cmd)
146 _, out, err = os.popen3(cmd)
143 return str(out), str(err)
147 return str(out), str(err)
144 else:
148 else:
145 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
149 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
146 stderr=subprocess.PIPE, env=env)
150 stderr=subprocess.PIPE, env=env)
147 out, err = p.communicate()
151 out, err = p.communicate()
148 return out, err
152 return out, err
149
153
150 def runhg(cmd, env):
154 def runhg(cmd, env):
151 out, err = runcmd(cmd, env)
155 out, err = runcmd(cmd, env)
152 # If root is executing setup.py, but the repository is owned by
156 # If root is executing setup.py, but the repository is owned by
153 # another user (as in "sudo python setup.py install") we will get
157 # another user (as in "sudo python setup.py install") we will get
154 # trust warnings since the .hg/hgrc file is untrusted. That is
158 # trust warnings since the .hg/hgrc file is untrusted. That is
155 # fine, we don't want to load it anyway. Python may warn about
159 # fine, we don't want to load it anyway. Python may warn about
156 # a missing __init__.py in mercurial/locale, we also ignore that.
160 # a missing __init__.py in mercurial/locale, we also ignore that.
157 err = [e for e in err.splitlines()
161 err = [e for e in err.splitlines()
158 if not e.startswith(b('not trusting file')) \
162 if not e.startswith(b('not trusting file')) \
159 and not e.startswith(b('warning: Not importing')) \
163 and not e.startswith(b('warning: Not importing')) \
160 and not e.startswith(b('obsolete feature not enabled'))]
164 and not e.startswith(b('obsolete feature not enabled'))]
161 if err:
165 if err:
162 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
166 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
163 printf(b('\n').join([b(' ') + e for e in err]), file=sys.stderr)
167 printf(b('\n').join([b(' ') + e for e in err]), file=sys.stderr)
164 return ''
168 return ''
165 return out
169 return out
166
170
167 version = ''
171 version = ''
168
172
169 # Execute hg out of this directory with a custom environment which
173 # Execute hg out of this directory with a custom environment which
170 # includes the pure Python modules in mercurial/pure. We also take
174 # includes the pure Python modules in mercurial/pure. We also take
171 # care to not use any hgrc files and do no localization.
175 # care to not use any hgrc files and do no localization.
172 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
176 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
173 env = {'PYTHONPATH': os.pathsep.join(pypath),
177 env = {'PYTHONPATH': os.pathsep.join(pypath),
174 'HGRCPATH': '',
178 'HGRCPATH': '',
175 'LANGUAGE': 'C'}
179 'LANGUAGE': 'C'}
176 if 'LD_LIBRARY_PATH' in os.environ:
180 if 'LD_LIBRARY_PATH' in os.environ:
177 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
181 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
178 if 'SystemRoot' in os.environ:
182 if 'SystemRoot' in os.environ:
179 # Copy SystemRoot into the custom environment for Python 2.6
183 # Copy SystemRoot into the custom environment for Python 2.6
180 # under Windows. Otherwise, the subprocess will fail with
184 # under Windows. Otherwise, the subprocess will fail with
181 # error 0xc0150004. See: http://bugs.python.org/issue3440
185 # error 0xc0150004. See: http://bugs.python.org/issue3440
182 env['SystemRoot'] = os.environ['SystemRoot']
186 env['SystemRoot'] = os.environ['SystemRoot']
183
187
184 if os.path.isdir('.hg'):
188 if os.path.isdir('.hg'):
185 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
189 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
186 numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
190 numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
187 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
191 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
188 if numerictags: # tag(s) found
192 if numerictags: # tag(s) found
189 version = numerictags[-1]
193 version = numerictags[-1]
190 if hgid.endswith('+'): # propagate the dirty status to the tag
194 if hgid.endswith('+'): # propagate the dirty status to the tag
191 version += '+'
195 version += '+'
192 else: # no tag found
196 else: # no tag found
193 cmd = [sys.executable, 'hg', 'parents', '--template',
197 cmd = [sys.executable, 'hg', 'parents', '--template',
194 '{latesttag}+{latesttagdistance}-']
198 '{latesttag}+{latesttagdistance}-']
195 version = runhg(cmd, env) + hgid
199 version = runhg(cmd, env) + hgid
196 if version.endswith('+'):
200 if version.endswith('+'):
197 version += time.strftime('%Y%m%d')
201 version += time.strftime('%Y%m%d')
198 elif os.path.exists('.hg_archival.txt'):
202 elif os.path.exists('.hg_archival.txt'):
199 kw = dict([[t.strip() for t in l.split(':', 1)]
203 kw = dict([[t.strip() for t in l.split(':', 1)]
200 for l in open('.hg_archival.txt')])
204 for l in open('.hg_archival.txt')])
201 if 'tag' in kw:
205 if 'tag' in kw:
202 version = kw['tag']
206 version = kw['tag']
203 elif 'latesttag' in kw:
207 elif 'latesttag' in kw:
204 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
208 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
205 else:
209 else:
206 version = kw.get('node', '')[:12]
210 version = kw.get('node', '')[:12]
207
211
208 if version:
212 if version:
209 f = open("mercurial/__version__.py", "w")
213 f = open("mercurial/__version__.py", "w")
210 f.write('# this file is autogenerated by setup.py\n')
214 f.write('# this file is autogenerated by setup.py\n')
211 f.write('version = "%s"\n' % version)
215 f.write('version = "%s"\n' % version)
212 f.close()
216 f.close()
213
217
214
218
215 try:
219 try:
216 from mercurial import __version__
220 from mercurial import __version__
217 version = __version__.version
221 version = __version__.version
218 except ImportError:
222 except ImportError:
219 version = 'unknown'
223 version = 'unknown'
220
224
221 class hgbuild(build):
225 class hgbuild(build):
222 # Insert hgbuildmo first so that files in mercurial/locale/ are found
226 # Insert hgbuildmo first so that files in mercurial/locale/ are found
223 # when build_py is run next.
227 # when build_py is run next.
224 sub_commands = [('build_mo', None),
228 sub_commands = [('build_mo', None),
225
229
226 # We also need build_ext before build_py. Otherwise, when 2to3 is
230 # We also need build_ext before build_py. Otherwise, when 2to3 is
227 # called (in build_py), it will not find osutil & friends,
231 # called (in build_py), it will not find osutil & friends,
228 # thinking that those modules are global and, consequently, making
232 # thinking that those modules are global and, consequently, making
229 # a mess, now that all module imports are global.
233 # a mess, now that all module imports are global.
230
234
231 ('build_ext', build.has_ext_modules),
235 ('build_ext', build.has_ext_modules),
232 ] + build.sub_commands
236 ] + build.sub_commands
233
237
234 class hgbuildmo(build):
238 class hgbuildmo(build):
235
239
236 description = "build translations (.mo files)"
240 description = "build translations (.mo files)"
237
241
238 def run(self):
242 def run(self):
239 if not find_executable('msgfmt'):
243 if not find_executable('msgfmt'):
240 self.warn("could not find msgfmt executable, no translations "
244 self.warn("could not find msgfmt executable, no translations "
241 "will be built")
245 "will be built")
242 return
246 return
243
247
244 podir = 'i18n'
248 podir = 'i18n'
245 if not os.path.isdir(podir):
249 if not os.path.isdir(podir):
246 self.warn("could not find %s/ directory" % podir)
250 self.warn("could not find %s/ directory" % podir)
247 return
251 return
248
252
249 join = os.path.join
253 join = os.path.join
250 for po in os.listdir(podir):
254 for po in os.listdir(podir):
251 if not po.endswith('.po'):
255 if not po.endswith('.po'):
252 continue
256 continue
253 pofile = join(podir, po)
257 pofile = join(podir, po)
254 modir = join('locale', po[:-3], 'LC_MESSAGES')
258 modir = join('locale', po[:-3], 'LC_MESSAGES')
255 mofile = join(modir, 'hg.mo')
259 mofile = join(modir, 'hg.mo')
256 mobuildfile = join('mercurial', mofile)
260 mobuildfile = join('mercurial', mofile)
257 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
261 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
258 if sys.platform != 'sunos5':
262 if sys.platform != 'sunos5':
259 # msgfmt on Solaris does not know about -c
263 # msgfmt on Solaris does not know about -c
260 cmd.append('-c')
264 cmd.append('-c')
261 self.mkpath(join('mercurial', modir))
265 self.mkpath(join('mercurial', modir))
262 self.make_file([pofile], mobuildfile, spawn, (cmd,))
266 self.make_file([pofile], mobuildfile, spawn, (cmd,))
263
267
264
268
265 class hgdist(Distribution):
269 class hgdist(Distribution):
266 pure = 0
270 pure = 0
267
271
268 global_options = Distribution.global_options + \
272 global_options = Distribution.global_options + \
269 [('pure', None, "use pure (slow) Python "
273 [('pure', None, "use pure (slow) Python "
270 "code instead of C extensions"),
274 "code instead of C extensions"),
271 ('c2to3', None, "(experimental!) convert "
275 ('c2to3', None, "(experimental!) convert "
272 "code with 2to3"),
276 "code with 2to3"),
273 ]
277 ]
274
278
275 def has_ext_modules(self):
279 def has_ext_modules(self):
276 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
280 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
277 # too late for some cases
281 # too late for some cases
278 return not self.pure and Distribution.has_ext_modules(self)
282 return not self.pure and Distribution.has_ext_modules(self)
279
283
280 class hgbuildext(build_ext):
284 class hgbuildext(build_ext):
281
285
282 def build_extension(self, ext):
286 def build_extension(self, ext):
283 try:
287 try:
284 build_ext.build_extension(self, ext)
288 build_ext.build_extension(self, ext)
285 except CCompilerError:
289 except CCompilerError:
286 if not getattr(ext, 'optional', False):
290 if not getattr(ext, 'optional', False):
287 raise
291 raise
288 log.warn("Failed to build optional extension '%s' (skipping)",
292 log.warn("Failed to build optional extension '%s' (skipping)",
289 ext.name)
293 ext.name)
290
294
291 class hgbuildpy(build_py):
295 class hgbuildpy(build_py):
292 if convert2to3:
296 if convert2to3:
293 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
297 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
294 getfixers("hgfixes")))
298 getfixers("hgfixes")))
295
299
296 def finalize_options(self):
300 def finalize_options(self):
297 build_py.finalize_options(self)
301 build_py.finalize_options(self)
298
302
299 if self.distribution.pure:
303 if self.distribution.pure:
300 if self.py_modules is None:
304 if self.py_modules is None:
301 self.py_modules = []
305 self.py_modules = []
302 for ext in self.distribution.ext_modules:
306 for ext in self.distribution.ext_modules:
303 if ext.name.startswith("mercurial."):
307 if ext.name.startswith("mercurial."):
304 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
308 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
305 self.distribution.ext_modules = []
309 self.distribution.ext_modules = []
306 else:
310 else:
307 h = os.path.join(get_python_inc(), 'Python.h')
311 h = os.path.join(get_python_inc(), 'Python.h')
308 if not os.path.exists(h):
312 if not os.path.exists(h):
309 raise SystemExit('Python headers are required to build '
313 raise SystemExit('Python headers are required to build '
310 'Mercurial but weren\'t found in %s' % h)
314 'Mercurial but weren\'t found in %s' % h)
311
315
312 def find_modules(self):
316 def find_modules(self):
313 modules = build_py.find_modules(self)
317 modules = build_py.find_modules(self)
314 for module in modules:
318 for module in modules:
315 if module[0] == "mercurial.pure":
319 if module[0] == "mercurial.pure":
316 if module[1] != "__init__":
320 if module[1] != "__init__":
317 yield ("mercurial", module[1], module[2])
321 yield ("mercurial", module[1], module[2])
318 else:
322 else:
319 yield module
323 yield module
320
324
321 class buildhgextindex(Command):
325 class buildhgextindex(Command):
322 description = 'generate prebuilt index of hgext (for frozen package)'
326 description = 'generate prebuilt index of hgext (for frozen package)'
323 user_options = []
327 user_options = []
324 _indexfilename = 'hgext/__index__.py'
328 _indexfilename = 'hgext/__index__.py'
325
329
326 def initialize_options(self):
330 def initialize_options(self):
327 pass
331 pass
328
332
329 def finalize_options(self):
333 def finalize_options(self):
330 pass
334 pass
331
335
332 def run(self):
336 def run(self):
333 if os.path.exists(self._indexfilename):
337 if os.path.exists(self._indexfilename):
334 f = open(self._indexfilename, 'w')
338 f = open(self._indexfilename, 'w')
335 f.write('# empty\n')
339 f.write('# empty\n')
336 f.close()
340 f.close()
337
341
338 # here no extension enabled, disabled() lists up everything
342 # here no extension enabled, disabled() lists up everything
339 code = ('import pprint; from mercurial import extensions; '
343 code = ('import pprint; from mercurial import extensions; '
340 'pprint.pprint(extensions.disabled())')
344 'pprint.pprint(extensions.disabled())')
341 out, err = runcmd([sys.executable, '-c', code], env)
345 out, err = runcmd([sys.executable, '-c', code], env)
342 if err:
346 if err:
343 raise DistutilsExecError(err)
347 raise DistutilsExecError(err)
344
348
345 f = open(self._indexfilename, 'w')
349 f = open(self._indexfilename, 'w')
346 f.write('# this file is autogenerated by setup.py\n')
350 f.write('# this file is autogenerated by setup.py\n')
347 f.write('docs = ')
351 f.write('docs = ')
348 f.write(out)
352 f.write(out)
349 f.close()
353 f.close()
350
354
351 class buildhgexe(build_ext):
355 class buildhgexe(build_ext):
352 description = 'compile hg.exe from mercurial/exewrapper.c'
356 description = 'compile hg.exe from mercurial/exewrapper.c'
353
357
354 def build_extensions(self):
358 def build_extensions(self):
355 if os.name != 'nt':
359 if os.name != 'nt':
356 return
360 return
357 if isinstance(self.compiler, HackedMingw32CCompiler):
361 if isinstance(self.compiler, HackedMingw32CCompiler):
358 self.compiler.compiler_so = self.compiler.compiler # no -mdll
362 self.compiler.compiler_so = self.compiler.compiler # no -mdll
359 self.compiler.dll_libraries = [] # no -lmsrvc90
363 self.compiler.dll_libraries = [] # no -lmsrvc90
360 hv = sys.hexversion
364 hv = sys.hexversion
361 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
365 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
362 f = open('mercurial/hgpythonlib.h', 'wb')
366 f = open('mercurial/hgpythonlib.h', 'wb')
363 f.write('/* this file is autogenerated by setup.py */\n')
367 f.write('/* this file is autogenerated by setup.py */\n')
364 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
368 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
365 f.close()
369 f.close()
366 objects = self.compiler.compile(['mercurial/exewrapper.c'],
370 objects = self.compiler.compile(['mercurial/exewrapper.c'],
367 output_dir=self.build_temp)
371 output_dir=self.build_temp)
368 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
372 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
369 target = os.path.join(dir, 'hg')
373 target = os.path.join(dir, 'hg')
370 self.compiler.link_executable(objects, target,
374 self.compiler.link_executable(objects, target,
371 libraries=[],
375 libraries=[],
372 output_dir=self.build_temp)
376 output_dir=self.build_temp)
373
377
374 class hginstallscripts(install_scripts):
378 class hginstallscripts(install_scripts):
375 '''
379 '''
376 This is a specialization of install_scripts that replaces the @LIBDIR@ with
380 This is a specialization of install_scripts that replaces the @LIBDIR@ with
377 the configured directory for modules. If possible, the path is made relative
381 the configured directory for modules. If possible, the path is made relative
378 to the directory for scripts.
382 to the directory for scripts.
379 '''
383 '''
380
384
381 def initialize_options(self):
385 def initialize_options(self):
382 install_scripts.initialize_options(self)
386 install_scripts.initialize_options(self)
383
387
384 self.install_lib = None
388 self.install_lib = None
385
389
386 def finalize_options(self):
390 def finalize_options(self):
387 install_scripts.finalize_options(self)
391 install_scripts.finalize_options(self)
388 self.set_undefined_options('install',
392 self.set_undefined_options('install',
389 ('install_lib', 'install_lib'))
393 ('install_lib', 'install_lib'))
390
394
391 def run(self):
395 def run(self):
392 install_scripts.run(self)
396 install_scripts.run(self)
393
397
394 if (os.path.splitdrive(self.install_dir)[0] !=
398 if (os.path.splitdrive(self.install_dir)[0] !=
395 os.path.splitdrive(self.install_lib)[0]):
399 os.path.splitdrive(self.install_lib)[0]):
396 # can't make relative paths from one drive to another, so use an
400 # can't make relative paths from one drive to another, so use an
397 # absolute path instead
401 # absolute path instead
398 libdir = self.install_lib
402 libdir = self.install_lib
399 else:
403 else:
400 common = os.path.commonprefix((self.install_dir, self.install_lib))
404 common = os.path.commonprefix((self.install_dir, self.install_lib))
401 rest = self.install_dir[len(common):]
405 rest = self.install_dir[len(common):]
402 uplevel = len([n for n in os.path.split(rest) if n])
406 uplevel = len([n for n in os.path.split(rest) if n])
403
407
404 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
408 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
405
409
406 for outfile in self.outfiles:
410 for outfile in self.outfiles:
407 fp = open(outfile, 'rb')
411 fp = open(outfile, 'rb')
408 data = fp.read()
412 data = fp.read()
409 fp.close()
413 fp.close()
410
414
411 # skip binary files
415 # skip binary files
412 if b('\0') in data:
416 if b('\0') in data:
413 continue
417 continue
414
418
415 data = data.replace(b('@LIBDIR@'), libdir.encode(libdir_escape))
419 data = data.replace(b('@LIBDIR@'), libdir.encode(libdir_escape))
416 fp = open(outfile, 'wb')
420 fp = open(outfile, 'wb')
417 fp.write(data)
421 fp.write(data)
418 fp.close()
422 fp.close()
419
423
420 cmdclass = {'build': hgbuild,
424 cmdclass = {'build': hgbuild,
421 'build_mo': hgbuildmo,
425 'build_mo': hgbuildmo,
422 'build_ext': hgbuildext,
426 'build_ext': hgbuildext,
423 'build_py': hgbuildpy,
427 'build_py': hgbuildpy,
424 'build_hgextindex': buildhgextindex,
428 'build_hgextindex': buildhgextindex,
425 'install_scripts': hginstallscripts,
429 'install_scripts': hginstallscripts,
426 'build_hgexe': buildhgexe,
430 'build_hgexe': buildhgexe,
427 }
431 }
428
432
429 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
433 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
430 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
434 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
431 'hgext.largefiles']
435 'hgext.largefiles']
432
436
433 pymodules = []
437 pymodules = []
434
438
435 common_depends = ['mercurial/util.h']
439 common_depends = ['mercurial/util.h']
436
440
437 extmodules = [
441 extmodules = [
438 Extension('mercurial.base85', ['mercurial/base85.c'],
442 Extension('mercurial.base85', ['mercurial/base85.c'],
439 depends=common_depends),
443 depends=common_depends),
440 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
444 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
441 depends=common_depends),
445 depends=common_depends),
442 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
446 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
443 depends=common_depends),
447 depends=common_depends),
444 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
448 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
445 depends=common_depends),
449 depends=common_depends),
446 Extension('mercurial.parsers', ['mercurial/dirs.c',
450 Extension('mercurial.parsers', ['mercurial/dirs.c',
447 'mercurial/parsers.c',
451 'mercurial/parsers.c',
448 'mercurial/pathencode.c'],
452 'mercurial/pathencode.c'],
449 depends=common_depends),
453 depends=common_depends),
450 ]
454 ]
451
455
452 osutil_ldflags = []
456 osutil_ldflags = []
453
457
454 if sys.platform == 'darwin':
458 if sys.platform == 'darwin':
455 osutil_ldflags += ['-framework', 'ApplicationServices']
459 osutil_ldflags += ['-framework', 'ApplicationServices']
456
460
457 # disable osutil.c under windows + python 2.4 (issue1364)
461 # disable osutil.c under windows + python 2.4 (issue1364)
458 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
462 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
459 pymodules.append('mercurial.pure.osutil')
463 pymodules.append('mercurial.pure.osutil')
460 else:
464 else:
461 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
465 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
462 extra_link_args=osutil_ldflags,
466 extra_link_args=osutil_ldflags,
463 depends=common_depends))
467 depends=common_depends))
464
468
465 # the -mno-cygwin option has been deprecated for years
469 # the -mno-cygwin option has been deprecated for years
466 Mingw32CCompiler = cygwinccompiler.Mingw32CCompiler
470 Mingw32CCompiler = cygwinccompiler.Mingw32CCompiler
467
471
468 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
472 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
469 def __init__(self, *args, **kwargs):
473 def __init__(self, *args, **kwargs):
470 Mingw32CCompiler.__init__(self, *args, **kwargs)
474 Mingw32CCompiler.__init__(self, *args, **kwargs)
471 for i in 'compiler compiler_so linker_exe linker_so'.split():
475 for i in 'compiler compiler_so linker_exe linker_so'.split():
472 try:
476 try:
473 getattr(self, i).remove('-mno-cygwin')
477 getattr(self, i).remove('-mno-cygwin')
474 except ValueError:
478 except ValueError:
475 pass
479 pass
476
480
477 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
481 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
478
482
479 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
483 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
480 'help/*.txt']}
484 'help/*.txt']}
481
485
482 def ordinarypath(p):
486 def ordinarypath(p):
483 return p and p[0] != '.' and p[-1] != '~'
487 return p and p[0] != '.' and p[-1] != '~'
484
488
485 for root in ('templates',):
489 for root in ('templates',):
486 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
490 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
487 curdir = curdir.split(os.sep, 1)[1]
491 curdir = curdir.split(os.sep, 1)[1]
488 dirs[:] = filter(ordinarypath, dirs)
492 dirs[:] = filter(ordinarypath, dirs)
489 for f in filter(ordinarypath, files):
493 for f in filter(ordinarypath, files):
490 f = os.path.join(curdir, f)
494 f = os.path.join(curdir, f)
491 packagedata['mercurial'].append(f)
495 packagedata['mercurial'].append(f)
492
496
493 datafiles = []
497 datafiles = []
494 setupversion = version
498 setupversion = version
495 extra = {}
499 extra = {}
496
500
497 if py2exeloaded:
501 if py2exeloaded:
498 extra['console'] = [
502 extra['console'] = [
499 {'script':'hg',
503 {'script':'hg',
500 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
504 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
501 'product_version':version}]
505 'product_version':version}]
502 # sub command of 'build' because 'py2exe' does not handle sub_commands
506 # sub command of 'build' because 'py2exe' does not handle sub_commands
503 build.sub_commands.insert(0, ('build_hgextindex', None))
507 build.sub_commands.insert(0, ('build_hgextindex', None))
504
508
505 if os.name == 'nt':
509 if os.name == 'nt':
506 # Windows binary file versions for exe/dll files must have the
510 # Windows binary file versions for exe/dll files must have the
507 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
511 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
508 setupversion = version.split('+', 1)[0]
512 setupversion = version.split('+', 1)[0]
509
513
510 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
514 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
511 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
515 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
512 if version:
516 if version:
513 version = version[0]
517 version = version[0]
514 xcode4 = (version.startswith('Xcode') and
518 xcode4 = (version.startswith('Xcode') and
515 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
519 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
516 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
520 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
517 else:
521 else:
518 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
522 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
519 # installed, but instead with only command-line tools. Assume
523 # installed, but instead with only command-line tools. Assume
520 # that only happens on >= Lion, thus no PPC support.
524 # that only happens on >= Lion, thus no PPC support.
521 xcode4 = True
525 xcode4 = True
522 xcode51 = False
526 xcode51 = False
523
527
524 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
528 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
525 # distutils.sysconfig
529 # distutils.sysconfig
526 if xcode4:
530 if xcode4:
527 os.environ['ARCHFLAGS'] = ''
531 os.environ['ARCHFLAGS'] = ''
528
532
529 # XCode 5.1 changes clang such that it now fails to compile if the
533 # XCode 5.1 changes clang such that it now fails to compile if the
530 # -mno-fused-madd flag is passed, but the version of Python shipped with
534 # -mno-fused-madd flag is passed, but the version of Python shipped with
531 # OS X 10.9 Mavericks includes this flag. This causes problems in all
535 # OS X 10.9 Mavericks includes this flag. This causes problems in all
532 # C extension modules, and a bug has been filed upstream at
536 # C extension modules, and a bug has been filed upstream at
533 # http://bugs.python.org/issue21244. We also need to patch this here
537 # http://bugs.python.org/issue21244. We also need to patch this here
534 # so Mercurial can continue to compile in the meantime.
538 # so Mercurial can continue to compile in the meantime.
535 if xcode51:
539 if xcode51:
536 cflags = get_config_var('CFLAGS')
540 cflags = get_config_var('CFLAGS')
537 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
541 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
538 os.environ['CFLAGS'] = (
542 os.environ['CFLAGS'] = (
539 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
543 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
540
544
541 setup(name='mercurial',
545 setup(name='mercurial',
542 version=setupversion,
546 version=setupversion,
543 author='Matt Mackall and many others',
547 author='Matt Mackall and many others',
544 author_email='mercurial@selenic.com',
548 author_email='mercurial@selenic.com',
545 url='http://mercurial.selenic.com/',
549 url='http://mercurial.selenic.com/',
546 download_url='http://mercurial.selenic.com/release/',
550 download_url='http://mercurial.selenic.com/release/',
547 description=('Fast scalable distributed SCM (revision control, version '
551 description=('Fast scalable distributed SCM (revision control, version '
548 'control) system'),
552 'control) system'),
549 long_description=('Mercurial is a distributed SCM tool written in Python.'
553 long_description=('Mercurial is a distributed SCM tool written in Python.'
550 ' It is used by a number of large projects that require'
554 ' It is used by a number of large projects that require'
551 ' fast, reliable distributed revision control, such as '
555 ' fast, reliable distributed revision control, such as '
552 'Mozilla.'),
556 'Mozilla.'),
553 license='GNU GPLv2 or any later version',
557 license='GNU GPLv2 or any later version',
554 classifiers=[
558 classifiers=[
555 'Development Status :: 6 - Mature',
559 'Development Status :: 6 - Mature',
556 'Environment :: Console',
560 'Environment :: Console',
557 'Intended Audience :: Developers',
561 'Intended Audience :: Developers',
558 'Intended Audience :: System Administrators',
562 'Intended Audience :: System Administrators',
559 'License :: OSI Approved :: GNU General Public License (GPL)',
563 'License :: OSI Approved :: GNU General Public License (GPL)',
560 'Natural Language :: Danish',
564 'Natural Language :: Danish',
561 'Natural Language :: English',
565 'Natural Language :: English',
562 'Natural Language :: German',
566 'Natural Language :: German',
563 'Natural Language :: Italian',
567 'Natural Language :: Italian',
564 'Natural Language :: Japanese',
568 'Natural Language :: Japanese',
565 'Natural Language :: Portuguese (Brazilian)',
569 'Natural Language :: Portuguese (Brazilian)',
566 'Operating System :: Microsoft :: Windows',
570 'Operating System :: Microsoft :: Windows',
567 'Operating System :: OS Independent',
571 'Operating System :: OS Independent',
568 'Operating System :: POSIX',
572 'Operating System :: POSIX',
569 'Programming Language :: C',
573 'Programming Language :: C',
570 'Programming Language :: Python',
574 'Programming Language :: Python',
571 'Topic :: Software Development :: Version Control',
575 'Topic :: Software Development :: Version Control',
572 ],
576 ],
573 scripts=scripts,
577 scripts=scripts,
574 packages=packages,
578 packages=packages,
575 py_modules=pymodules,
579 py_modules=pymodules,
576 ext_modules=extmodules,
580 ext_modules=extmodules,
577 data_files=datafiles,
581 data_files=datafiles,
578 package_data=packagedata,
582 package_data=packagedata,
579 cmdclass=cmdclass,
583 cmdclass=cmdclass,
580 distclass=hgdist,
584 distclass=hgdist,
581 options={'py2exe': {'packages': ['hgext', 'email']},
585 options={'py2exe': {'packages': ['hgext', 'email']},
582 'bdist_mpkg': {'zipdist': True,
586 'bdist_mpkg': {'zipdist': True,
583 'license': 'COPYING',
587 'license': 'COPYING',
584 'readme': 'contrib/macosx/Readme.html',
588 'readme': 'contrib/macosx/Readme.html',
585 'welcome': 'contrib/macosx/Welcome.html',
589 'welcome': 'contrib/macosx/Welcome.html',
586 },
590 },
587 },
591 },
588 **extra)
592 **extra)
@@ -1,339 +1,343 b''
1 import os, stat
1 import os, stat
2 import re
2 import re
3 import sys
3 import sys
4 import tempfile
4 import tempfile
5
5
6 tempprefix = 'hg-hghave-'
6 tempprefix = 'hg-hghave-'
7
7
8 checks = {
8 checks = {
9 "true": (lambda: True, "yak shaving"),
9 "true": (lambda: True, "yak shaving"),
10 "false": (lambda: False, "nail clipper"),
10 "false": (lambda: False, "nail clipper"),
11 }
11 }
12
12
13 def check(name, desc):
13 def check(name, desc):
14 def decorator(func):
14 def decorator(func):
15 checks[name] = (func, desc)
15 checks[name] = (func, desc)
16 return func
16 return func
17 return decorator
17 return decorator
18
18
19 def matchoutput(cmd, regexp, ignorestatus=False):
19 def matchoutput(cmd, regexp, ignorestatus=False):
20 """Return True if cmd executes successfully and its output
20 """Return True if cmd executes successfully and its output
21 is matched by the supplied regular expression.
21 is matched by the supplied regular expression.
22 """
22 """
23 r = re.compile(regexp)
23 r = re.compile(regexp)
24 fh = os.popen(cmd)
24 fh = os.popen(cmd)
25 s = fh.read()
25 s = fh.read()
26 try:
26 try:
27 ret = fh.close()
27 ret = fh.close()
28 except IOError:
28 except IOError:
29 # Happen in Windows test environment
29 # Happen in Windows test environment
30 ret = 1
30 ret = 1
31 return (ignorestatus or ret is None) and r.search(s)
31 return (ignorestatus or ret is None) and r.search(s)
32
32
33 @check("baz", "GNU Arch baz client")
33 @check("baz", "GNU Arch baz client")
34 def has_baz():
34 def has_baz():
35 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
35 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
36
36
37 @check("bzr", "Canonical's Bazaar client")
37 @check("bzr", "Canonical's Bazaar client")
38 def has_bzr():
38 def has_bzr():
39 try:
39 try:
40 import bzrlib
40 import bzrlib
41 return bzrlib.__doc__ is not None
41 return bzrlib.__doc__ is not None
42 except ImportError:
42 except ImportError:
43 return False
43 return False
44
44
45 @check("bzr114", "Canonical's Bazaar client >= 1.14")
45 @check("bzr114", "Canonical's Bazaar client >= 1.14")
46 def has_bzr114():
46 def has_bzr114():
47 try:
47 try:
48 import bzrlib
48 import bzrlib
49 return (bzrlib.__doc__ is not None
49 return (bzrlib.__doc__ is not None
50 and bzrlib.version_info[:2] >= (1, 14))
50 and bzrlib.version_info[:2] >= (1, 14))
51 except ImportError:
51 except ImportError:
52 return False
52 return False
53
53
54 @check("cvs", "cvs client/server")
54 @check("cvs", "cvs client/server")
55 def has_cvs():
55 def has_cvs():
56 re = r'Concurrent Versions System.*?server'
56 re = r'Concurrent Versions System.*?server'
57 return matchoutput('cvs --version 2>&1', re) and not has_msys()
57 return matchoutput('cvs --version 2>&1', re) and not has_msys()
58
58
59 @check("cvs112", "cvs client/server >= 1.12")
59 @check("cvs112", "cvs client/server >= 1.12")
60 def has_cvs112():
60 def has_cvs112():
61 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
61 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
62 return matchoutput('cvs --version 2>&1', re) and not has_msys()
62 return matchoutput('cvs --version 2>&1', re) and not has_msys()
63
63
64 @check("darcs", "darcs client")
64 @check("darcs", "darcs client")
65 def has_darcs():
65 def has_darcs():
66 return matchoutput('darcs --version', r'2\.[2-9]', True)
66 return matchoutput('darcs --version', r'2\.[2-9]', True)
67
67
68 @check("mtn", "monotone client (>= 1.0)")
68 @check("mtn", "monotone client (>= 1.0)")
69 def has_mtn():
69 def has_mtn():
70 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
70 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
71 'mtn --version', r'monotone 0\.', True)
71 'mtn --version', r'monotone 0\.', True)
72
72
73 @check("eol-in-paths", "end-of-lines in paths")
73 @check("eol-in-paths", "end-of-lines in paths")
74 def has_eol_in_paths():
74 def has_eol_in_paths():
75 try:
75 try:
76 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
76 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
77 os.close(fd)
77 os.close(fd)
78 os.remove(path)
78 os.remove(path)
79 return True
79 return True
80 except (IOError, OSError):
80 except (IOError, OSError):
81 return False
81 return False
82
82
83 @check("execbit", "executable bit")
83 @check("execbit", "executable bit")
84 def has_executablebit():
84 def has_executablebit():
85 try:
85 try:
86 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
86 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
87 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
87 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
88 try:
88 try:
89 os.close(fh)
89 os.close(fh)
90 m = os.stat(fn).st_mode & 0777
90 m = os.stat(fn).st_mode & 0777
91 new_file_has_exec = m & EXECFLAGS
91 new_file_has_exec = m & EXECFLAGS
92 os.chmod(fn, m ^ EXECFLAGS)
92 os.chmod(fn, m ^ EXECFLAGS)
93 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
93 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
94 finally:
94 finally:
95 os.unlink(fn)
95 os.unlink(fn)
96 except (IOError, OSError):
96 except (IOError, OSError):
97 # we don't care, the user probably won't be able to commit anyway
97 # we don't care, the user probably won't be able to commit anyway
98 return False
98 return False
99 return not (new_file_has_exec or exec_flags_cannot_flip)
99 return not (new_file_has_exec or exec_flags_cannot_flip)
100
100
101 @check("icasefs", "case insensitive file system")
101 @check("icasefs", "case insensitive file system")
102 def has_icasefs():
102 def has_icasefs():
103 # Stolen from mercurial.util
103 # Stolen from mercurial.util
104 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
104 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
105 os.close(fd)
105 os.close(fd)
106 try:
106 try:
107 s1 = os.stat(path)
107 s1 = os.stat(path)
108 d, b = os.path.split(path)
108 d, b = os.path.split(path)
109 p2 = os.path.join(d, b.upper())
109 p2 = os.path.join(d, b.upper())
110 if path == p2:
110 if path == p2:
111 p2 = os.path.join(d, b.lower())
111 p2 = os.path.join(d, b.lower())
112 try:
112 try:
113 s2 = os.stat(p2)
113 s2 = os.stat(p2)
114 return s2 == s1
114 return s2 == s1
115 except OSError:
115 except OSError:
116 return False
116 return False
117 finally:
117 finally:
118 os.remove(path)
118 os.remove(path)
119
119
120 @check("fifo", "named pipes")
120 @check("fifo", "named pipes")
121 def has_fifo():
121 def has_fifo():
122 if getattr(os, "mkfifo", None) is None:
122 if getattr(os, "mkfifo", None) is None:
123 return False
123 return False
124 name = tempfile.mktemp(dir='.', prefix=tempprefix)
124 name = tempfile.mktemp(dir='.', prefix=tempprefix)
125 try:
125 try:
126 os.mkfifo(name)
126 os.mkfifo(name)
127 os.unlink(name)
127 os.unlink(name)
128 return True
128 return True
129 except OSError:
129 except OSError:
130 return False
130 return False
131
131
132 @check("killdaemons", 'killdaemons.py support')
132 @check("killdaemons", 'killdaemons.py support')
133 def has_killdaemons():
133 def has_killdaemons():
134 return True
134 return True
135
135
136 @check("cacheable", "cacheable filesystem")
136 @check("cacheable", "cacheable filesystem")
137 def has_cacheable_fs():
137 def has_cacheable_fs():
138 from mercurial import util
138 from mercurial import util
139
139
140 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
140 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
141 os.close(fd)
141 os.close(fd)
142 try:
142 try:
143 return util.cachestat(path).cacheable()
143 return util.cachestat(path).cacheable()
144 finally:
144 finally:
145 os.remove(path)
145 os.remove(path)
146
146
147 @check("lsprof", "python lsprof module")
147 @check("lsprof", "python lsprof module")
148 def has_lsprof():
148 def has_lsprof():
149 try:
149 try:
150 import _lsprof
150 import _lsprof
151 _lsprof.Profiler # silence unused import warning
151 return True
152 return True
152 except ImportError:
153 except ImportError:
153 return False
154 return False
154
155
155 @check("gettext", "GNU Gettext (msgfmt)")
156 @check("gettext", "GNU Gettext (msgfmt)")
156 def has_gettext():
157 def has_gettext():
157 return matchoutput('msgfmt --version', 'GNU gettext-tools')
158 return matchoutput('msgfmt --version', 'GNU gettext-tools')
158
159
159 @check("git", "git command line client")
160 @check("git", "git command line client")
160 def has_git():
161 def has_git():
161 return matchoutput('git --version 2>&1', r'^git version')
162 return matchoutput('git --version 2>&1', r'^git version')
162
163
163 @check("docutils", "Docutils text processing library")
164 @check("docutils", "Docutils text processing library")
164 def has_docutils():
165 def has_docutils():
165 try:
166 try:
166 from docutils.core import publish_cmdline
167 from docutils.core import publish_cmdline
168 publish_cmdline # silence unused import
167 return True
169 return True
168 except ImportError:
170 except ImportError:
169 return False
171 return False
170
172
171 def getsvnversion():
173 def getsvnversion():
172 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
174 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
173 if not m:
175 if not m:
174 return (0, 0)
176 return (0, 0)
175 return (int(m.group(1)), int(m.group(2)))
177 return (int(m.group(1)), int(m.group(2)))
176
178
177 @check("svn15", "subversion client and admin tools >= 1.5")
179 @check("svn15", "subversion client and admin tools >= 1.5")
178 def has_svn15():
180 def has_svn15():
179 return getsvnversion() >= (1, 5)
181 return getsvnversion() >= (1, 5)
180
182
181 @check("svn13", "subversion client and admin tools >= 1.3")
183 @check("svn13", "subversion client and admin tools >= 1.3")
182 def has_svn13():
184 def has_svn13():
183 return getsvnversion() >= (1, 3)
185 return getsvnversion() >= (1, 3)
184
186
185 @check("svn", "subversion client and admin tools")
187 @check("svn", "subversion client and admin tools")
186 def has_svn():
188 def has_svn():
187 return matchoutput('svn --version 2>&1', r'^svn, version') and \
189 return matchoutput('svn --version 2>&1', r'^svn, version') and \
188 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
190 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
189
191
190 @check("svn-bindings", "subversion python bindings")
192 @check("svn-bindings", "subversion python bindings")
191 def has_svn_bindings():
193 def has_svn_bindings():
192 try:
194 try:
193 import svn.core
195 import svn.core
194 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
196 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
195 if version < (1, 4):
197 if version < (1, 4):
196 return False
198 return False
197 return True
199 return True
198 except ImportError:
200 except ImportError:
199 return False
201 return False
200
202
201 @check("p4", "Perforce server and client")
203 @check("p4", "Perforce server and client")
202 def has_p4():
204 def has_p4():
203 return (matchoutput('p4 -V', r'Rev\. P4/') and
205 return (matchoutput('p4 -V', r'Rev\. P4/') and
204 matchoutput('p4d -V', r'Rev\. P4D/'))
206 matchoutput('p4d -V', r'Rev\. P4D/'))
205
207
206 @check("symlink", "symbolic links")
208 @check("symlink", "symbolic links")
207 def has_symlink():
209 def has_symlink():
208 if getattr(os, "symlink", None) is None:
210 if getattr(os, "symlink", None) is None:
209 return False
211 return False
210 name = tempfile.mktemp(dir='.', prefix=tempprefix)
212 name = tempfile.mktemp(dir='.', prefix=tempprefix)
211 try:
213 try:
212 os.symlink(".", name)
214 os.symlink(".", name)
213 os.unlink(name)
215 os.unlink(name)
214 return True
216 return True
215 except (OSError, AttributeError):
217 except (OSError, AttributeError):
216 return False
218 return False
217
219
218 @check("hardlink", "hardlinks")
220 @check("hardlink", "hardlinks")
219 def has_hardlink():
221 def has_hardlink():
220 from mercurial import util
222 from mercurial import util
221 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
223 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
222 os.close(fh)
224 os.close(fh)
223 name = tempfile.mktemp(dir='.', prefix=tempprefix)
225 name = tempfile.mktemp(dir='.', prefix=tempprefix)
224 try:
226 try:
225 try:
227 try:
226 util.oslink(fn, name)
228 util.oslink(fn, name)
227 os.unlink(name)
229 os.unlink(name)
228 return True
230 return True
229 except OSError:
231 except OSError:
230 return False
232 return False
231 finally:
233 finally:
232 os.unlink(fn)
234 os.unlink(fn)
233
235
234 @check("tla", "GNU Arch tla client")
236 @check("tla", "GNU Arch tla client")
235 def has_tla():
237 def has_tla():
236 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
238 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
237
239
238 @check("gpg", "gpg client")
240 @check("gpg", "gpg client")
239 def has_gpg():
241 def has_gpg():
240 return matchoutput('gpg --version 2>&1', r'GnuPG')
242 return matchoutput('gpg --version 2>&1', r'GnuPG')
241
243
242 @check("unix-permissions", "unix-style permissions")
244 @check("unix-permissions", "unix-style permissions")
243 def has_unix_permissions():
245 def has_unix_permissions():
244 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
246 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
245 try:
247 try:
246 fname = os.path.join(d, 'foo')
248 fname = os.path.join(d, 'foo')
247 for umask in (077, 007, 022):
249 for umask in (077, 007, 022):
248 os.umask(umask)
250 os.umask(umask)
249 f = open(fname, 'w')
251 f = open(fname, 'w')
250 f.close()
252 f.close()
251 mode = os.stat(fname).st_mode
253 mode = os.stat(fname).st_mode
252 os.unlink(fname)
254 os.unlink(fname)
253 if mode & 0777 != ~umask & 0666:
255 if mode & 0777 != ~umask & 0666:
254 return False
256 return False
255 return True
257 return True
256 finally:
258 finally:
257 os.rmdir(d)
259 os.rmdir(d)
258
260
259 @check("root", "root permissions")
261 @check("root", "root permissions")
260 def has_root():
262 def has_root():
261 return getattr(os, 'geteuid', None) and os.geteuid() == 0
263 return getattr(os, 'geteuid', None) and os.geteuid() == 0
262
264
263 @check("pyflakes", "Pyflakes python linter")
265 @check("pyflakes", "Pyflakes python linter")
264 def has_pyflakes():
266 def has_pyflakes():
265 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
267 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
266 r"<stdin>:1: 're' imported but unused",
268 r"<stdin>:1: 're' imported but unused",
267 True)
269 True)
268
270
269 @check("pygments", "Pygments source highlighting library")
271 @check("pygments", "Pygments source highlighting library")
270 def has_pygments():
272 def has_pygments():
271 try:
273 try:
272 import pygments
274 import pygments
275 pygments.highlight # silence unused import warning
273 return True
276 return True
274 except ImportError:
277 except ImportError:
275 return False
278 return False
276
279
277 @check("python243", "python >= 2.4.3")
280 @check("python243", "python >= 2.4.3")
278 def has_python243():
281 def has_python243():
279 return sys.version_info >= (2, 4, 3)
282 return sys.version_info >= (2, 4, 3)
280
283
281 @check("outer-repo", "outer repo")
284 @check("outer-repo", "outer repo")
282 def has_outer_repo():
285 def has_outer_repo():
283 # failing for other reasons than 'no repo' imply that there is a repo
286 # failing for other reasons than 'no repo' imply that there is a repo
284 return not matchoutput('hg root 2>&1',
287 return not matchoutput('hg root 2>&1',
285 r'abort: no repository found', True)
288 r'abort: no repository found', True)
286
289
287 @check("ssl", "python >= 2.6 ssl module and python OpenSSL")
290 @check("ssl", "python >= 2.6 ssl module and python OpenSSL")
288 def has_ssl():
291 def has_ssl():
289 try:
292 try:
290 import ssl
293 import ssl
294 ssl.wrap_socket # silence unused import warning
291 import OpenSSL
295 import OpenSSL
292 OpenSSL.SSL.Context
296 OpenSSL.SSL.Context
293 return True
297 return True
294 except ImportError:
298 except ImportError:
295 return False
299 return False
296
300
297 @check("windows", "Windows")
301 @check("windows", "Windows")
298 def has_windows():
302 def has_windows():
299 return os.name == 'nt'
303 return os.name == 'nt'
300
304
301 @check("system-sh", "system() uses sh")
305 @check("system-sh", "system() uses sh")
302 def has_system_sh():
306 def has_system_sh():
303 return os.name != 'nt'
307 return os.name != 'nt'
304
308
305 @check("serve", "platform and python can manage 'hg serve -d'")
309 @check("serve", "platform and python can manage 'hg serve -d'")
306 def has_serve():
310 def has_serve():
307 return os.name != 'nt' # gross approximation
311 return os.name != 'nt' # gross approximation
308
312
309 @check("test-repo", "running tests from repository")
313 @check("test-repo", "running tests from repository")
310 def has_test_repo():
314 def has_test_repo():
311 t = os.environ["TESTDIR"]
315 t = os.environ["TESTDIR"]
312 return os.path.isdir(os.path.join(t, "..", ".hg"))
316 return os.path.isdir(os.path.join(t, "..", ".hg"))
313
317
314 @check("tic", "terminfo compiler and curses module")
318 @check("tic", "terminfo compiler and curses module")
315 def has_tic():
319 def has_tic():
316 try:
320 try:
317 import curses
321 import curses
318 curses.COLOR_BLUE
322 curses.COLOR_BLUE
319 return matchoutput('test -x "`which tic`"', '')
323 return matchoutput('test -x "`which tic`"', '')
320 except ImportError:
324 except ImportError:
321 return False
325 return False
322
326
323 @check("msys", "Windows with MSYS")
327 @check("msys", "Windows with MSYS")
324 def has_msys():
328 def has_msys():
325 return os.getenv('MSYSTEM')
329 return os.getenv('MSYSTEM')
326
330
327 @check("aix", "AIX")
331 @check("aix", "AIX")
328 def has_aix():
332 def has_aix():
329 return sys.platform.startswith("aix")
333 return sys.platform.startswith("aix")
330
334
331 @check("absimport", "absolute_import in __future__")
335 @check("absimport", "absolute_import in __future__")
332 def has_absimport():
336 def has_absimport():
333 import __future__
337 import __future__
334 from mercurial import util
338 from mercurial import util
335 return util.safehasattr(__future__, "absolute_import")
339 return util.safehasattr(__future__, "absolute_import")
336
340
337 @check("py3k", "running with Python 3.x")
341 @check("py3k", "running with Python 3.x")
338 def has_py3k():
342 def has_py3k():
339 return 3 == sys.version_info[0]
343 return 3 == sys.version_info[0]
@@ -1,22 +1,12 b''
1 #require test-repo pyflakes
1 #require test-repo pyflakes
2
2
3 $ cd "`dirname "$TESTDIR"`"
3 $ cd "`dirname "$TESTDIR"`"
4
4
5 run pyflakes on all tracked files ending in .py or without a file ending
5 run pyflakes on all tracked files ending in .py or without a file ending
6 (skipping binary file random-seed)
6 (skipping binary file random-seed)
7
7
8 $ hg locate 'set:**.py or grep("^!#.*python")' 2>/dev/null \
8 $ hg locate 'set:**.py or grep("^!#.*python")' 2>/dev/null \
9 > | xargs pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py"
9 > | xargs pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py"
10 contrib/win32/hgwebdir_wsgi.py:*: 'win32traceutil' imported but unused (glob)
11 setup.py:*: 'sha' imported but unused (glob)
12 setup.py:*: 'zlib' imported but unused (glob)
13 setup.py:*: 'bz2' imported but unused (glob)
14 setup.py:*: 'py2exe' imported but unused (glob)
15 tests/hghave.py:*: '_lsprof' imported but unused (glob)
16 tests/hghave.py:*: 'publish_cmdline' imported but unused (glob)
17 tests/hghave.py:*: 'pygments' imported but unused (glob)
18 tests/hghave.py:*: 'ssl' imported but unused (glob)
19 contrib/win32/hgwebdir_wsgi.py:93: 'from isapi.install import *' used; unable to detect undefined names (glob)
20 tests/filterpyflakes.py:58: undefined name 'undefinedname'
10 tests/filterpyflakes.py:58: undefined name 'undefinedname'
21
11
22
12
General Comments 0
You need to be logged in to leave comments. Login now