##// END OF EJS Templates
ssl: on OS X, use a dummy cert to trick Python/OpenSSL to use system CA certs...
Mads Kiilerich -
r22575:d7f7f186 default
parent child Browse files
Show More
@@ -0,0 +1,56
1 A dummy certificate that will make OS X 10.6+ Python use the system CA
2 certificate store:
3
4 -----BEGIN CERTIFICATE-----
5 MIIBIzCBzgIJANjmj39sb3FmMA0GCSqGSIb3DQEBBQUAMBkxFzAVBgNVBAMTDmhn
6 LmV4YW1wbGUuY29tMB4XDTE0MDgzMDA4NDU1OVoXDTE0MDgyOTA4NDU1OVowGTEX
7 MBUGA1UEAxMOaGcuZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEA
8 mh/ZySGlcq0ALNLmA1gZqt61HruywPrRk6WyrLJRgt+X7OP9FFlEfl2tzHfzqvmK
9 CtSQoPINWOdAJMekBYFgKQIDAQABMA0GCSqGSIb3DQEBBQUAA0EAF9h49LkSqJ6a
10 IlpogZuUHtihXeKZBsiktVIDlDccYsNy0RSh9XxUfhk+XMLw8jBlYvcltSXdJ7We
11 aKdQRekuMQ==
12 -----END CERTIFICATE-----
13
14 This certificate was generated to be syntactically valid but never be usable;
15 it expired before it became valid.
16
17 Created as:
18
19 $ cat > cn.conf << EOT
20 > [req]
21 > distinguished_name = req_distinguished_name
22 > [req_distinguished_name]
23 > commonName = Common Name
24 > commonName_default = no.example.com
25 > EOT
26 $ openssl req -nodes -new -x509 -keyout /dev/null \
27 > -out dummycert.pem -days -1 -config cn.conf -subj '/CN=hg.example.com'
28
29 To verify the content of this certificate:
30
31 $ openssl x509 -in dummycert.pem -noout -text
32 Certificate:
33 Data:
34 Version: 1 (0x0)
35 Serial Number: 15629337334278746470 (0xd8e68f7f6c6f7166)
36 Signature Algorithm: sha1WithRSAEncryption
37 Issuer: CN=hg.example.com
38 Validity
39 Not Before: Aug 30 08:45:59 2014 GMT
40 Not After : Aug 29 08:45:59 2014 GMT
41 Subject: CN=hg.example.com
42 Subject Public Key Info:
43 Public Key Algorithm: rsaEncryption
44 Public-Key: (512 bit)
45 Modulus:
46 00:9a:1f:d9:c9:21:a5:72:ad:00:2c:d2:e6:03:58:
47 19:aa:de:b5:1e:bb:b2:c0:fa:d1:93:a5:b2:ac:b2:
48 51:82:df:97:ec:e3:fd:14:59:44:7e:5d:ad:cc:77:
49 f3:aa:f9:8a:0a:d4:90:a0:f2:0d:58:e7:40:24:c7:
50 a4:05:81:60:29
51 Exponent: 65537 (0x10001)
52 Signature Algorithm: sha1WithRSAEncryption
53 17:d8:78:f4:b9:12:a8:9e:9a:22:5a:68:81:9b:94:1e:d8:a1:
54 5d:e2:99:06:c8:a4:b5:52:03:94:37:1c:62:c3:72:d1:14:a1:
55 f5:7c:54:7e:19:3e:5c:c2:f0:f2:30:65:62:f7:25:b5:25:dd:
56 27:b5:9e:68:a7:50:45:e9:2e:31
@@ -1,172 +1,179
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9 import os
9 import os, sys
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 try:
13 try:
14 # avoid using deprecated/broken FakeSocket in python 2.6
14 # avoid using deprecated/broken FakeSocket in python 2.6
15 import ssl
15 import ssl
16 CERT_REQUIRED = ssl.CERT_REQUIRED
16 CERT_REQUIRED = ssl.CERT_REQUIRED
17 PROTOCOL_SSLv23 = ssl.PROTOCOL_SSLv23
17 PROTOCOL_SSLv23 = ssl.PROTOCOL_SSLv23
18 PROTOCOL_TLSv1 = ssl.PROTOCOL_TLSv1
18 PROTOCOL_TLSv1 = ssl.PROTOCOL_TLSv1
19 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
19 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
20 cert_reqs=ssl.CERT_NONE, ca_certs=None):
20 cert_reqs=ssl.CERT_NONE, ca_certs=None):
21 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
21 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
22 cert_reqs=cert_reqs, ca_certs=ca_certs,
22 cert_reqs=cert_reqs, ca_certs=ca_certs,
23 ssl_version=ssl_version)
23 ssl_version=ssl_version)
24 # check if wrap_socket failed silently because socket had been closed
24 # check if wrap_socket failed silently because socket had been closed
25 # - see http://bugs.python.org/issue13721
25 # - see http://bugs.python.org/issue13721
26 if not sslsocket.cipher():
26 if not sslsocket.cipher():
27 raise util.Abort(_('ssl connection failed'))
27 raise util.Abort(_('ssl connection failed'))
28 return sslsocket
28 return sslsocket
29 except ImportError:
29 except ImportError:
30 CERT_REQUIRED = 2
30 CERT_REQUIRED = 2
31
31
32 PROTOCOL_SSLv23 = 2
32 PROTOCOL_SSLv23 = 2
33 PROTOCOL_TLSv1 = 3
33 PROTOCOL_TLSv1 = 3
34
34
35 import socket, httplib
35 import socket, httplib
36
36
37 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
37 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
38 cert_reqs=CERT_REQUIRED, ca_certs=None):
38 cert_reqs=CERT_REQUIRED, ca_certs=None):
39 if not util.safehasattr(socket, 'ssl'):
39 if not util.safehasattr(socket, 'ssl'):
40 raise util.Abort(_('Python SSL support not found'))
40 raise util.Abort(_('Python SSL support not found'))
41 if ca_certs:
41 if ca_certs:
42 raise util.Abort(_(
42 raise util.Abort(_(
43 'certificate checking requires Python 2.6'))
43 'certificate checking requires Python 2.6'))
44
44
45 ssl = socket.ssl(sock, keyfile, certfile)
45 ssl = socket.ssl(sock, keyfile, certfile)
46 return httplib.FakeSocket(sock, ssl)
46 return httplib.FakeSocket(sock, ssl)
47
47
48 def _verifycert(cert, hostname):
48 def _verifycert(cert, hostname):
49 '''Verify that cert (in socket.getpeercert() format) matches hostname.
49 '''Verify that cert (in socket.getpeercert() format) matches hostname.
50 CRLs is not handled.
50 CRLs is not handled.
51
51
52 Returns error message if any problems are found and None on success.
52 Returns error message if any problems are found and None on success.
53 '''
53 '''
54 if not cert:
54 if not cert:
55 return _('no certificate received')
55 return _('no certificate received')
56 dnsname = hostname.lower()
56 dnsname = hostname.lower()
57 def matchdnsname(certname):
57 def matchdnsname(certname):
58 return (certname == dnsname or
58 return (certname == dnsname or
59 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
59 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
60
60
61 san = cert.get('subjectAltName', [])
61 san = cert.get('subjectAltName', [])
62 if san:
62 if san:
63 certnames = [value.lower() for key, value in san if key == 'DNS']
63 certnames = [value.lower() for key, value in san if key == 'DNS']
64 for name in certnames:
64 for name in certnames:
65 if matchdnsname(name):
65 if matchdnsname(name):
66 return None
66 return None
67 if certnames:
67 if certnames:
68 return _('certificate is for %s') % ', '.join(certnames)
68 return _('certificate is for %s') % ', '.join(certnames)
69
69
70 # subject is only checked when subjectAltName is empty
70 # subject is only checked when subjectAltName is empty
71 for s in cert.get('subject', []):
71 for s in cert.get('subject', []):
72 key, value = s[0]
72 key, value = s[0]
73 if key == 'commonName':
73 if key == 'commonName':
74 try:
74 try:
75 # 'subject' entries are unicode
75 # 'subject' entries are unicode
76 certname = value.lower().encode('ascii')
76 certname = value.lower().encode('ascii')
77 except UnicodeEncodeError:
77 except UnicodeEncodeError:
78 return _('IDN in certificate not supported')
78 return _('IDN in certificate not supported')
79 if matchdnsname(certname):
79 if matchdnsname(certname):
80 return None
80 return None
81 return _('certificate is for %s') % certname
81 return _('certificate is for %s') % certname
82 return _('no commonName or subjectAltName found in certificate')
82 return _('no commonName or subjectAltName found in certificate')
83
83
84
84
85 # CERT_REQUIRED means fetch the cert from the server all the time AND
85 # CERT_REQUIRED means fetch the cert from the server all the time AND
86 # validate it against the CA store provided in web.cacerts.
86 # validate it against the CA store provided in web.cacerts.
87 #
87 #
88 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
88 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
89 # busted on those versions.
89 # busted on those versions.
90
90
91 def sslkwargs(ui, host):
91 def sslkwargs(ui, host):
92 forcetls = ui.configbool('ui', 'tls', default=True)
92 forcetls = ui.configbool('ui', 'tls', default=True)
93 if forcetls:
93 if forcetls:
94 ssl_version = PROTOCOL_TLSv1
94 ssl_version = PROTOCOL_TLSv1
95 else:
95 else:
96 ssl_version = PROTOCOL_SSLv23
96 ssl_version = PROTOCOL_SSLv23
97 kws = {'ssl_version': ssl_version,
97 kws = {'ssl_version': ssl_version,
98 }
98 }
99 hostfingerprint = ui.config('hostfingerprints', host)
99 hostfingerprint = ui.config('hostfingerprints', host)
100 if hostfingerprint:
100 if hostfingerprint:
101 return kws
101 return kws
102 cacerts = ui.config('web', 'cacerts')
102 cacerts = ui.config('web', 'cacerts')
103 if cacerts:
103 if cacerts:
104 cacerts = util.expandpath(cacerts)
104 cacerts = util.expandpath(cacerts)
105 if not os.path.exists(cacerts):
105 if not os.path.exists(cacerts):
106 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
106 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
107 elif cacerts is None and sys.platform == 'darwin' and not util.mainfrozen():
108 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
109 if os.path.exists(dummycert):
110 ui.debug('using %s to enable OS X system CA\n' % dummycert)
111 ui.setconfig('web', 'cacerts', dummycert, 'dummy')
112 cacerts = dummycert
113 if cacerts:
107 kws.update({'ca_certs': cacerts,
114 kws.update({'ca_certs': cacerts,
108 'cert_reqs': CERT_REQUIRED,
115 'cert_reqs': CERT_REQUIRED,
109 })
116 })
110 return kws
117 return kws
111
118
112 class validator(object):
119 class validator(object):
113 def __init__(self, ui, host):
120 def __init__(self, ui, host):
114 self.ui = ui
121 self.ui = ui
115 self.host = host
122 self.host = host
116
123
117 def __call__(self, sock, strict=False):
124 def __call__(self, sock, strict=False):
118 host = self.host
125 host = self.host
119 cacerts = self.ui.config('web', 'cacerts')
126 cacerts = self.ui.config('web', 'cacerts')
120 hostfingerprint = self.ui.config('hostfingerprints', host)
127 hostfingerprint = self.ui.config('hostfingerprints', host)
121 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
128 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
122 if hostfingerprint:
129 if hostfingerprint:
123 raise util.Abort(_("host fingerprint for %s can't be "
130 raise util.Abort(_("host fingerprint for %s can't be "
124 "verified (Python too old)") % host)
131 "verified (Python too old)") % host)
125 if strict:
132 if strict:
126 raise util.Abort(_("certificate for %s can't be verified "
133 raise util.Abort(_("certificate for %s can't be verified "
127 "(Python too old)") % host)
134 "(Python too old)") % host)
128 if self.ui.configbool('ui', 'reportoldssl', True):
135 if self.ui.configbool('ui', 'reportoldssl', True):
129 self.ui.warn(_("warning: certificate for %s can't be verified "
136 self.ui.warn(_("warning: certificate for %s can't be verified "
130 "(Python too old)\n") % host)
137 "(Python too old)\n") % host)
131 return
138 return
132
139
133 if not sock.cipher(): # work around http://bugs.python.org/issue13721
140 if not sock.cipher(): # work around http://bugs.python.org/issue13721
134 raise util.Abort(_('%s ssl connection error') % host)
141 raise util.Abort(_('%s ssl connection error') % host)
135 try:
142 try:
136 peercert = sock.getpeercert(True)
143 peercert = sock.getpeercert(True)
137 peercert2 = sock.getpeercert()
144 peercert2 = sock.getpeercert()
138 except AttributeError:
145 except AttributeError:
139 raise util.Abort(_('%s ssl connection error') % host)
146 raise util.Abort(_('%s ssl connection error') % host)
140
147
141 if not peercert:
148 if not peercert:
142 raise util.Abort(_('%s certificate error: '
149 raise util.Abort(_('%s certificate error: '
143 'no certificate received') % host)
150 'no certificate received') % host)
144 peerfingerprint = util.sha1(peercert).hexdigest()
151 peerfingerprint = util.sha1(peercert).hexdigest()
145 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
152 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
146 for x in xrange(0, len(peerfingerprint), 2)])
153 for x in xrange(0, len(peerfingerprint), 2)])
147 if hostfingerprint:
154 if hostfingerprint:
148 if peerfingerprint.lower() != \
155 if peerfingerprint.lower() != \
149 hostfingerprint.replace(':', '').lower():
156 hostfingerprint.replace(':', '').lower():
150 raise util.Abort(_('certificate for %s has unexpected '
157 raise util.Abort(_('certificate for %s has unexpected '
151 'fingerprint %s') % (host, nicefingerprint),
158 'fingerprint %s') % (host, nicefingerprint),
152 hint=_('check hostfingerprint configuration'))
159 hint=_('check hostfingerprint configuration'))
153 self.ui.debug('%s certificate matched fingerprint %s\n' %
160 self.ui.debug('%s certificate matched fingerprint %s\n' %
154 (host, nicefingerprint))
161 (host, nicefingerprint))
155 elif cacerts:
162 elif cacerts:
156 msg = _verifycert(peercert2, host)
163 msg = _verifycert(peercert2, host)
157 if msg:
164 if msg:
158 raise util.Abort(_('%s certificate error: %s') % (host, msg),
165 raise util.Abort(_('%s certificate error: %s') % (host, msg),
159 hint=_('configure hostfingerprint %s or use '
166 hint=_('configure hostfingerprint %s or use '
160 '--insecure to connect insecurely') %
167 '--insecure to connect insecurely') %
161 nicefingerprint)
168 nicefingerprint)
162 self.ui.debug('%s certificate successfully verified\n' % host)
169 self.ui.debug('%s certificate successfully verified\n' % host)
163 elif strict:
170 elif strict:
164 raise util.Abort(_('%s certificate with fingerprint %s not '
171 raise util.Abort(_('%s certificate with fingerprint %s not '
165 'verified') % (host, nicefingerprint),
172 'verified') % (host, nicefingerprint),
166 hint=_('check hostfingerprints or web.cacerts '
173 hint=_('check hostfingerprints or web.cacerts '
167 'config setting'))
174 'config setting'))
168 else:
175 else:
169 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
176 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
170 'verified (check hostfingerprints or web.cacerts '
177 'verified (check hostfingerprints or web.cacerts '
171 'config setting)\n') %
178 'config setting)\n') %
172 (host, nicefingerprint))
179 (host, nicefingerprint))
@@ -1,592 +1,593
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 sha.sha # silence unused import warning
37 except ImportError:
37 except ImportError:
38 raise SystemExit(
38 raise SystemExit(
39 "Couldn't import standard hashlib (incomplete Python install).")
39 "Couldn't import standard hashlib (incomplete Python install).")
40
40
41 try:
41 try:
42 import zlib
42 import zlib
43 zlib.compressobj # silence unused import warning
43 zlib.compressobj # silence unused import warning
44 except ImportError:
44 except ImportError:
45 raise SystemExit(
45 raise SystemExit(
46 "Couldn't import standard zlib (incomplete Python install).")
46 "Couldn't import standard zlib (incomplete Python install).")
47
47
48 # 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
49 isironpython = False
49 isironpython = False
50 try:
50 try:
51 isironpython = (platform.python_implementation()
51 isironpython = (platform.python_implementation()
52 .lower().find("ironpython") != -1)
52 .lower().find("ironpython") != -1)
53 except AttributeError:
53 except AttributeError:
54 pass
54 pass
55
55
56 if isironpython:
56 if isironpython:
57 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
57 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
58 else:
58 else:
59 try:
59 try:
60 import bz2
60 import bz2
61 bz2.BZ2Compressor # silence unused import warning
61 bz2.BZ2Compressor # silence unused import warning
62 except ImportError:
62 except ImportError:
63 raise SystemExit(
63 raise SystemExit(
64 "Couldn't import standard bz2 (incomplete Python install).")
64 "Couldn't import standard bz2 (incomplete Python install).")
65
65
66 import os, subprocess, time
66 import os, subprocess, time
67 import re
67 import re
68 import shutil
68 import shutil
69 import tempfile
69 import tempfile
70 from distutils import log
70 from distutils import log
71 from distutils.core import setup, Command, Extension
71 from distutils.core import setup, Command, Extension
72 from distutils.dist import Distribution
72 from distutils.dist import Distribution
73 from distutils.command.build import build
73 from distutils.command.build import build
74 from distutils.command.build_ext import build_ext
74 from distutils.command.build_ext import build_ext
75 from distutils.command.build_py import build_py
75 from distutils.command.build_py import build_py
76 from distutils.command.install_scripts import install_scripts
76 from distutils.command.install_scripts import install_scripts
77 from distutils.spawn import spawn, find_executable
77 from distutils.spawn import spawn, find_executable
78 from distutils import cygwinccompiler
78 from distutils import cygwinccompiler
79 from distutils.errors import CCompilerError, DistutilsExecError
79 from distutils.errors import CCompilerError, DistutilsExecError
80 from distutils.sysconfig import get_python_inc, get_config_var
80 from distutils.sysconfig import get_python_inc, get_config_var
81 from distutils.version import StrictVersion
81 from distutils.version import StrictVersion
82
82
83 convert2to3 = '--c2to3' in sys.argv
83 convert2to3 = '--c2to3' in sys.argv
84 if convert2to3:
84 if convert2to3:
85 try:
85 try:
86 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
87 from lib2to3.refactor import get_fixers_from_package as getfixers
87 from lib2to3.refactor import get_fixers_from_package as getfixers
88 except ImportError:
88 except ImportError:
89 if sys.version_info[0] < 3:
89 if sys.version_info[0] < 3:
90 raise SystemExit("--c2to3 is only compatible with python3.")
90 raise SystemExit("--c2to3 is only compatible with python3.")
91 raise
91 raise
92 sys.path.append('contrib')
92 sys.path.append('contrib')
93 elif sys.version_info[0] >= 3:
93 elif sys.version_info[0] >= 3:
94 raise SystemExit("setup.py with python3 needs --c2to3 (experimental)")
94 raise SystemExit("setup.py with python3 needs --c2to3 (experimental)")
95
95
96 scripts = ['hg']
96 scripts = ['hg']
97 if os.name == 'nt':
97 if os.name == 'nt':
98 scripts.append('contrib/win32/hg.bat')
98 scripts.append('contrib/win32/hg.bat')
99
99
100 # simplified version of distutils.ccompiler.CCompiler.has_function
100 # simplified version of distutils.ccompiler.CCompiler.has_function
101 # that actually removes its temporary files.
101 # that actually removes its temporary files.
102 def hasfunction(cc, funcname):
102 def hasfunction(cc, funcname):
103 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
103 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
104 devnull = oldstderr = None
104 devnull = oldstderr = None
105 try:
105 try:
106 try:
106 try:
107 fname = os.path.join(tmpdir, 'funcname.c')
107 fname = os.path.join(tmpdir, 'funcname.c')
108 f = open(fname, 'w')
108 f = open(fname, 'w')
109 f.write('int main(void) {\n')
109 f.write('int main(void) {\n')
110 f.write(' %s();\n' % funcname)
110 f.write(' %s();\n' % funcname)
111 f.write('}\n')
111 f.write('}\n')
112 f.close()
112 f.close()
113 # Redirect stderr to /dev/null to hide any error messages
113 # Redirect stderr to /dev/null to hide any error messages
114 # from the compiler.
114 # from the compiler.
115 # 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
116 # for a function on Windows.
116 # for a function on Windows.
117 devnull = open('/dev/null', 'w')
117 devnull = open('/dev/null', 'w')
118 oldstderr = os.dup(sys.stderr.fileno())
118 oldstderr = os.dup(sys.stderr.fileno())
119 os.dup2(devnull.fileno(), sys.stderr.fileno())
119 os.dup2(devnull.fileno(), sys.stderr.fileno())
120 objects = cc.compile([fname], output_dir=tmpdir)
120 objects = cc.compile([fname], output_dir=tmpdir)
121 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
121 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
122 except Exception:
122 except Exception:
123 return False
123 return False
124 return True
124 return True
125 finally:
125 finally:
126 if oldstderr is not None:
126 if oldstderr is not None:
127 os.dup2(oldstderr, sys.stderr.fileno())
127 os.dup2(oldstderr, sys.stderr.fileno())
128 if devnull is not None:
128 if devnull is not None:
129 devnull.close()
129 devnull.close()
130 shutil.rmtree(tmpdir)
130 shutil.rmtree(tmpdir)
131
131
132 # py2exe needs to be installed to work
132 # py2exe needs to be installed to work
133 try:
133 try:
134 import py2exe
134 import py2exe
135 py2exe.Distribution # silence unused import warning
135 py2exe.Distribution # silence unused import warning
136 py2exeloaded = True
136 py2exeloaded = True
137 # import py2exe's patched Distribution class
137 # import py2exe's patched Distribution class
138 from distutils.core import Distribution
138 from distutils.core import Distribution
139 except ImportError:
139 except ImportError:
140 py2exeloaded = False
140 py2exeloaded = False
141
141
142 def runcmd(cmd, env):
142 def runcmd(cmd, env):
143 if sys.platform == 'plan9':
143 if sys.platform == 'plan9':
144 # subprocess kludge to work around issues in half-baked Python
144 # subprocess kludge to work around issues in half-baked Python
145 # ports, notably bichued/python:
145 # ports, notably bichued/python:
146 _, out, err = os.popen3(cmd)
146 _, out, err = os.popen3(cmd)
147 return str(out), str(err)
147 return str(out), str(err)
148 else:
148 else:
149 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
149 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
150 stderr=subprocess.PIPE, env=env)
150 stderr=subprocess.PIPE, env=env)
151 out, err = p.communicate()
151 out, err = p.communicate()
152 return out, err
152 return out, err
153
153
154 def runhg(cmd, env):
154 def runhg(cmd, env):
155 out, err = runcmd(cmd, env)
155 out, err = runcmd(cmd, env)
156 # 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
157 # 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
158 # trust warnings since the .hg/hgrc file is untrusted. That is
158 # trust warnings since the .hg/hgrc file is untrusted. That is
159 # 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
160 # a missing __init__.py in mercurial/locale, we also ignore that.
160 # a missing __init__.py in mercurial/locale, we also ignore that.
161 err = [e for e in err.splitlines()
161 err = [e for e in err.splitlines()
162 if not e.startswith(b('not trusting file')) \
162 if not e.startswith(b('not trusting file')) \
163 and not e.startswith(b('warning: Not importing')) \
163 and not e.startswith(b('warning: Not importing')) \
164 and not e.startswith(b('obsolete feature not enabled'))]
164 and not e.startswith(b('obsolete feature not enabled'))]
165 if err:
165 if err:
166 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
166 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
167 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)
168 return ''
168 return ''
169 return out
169 return out
170
170
171 version = ''
171 version = ''
172
172
173 # Execute hg out of this directory with a custom environment which
173 # Execute hg out of this directory with a custom environment which
174 # includes the pure Python modules in mercurial/pure. We also take
174 # includes the pure Python modules in mercurial/pure. We also take
175 # care to not use any hgrc files and do no localization.
175 # care to not use any hgrc files and do no localization.
176 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
176 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
177 env = {'PYTHONPATH': os.pathsep.join(pypath),
177 env = {'PYTHONPATH': os.pathsep.join(pypath),
178 'HGRCPATH': '',
178 'HGRCPATH': '',
179 'LANGUAGE': 'C'}
179 'LANGUAGE': 'C'}
180 if 'LD_LIBRARY_PATH' in os.environ:
180 if 'LD_LIBRARY_PATH' in os.environ:
181 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
181 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
182 if 'SystemRoot' in os.environ:
182 if 'SystemRoot' in os.environ:
183 # Copy SystemRoot into the custom environment for Python 2.6
183 # Copy SystemRoot into the custom environment for Python 2.6
184 # under Windows. Otherwise, the subprocess will fail with
184 # under Windows. Otherwise, the subprocess will fail with
185 # error 0xc0150004. See: http://bugs.python.org/issue3440
185 # error 0xc0150004. See: http://bugs.python.org/issue3440
186 env['SystemRoot'] = os.environ['SystemRoot']
186 env['SystemRoot'] = os.environ['SystemRoot']
187
187
188 if os.path.isdir('.hg'):
188 if os.path.isdir('.hg'):
189 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
189 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
190 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()]
191 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
191 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
192 if numerictags: # tag(s) found
192 if numerictags: # tag(s) found
193 version = numerictags[-1]
193 version = numerictags[-1]
194 if hgid.endswith('+'): # propagate the dirty status to the tag
194 if hgid.endswith('+'): # propagate the dirty status to the tag
195 version += '+'
195 version += '+'
196 else: # no tag found
196 else: # no tag found
197 cmd = [sys.executable, 'hg', 'parents', '--template',
197 cmd = [sys.executable, 'hg', 'parents', '--template',
198 '{latesttag}+{latesttagdistance}-']
198 '{latesttag}+{latesttagdistance}-']
199 version = runhg(cmd, env) + hgid
199 version = runhg(cmd, env) + hgid
200 if version.endswith('+'):
200 if version.endswith('+'):
201 version += time.strftime('%Y%m%d')
201 version += time.strftime('%Y%m%d')
202 elif os.path.exists('.hg_archival.txt'):
202 elif os.path.exists('.hg_archival.txt'):
203 kw = dict([[t.strip() for t in l.split(':', 1)]
203 kw = dict([[t.strip() for t in l.split(':', 1)]
204 for l in open('.hg_archival.txt')])
204 for l in open('.hg_archival.txt')])
205 if 'tag' in kw:
205 if 'tag' in kw:
206 version = kw['tag']
206 version = kw['tag']
207 elif 'latesttag' in kw:
207 elif 'latesttag' in kw:
208 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
208 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
209 else:
209 else:
210 version = kw.get('node', '')[:12]
210 version = kw.get('node', '')[:12]
211
211
212 if version:
212 if version:
213 f = open("mercurial/__version__.py", "w")
213 f = open("mercurial/__version__.py", "w")
214 f.write('# this file is autogenerated by setup.py\n')
214 f.write('# this file is autogenerated by setup.py\n')
215 f.write('version = "%s"\n' % version)
215 f.write('version = "%s"\n' % version)
216 f.close()
216 f.close()
217
217
218
218
219 try:
219 try:
220 from mercurial import __version__
220 from mercurial import __version__
221 version = __version__.version
221 version = __version__.version
222 except ImportError:
222 except ImportError:
223 version = 'unknown'
223 version = 'unknown'
224
224
225 class hgbuild(build):
225 class hgbuild(build):
226 # Insert hgbuildmo first so that files in mercurial/locale/ are found
226 # Insert hgbuildmo first so that files in mercurial/locale/ are found
227 # when build_py is run next.
227 # when build_py is run next.
228 sub_commands = [('build_mo', None),
228 sub_commands = [('build_mo', None),
229
229
230 # 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
231 # called (in build_py), it will not find osutil & friends,
231 # called (in build_py), it will not find osutil & friends,
232 # thinking that those modules are global and, consequently, making
232 # thinking that those modules are global and, consequently, making
233 # a mess, now that all module imports are global.
233 # a mess, now that all module imports are global.
234
234
235 ('build_ext', build.has_ext_modules),
235 ('build_ext', build.has_ext_modules),
236 ] + build.sub_commands
236 ] + build.sub_commands
237
237
238 class hgbuildmo(build):
238 class hgbuildmo(build):
239
239
240 description = "build translations (.mo files)"
240 description = "build translations (.mo files)"
241
241
242 def run(self):
242 def run(self):
243 if not find_executable('msgfmt'):
243 if not find_executable('msgfmt'):
244 self.warn("could not find msgfmt executable, no translations "
244 self.warn("could not find msgfmt executable, no translations "
245 "will be built")
245 "will be built")
246 return
246 return
247
247
248 podir = 'i18n'
248 podir = 'i18n'
249 if not os.path.isdir(podir):
249 if not os.path.isdir(podir):
250 self.warn("could not find %s/ directory" % podir)
250 self.warn("could not find %s/ directory" % podir)
251 return
251 return
252
252
253 join = os.path.join
253 join = os.path.join
254 for po in os.listdir(podir):
254 for po in os.listdir(podir):
255 if not po.endswith('.po'):
255 if not po.endswith('.po'):
256 continue
256 continue
257 pofile = join(podir, po)
257 pofile = join(podir, po)
258 modir = join('locale', po[:-3], 'LC_MESSAGES')
258 modir = join('locale', po[:-3], 'LC_MESSAGES')
259 mofile = join(modir, 'hg.mo')
259 mofile = join(modir, 'hg.mo')
260 mobuildfile = join('mercurial', mofile)
260 mobuildfile = join('mercurial', mofile)
261 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
261 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
262 if sys.platform != 'sunos5':
262 if sys.platform != 'sunos5':
263 # msgfmt on Solaris does not know about -c
263 # msgfmt on Solaris does not know about -c
264 cmd.append('-c')
264 cmd.append('-c')
265 self.mkpath(join('mercurial', modir))
265 self.mkpath(join('mercurial', modir))
266 self.make_file([pofile], mobuildfile, spawn, (cmd,))
266 self.make_file([pofile], mobuildfile, spawn, (cmd,))
267
267
268
268
269 class hgdist(Distribution):
269 class hgdist(Distribution):
270 pure = 0
270 pure = 0
271
271
272 global_options = Distribution.global_options + \
272 global_options = Distribution.global_options + \
273 [('pure', None, "use pure (slow) Python "
273 [('pure', None, "use pure (slow) Python "
274 "code instead of C extensions"),
274 "code instead of C extensions"),
275 ('c2to3', None, "(experimental!) convert "
275 ('c2to3', None, "(experimental!) convert "
276 "code with 2to3"),
276 "code with 2to3"),
277 ]
277 ]
278
278
279 def has_ext_modules(self):
279 def has_ext_modules(self):
280 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
280 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
281 # too late for some cases
281 # too late for some cases
282 return not self.pure and Distribution.has_ext_modules(self)
282 return not self.pure and Distribution.has_ext_modules(self)
283
283
284 class hgbuildext(build_ext):
284 class hgbuildext(build_ext):
285
285
286 def build_extension(self, ext):
286 def build_extension(self, ext):
287 try:
287 try:
288 build_ext.build_extension(self, ext)
288 build_ext.build_extension(self, ext)
289 except CCompilerError:
289 except CCompilerError:
290 if not getattr(ext, 'optional', False):
290 if not getattr(ext, 'optional', False):
291 raise
291 raise
292 log.warn("Failed to build optional extension '%s' (skipping)",
292 log.warn("Failed to build optional extension '%s' (skipping)",
293 ext.name)
293 ext.name)
294
294
295 class hgbuildpy(build_py):
295 class hgbuildpy(build_py):
296 if convert2to3:
296 if convert2to3:
297 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
297 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
298 getfixers("hgfixes")))
298 getfixers("hgfixes")))
299
299
300 def finalize_options(self):
300 def finalize_options(self):
301 build_py.finalize_options(self)
301 build_py.finalize_options(self)
302
302
303 if self.distribution.pure:
303 if self.distribution.pure:
304 if self.py_modules is None:
304 if self.py_modules is None:
305 self.py_modules = []
305 self.py_modules = []
306 for ext in self.distribution.ext_modules:
306 for ext in self.distribution.ext_modules:
307 if ext.name.startswith("mercurial."):
307 if ext.name.startswith("mercurial."):
308 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
308 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
309 self.distribution.ext_modules = []
309 self.distribution.ext_modules = []
310 else:
310 else:
311 h = os.path.join(get_python_inc(), 'Python.h')
311 h = os.path.join(get_python_inc(), 'Python.h')
312 if not os.path.exists(h):
312 if not os.path.exists(h):
313 raise SystemExit('Python headers are required to build '
313 raise SystemExit('Python headers are required to build '
314 'Mercurial but weren\'t found in %s' % h)
314 'Mercurial but weren\'t found in %s' % h)
315
315
316 def find_modules(self):
316 def find_modules(self):
317 modules = build_py.find_modules(self)
317 modules = build_py.find_modules(self)
318 for module in modules:
318 for module in modules:
319 if module[0] == "mercurial.pure":
319 if module[0] == "mercurial.pure":
320 if module[1] != "__init__":
320 if module[1] != "__init__":
321 yield ("mercurial", module[1], module[2])
321 yield ("mercurial", module[1], module[2])
322 else:
322 else:
323 yield module
323 yield module
324
324
325 class buildhgextindex(Command):
325 class buildhgextindex(Command):
326 description = 'generate prebuilt index of hgext (for frozen package)'
326 description = 'generate prebuilt index of hgext (for frozen package)'
327 user_options = []
327 user_options = []
328 _indexfilename = 'hgext/__index__.py'
328 _indexfilename = 'hgext/__index__.py'
329
329
330 def initialize_options(self):
330 def initialize_options(self):
331 pass
331 pass
332
332
333 def finalize_options(self):
333 def finalize_options(self):
334 pass
334 pass
335
335
336 def run(self):
336 def run(self):
337 if os.path.exists(self._indexfilename):
337 if os.path.exists(self._indexfilename):
338 f = open(self._indexfilename, 'w')
338 f = open(self._indexfilename, 'w')
339 f.write('# empty\n')
339 f.write('# empty\n')
340 f.close()
340 f.close()
341
341
342 # here no extension enabled, disabled() lists up everything
342 # here no extension enabled, disabled() lists up everything
343 code = ('import pprint; from mercurial import extensions; '
343 code = ('import pprint; from mercurial import extensions; '
344 'pprint.pprint(extensions.disabled())')
344 'pprint.pprint(extensions.disabled())')
345 out, err = runcmd([sys.executable, '-c', code], env)
345 out, err = runcmd([sys.executable, '-c', code], env)
346 if err:
346 if err:
347 raise DistutilsExecError(err)
347 raise DistutilsExecError(err)
348
348
349 f = open(self._indexfilename, 'w')
349 f = open(self._indexfilename, 'w')
350 f.write('# this file is autogenerated by setup.py\n')
350 f.write('# this file is autogenerated by setup.py\n')
351 f.write('docs = ')
351 f.write('docs = ')
352 f.write(out)
352 f.write(out)
353 f.close()
353 f.close()
354
354
355 class buildhgexe(build_ext):
355 class buildhgexe(build_ext):
356 description = 'compile hg.exe from mercurial/exewrapper.c'
356 description = 'compile hg.exe from mercurial/exewrapper.c'
357
357
358 def build_extensions(self):
358 def build_extensions(self):
359 if os.name != 'nt':
359 if os.name != 'nt':
360 return
360 return
361 if isinstance(self.compiler, HackedMingw32CCompiler):
361 if isinstance(self.compiler, HackedMingw32CCompiler):
362 self.compiler.compiler_so = self.compiler.compiler # no -mdll
362 self.compiler.compiler_so = self.compiler.compiler # no -mdll
363 self.compiler.dll_libraries = [] # no -lmsrvc90
363 self.compiler.dll_libraries = [] # no -lmsrvc90
364 hv = sys.hexversion
364 hv = sys.hexversion
365 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
365 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
366 f = open('mercurial/hgpythonlib.h', 'wb')
366 f = open('mercurial/hgpythonlib.h', 'wb')
367 f.write('/* this file is autogenerated by setup.py */\n')
367 f.write('/* this file is autogenerated by setup.py */\n')
368 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
368 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
369 f.close()
369 f.close()
370 objects = self.compiler.compile(['mercurial/exewrapper.c'],
370 objects = self.compiler.compile(['mercurial/exewrapper.c'],
371 output_dir=self.build_temp)
371 output_dir=self.build_temp)
372 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
372 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
373 target = os.path.join(dir, 'hg')
373 target = os.path.join(dir, 'hg')
374 self.compiler.link_executable(objects, target,
374 self.compiler.link_executable(objects, target,
375 libraries=[],
375 libraries=[],
376 output_dir=self.build_temp)
376 output_dir=self.build_temp)
377
377
378 class hginstallscripts(install_scripts):
378 class hginstallscripts(install_scripts):
379 '''
379 '''
380 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
381 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
382 to the directory for scripts.
382 to the directory for scripts.
383 '''
383 '''
384
384
385 def initialize_options(self):
385 def initialize_options(self):
386 install_scripts.initialize_options(self)
386 install_scripts.initialize_options(self)
387
387
388 self.install_lib = None
388 self.install_lib = None
389
389
390 def finalize_options(self):
390 def finalize_options(self):
391 install_scripts.finalize_options(self)
391 install_scripts.finalize_options(self)
392 self.set_undefined_options('install',
392 self.set_undefined_options('install',
393 ('install_lib', 'install_lib'))
393 ('install_lib', 'install_lib'))
394
394
395 def run(self):
395 def run(self):
396 install_scripts.run(self)
396 install_scripts.run(self)
397
397
398 if (os.path.splitdrive(self.install_dir)[0] !=
398 if (os.path.splitdrive(self.install_dir)[0] !=
399 os.path.splitdrive(self.install_lib)[0]):
399 os.path.splitdrive(self.install_lib)[0]):
400 # 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
401 # absolute path instead
401 # absolute path instead
402 libdir = self.install_lib
402 libdir = self.install_lib
403 else:
403 else:
404 common = os.path.commonprefix((self.install_dir, self.install_lib))
404 common = os.path.commonprefix((self.install_dir, self.install_lib))
405 rest = self.install_dir[len(common):]
405 rest = self.install_dir[len(common):]
406 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])
407
407
408 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
408 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
409
409
410 for outfile in self.outfiles:
410 for outfile in self.outfiles:
411 fp = open(outfile, 'rb')
411 fp = open(outfile, 'rb')
412 data = fp.read()
412 data = fp.read()
413 fp.close()
413 fp.close()
414
414
415 # skip binary files
415 # skip binary files
416 if b('\0') in data:
416 if b('\0') in data:
417 continue
417 continue
418
418
419 data = data.replace(b('@LIBDIR@'), libdir.encode(libdir_escape))
419 data = data.replace(b('@LIBDIR@'), libdir.encode(libdir_escape))
420 fp = open(outfile, 'wb')
420 fp = open(outfile, 'wb')
421 fp.write(data)
421 fp.write(data)
422 fp.close()
422 fp.close()
423
423
424 cmdclass = {'build': hgbuild,
424 cmdclass = {'build': hgbuild,
425 'build_mo': hgbuildmo,
425 'build_mo': hgbuildmo,
426 'build_ext': hgbuildext,
426 'build_ext': hgbuildext,
427 'build_py': hgbuildpy,
427 'build_py': hgbuildpy,
428 'build_hgextindex': buildhgextindex,
428 'build_hgextindex': buildhgextindex,
429 'install_scripts': hginstallscripts,
429 'install_scripts': hginstallscripts,
430 'build_hgexe': buildhgexe,
430 'build_hgexe': buildhgexe,
431 }
431 }
432
432
433 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
433 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
434 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
434 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
435 'hgext.largefiles']
435 'hgext.largefiles']
436
436
437 pymodules = []
437 pymodules = []
438
438
439 common_depends = ['mercurial/util.h']
439 common_depends = ['mercurial/util.h']
440
440
441 extmodules = [
441 extmodules = [
442 Extension('mercurial.base85', ['mercurial/base85.c'],
442 Extension('mercurial.base85', ['mercurial/base85.c'],
443 depends=common_depends),
443 depends=common_depends),
444 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
444 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
445 depends=common_depends),
445 depends=common_depends),
446 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
446 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
447 depends=common_depends),
447 depends=common_depends),
448 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
448 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
449 depends=common_depends),
449 depends=common_depends),
450 Extension('mercurial.parsers', ['mercurial/dirs.c',
450 Extension('mercurial.parsers', ['mercurial/dirs.c',
451 'mercurial/parsers.c',
451 'mercurial/parsers.c',
452 'mercurial/pathencode.c'],
452 'mercurial/pathencode.c'],
453 depends=common_depends),
453 depends=common_depends),
454 ]
454 ]
455
455
456 osutil_ldflags = []
456 osutil_ldflags = []
457
457
458 if sys.platform == 'darwin':
458 if sys.platform == 'darwin':
459 osutil_ldflags += ['-framework', 'ApplicationServices']
459 osutil_ldflags += ['-framework', 'ApplicationServices']
460
460
461 # disable osutil.c under windows + python 2.4 (issue1364)
461 # disable osutil.c under windows + python 2.4 (issue1364)
462 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'):
463 pymodules.append('mercurial.pure.osutil')
463 pymodules.append('mercurial.pure.osutil')
464 else:
464 else:
465 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
465 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
466 extra_link_args=osutil_ldflags,
466 extra_link_args=osutil_ldflags,
467 depends=common_depends))
467 depends=common_depends))
468
468
469 # the -mno-cygwin option has been deprecated for years
469 # the -mno-cygwin option has been deprecated for years
470 Mingw32CCompiler = cygwinccompiler.Mingw32CCompiler
470 Mingw32CCompiler = cygwinccompiler.Mingw32CCompiler
471
471
472 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
472 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
473 def __init__(self, *args, **kwargs):
473 def __init__(self, *args, **kwargs):
474 Mingw32CCompiler.__init__(self, *args, **kwargs)
474 Mingw32CCompiler.__init__(self, *args, **kwargs)
475 for i in 'compiler compiler_so linker_exe linker_so'.split():
475 for i in 'compiler compiler_so linker_exe linker_so'.split():
476 try:
476 try:
477 getattr(self, i).remove('-mno-cygwin')
477 getattr(self, i).remove('-mno-cygwin')
478 except ValueError:
478 except ValueError:
479 pass
479 pass
480
480
481 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
481 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
482
482
483 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
483 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
484 'help/*.txt']}
484 'help/*.txt',
485 'dummycert.pem']}
485
486
486 def ordinarypath(p):
487 def ordinarypath(p):
487 return p and p[0] != '.' and p[-1] != '~'
488 return p and p[0] != '.' and p[-1] != '~'
488
489
489 for root in ('templates',):
490 for root in ('templates',):
490 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
491 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
491 curdir = curdir.split(os.sep, 1)[1]
492 curdir = curdir.split(os.sep, 1)[1]
492 dirs[:] = filter(ordinarypath, dirs)
493 dirs[:] = filter(ordinarypath, dirs)
493 for f in filter(ordinarypath, files):
494 for f in filter(ordinarypath, files):
494 f = os.path.join(curdir, f)
495 f = os.path.join(curdir, f)
495 packagedata['mercurial'].append(f)
496 packagedata['mercurial'].append(f)
496
497
497 datafiles = []
498 datafiles = []
498 setupversion = version
499 setupversion = version
499 extra = {}
500 extra = {}
500
501
501 if py2exeloaded:
502 if py2exeloaded:
502 extra['console'] = [
503 extra['console'] = [
503 {'script':'hg',
504 {'script':'hg',
504 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
505 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
505 'product_version':version}]
506 'product_version':version}]
506 # sub command of 'build' because 'py2exe' does not handle sub_commands
507 # sub command of 'build' because 'py2exe' does not handle sub_commands
507 build.sub_commands.insert(0, ('build_hgextindex', None))
508 build.sub_commands.insert(0, ('build_hgextindex', None))
508
509
509 if os.name == 'nt':
510 if os.name == 'nt':
510 # Windows binary file versions for exe/dll files must have the
511 # Windows binary file versions for exe/dll files must have the
511 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
512 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
512 setupversion = version.split('+', 1)[0]
513 setupversion = version.split('+', 1)[0]
513
514
514 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
515 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
515 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
516 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
516 if version:
517 if version:
517 version = version[0]
518 version = version[0]
518 xcode4 = (version.startswith('Xcode') and
519 xcode4 = (version.startswith('Xcode') and
519 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
520 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
520 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
521 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
521 else:
522 else:
522 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
523 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
523 # installed, but instead with only command-line tools. Assume
524 # installed, but instead with only command-line tools. Assume
524 # that only happens on >= Lion, thus no PPC support.
525 # that only happens on >= Lion, thus no PPC support.
525 xcode4 = True
526 xcode4 = True
526 xcode51 = False
527 xcode51 = False
527
528
528 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
529 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
529 # distutils.sysconfig
530 # distutils.sysconfig
530 if xcode4:
531 if xcode4:
531 os.environ['ARCHFLAGS'] = ''
532 os.environ['ARCHFLAGS'] = ''
532
533
533 # XCode 5.1 changes clang such that it now fails to compile if the
534 # XCode 5.1 changes clang such that it now fails to compile if the
534 # -mno-fused-madd flag is passed, but the version of Python shipped with
535 # -mno-fused-madd flag is passed, but the version of Python shipped with
535 # OS X 10.9 Mavericks includes this flag. This causes problems in all
536 # OS X 10.9 Mavericks includes this flag. This causes problems in all
536 # C extension modules, and a bug has been filed upstream at
537 # C extension modules, and a bug has been filed upstream at
537 # http://bugs.python.org/issue21244. We also need to patch this here
538 # http://bugs.python.org/issue21244. We also need to patch this here
538 # so Mercurial can continue to compile in the meantime.
539 # so Mercurial can continue to compile in the meantime.
539 if xcode51:
540 if xcode51:
540 cflags = get_config_var('CFLAGS')
541 cflags = get_config_var('CFLAGS')
541 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
542 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
542 os.environ['CFLAGS'] = (
543 os.environ['CFLAGS'] = (
543 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
544 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
544
545
545 setup(name='mercurial',
546 setup(name='mercurial',
546 version=setupversion,
547 version=setupversion,
547 author='Matt Mackall and many others',
548 author='Matt Mackall and many others',
548 author_email='mercurial@selenic.com',
549 author_email='mercurial@selenic.com',
549 url='http://mercurial.selenic.com/',
550 url='http://mercurial.selenic.com/',
550 download_url='http://mercurial.selenic.com/release/',
551 download_url='http://mercurial.selenic.com/release/',
551 description=('Fast scalable distributed SCM (revision control, version '
552 description=('Fast scalable distributed SCM (revision control, version '
552 'control) system'),
553 'control) system'),
553 long_description=('Mercurial is a distributed SCM tool written in Python.'
554 long_description=('Mercurial is a distributed SCM tool written in Python.'
554 ' It is used by a number of large projects that require'
555 ' It is used by a number of large projects that require'
555 ' fast, reliable distributed revision control, such as '
556 ' fast, reliable distributed revision control, such as '
556 'Mozilla.'),
557 'Mozilla.'),
557 license='GNU GPLv2 or any later version',
558 license='GNU GPLv2 or any later version',
558 classifiers=[
559 classifiers=[
559 'Development Status :: 6 - Mature',
560 'Development Status :: 6 - Mature',
560 'Environment :: Console',
561 'Environment :: Console',
561 'Intended Audience :: Developers',
562 'Intended Audience :: Developers',
562 'Intended Audience :: System Administrators',
563 'Intended Audience :: System Administrators',
563 'License :: OSI Approved :: GNU General Public License (GPL)',
564 'License :: OSI Approved :: GNU General Public License (GPL)',
564 'Natural Language :: Danish',
565 'Natural Language :: Danish',
565 'Natural Language :: English',
566 'Natural Language :: English',
566 'Natural Language :: German',
567 'Natural Language :: German',
567 'Natural Language :: Italian',
568 'Natural Language :: Italian',
568 'Natural Language :: Japanese',
569 'Natural Language :: Japanese',
569 'Natural Language :: Portuguese (Brazilian)',
570 'Natural Language :: Portuguese (Brazilian)',
570 'Operating System :: Microsoft :: Windows',
571 'Operating System :: Microsoft :: Windows',
571 'Operating System :: OS Independent',
572 'Operating System :: OS Independent',
572 'Operating System :: POSIX',
573 'Operating System :: POSIX',
573 'Programming Language :: C',
574 'Programming Language :: C',
574 'Programming Language :: Python',
575 'Programming Language :: Python',
575 'Topic :: Software Development :: Version Control',
576 'Topic :: Software Development :: Version Control',
576 ],
577 ],
577 scripts=scripts,
578 scripts=scripts,
578 packages=packages,
579 packages=packages,
579 py_modules=pymodules,
580 py_modules=pymodules,
580 ext_modules=extmodules,
581 ext_modules=extmodules,
581 data_files=datafiles,
582 data_files=datafiles,
582 package_data=packagedata,
583 package_data=packagedata,
583 cmdclass=cmdclass,
584 cmdclass=cmdclass,
584 distclass=hgdist,
585 distclass=hgdist,
585 options={'py2exe': {'packages': ['hgext', 'email']},
586 options={'py2exe': {'packages': ['hgext', 'email']},
586 'bdist_mpkg': {'zipdist': False,
587 'bdist_mpkg': {'zipdist': False,
587 'license': 'COPYING',
588 'license': 'COPYING',
588 'readme': 'contrib/macosx/Readme.html',
589 'readme': 'contrib/macosx/Readme.html',
589 'welcome': 'contrib/macosx/Welcome.html',
590 'welcome': 'contrib/macosx/Welcome.html',
590 },
591 },
591 },
592 },
592 **extra)
593 **extra)
@@ -1,343 +1,347
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 _lsprof.Profiler # silence unused import warning
152 return True
152 return True
153 except ImportError:
153 except ImportError:
154 return False
154 return False
155
155
156 @check("gettext", "GNU Gettext (msgfmt)")
156 @check("gettext", "GNU Gettext (msgfmt)")
157 def has_gettext():
157 def has_gettext():
158 return matchoutput('msgfmt --version', 'GNU gettext-tools')
158 return matchoutput('msgfmt --version', 'GNU gettext-tools')
159
159
160 @check("git", "git command line client")
160 @check("git", "git command line client")
161 def has_git():
161 def has_git():
162 return matchoutput('git --version 2>&1', r'^git version')
162 return matchoutput('git --version 2>&1', r'^git version')
163
163
164 @check("docutils", "Docutils text processing library")
164 @check("docutils", "Docutils text processing library")
165 def has_docutils():
165 def has_docutils():
166 try:
166 try:
167 from docutils.core import publish_cmdline
167 from docutils.core import publish_cmdline
168 publish_cmdline # silence unused import
168 publish_cmdline # silence unused import
169 return True
169 return True
170 except ImportError:
170 except ImportError:
171 return False
171 return False
172
172
173 def getsvnversion():
173 def getsvnversion():
174 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
174 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
175 if not m:
175 if not m:
176 return (0, 0)
176 return (0, 0)
177 return (int(m.group(1)), int(m.group(2)))
177 return (int(m.group(1)), int(m.group(2)))
178
178
179 @check("svn15", "subversion client and admin tools >= 1.5")
179 @check("svn15", "subversion client and admin tools >= 1.5")
180 def has_svn15():
180 def has_svn15():
181 return getsvnversion() >= (1, 5)
181 return getsvnversion() >= (1, 5)
182
182
183 @check("svn13", "subversion client and admin tools >= 1.3")
183 @check("svn13", "subversion client and admin tools >= 1.3")
184 def has_svn13():
184 def has_svn13():
185 return getsvnversion() >= (1, 3)
185 return getsvnversion() >= (1, 3)
186
186
187 @check("svn", "subversion client and admin tools")
187 @check("svn", "subversion client and admin tools")
188 def has_svn():
188 def has_svn():
189 return matchoutput('svn --version 2>&1', r'^svn, version') and \
189 return matchoutput('svn --version 2>&1', r'^svn, version') and \
190 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
190 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
191
191
192 @check("svn-bindings", "subversion python bindings")
192 @check("svn-bindings", "subversion python bindings")
193 def has_svn_bindings():
193 def has_svn_bindings():
194 try:
194 try:
195 import svn.core
195 import svn.core
196 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
196 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
197 if version < (1, 4):
197 if version < (1, 4):
198 return False
198 return False
199 return True
199 return True
200 except ImportError:
200 except ImportError:
201 return False
201 return False
202
202
203 @check("p4", "Perforce server and client")
203 @check("p4", "Perforce server and client")
204 def has_p4():
204 def has_p4():
205 return (matchoutput('p4 -V', r'Rev\. P4/') and
205 return (matchoutput('p4 -V', r'Rev\. P4/') and
206 matchoutput('p4d -V', r'Rev\. P4D/'))
206 matchoutput('p4d -V', r'Rev\. P4D/'))
207
207
208 @check("symlink", "symbolic links")
208 @check("symlink", "symbolic links")
209 def has_symlink():
209 def has_symlink():
210 if getattr(os, "symlink", None) is None:
210 if getattr(os, "symlink", None) is None:
211 return False
211 return False
212 name = tempfile.mktemp(dir='.', prefix=tempprefix)
212 name = tempfile.mktemp(dir='.', prefix=tempprefix)
213 try:
213 try:
214 os.symlink(".", name)
214 os.symlink(".", name)
215 os.unlink(name)
215 os.unlink(name)
216 return True
216 return True
217 except (OSError, AttributeError):
217 except (OSError, AttributeError):
218 return False
218 return False
219
219
220 @check("hardlink", "hardlinks")
220 @check("hardlink", "hardlinks")
221 def has_hardlink():
221 def has_hardlink():
222 from mercurial import util
222 from mercurial import util
223 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
223 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
224 os.close(fh)
224 os.close(fh)
225 name = tempfile.mktemp(dir='.', prefix=tempprefix)
225 name = tempfile.mktemp(dir='.', prefix=tempprefix)
226 try:
226 try:
227 try:
227 try:
228 util.oslink(fn, name)
228 util.oslink(fn, name)
229 os.unlink(name)
229 os.unlink(name)
230 return True
230 return True
231 except OSError:
231 except OSError:
232 return False
232 return False
233 finally:
233 finally:
234 os.unlink(fn)
234 os.unlink(fn)
235
235
236 @check("tla", "GNU Arch tla client")
236 @check("tla", "GNU Arch tla client")
237 def has_tla():
237 def has_tla():
238 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
238 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
239
239
240 @check("gpg", "gpg client")
240 @check("gpg", "gpg client")
241 def has_gpg():
241 def has_gpg():
242 return matchoutput('gpg --version 2>&1', r'GnuPG')
242 return matchoutput('gpg --version 2>&1', r'GnuPG')
243
243
244 @check("unix-permissions", "unix-style permissions")
244 @check("unix-permissions", "unix-style permissions")
245 def has_unix_permissions():
245 def has_unix_permissions():
246 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
246 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
247 try:
247 try:
248 fname = os.path.join(d, 'foo')
248 fname = os.path.join(d, 'foo')
249 for umask in (077, 007, 022):
249 for umask in (077, 007, 022):
250 os.umask(umask)
250 os.umask(umask)
251 f = open(fname, 'w')
251 f = open(fname, 'w')
252 f.close()
252 f.close()
253 mode = os.stat(fname).st_mode
253 mode = os.stat(fname).st_mode
254 os.unlink(fname)
254 os.unlink(fname)
255 if mode & 0777 != ~umask & 0666:
255 if mode & 0777 != ~umask & 0666:
256 return False
256 return False
257 return True
257 return True
258 finally:
258 finally:
259 os.rmdir(d)
259 os.rmdir(d)
260
260
261 @check("root", "root permissions")
261 @check("root", "root permissions")
262 def has_root():
262 def has_root():
263 return getattr(os, 'geteuid', None) and os.geteuid() == 0
263 return getattr(os, 'geteuid', None) and os.geteuid() == 0
264
264
265 @check("pyflakes", "Pyflakes python linter")
265 @check("pyflakes", "Pyflakes python linter")
266 def has_pyflakes():
266 def has_pyflakes():
267 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
267 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
268 r"<stdin>:1: 're' imported but unused",
268 r"<stdin>:1: 're' imported but unused",
269 True)
269 True)
270
270
271 @check("pygments", "Pygments source highlighting library")
271 @check("pygments", "Pygments source highlighting library")
272 def has_pygments():
272 def has_pygments():
273 try:
273 try:
274 import pygments
274 import pygments
275 pygments.highlight # silence unused import warning
275 pygments.highlight # silence unused import warning
276 return True
276 return True
277 except ImportError:
277 except ImportError:
278 return False
278 return False
279
279
280 @check("python243", "python >= 2.4.3")
280 @check("python243", "python >= 2.4.3")
281 def has_python243():
281 def has_python243():
282 return sys.version_info >= (2, 4, 3)
282 return sys.version_info >= (2, 4, 3)
283
283
284 @check("outer-repo", "outer repo")
284 @check("outer-repo", "outer repo")
285 def has_outer_repo():
285 def has_outer_repo():
286 # 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
287 return not matchoutput('hg root 2>&1',
287 return not matchoutput('hg root 2>&1',
288 r'abort: no repository found', True)
288 r'abort: no repository found', True)
289
289
290 @check("ssl", "python >= 2.6 ssl module and python OpenSSL")
290 @check("ssl", "python >= 2.6 ssl module and python OpenSSL")
291 def has_ssl():
291 def has_ssl():
292 try:
292 try:
293 import ssl
293 import ssl
294 ssl.wrap_socket # silence unused import warning
294 ssl.wrap_socket # silence unused import warning
295 import OpenSSL
295 import OpenSSL
296 OpenSSL.SSL.Context
296 OpenSSL.SSL.Context
297 return True
297 return True
298 except ImportError:
298 except ImportError:
299 return False
299 return False
300
300
301 @check("windows", "Windows")
301 @check("windows", "Windows")
302 def has_windows():
302 def has_windows():
303 return os.name == 'nt'
303 return os.name == 'nt'
304
304
305 @check("system-sh", "system() uses sh")
305 @check("system-sh", "system() uses sh")
306 def has_system_sh():
306 def has_system_sh():
307 return os.name != 'nt'
307 return os.name != 'nt'
308
308
309 @check("serve", "platform and python can manage 'hg serve -d'")
309 @check("serve", "platform and python can manage 'hg serve -d'")
310 def has_serve():
310 def has_serve():
311 return os.name != 'nt' # gross approximation
311 return os.name != 'nt' # gross approximation
312
312
313 @check("test-repo", "running tests from repository")
313 @check("test-repo", "running tests from repository")
314 def has_test_repo():
314 def has_test_repo():
315 t = os.environ["TESTDIR"]
315 t = os.environ["TESTDIR"]
316 return os.path.isdir(os.path.join(t, "..", ".hg"))
316 return os.path.isdir(os.path.join(t, "..", ".hg"))
317
317
318 @check("tic", "terminfo compiler and curses module")
318 @check("tic", "terminfo compiler and curses module")
319 def has_tic():
319 def has_tic():
320 try:
320 try:
321 import curses
321 import curses
322 curses.COLOR_BLUE
322 curses.COLOR_BLUE
323 return matchoutput('test -x "`which tic`"', '')
323 return matchoutput('test -x "`which tic`"', '')
324 except ImportError:
324 except ImportError:
325 return False
325 return False
326
326
327 @check("msys", "Windows with MSYS")
327 @check("msys", "Windows with MSYS")
328 def has_msys():
328 def has_msys():
329 return os.getenv('MSYSTEM')
329 return os.getenv('MSYSTEM')
330
330
331 @check("aix", "AIX")
331 @check("aix", "AIX")
332 def has_aix():
332 def has_aix():
333 return sys.platform.startswith("aix")
333 return sys.platform.startswith("aix")
334
334
335 @check("osx", "OS X")
336 def has_osx():
337 return sys.platform == 'darwin'
338
335 @check("absimport", "absolute_import in __future__")
339 @check("absimport", "absolute_import in __future__")
336 def has_absimport():
340 def has_absimport():
337 import __future__
341 import __future__
338 from mercurial import util
342 from mercurial import util
339 return util.safehasattr(__future__, "absolute_import")
343 return util.safehasattr(__future__, "absolute_import")
340
344
341 @check("py3k", "running with Python 3.x")
345 @check("py3k", "running with Python 3.x")
342 def has_py3k():
346 def has_py3k():
343 return 3 == sys.version_info[0]
347 return 3 == sys.version_info[0]
@@ -1,281 +1,292
1 #require serve ssl
1 #require serve ssl
2
2
3 Proper https client requires the built-in ssl from Python 2.6.
3 Proper https client requires the built-in ssl from Python 2.6.
4
4
5 Certificates created with:
5 Certificates created with:
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 Can be dumped with:
8 Can be dumped with:
9 openssl x509 -in pub.pem -text
9 openssl x509 -in pub.pem -text
10
10
11 $ cat << EOT > priv.pem
11 $ cat << EOT > priv.pem
12 > -----BEGIN PRIVATE KEY-----
12 > -----BEGIN PRIVATE KEY-----
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 > HY8gUVkVRVs=
20 > HY8gUVkVRVs=
21 > -----END PRIVATE KEY-----
21 > -----END PRIVATE KEY-----
22 > EOT
22 > EOT
23
23
24 $ cat << EOT > pub.pem
24 $ cat << EOT > pub.pem
25 > -----BEGIN CERTIFICATE-----
25 > -----BEGIN CERTIFICATE-----
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 > -----END CERTIFICATE-----
35 > -----END CERTIFICATE-----
36 > EOT
36 > EOT
37 $ cat priv.pem pub.pem >> server.pem
37 $ cat priv.pem pub.pem >> server.pem
38 $ PRIV=`pwd`/server.pem
38 $ PRIV=`pwd`/server.pem
39
39
40 $ cat << EOT > pub-other.pem
40 $ cat << EOT > pub-other.pem
41 > -----BEGIN CERTIFICATE-----
41 > -----BEGIN CERTIFICATE-----
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 > -----END CERTIFICATE-----
51 > -----END CERTIFICATE-----
52 > EOT
52 > EOT
53
53
54 pub.pem patched with other notBefore / notAfter:
54 pub.pem patched with other notBefore / notAfter:
55
55
56 $ cat << EOT > pub-not-yet.pem
56 $ cat << EOT > pub-not-yet.pem
57 > -----BEGIN CERTIFICATE-----
57 > -----BEGIN CERTIFICATE-----
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 > -----END CERTIFICATE-----
66 > -----END CERTIFICATE-----
67 > EOT
67 > EOT
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69
69
70 $ cat << EOT > pub-expired.pem
70 $ cat << EOT > pub-expired.pem
71 > -----BEGIN CERTIFICATE-----
71 > -----BEGIN CERTIFICATE-----
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 > -----END CERTIFICATE-----
80 > -----END CERTIFICATE-----
81 > EOT
81 > EOT
82 $ cat priv.pem pub-expired.pem > server-expired.pem
82 $ cat priv.pem pub-expired.pem > server-expired.pem
83
83
84 $ hg init test
84 $ hg init test
85 $ cd test
85 $ cd test
86 $ echo foo>foo
86 $ echo foo>foo
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
88 $ echo foo>foo.d/foo
88 $ echo foo>foo.d/foo
89 $ echo bar>foo.d/bAr.hg.d/BaR
89 $ echo bar>foo.d/bAr.hg.d/BaR
90 $ echo bar>foo.d/baR.d.hg/bAR
90 $ echo bar>foo.d/baR.d.hg/bAR
91 $ hg commit -A -m 1
91 $ hg commit -A -m 1
92 adding foo
92 adding foo
93 adding foo.d/bAr.hg.d/BaR
93 adding foo.d/bAr.hg.d/BaR
94 adding foo.d/baR.d.hg/bAR
94 adding foo.d/baR.d.hg/bAR
95 adding foo.d/foo
95 adding foo.d/foo
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
97 $ cat ../hg0.pid >> $DAEMON_PIDS
97 $ cat ../hg0.pid >> $DAEMON_PIDS
98
98
99 cacert not found
99 cacert not found
100
100
101 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
101 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
102 abort: could not find web.cacerts: no-such.pem
102 abort: could not find web.cacerts: no-such.pem
103 [255]
103 [255]
104
104
105 Test server address cannot be reused
105 Test server address cannot be reused
106
106
107 #if windows
107 #if windows
108 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
108 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
109 abort: cannot start server at ':$HGPORT':
109 abort: cannot start server at ':$HGPORT':
110 [255]
110 [255]
111 #else
111 #else
112 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
112 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
113 abort: cannot start server at ':$HGPORT': Address already in use
113 abort: cannot start server at ':$HGPORT': Address already in use
114 [255]
114 [255]
115 #endif
115 #endif
116 $ cd ..
116 $ cd ..
117
117
118 OS X has a dummy CA cert that enables use of the system CA store
119
120 $ DISABLEOSXDUMMYCERT=
121 #if osx
122 $ hg clone https://localhost:$HGPORT/ copy-pull
123 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
124 [255]
125
126 $ DISABLEOSXDUMMYCERT="--config=web.cacerts="
127 #endif
128
118 clone via pull
129 clone via pull
119
130
120 $ hg clone https://localhost:$HGPORT/ copy-pull
131 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLEOSXDUMMYCERT
121 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
132 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
122 requesting all changes
133 requesting all changes
123 adding changesets
134 adding changesets
124 adding manifests
135 adding manifests
125 adding file changes
136 adding file changes
126 added 1 changesets with 4 changes to 4 files
137 added 1 changesets with 4 changes to 4 files
127 updating to branch default
138 updating to branch default
128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 $ hg verify -R copy-pull
140 $ hg verify -R copy-pull
130 checking changesets
141 checking changesets
131 checking manifests
142 checking manifests
132 crosschecking files in changesets and manifests
143 crosschecking files in changesets and manifests
133 checking files
144 checking files
134 4 files, 1 changesets, 4 total revisions
145 4 files, 1 changesets, 4 total revisions
135 $ cd test
146 $ cd test
136 $ echo bar > bar
147 $ echo bar > bar
137 $ hg commit -A -d '1 0' -m 2
148 $ hg commit -A -d '1 0' -m 2
138 adding bar
149 adding bar
139 $ cd ..
150 $ cd ..
140
151
141 pull without cacert
152 pull without cacert
142
153
143 $ cd copy-pull
154 $ cd copy-pull
144 $ echo '[hooks]' >> .hg/hgrc
155 $ echo '[hooks]' >> .hg/hgrc
145 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
156 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
146 $ hg pull
157 $ hg pull $DISABLEOSXDUMMYCERT
147 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
158 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
148 pulling from https://localhost:$HGPORT/
159 pulling from https://localhost:$HGPORT/
149 searching for changes
160 searching for changes
150 adding changesets
161 adding changesets
151 adding manifests
162 adding manifests
152 adding file changes
163 adding file changes
153 added 1 changesets with 1 changes to 1 files
164 added 1 changesets with 1 changes to 1 files
154 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
165 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
155 (run 'hg update' to get a working copy)
166 (run 'hg update' to get a working copy)
156 $ cd ..
167 $ cd ..
157
168
158 cacert configured in local repo
169 cacert configured in local repo
159
170
160 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
171 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
161 $ echo "[web]" >> copy-pull/.hg/hgrc
172 $ echo "[web]" >> copy-pull/.hg/hgrc
162 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
173 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
163 $ hg -R copy-pull pull --traceback
174 $ hg -R copy-pull pull --traceback
164 pulling from https://localhost:$HGPORT/
175 pulling from https://localhost:$HGPORT/
165 searching for changes
176 searching for changes
166 no changes found
177 no changes found
167 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
178 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
168
179
169 cacert configured globally, also testing expansion of environment
180 cacert configured globally, also testing expansion of environment
170 variables in the filename
181 variables in the filename
171
182
172 $ echo "[web]" >> $HGRCPATH
183 $ echo "[web]" >> $HGRCPATH
173 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
184 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
174 $ P=`pwd` hg -R copy-pull pull
185 $ P=`pwd` hg -R copy-pull pull
175 pulling from https://localhost:$HGPORT/
186 pulling from https://localhost:$HGPORT/
176 searching for changes
187 searching for changes
177 no changes found
188 no changes found
178 $ P=`pwd` hg -R copy-pull pull --insecure
189 $ P=`pwd` hg -R copy-pull pull --insecure
179 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
190 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
180 pulling from https://localhost:$HGPORT/
191 pulling from https://localhost:$HGPORT/
181 searching for changes
192 searching for changes
182 no changes found
193 no changes found
183
194
184 cacert mismatch
195 cacert mismatch
185
196
186 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
197 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
187 abort: 127.0.0.1 certificate error: certificate is for localhost
198 abort: 127.0.0.1 certificate error: certificate is for localhost
188 (configure hostfingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca or use --insecure to connect insecurely)
199 (configure hostfingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca or use --insecure to connect insecurely)
189 [255]
200 [255]
190 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
201 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
191 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
202 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
192 pulling from https://127.0.0.1:$HGPORT/
203 pulling from https://127.0.0.1:$HGPORT/
193 searching for changes
204 searching for changes
194 no changes found
205 no changes found
195 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
206 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
196 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
207 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
197 [255]
208 [255]
198 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
209 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
199 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
210 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
200 pulling from https://localhost:$HGPORT/
211 pulling from https://localhost:$HGPORT/
201 searching for changes
212 searching for changes
202 no changes found
213 no changes found
203
214
204 Test server cert which isn't valid yet
215 Test server cert which isn't valid yet
205
216
206 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
217 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
207 $ cat hg1.pid >> $DAEMON_PIDS
218 $ cat hg1.pid >> $DAEMON_PIDS
208 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
219 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
209 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
220 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
210 [255]
221 [255]
211
222
212 Test server cert which no longer is valid
223 Test server cert which no longer is valid
213
224
214 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
225 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
215 $ cat hg2.pid >> $DAEMON_PIDS
226 $ cat hg2.pid >> $DAEMON_PIDS
216 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
227 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
217 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
228 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
218 [255]
229 [255]
219
230
220 Fingerprints
231 Fingerprints
221
232
222 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
233 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
223 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
234 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
224 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
235 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
225
236
226 - works without cacerts
237 - works without cacerts
227 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
238 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
228 5fed3813f7f5
239 5fed3813f7f5
229
240
230 - fails when cert doesn't match hostname (port is ignored)
241 - fails when cert doesn't match hostname (port is ignored)
231 $ hg -R copy-pull id https://localhost:$HGPORT1/
242 $ hg -R copy-pull id https://localhost:$HGPORT1/
232 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
243 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
233 (check hostfingerprint configuration)
244 (check hostfingerprint configuration)
234 [255]
245 [255]
235
246
236
247
237 - ignores that certificate doesn't match hostname
248 - ignores that certificate doesn't match hostname
238 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
249 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
239 5fed3813f7f5
250 5fed3813f7f5
240
251
241 HGPORT1 is reused below for tinyproxy tests. Kill that server.
252 HGPORT1 is reused below for tinyproxy tests. Kill that server.
242 $ "$TESTDIR/killdaemons.py" hg1.pid
253 $ "$TESTDIR/killdaemons.py" hg1.pid
243
254
244 Prepare for connecting through proxy
255 Prepare for connecting through proxy
245
256
246 $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
257 $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
247 $ while [ ! -f proxy.pid ]; do sleep 0; done
258 $ while [ ! -f proxy.pid ]; do sleep 0; done
248 $ cat proxy.pid >> $DAEMON_PIDS
259 $ cat proxy.pid >> $DAEMON_PIDS
249
260
250 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
261 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
251 $ echo "always=True" >> copy-pull/.hg/hgrc
262 $ echo "always=True" >> copy-pull/.hg/hgrc
252 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
263 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
253 $ echo "localhost =" >> copy-pull/.hg/hgrc
264 $ echo "localhost =" >> copy-pull/.hg/hgrc
254
265
255 Test unvalidated https through proxy
266 Test unvalidated https through proxy
256
267
257 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
268 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
258 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
269 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
259 pulling from https://localhost:$HGPORT/
270 pulling from https://localhost:$HGPORT/
260 searching for changes
271 searching for changes
261 no changes found
272 no changes found
262
273
263 Test https with cacert and fingerprint through proxy
274 Test https with cacert and fingerprint through proxy
264
275
265 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
276 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
266 pulling from https://localhost:$HGPORT/
277 pulling from https://localhost:$HGPORT/
267 searching for changes
278 searching for changes
268 no changes found
279 no changes found
269 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
280 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
270 pulling from https://127.0.0.1:$HGPORT/
281 pulling from https://127.0.0.1:$HGPORT/
271 searching for changes
282 searching for changes
272 no changes found
283 no changes found
273
284
274 Test https with cert problems through proxy
285 Test https with cert problems through proxy
275
286
276 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
287 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
277 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
288 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
278 [255]
289 [255]
279 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
290 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
280 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
291 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
281 [255]
292 [255]
General Comments 0
You need to be logged in to leave comments. Login now