##// END OF EJS Templates
setup: tweak error message for Python 3...
Gregory Szorc -
r42366:cd1bede3 stable
parent child Browse files
Show More
@@ -1,1466 +1,1477
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 os
7 import os
8
8
9 supportedpy = '~= 2.7'
9 supportedpy = '~= 2.7'
10 if os.environ.get('HGALLOWPYTHON3', ''):
10 if os.environ.get('HGALLOWPYTHON3', ''):
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
13 # due to a bug in % formatting in bytestrings.
13 # due to a bug in % formatting in bytestrings.
14 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
14 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
15 # codecs.escape_encode() where it raises SystemError on empty bytestring
15 # codecs.escape_encode() where it raises SystemError on empty bytestring
16 # bug link: https://bugs.python.org/issue25270
16 # bug link: https://bugs.python.org/issue25270
17 #
17 #
18 # TODO: when we actually work on Python 3, use this string as the
18 # TODO: when we actually work on Python 3, use this string as the
19 # actual supportedpy string.
19 # actual supportedpy string.
20 supportedpy = ','.join([
20 supportedpy = ','.join([
21 '>=2.7',
21 '>=2.7',
22 '!=3.0.*',
22 '!=3.0.*',
23 '!=3.1.*',
23 '!=3.1.*',
24 '!=3.2.*',
24 '!=3.2.*',
25 '!=3.3.*',
25 '!=3.3.*',
26 '!=3.4.*',
26 '!=3.4.*',
27 '!=3.5.0',
27 '!=3.5.0',
28 '!=3.5.1',
28 '!=3.5.1',
29 '!=3.5.2',
29 '!=3.5.2',
30 '!=3.6.0',
30 '!=3.6.0',
31 '!=3.6.1',
31 '!=3.6.1',
32 ])
32 ])
33
33
34 import sys, platform
34 import sys, platform
35 if sys.version_info[0] >= 3:
35 if sys.version_info[0] >= 3:
36 printf = eval('print')
36 printf = eval('print')
37 libdir_escape = 'unicode_escape'
37 libdir_escape = 'unicode_escape'
38 def sysstr(s):
38 def sysstr(s):
39 return s.decode('latin-1')
39 return s.decode('latin-1')
40 else:
40 else:
41 libdir_escape = 'string_escape'
41 libdir_escape = 'string_escape'
42 def printf(*args, **kwargs):
42 def printf(*args, **kwargs):
43 f = kwargs.get('file', sys.stdout)
43 f = kwargs.get('file', sys.stdout)
44 end = kwargs.get('end', '\n')
44 end = kwargs.get('end', '\n')
45 f.write(b' '.join(args) + end)
45 f.write(b' '.join(args) + end)
46 def sysstr(s):
46 def sysstr(s):
47 return s
47 return s
48
48
49 # Attempt to guide users to a modern pip - this means that 2.6 users
49 # Attempt to guide users to a modern pip - this means that 2.6 users
50 # should have a chance of getting a 4.2 release, and when we ratchet
50 # should have a chance of getting a 4.2 release, and when we ratchet
51 # the version requirement forward again hopefully everyone will get
51 # the version requirement forward again hopefully everyone will get
52 # something that works for them.
52 # something that works for them.
53 if sys.version_info < (2, 7, 0, 'final'):
53 if sys.version_info < (2, 7, 0, 'final'):
54 pip_message = ('This may be due to an out of date pip. '
54 pip_message = ('This may be due to an out of date pip. '
55 'Make sure you have pip >= 9.0.1.')
55 'Make sure you have pip >= 9.0.1.')
56 try:
56 try:
57 import pip
57 import pip
58 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
58 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
59 if pip_version < (9, 0, 1) :
59 if pip_version < (9, 0, 1) :
60 pip_message = (
60 pip_message = (
61 'Your pip version is out of date, please install '
61 'Your pip version is out of date, please install '
62 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
62 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
63 else:
63 else:
64 # pip is new enough - it must be something else
64 # pip is new enough - it must be something else
65 pip_message = ''
65 pip_message = ''
66 except Exception:
66 except Exception:
67 pass
67 pass
68 error = """
68 error = """
69 Mercurial does not support Python older than 2.7.
69 Mercurial does not support Python older than 2.7.
70 Python {py} detected.
70 Python {py} detected.
71 {pip}
71 {pip}
72 """.format(py=sys.version_info, pip=pip_message)
72 """.format(py=sys.version_info, pip=pip_message)
73 printf(error, file=sys.stderr)
73 printf(error, file=sys.stderr)
74 sys.exit(1)
74 sys.exit(1)
75
75
76 # We don't yet officially support Python 3. But we want to allow developers to
76 # We don't yet officially support Python 3. But we want to allow developers to
77 # hack on. Detect and disallow running on Python 3 by default. But provide a
77 # hack on. Detect and disallow running on Python 3 by default. But provide a
78 # backdoor to enable working on Python 3.
78 # backdoor to enable working on Python 3.
79 if sys.version_info[0] != 2:
79 if sys.version_info[0] != 2:
80 badpython = True
80 badpython = True
81
81
82 # Allow Python 3 from source checkouts.
82 # Allow Python 3 from source checkouts.
83 if os.path.isdir('.hg') or 'HGPYTHON3' in os.environ:
83 if os.path.isdir('.hg') or 'HGPYTHON3' in os.environ:
84 badpython = False
84 badpython = False
85
85
86 if badpython:
86 if badpython:
87 error = """
87 error = """
88 Mercurial only supports Python 2.7.
89 Python {py} detected.
88 Python {py} detected.
90 Please re-run with Python 2.7.
89
91 """.format(py=sys.version_info)
90 Mercurial currently has beta support for Python 3 and use of Python 2.7 is
91 recommended for the best experience.
92
93 Please re-run with Python 2.7 for a faster, less buggy experience.
94
95 If you would like to beta test Mercurial with Python 3, this error can
96 be suppressed by defining the HGPYTHON3 environment variable when invoking
97 this command. No special environment variables or configuration changes are
98 necessary to run `hg` with Python 3.
99
100 See https://www.mercurial-scm.org/wiki/Python3 for more on Mercurial's
101 Python 3 support.
102 """.format(py='.'.join('%d' % x for x in sys.version_info[0:2]))
92
103
93 printf(error, file=sys.stderr)
104 printf(error, file=sys.stderr)
94 sys.exit(1)
105 sys.exit(1)
95
106
96 # Solaris Python packaging brain damage
107 # Solaris Python packaging brain damage
97 try:
108 try:
98 import hashlib
109 import hashlib
99 sha = hashlib.sha1()
110 sha = hashlib.sha1()
100 except ImportError:
111 except ImportError:
101 try:
112 try:
102 import sha
113 import sha
103 sha.sha # silence unused import warning
114 sha.sha # silence unused import warning
104 except ImportError:
115 except ImportError:
105 raise SystemExit(
116 raise SystemExit(
106 "Couldn't import standard hashlib (incomplete Python install).")
117 "Couldn't import standard hashlib (incomplete Python install).")
107
118
108 try:
119 try:
109 import zlib
120 import zlib
110 zlib.compressobj # silence unused import warning
121 zlib.compressobj # silence unused import warning
111 except ImportError:
122 except ImportError:
112 raise SystemExit(
123 raise SystemExit(
113 "Couldn't import standard zlib (incomplete Python install).")
124 "Couldn't import standard zlib (incomplete Python install).")
114
125
115 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
126 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
116 isironpython = False
127 isironpython = False
117 try:
128 try:
118 isironpython = (platform.python_implementation()
129 isironpython = (platform.python_implementation()
119 .lower().find("ironpython") != -1)
130 .lower().find("ironpython") != -1)
120 except AttributeError:
131 except AttributeError:
121 pass
132 pass
122
133
123 if isironpython:
134 if isironpython:
124 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
135 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
125 else:
136 else:
126 try:
137 try:
127 import bz2
138 import bz2
128 bz2.BZ2Compressor # silence unused import warning
139 bz2.BZ2Compressor # silence unused import warning
129 except ImportError:
140 except ImportError:
130 raise SystemExit(
141 raise SystemExit(
131 "Couldn't import standard bz2 (incomplete Python install).")
142 "Couldn't import standard bz2 (incomplete Python install).")
132
143
133 ispypy = "PyPy" in sys.version
144 ispypy = "PyPy" in sys.version
134
145
135 hgrustext = os.environ.get('HGWITHRUSTEXT')
146 hgrustext = os.environ.get('HGWITHRUSTEXT')
136 # TODO record it for proper rebuild upon changes
147 # TODO record it for proper rebuild upon changes
137 # (see mercurial/__modulepolicy__.py)
148 # (see mercurial/__modulepolicy__.py)
138 if hgrustext != 'cpython' and hgrustext is not None:
149 if hgrustext != 'cpython' and hgrustext is not None:
139 hgrustext = 'direct-ffi'
150 hgrustext = 'direct-ffi'
140
151
141 import ctypes
152 import ctypes
142 import errno
153 import errno
143 import stat, subprocess, time
154 import stat, subprocess, time
144 import re
155 import re
145 import shutil
156 import shutil
146 import tempfile
157 import tempfile
147 from distutils import log
158 from distutils import log
148 # We have issues with setuptools on some platforms and builders. Until
159 # We have issues with setuptools on some platforms and builders. Until
149 # those are resolved, setuptools is opt-in except for platforms where
160 # those are resolved, setuptools is opt-in except for platforms where
150 # we don't have issues.
161 # we don't have issues.
151 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
162 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
152 if issetuptools:
163 if issetuptools:
153 from setuptools import setup
164 from setuptools import setup
154 else:
165 else:
155 from distutils.core import setup
166 from distutils.core import setup
156 from distutils.ccompiler import new_compiler
167 from distutils.ccompiler import new_compiler
157 from distutils.core import Command, Extension
168 from distutils.core import Command, Extension
158 from distutils.dist import Distribution
169 from distutils.dist import Distribution
159 from distutils.command.build import build
170 from distutils.command.build import build
160 from distutils.command.build_ext import build_ext
171 from distutils.command.build_ext import build_ext
161 from distutils.command.build_py import build_py
172 from distutils.command.build_py import build_py
162 from distutils.command.build_scripts import build_scripts
173 from distutils.command.build_scripts import build_scripts
163 from distutils.command.install import install
174 from distutils.command.install import install
164 from distutils.command.install_lib import install_lib
175 from distutils.command.install_lib import install_lib
165 from distutils.command.install_scripts import install_scripts
176 from distutils.command.install_scripts import install_scripts
166 from distutils.spawn import spawn, find_executable
177 from distutils.spawn import spawn, find_executable
167 from distutils import file_util
178 from distutils import file_util
168 from distutils.errors import (
179 from distutils.errors import (
169 CCompilerError,
180 CCompilerError,
170 DistutilsError,
181 DistutilsError,
171 DistutilsExecError,
182 DistutilsExecError,
172 )
183 )
173 from distutils.sysconfig import get_python_inc, get_config_var
184 from distutils.sysconfig import get_python_inc, get_config_var
174 from distutils.version import StrictVersion
185 from distutils.version import StrictVersion
175
186
176 # Explain to distutils.StrictVersion how our release candidates are versionned
187 # Explain to distutils.StrictVersion how our release candidates are versionned
177 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
188 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
178
189
179 def write_if_changed(path, content):
190 def write_if_changed(path, content):
180 """Write content to a file iff the content hasn't changed."""
191 """Write content to a file iff the content hasn't changed."""
181 if os.path.exists(path):
192 if os.path.exists(path):
182 with open(path, 'rb') as fh:
193 with open(path, 'rb') as fh:
183 current = fh.read()
194 current = fh.read()
184 else:
195 else:
185 current = b''
196 current = b''
186
197
187 if current != content:
198 if current != content:
188 with open(path, 'wb') as fh:
199 with open(path, 'wb') as fh:
189 fh.write(content)
200 fh.write(content)
190
201
191 scripts = ['hg']
202 scripts = ['hg']
192 if os.name == 'nt':
203 if os.name == 'nt':
193 # We remove hg.bat if we are able to build hg.exe.
204 # We remove hg.bat if we are able to build hg.exe.
194 scripts.append('contrib/win32/hg.bat')
205 scripts.append('contrib/win32/hg.bat')
195
206
196 def cancompile(cc, code):
207 def cancompile(cc, code):
197 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
208 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
198 devnull = oldstderr = None
209 devnull = oldstderr = None
199 try:
210 try:
200 fname = os.path.join(tmpdir, 'testcomp.c')
211 fname = os.path.join(tmpdir, 'testcomp.c')
201 f = open(fname, 'w')
212 f = open(fname, 'w')
202 f.write(code)
213 f.write(code)
203 f.close()
214 f.close()
204 # Redirect stderr to /dev/null to hide any error messages
215 # Redirect stderr to /dev/null to hide any error messages
205 # from the compiler.
216 # from the compiler.
206 # This will have to be changed if we ever have to check
217 # This will have to be changed if we ever have to check
207 # for a function on Windows.
218 # for a function on Windows.
208 devnull = open('/dev/null', 'w')
219 devnull = open('/dev/null', 'w')
209 oldstderr = os.dup(sys.stderr.fileno())
220 oldstderr = os.dup(sys.stderr.fileno())
210 os.dup2(devnull.fileno(), sys.stderr.fileno())
221 os.dup2(devnull.fileno(), sys.stderr.fileno())
211 objects = cc.compile([fname], output_dir=tmpdir)
222 objects = cc.compile([fname], output_dir=tmpdir)
212 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
223 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
213 return True
224 return True
214 except Exception:
225 except Exception:
215 return False
226 return False
216 finally:
227 finally:
217 if oldstderr is not None:
228 if oldstderr is not None:
218 os.dup2(oldstderr, sys.stderr.fileno())
229 os.dup2(oldstderr, sys.stderr.fileno())
219 if devnull is not None:
230 if devnull is not None:
220 devnull.close()
231 devnull.close()
221 shutil.rmtree(tmpdir)
232 shutil.rmtree(tmpdir)
222
233
223 # simplified version of distutils.ccompiler.CCompiler.has_function
234 # simplified version of distutils.ccompiler.CCompiler.has_function
224 # that actually removes its temporary files.
235 # that actually removes its temporary files.
225 def hasfunction(cc, funcname):
236 def hasfunction(cc, funcname):
226 code = 'int main(void) { %s(); }\n' % funcname
237 code = 'int main(void) { %s(); }\n' % funcname
227 return cancompile(cc, code)
238 return cancompile(cc, code)
228
239
229 def hasheader(cc, headername):
240 def hasheader(cc, headername):
230 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
241 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
231 return cancompile(cc, code)
242 return cancompile(cc, code)
232
243
233 # py2exe needs to be installed to work
244 # py2exe needs to be installed to work
234 try:
245 try:
235 import py2exe
246 import py2exe
236 py2exe.Distribution # silence unused import warning
247 py2exe.Distribution # silence unused import warning
237 py2exeloaded = True
248 py2exeloaded = True
238 # import py2exe's patched Distribution class
249 # import py2exe's patched Distribution class
239 from distutils.core import Distribution
250 from distutils.core import Distribution
240 except ImportError:
251 except ImportError:
241 py2exeloaded = False
252 py2exeloaded = False
242
253
243 def runcmd(cmd, env, cwd=None):
254 def runcmd(cmd, env, cwd=None):
244 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
255 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
245 stderr=subprocess.PIPE, env=env, cwd=cwd)
256 stderr=subprocess.PIPE, env=env, cwd=cwd)
246 out, err = p.communicate()
257 out, err = p.communicate()
247 return p.returncode, out, err
258 return p.returncode, out, err
248
259
249 class hgcommand(object):
260 class hgcommand(object):
250 def __init__(self, cmd, env):
261 def __init__(self, cmd, env):
251 self.cmd = cmd
262 self.cmd = cmd
252 self.env = env
263 self.env = env
253
264
254 def run(self, args):
265 def run(self, args):
255 cmd = self.cmd + args
266 cmd = self.cmd + args
256 returncode, out, err = runcmd(cmd, self.env)
267 returncode, out, err = runcmd(cmd, self.env)
257 err = filterhgerr(err)
268 err = filterhgerr(err)
258 if err or returncode != 0:
269 if err or returncode != 0:
259 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
270 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
260 printf(err, file=sys.stderr)
271 printf(err, file=sys.stderr)
261 return ''
272 return ''
262 return out
273 return out
263
274
264 def filterhgerr(err):
275 def filterhgerr(err):
265 # If root is executing setup.py, but the repository is owned by
276 # If root is executing setup.py, but the repository is owned by
266 # another user (as in "sudo python setup.py install") we will get
277 # another user (as in "sudo python setup.py install") we will get
267 # trust warnings since the .hg/hgrc file is untrusted. That is
278 # trust warnings since the .hg/hgrc file is untrusted. That is
268 # fine, we don't want to load it anyway. Python may warn about
279 # fine, we don't want to load it anyway. Python may warn about
269 # a missing __init__.py in mercurial/locale, we also ignore that.
280 # a missing __init__.py in mercurial/locale, we also ignore that.
270 err = [e for e in err.splitlines()
281 err = [e for e in err.splitlines()
271 if (not e.startswith(b'not trusting file')
282 if (not e.startswith(b'not trusting file')
272 and not e.startswith(b'warning: Not importing')
283 and not e.startswith(b'warning: Not importing')
273 and not e.startswith(b'obsolete feature not enabled')
284 and not e.startswith(b'obsolete feature not enabled')
274 and not e.startswith(b'*** failed to import extension')
285 and not e.startswith(b'*** failed to import extension')
275 and not e.startswith(b'devel-warn:')
286 and not e.startswith(b'devel-warn:')
276 and not (e.startswith(b'(third party extension')
287 and not (e.startswith(b'(third party extension')
277 and e.endswith(b'or newer of Mercurial; disabling)')))]
288 and e.endswith(b'or newer of Mercurial; disabling)')))]
278 return b'\n'.join(b' ' + e for e in err)
289 return b'\n'.join(b' ' + e for e in err)
279
290
280 def findhg():
291 def findhg():
281 """Try to figure out how we should invoke hg for examining the local
292 """Try to figure out how we should invoke hg for examining the local
282 repository contents.
293 repository contents.
283
294
284 Returns an hgcommand object."""
295 Returns an hgcommand object."""
285 # By default, prefer the "hg" command in the user's path. This was
296 # By default, prefer the "hg" command in the user's path. This was
286 # presumably the hg command that the user used to create this repository.
297 # presumably the hg command that the user used to create this repository.
287 #
298 #
288 # This repository may require extensions or other settings that would not
299 # This repository may require extensions or other settings that would not
289 # be enabled by running the hg script directly from this local repository.
300 # be enabled by running the hg script directly from this local repository.
290 hgenv = os.environ.copy()
301 hgenv = os.environ.copy()
291 # Use HGPLAIN to disable hgrc settings that would change output formatting,
302 # Use HGPLAIN to disable hgrc settings that would change output formatting,
292 # and disable localization for the same reasons.
303 # and disable localization for the same reasons.
293 hgenv['HGPLAIN'] = '1'
304 hgenv['HGPLAIN'] = '1'
294 hgenv['LANGUAGE'] = 'C'
305 hgenv['LANGUAGE'] = 'C'
295 hgcmd = ['hg']
306 hgcmd = ['hg']
296 # Run a simple "hg log" command just to see if using hg from the user's
307 # Run a simple "hg log" command just to see if using hg from the user's
297 # path works and can successfully interact with this repository. Windows
308 # path works and can successfully interact with this repository. Windows
298 # gives precedence to hg.exe in the current directory, so fall back to the
309 # gives precedence to hg.exe in the current directory, so fall back to the
299 # python invocation of local hg, where pythonXY.dll can always be found.
310 # python invocation of local hg, where pythonXY.dll can always be found.
300 check_cmd = ['log', '-r.', '-Ttest']
311 check_cmd = ['log', '-r.', '-Ttest']
301 if os.name != 'nt':
312 if os.name != 'nt':
302 try:
313 try:
303 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
314 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
304 except EnvironmentError:
315 except EnvironmentError:
305 retcode = -1
316 retcode = -1
306 if retcode == 0 and not filterhgerr(err):
317 if retcode == 0 and not filterhgerr(err):
307 return hgcommand(hgcmd, hgenv)
318 return hgcommand(hgcmd, hgenv)
308
319
309 # Fall back to trying the local hg installation.
320 # Fall back to trying the local hg installation.
310 hgenv = localhgenv()
321 hgenv = localhgenv()
311 hgcmd = [sys.executable, 'hg']
322 hgcmd = [sys.executable, 'hg']
312 try:
323 try:
313 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
324 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
314 except EnvironmentError:
325 except EnvironmentError:
315 retcode = -1
326 retcode = -1
316 if retcode == 0 and not filterhgerr(err):
327 if retcode == 0 and not filterhgerr(err):
317 return hgcommand(hgcmd, hgenv)
328 return hgcommand(hgcmd, hgenv)
318
329
319 raise SystemExit('Unable to find a working hg binary to extract the '
330 raise SystemExit('Unable to find a working hg binary to extract the '
320 'version from the repository tags')
331 'version from the repository tags')
321
332
322 def localhgenv():
333 def localhgenv():
323 """Get an environment dictionary to use for invoking or importing
334 """Get an environment dictionary to use for invoking or importing
324 mercurial from the local repository."""
335 mercurial from the local repository."""
325 # Execute hg out of this directory with a custom environment which takes
336 # Execute hg out of this directory with a custom environment which takes
326 # care to not use any hgrc files and do no localization.
337 # care to not use any hgrc files and do no localization.
327 env = {'HGMODULEPOLICY': 'py',
338 env = {'HGMODULEPOLICY': 'py',
328 'HGRCPATH': '',
339 'HGRCPATH': '',
329 'LANGUAGE': 'C',
340 'LANGUAGE': 'C',
330 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
341 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
331 if 'LD_LIBRARY_PATH' in os.environ:
342 if 'LD_LIBRARY_PATH' in os.environ:
332 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
343 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
333 if 'SystemRoot' in os.environ:
344 if 'SystemRoot' in os.environ:
334 # SystemRoot is required by Windows to load various DLLs. See:
345 # SystemRoot is required by Windows to load various DLLs. See:
335 # https://bugs.python.org/issue13524#msg148850
346 # https://bugs.python.org/issue13524#msg148850
336 env['SystemRoot'] = os.environ['SystemRoot']
347 env['SystemRoot'] = os.environ['SystemRoot']
337 return env
348 return env
338
349
339 version = ''
350 version = ''
340
351
341 if os.path.isdir('.hg'):
352 if os.path.isdir('.hg'):
342 hg = findhg()
353 hg = findhg()
343 cmd = ['log', '-r', '.', '--template', '{tags}\n']
354 cmd = ['log', '-r', '.', '--template', '{tags}\n']
344 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
355 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
345 hgid = sysstr(hg.run(['id', '-i'])).strip()
356 hgid = sysstr(hg.run(['id', '-i'])).strip()
346 if not hgid:
357 if not hgid:
347 # Bail out if hg is having problems interacting with this repository,
358 # Bail out if hg is having problems interacting with this repository,
348 # rather than falling through and producing a bogus version number.
359 # rather than falling through and producing a bogus version number.
349 # Continuing with an invalid version number will break extensions
360 # Continuing with an invalid version number will break extensions
350 # that define minimumhgversion.
361 # that define minimumhgversion.
351 raise SystemExit('Unable to determine hg version from local repository')
362 raise SystemExit('Unable to determine hg version from local repository')
352 if numerictags: # tag(s) found
363 if numerictags: # tag(s) found
353 version = numerictags[-1]
364 version = numerictags[-1]
354 if hgid.endswith('+'): # propagate the dirty status to the tag
365 if hgid.endswith('+'): # propagate the dirty status to the tag
355 version += '+'
366 version += '+'
356 else: # no tag found
367 else: # no tag found
357 ltagcmd = ['parents', '--template', '{latesttag}']
368 ltagcmd = ['parents', '--template', '{latesttag}']
358 ltag = sysstr(hg.run(ltagcmd))
369 ltag = sysstr(hg.run(ltagcmd))
359 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
370 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
360 changessince = len(hg.run(changessincecmd).splitlines())
371 changessince = len(hg.run(changessincecmd).splitlines())
361 version = '%s+%s-%s' % (ltag, changessince, hgid)
372 version = '%s+%s-%s' % (ltag, changessince, hgid)
362 if version.endswith('+'):
373 if version.endswith('+'):
363 version += time.strftime('%Y%m%d')
374 version += time.strftime('%Y%m%d')
364 elif os.path.exists('.hg_archival.txt'):
375 elif os.path.exists('.hg_archival.txt'):
365 kw = dict([[t.strip() for t in l.split(':', 1)]
376 kw = dict([[t.strip() for t in l.split(':', 1)]
366 for l in open('.hg_archival.txt')])
377 for l in open('.hg_archival.txt')])
367 if 'tag' in kw:
378 if 'tag' in kw:
368 version = kw['tag']
379 version = kw['tag']
369 elif 'latesttag' in kw:
380 elif 'latesttag' in kw:
370 if 'changessincelatesttag' in kw:
381 if 'changessincelatesttag' in kw:
371 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
382 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
372 else:
383 else:
373 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
384 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
374 else:
385 else:
375 version = kw.get('node', '')[:12]
386 version = kw.get('node', '')[:12]
376
387
377 if version:
388 if version:
378 versionb = version
389 versionb = version
379 if not isinstance(versionb, bytes):
390 if not isinstance(versionb, bytes):
380 versionb = versionb.encode('ascii')
391 versionb = versionb.encode('ascii')
381
392
382 write_if_changed('mercurial/__version__.py', b''.join([
393 write_if_changed('mercurial/__version__.py', b''.join([
383 b'# this file is autogenerated by setup.py\n'
394 b'# this file is autogenerated by setup.py\n'
384 b'version = b"%s"\n' % versionb,
395 b'version = b"%s"\n' % versionb,
385 ]))
396 ]))
386
397
387 try:
398 try:
388 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
399 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
389 os.environ['HGMODULEPOLICY'] = 'py'
400 os.environ['HGMODULEPOLICY'] = 'py'
390 from mercurial import __version__
401 from mercurial import __version__
391 version = __version__.version
402 version = __version__.version
392 except ImportError:
403 except ImportError:
393 version = b'unknown'
404 version = b'unknown'
394 finally:
405 finally:
395 if oldpolicy is None:
406 if oldpolicy is None:
396 del os.environ['HGMODULEPOLICY']
407 del os.environ['HGMODULEPOLICY']
397 else:
408 else:
398 os.environ['HGMODULEPOLICY'] = oldpolicy
409 os.environ['HGMODULEPOLICY'] = oldpolicy
399
410
400 class hgbuild(build):
411 class hgbuild(build):
401 # Insert hgbuildmo first so that files in mercurial/locale/ are found
412 # Insert hgbuildmo first so that files in mercurial/locale/ are found
402 # when build_py is run next.
413 # when build_py is run next.
403 sub_commands = [('build_mo', None)] + build.sub_commands
414 sub_commands = [('build_mo', None)] + build.sub_commands
404
415
405 class hgbuildmo(build):
416 class hgbuildmo(build):
406
417
407 description = "build translations (.mo files)"
418 description = "build translations (.mo files)"
408
419
409 def run(self):
420 def run(self):
410 if not find_executable('msgfmt'):
421 if not find_executable('msgfmt'):
411 self.warn("could not find msgfmt executable, no translations "
422 self.warn("could not find msgfmt executable, no translations "
412 "will be built")
423 "will be built")
413 return
424 return
414
425
415 podir = 'i18n'
426 podir = 'i18n'
416 if not os.path.isdir(podir):
427 if not os.path.isdir(podir):
417 self.warn("could not find %s/ directory" % podir)
428 self.warn("could not find %s/ directory" % podir)
418 return
429 return
419
430
420 join = os.path.join
431 join = os.path.join
421 for po in os.listdir(podir):
432 for po in os.listdir(podir):
422 if not po.endswith('.po'):
433 if not po.endswith('.po'):
423 continue
434 continue
424 pofile = join(podir, po)
435 pofile = join(podir, po)
425 modir = join('locale', po[:-3], 'LC_MESSAGES')
436 modir = join('locale', po[:-3], 'LC_MESSAGES')
426 mofile = join(modir, 'hg.mo')
437 mofile = join(modir, 'hg.mo')
427 mobuildfile = join('mercurial', mofile)
438 mobuildfile = join('mercurial', mofile)
428 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
439 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
429 if sys.platform != 'sunos5':
440 if sys.platform != 'sunos5':
430 # msgfmt on Solaris does not know about -c
441 # msgfmt on Solaris does not know about -c
431 cmd.append('-c')
442 cmd.append('-c')
432 self.mkpath(join('mercurial', modir))
443 self.mkpath(join('mercurial', modir))
433 self.make_file([pofile], mobuildfile, spawn, (cmd,))
444 self.make_file([pofile], mobuildfile, spawn, (cmd,))
434
445
435
446
436 class hgdist(Distribution):
447 class hgdist(Distribution):
437 pure = False
448 pure = False
438 cffi = ispypy
449 cffi = ispypy
439
450
440 global_options = Distribution.global_options + [
451 global_options = Distribution.global_options + [
441 ('pure', None, "use pure (slow) Python code instead of C extensions"),
452 ('pure', None, "use pure (slow) Python code instead of C extensions"),
442 ]
453 ]
443
454
444 def has_ext_modules(self):
455 def has_ext_modules(self):
445 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
456 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
446 # too late for some cases
457 # too late for some cases
447 return not self.pure and Distribution.has_ext_modules(self)
458 return not self.pure and Distribution.has_ext_modules(self)
448
459
449 # This is ugly as a one-liner. So use a variable.
460 # This is ugly as a one-liner. So use a variable.
450 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
461 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
451 buildextnegops['no-zstd'] = 'zstd'
462 buildextnegops['no-zstd'] = 'zstd'
452
463
453 class hgbuildext(build_ext):
464 class hgbuildext(build_ext):
454 user_options = build_ext.user_options + [
465 user_options = build_ext.user_options + [
455 ('zstd', None, 'compile zstd bindings [default]'),
466 ('zstd', None, 'compile zstd bindings [default]'),
456 ('no-zstd', None, 'do not compile zstd bindings'),
467 ('no-zstd', None, 'do not compile zstd bindings'),
457 ]
468 ]
458
469
459 boolean_options = build_ext.boolean_options + ['zstd']
470 boolean_options = build_ext.boolean_options + ['zstd']
460 negative_opt = buildextnegops
471 negative_opt = buildextnegops
461
472
462 def initialize_options(self):
473 def initialize_options(self):
463 self.zstd = True
474 self.zstd = True
464 return build_ext.initialize_options(self)
475 return build_ext.initialize_options(self)
465
476
466 def build_extensions(self):
477 def build_extensions(self):
467 ruststandalones = [e for e in self.extensions
478 ruststandalones = [e for e in self.extensions
468 if isinstance(e, RustStandaloneExtension)]
479 if isinstance(e, RustStandaloneExtension)]
469 self.extensions = [e for e in self.extensions
480 self.extensions = [e for e in self.extensions
470 if e not in ruststandalones]
481 if e not in ruststandalones]
471 # Filter out zstd if disabled via argument.
482 # Filter out zstd if disabled via argument.
472 if not self.zstd:
483 if not self.zstd:
473 self.extensions = [e for e in self.extensions
484 self.extensions = [e for e in self.extensions
474 if e.name != 'mercurial.zstd']
485 if e.name != 'mercurial.zstd']
475
486
476 for rustext in ruststandalones:
487 for rustext in ruststandalones:
477 rustext.build('' if self.inplace else self.build_lib)
488 rustext.build('' if self.inplace else self.build_lib)
478
489
479 return build_ext.build_extensions(self)
490 return build_ext.build_extensions(self)
480
491
481 def build_extension(self, ext):
492 def build_extension(self, ext):
482 if isinstance(ext, RustExtension):
493 if isinstance(ext, RustExtension):
483 ext.rustbuild()
494 ext.rustbuild()
484 try:
495 try:
485 build_ext.build_extension(self, ext)
496 build_ext.build_extension(self, ext)
486 except CCompilerError:
497 except CCompilerError:
487 if not getattr(ext, 'optional', False):
498 if not getattr(ext, 'optional', False):
488 raise
499 raise
489 log.warn("Failed to build optional extension '%s' (skipping)",
500 log.warn("Failed to build optional extension '%s' (skipping)",
490 ext.name)
501 ext.name)
491
502
492 class hgbuildscripts(build_scripts):
503 class hgbuildscripts(build_scripts):
493 def run(self):
504 def run(self):
494 if os.name != 'nt' or self.distribution.pure:
505 if os.name != 'nt' or self.distribution.pure:
495 return build_scripts.run(self)
506 return build_scripts.run(self)
496
507
497 exebuilt = False
508 exebuilt = False
498 try:
509 try:
499 self.run_command('build_hgexe')
510 self.run_command('build_hgexe')
500 exebuilt = True
511 exebuilt = True
501 except (DistutilsError, CCompilerError):
512 except (DistutilsError, CCompilerError):
502 log.warn('failed to build optional hg.exe')
513 log.warn('failed to build optional hg.exe')
503
514
504 if exebuilt:
515 if exebuilt:
505 # Copying hg.exe to the scripts build directory ensures it is
516 # Copying hg.exe to the scripts build directory ensures it is
506 # installed by the install_scripts command.
517 # installed by the install_scripts command.
507 hgexecommand = self.get_finalized_command('build_hgexe')
518 hgexecommand = self.get_finalized_command('build_hgexe')
508 dest = os.path.join(self.build_dir, 'hg.exe')
519 dest = os.path.join(self.build_dir, 'hg.exe')
509 self.mkpath(self.build_dir)
520 self.mkpath(self.build_dir)
510 self.copy_file(hgexecommand.hgexepath, dest)
521 self.copy_file(hgexecommand.hgexepath, dest)
511
522
512 # Remove hg.bat because it is redundant with hg.exe.
523 # Remove hg.bat because it is redundant with hg.exe.
513 self.scripts.remove('contrib/win32/hg.bat')
524 self.scripts.remove('contrib/win32/hg.bat')
514
525
515 return build_scripts.run(self)
526 return build_scripts.run(self)
516
527
517 class hgbuildpy(build_py):
528 class hgbuildpy(build_py):
518 def finalize_options(self):
529 def finalize_options(self):
519 build_py.finalize_options(self)
530 build_py.finalize_options(self)
520
531
521 if self.distribution.pure:
532 if self.distribution.pure:
522 self.distribution.ext_modules = []
533 self.distribution.ext_modules = []
523 elif self.distribution.cffi:
534 elif self.distribution.cffi:
524 from mercurial.cffi import (
535 from mercurial.cffi import (
525 bdiffbuild,
536 bdiffbuild,
526 mpatchbuild,
537 mpatchbuild,
527 )
538 )
528 exts = [mpatchbuild.ffi.distutils_extension(),
539 exts = [mpatchbuild.ffi.distutils_extension(),
529 bdiffbuild.ffi.distutils_extension()]
540 bdiffbuild.ffi.distutils_extension()]
530 # cffi modules go here
541 # cffi modules go here
531 if sys.platform == 'darwin':
542 if sys.platform == 'darwin':
532 from mercurial.cffi import osutilbuild
543 from mercurial.cffi import osutilbuild
533 exts.append(osutilbuild.ffi.distutils_extension())
544 exts.append(osutilbuild.ffi.distutils_extension())
534 self.distribution.ext_modules = exts
545 self.distribution.ext_modules = exts
535 else:
546 else:
536 h = os.path.join(get_python_inc(), 'Python.h')
547 h = os.path.join(get_python_inc(), 'Python.h')
537 if not os.path.exists(h):
548 if not os.path.exists(h):
538 raise SystemExit('Python headers are required to build '
549 raise SystemExit('Python headers are required to build '
539 'Mercurial but weren\'t found in %s' % h)
550 'Mercurial but weren\'t found in %s' % h)
540
551
541 def run(self):
552 def run(self):
542 basepath = os.path.join(self.build_lib, 'mercurial')
553 basepath = os.path.join(self.build_lib, 'mercurial')
543 self.mkpath(basepath)
554 self.mkpath(basepath)
544
555
545 if self.distribution.pure:
556 if self.distribution.pure:
546 modulepolicy = 'py'
557 modulepolicy = 'py'
547 elif self.build_lib == '.':
558 elif self.build_lib == '.':
548 # in-place build should run without rebuilding C extensions
559 # in-place build should run without rebuilding C extensions
549 modulepolicy = 'allow'
560 modulepolicy = 'allow'
550 else:
561 else:
551 modulepolicy = 'c'
562 modulepolicy = 'c'
552
563
553 content = b''.join([
564 content = b''.join([
554 b'# this file is autogenerated by setup.py\n',
565 b'# this file is autogenerated by setup.py\n',
555 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
566 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
556 ])
567 ])
557 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
568 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
558 content)
569 content)
559
570
560 build_py.run(self)
571 build_py.run(self)
561
572
562 class buildhgextindex(Command):
573 class buildhgextindex(Command):
563 description = 'generate prebuilt index of hgext (for frozen package)'
574 description = 'generate prebuilt index of hgext (for frozen package)'
564 user_options = []
575 user_options = []
565 _indexfilename = 'hgext/__index__.py'
576 _indexfilename = 'hgext/__index__.py'
566
577
567 def initialize_options(self):
578 def initialize_options(self):
568 pass
579 pass
569
580
570 def finalize_options(self):
581 def finalize_options(self):
571 pass
582 pass
572
583
573 def run(self):
584 def run(self):
574 if os.path.exists(self._indexfilename):
585 if os.path.exists(self._indexfilename):
575 with open(self._indexfilename, 'w') as f:
586 with open(self._indexfilename, 'w') as f:
576 f.write('# empty\n')
587 f.write('# empty\n')
577
588
578 # here no extension enabled, disabled() lists up everything
589 # here no extension enabled, disabled() lists up everything
579 code = ('import pprint; from mercurial import extensions; '
590 code = ('import pprint; from mercurial import extensions; '
580 'pprint.pprint(extensions.disabled())')
591 'pprint.pprint(extensions.disabled())')
581 returncode, out, err = runcmd([sys.executable, '-c', code],
592 returncode, out, err = runcmd([sys.executable, '-c', code],
582 localhgenv())
593 localhgenv())
583 if err or returncode != 0:
594 if err or returncode != 0:
584 raise DistutilsExecError(err)
595 raise DistutilsExecError(err)
585
596
586 with open(self._indexfilename, 'wb') as f:
597 with open(self._indexfilename, 'wb') as f:
587 f.write(b'# this file is autogenerated by setup.py\n')
598 f.write(b'# this file is autogenerated by setup.py\n')
588 f.write(b'docs = ')
599 f.write(b'docs = ')
589 f.write(out)
600 f.write(out)
590
601
591 class buildhgexe(build_ext):
602 class buildhgexe(build_ext):
592 description = 'compile hg.exe from mercurial/exewrapper.c'
603 description = 'compile hg.exe from mercurial/exewrapper.c'
593 user_options = build_ext.user_options + [
604 user_options = build_ext.user_options + [
594 ('long-paths-support', None, 'enable support for long paths on '
605 ('long-paths-support', None, 'enable support for long paths on '
595 'Windows (off by default and '
606 'Windows (off by default and '
596 'experimental)'),
607 'experimental)'),
597 ]
608 ]
598
609
599 LONG_PATHS_MANIFEST = """
610 LONG_PATHS_MANIFEST = """
600 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
611 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
601 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
612 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
602 <application>
613 <application>
603 <windowsSettings
614 <windowsSettings
604 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
615 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
605 <ws2:longPathAware>true</ws2:longPathAware>
616 <ws2:longPathAware>true</ws2:longPathAware>
606 </windowsSettings>
617 </windowsSettings>
607 </application>
618 </application>
608 </assembly>"""
619 </assembly>"""
609
620
610 def initialize_options(self):
621 def initialize_options(self):
611 build_ext.initialize_options(self)
622 build_ext.initialize_options(self)
612 self.long_paths_support = False
623 self.long_paths_support = False
613
624
614 def build_extensions(self):
625 def build_extensions(self):
615 if os.name != 'nt':
626 if os.name != 'nt':
616 return
627 return
617 if isinstance(self.compiler, HackedMingw32CCompiler):
628 if isinstance(self.compiler, HackedMingw32CCompiler):
618 self.compiler.compiler_so = self.compiler.compiler # no -mdll
629 self.compiler.compiler_so = self.compiler.compiler # no -mdll
619 self.compiler.dll_libraries = [] # no -lmsrvc90
630 self.compiler.dll_libraries = [] # no -lmsrvc90
620
631
621 # Different Python installs can have different Python library
632 # Different Python installs can have different Python library
622 # names. e.g. the official CPython distribution uses pythonXY.dll
633 # names. e.g. the official CPython distribution uses pythonXY.dll
623 # and MinGW uses libpythonX.Y.dll.
634 # and MinGW uses libpythonX.Y.dll.
624 _kernel32 = ctypes.windll.kernel32
635 _kernel32 = ctypes.windll.kernel32
625 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
636 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
626 ctypes.c_void_p,
637 ctypes.c_void_p,
627 ctypes.c_ulong]
638 ctypes.c_ulong]
628 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
639 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
629 size = 1000
640 size = 1000
630 buf = ctypes.create_string_buffer(size + 1)
641 buf = ctypes.create_string_buffer(size + 1)
631 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
642 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
632 size)
643 size)
633
644
634 if filelen > 0 and filelen != size:
645 if filelen > 0 and filelen != size:
635 dllbasename = os.path.basename(buf.value)
646 dllbasename = os.path.basename(buf.value)
636 if not dllbasename.lower().endswith(b'.dll'):
647 if not dllbasename.lower().endswith(b'.dll'):
637 raise SystemExit('Python DLL does not end with .dll: %s' %
648 raise SystemExit('Python DLL does not end with .dll: %s' %
638 dllbasename)
649 dllbasename)
639 pythonlib = dllbasename[:-4]
650 pythonlib = dllbasename[:-4]
640 else:
651 else:
641 log.warn('could not determine Python DLL filename; '
652 log.warn('could not determine Python DLL filename; '
642 'assuming pythonXY')
653 'assuming pythonXY')
643
654
644 hv = sys.hexversion
655 hv = sys.hexversion
645 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
656 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
646
657
647 log.info('using %s as Python library name' % pythonlib)
658 log.info('using %s as Python library name' % pythonlib)
648 with open('mercurial/hgpythonlib.h', 'wb') as f:
659 with open('mercurial/hgpythonlib.h', 'wb') as f:
649 f.write(b'/* this file is autogenerated by setup.py */\n')
660 f.write(b'/* this file is autogenerated by setup.py */\n')
650 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
661 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
651
662
652 macros = None
663 macros = None
653 if sys.version_info[0] >= 3:
664 if sys.version_info[0] >= 3:
654 macros = [('_UNICODE', None), ('UNICODE', None)]
665 macros = [('_UNICODE', None), ('UNICODE', None)]
655
666
656 objects = self.compiler.compile(['mercurial/exewrapper.c'],
667 objects = self.compiler.compile(['mercurial/exewrapper.c'],
657 output_dir=self.build_temp,
668 output_dir=self.build_temp,
658 macros=macros)
669 macros=macros)
659 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
670 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
660 self.hgtarget = os.path.join(dir, 'hg')
671 self.hgtarget = os.path.join(dir, 'hg')
661 self.compiler.link_executable(objects, self.hgtarget,
672 self.compiler.link_executable(objects, self.hgtarget,
662 libraries=[],
673 libraries=[],
663 output_dir=self.build_temp)
674 output_dir=self.build_temp)
664 if self.long_paths_support:
675 if self.long_paths_support:
665 self.addlongpathsmanifest()
676 self.addlongpathsmanifest()
666
677
667 def addlongpathsmanifest(self):
678 def addlongpathsmanifest(self):
668 r"""Add manifest pieces so that hg.exe understands long paths
679 r"""Add manifest pieces so that hg.exe understands long paths
669
680
670 This is an EXPERIMENTAL feature, use with care.
681 This is an EXPERIMENTAL feature, use with care.
671 To enable long paths support, one needs to do two things:
682 To enable long paths support, one needs to do two things:
672 - build Mercurial with --long-paths-support option
683 - build Mercurial with --long-paths-support option
673 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
684 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
674 LongPathsEnabled to have value 1.
685 LongPathsEnabled to have value 1.
675
686
676 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
687 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
677 it happens because Mercurial uses mt.exe circa 2008, which is not
688 it happens because Mercurial uses mt.exe circa 2008, which is not
678 yet aware of long paths support in the manifest (I think so at least).
689 yet aware of long paths support in the manifest (I think so at least).
679 This does not stop mt.exe from embedding/merging the XML properly.
690 This does not stop mt.exe from embedding/merging the XML properly.
680
691
681 Why resource #1 should be used for .exe manifests? I don't know and
692 Why resource #1 should be used for .exe manifests? I don't know and
682 wasn't able to find an explanation for mortals. But it seems to work.
693 wasn't able to find an explanation for mortals. But it seems to work.
683 """
694 """
684 exefname = self.compiler.executable_filename(self.hgtarget)
695 exefname = self.compiler.executable_filename(self.hgtarget)
685 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
696 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
686 os.close(fdauto)
697 os.close(fdauto)
687 with open(manfname, 'w') as f:
698 with open(manfname, 'w') as f:
688 f.write(self.LONG_PATHS_MANIFEST)
699 f.write(self.LONG_PATHS_MANIFEST)
689 log.info("long paths manifest is written to '%s'" % manfname)
700 log.info("long paths manifest is written to '%s'" % manfname)
690 inputresource = '-inputresource:%s;#1' % exefname
701 inputresource = '-inputresource:%s;#1' % exefname
691 outputresource = '-outputresource:%s;#1' % exefname
702 outputresource = '-outputresource:%s;#1' % exefname
692 log.info("running mt.exe to update hg.exe's manifest in-place")
703 log.info("running mt.exe to update hg.exe's manifest in-place")
693 # supplying both -manifest and -inputresource to mt.exe makes
704 # supplying both -manifest and -inputresource to mt.exe makes
694 # it merge the embedded and supplied manifests in the -outputresource
705 # it merge the embedded and supplied manifests in the -outputresource
695 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
706 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
696 inputresource, outputresource])
707 inputresource, outputresource])
697 log.info("done updating hg.exe's manifest")
708 log.info("done updating hg.exe's manifest")
698 os.remove(manfname)
709 os.remove(manfname)
699
710
700 @property
711 @property
701 def hgexepath(self):
712 def hgexepath(self):
702 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
713 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
703 return os.path.join(self.build_temp, dir, 'hg.exe')
714 return os.path.join(self.build_temp, dir, 'hg.exe')
704
715
705 class hgbuilddoc(Command):
716 class hgbuilddoc(Command):
706 description = 'build documentation'
717 description = 'build documentation'
707 user_options = [
718 user_options = [
708 ('man', None, 'generate man pages'),
719 ('man', None, 'generate man pages'),
709 ('html', None, 'generate html pages'),
720 ('html', None, 'generate html pages'),
710 ]
721 ]
711
722
712 def initialize_options(self):
723 def initialize_options(self):
713 self.man = None
724 self.man = None
714 self.html = None
725 self.html = None
715
726
716 def finalize_options(self):
727 def finalize_options(self):
717 # If --man or --html are set, only generate what we're told to.
728 # If --man or --html are set, only generate what we're told to.
718 # Otherwise generate everything.
729 # Otherwise generate everything.
719 have_subset = self.man is not None or self.html is not None
730 have_subset = self.man is not None or self.html is not None
720
731
721 if have_subset:
732 if have_subset:
722 self.man = True if self.man else False
733 self.man = True if self.man else False
723 self.html = True if self.html else False
734 self.html = True if self.html else False
724 else:
735 else:
725 self.man = True
736 self.man = True
726 self.html = True
737 self.html = True
727
738
728 def run(self):
739 def run(self):
729 def normalizecrlf(p):
740 def normalizecrlf(p):
730 with open(p, 'rb') as fh:
741 with open(p, 'rb') as fh:
731 orig = fh.read()
742 orig = fh.read()
732
743
733 if b'\r\n' not in orig:
744 if b'\r\n' not in orig:
734 return
745 return
735
746
736 log.info('normalizing %s to LF line endings' % p)
747 log.info('normalizing %s to LF line endings' % p)
737 with open(p, 'wb') as fh:
748 with open(p, 'wb') as fh:
738 fh.write(orig.replace(b'\r\n', b'\n'))
749 fh.write(orig.replace(b'\r\n', b'\n'))
739
750
740 def gentxt(root):
751 def gentxt(root):
741 txt = 'doc/%s.txt' % root
752 txt = 'doc/%s.txt' % root
742 log.info('generating %s' % txt)
753 log.info('generating %s' % txt)
743 res, out, err = runcmd(
754 res, out, err = runcmd(
744 [sys.executable, 'gendoc.py', root],
755 [sys.executable, 'gendoc.py', root],
745 os.environ,
756 os.environ,
746 cwd='doc')
757 cwd='doc')
747 if res:
758 if res:
748 raise SystemExit('error running gendoc.py: %s' %
759 raise SystemExit('error running gendoc.py: %s' %
749 '\n'.join([out, err]))
760 '\n'.join([out, err]))
750
761
751 with open(txt, 'wb') as fh:
762 with open(txt, 'wb') as fh:
752 fh.write(out)
763 fh.write(out)
753
764
754 def gengendoc(root):
765 def gengendoc(root):
755 gendoc = 'doc/%s.gendoc.txt' % root
766 gendoc = 'doc/%s.gendoc.txt' % root
756
767
757 log.info('generating %s' % gendoc)
768 log.info('generating %s' % gendoc)
758 res, out, err = runcmd(
769 res, out, err = runcmd(
759 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
770 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
760 os.environ,
771 os.environ,
761 cwd='doc')
772 cwd='doc')
762 if res:
773 if res:
763 raise SystemExit('error running gendoc: %s' %
774 raise SystemExit('error running gendoc: %s' %
764 '\n'.join([out, err]))
775 '\n'.join([out, err]))
765
776
766 with open(gendoc, 'wb') as fh:
777 with open(gendoc, 'wb') as fh:
767 fh.write(out)
778 fh.write(out)
768
779
769 def genman(root):
780 def genman(root):
770 log.info('generating doc/%s' % root)
781 log.info('generating doc/%s' % root)
771 res, out, err = runcmd(
782 res, out, err = runcmd(
772 [sys.executable, 'runrst', 'hgmanpage', '--halt', 'warning',
783 [sys.executable, 'runrst', 'hgmanpage', '--halt', 'warning',
773 '--strip-elements-with-class', 'htmlonly',
784 '--strip-elements-with-class', 'htmlonly',
774 '%s.txt' % root, root],
785 '%s.txt' % root, root],
775 os.environ,
786 os.environ,
776 cwd='doc')
787 cwd='doc')
777 if res:
788 if res:
778 raise SystemExit('error running runrst: %s' %
789 raise SystemExit('error running runrst: %s' %
779 '\n'.join([out, err]))
790 '\n'.join([out, err]))
780
791
781 normalizecrlf('doc/%s' % root)
792 normalizecrlf('doc/%s' % root)
782
793
783 def genhtml(root):
794 def genhtml(root):
784 log.info('generating doc/%s.html' % root)
795 log.info('generating doc/%s.html' % root)
785 res, out, err = runcmd(
796 res, out, err = runcmd(
786 [sys.executable, 'runrst', 'html', '--halt', 'warning',
797 [sys.executable, 'runrst', 'html', '--halt', 'warning',
787 '--link-stylesheet', '--stylesheet-path', 'style.css',
798 '--link-stylesheet', '--stylesheet-path', 'style.css',
788 '%s.txt' % root, '%s.html' % root],
799 '%s.txt' % root, '%s.html' % root],
789 os.environ,
800 os.environ,
790 cwd='doc')
801 cwd='doc')
791 if res:
802 if res:
792 raise SystemExit('error running runrst: %s' %
803 raise SystemExit('error running runrst: %s' %
793 '\n'.join([out, err]))
804 '\n'.join([out, err]))
794
805
795 normalizecrlf('doc/%s.html' % root)
806 normalizecrlf('doc/%s.html' % root)
796
807
797 # This logic is duplicated in doc/Makefile.
808 # This logic is duplicated in doc/Makefile.
798 sources = set(f for f in os.listdir('mercurial/help')
809 sources = set(f for f in os.listdir('mercurial/help')
799 if re.search(r'[0-9]\.txt$', f))
810 if re.search(r'[0-9]\.txt$', f))
800
811
801 # common.txt is a one-off.
812 # common.txt is a one-off.
802 gentxt('common')
813 gentxt('common')
803
814
804 for source in sorted(sources):
815 for source in sorted(sources):
805 assert source[-4:] == '.txt'
816 assert source[-4:] == '.txt'
806 root = source[:-4]
817 root = source[:-4]
807
818
808 gentxt(root)
819 gentxt(root)
809 gengendoc(root)
820 gengendoc(root)
810
821
811 if self.man:
822 if self.man:
812 genman(root)
823 genman(root)
813 if self.html:
824 if self.html:
814 genhtml(root)
825 genhtml(root)
815
826
816 class hginstall(install):
827 class hginstall(install):
817
828
818 user_options = install.user_options + [
829 user_options = install.user_options + [
819 ('old-and-unmanageable', None,
830 ('old-and-unmanageable', None,
820 'noop, present for eggless setuptools compat'),
831 'noop, present for eggless setuptools compat'),
821 ('single-version-externally-managed', None,
832 ('single-version-externally-managed', None,
822 'noop, present for eggless setuptools compat'),
833 'noop, present for eggless setuptools compat'),
823 ]
834 ]
824
835
825 # Also helps setuptools not be sad while we refuse to create eggs.
836 # Also helps setuptools not be sad while we refuse to create eggs.
826 single_version_externally_managed = True
837 single_version_externally_managed = True
827
838
828 def get_sub_commands(self):
839 def get_sub_commands(self):
829 # Screen out egg related commands to prevent egg generation. But allow
840 # Screen out egg related commands to prevent egg generation. But allow
830 # mercurial.egg-info generation, since that is part of modern
841 # mercurial.egg-info generation, since that is part of modern
831 # packaging.
842 # packaging.
832 excl = set(['bdist_egg'])
843 excl = set(['bdist_egg'])
833 return filter(lambda x: x not in excl, install.get_sub_commands(self))
844 return filter(lambda x: x not in excl, install.get_sub_commands(self))
834
845
835 class hginstalllib(install_lib):
846 class hginstalllib(install_lib):
836 '''
847 '''
837 This is a specialization of install_lib that replaces the copy_file used
848 This is a specialization of install_lib that replaces the copy_file used
838 there so that it supports setting the mode of files after copying them,
849 there so that it supports setting the mode of files after copying them,
839 instead of just preserving the mode that the files originally had. If your
850 instead of just preserving the mode that the files originally had. If your
840 system has a umask of something like 027, preserving the permissions when
851 system has a umask of something like 027, preserving the permissions when
841 copying will lead to a broken install.
852 copying will lead to a broken install.
842
853
843 Note that just passing keep_permissions=False to copy_file would be
854 Note that just passing keep_permissions=False to copy_file would be
844 insufficient, as it might still be applying a umask.
855 insufficient, as it might still be applying a umask.
845 '''
856 '''
846
857
847 def run(self):
858 def run(self):
848 realcopyfile = file_util.copy_file
859 realcopyfile = file_util.copy_file
849 def copyfileandsetmode(*args, **kwargs):
860 def copyfileandsetmode(*args, **kwargs):
850 src, dst = args[0], args[1]
861 src, dst = args[0], args[1]
851 dst, copied = realcopyfile(*args, **kwargs)
862 dst, copied = realcopyfile(*args, **kwargs)
852 if copied:
863 if copied:
853 st = os.stat(src)
864 st = os.stat(src)
854 # Persist executable bit (apply it to group and other if user
865 # Persist executable bit (apply it to group and other if user
855 # has it)
866 # has it)
856 if st[stat.ST_MODE] & stat.S_IXUSR:
867 if st[stat.ST_MODE] & stat.S_IXUSR:
857 setmode = int('0755', 8)
868 setmode = int('0755', 8)
858 else:
869 else:
859 setmode = int('0644', 8)
870 setmode = int('0644', 8)
860 m = stat.S_IMODE(st[stat.ST_MODE])
871 m = stat.S_IMODE(st[stat.ST_MODE])
861 m = (m & ~int('0777', 8)) | setmode
872 m = (m & ~int('0777', 8)) | setmode
862 os.chmod(dst, m)
873 os.chmod(dst, m)
863 file_util.copy_file = copyfileandsetmode
874 file_util.copy_file = copyfileandsetmode
864 try:
875 try:
865 install_lib.run(self)
876 install_lib.run(self)
866 finally:
877 finally:
867 file_util.copy_file = realcopyfile
878 file_util.copy_file = realcopyfile
868
879
869 class hginstallscripts(install_scripts):
880 class hginstallscripts(install_scripts):
870 '''
881 '''
871 This is a specialization of install_scripts that replaces the @LIBDIR@ with
882 This is a specialization of install_scripts that replaces the @LIBDIR@ with
872 the configured directory for modules. If possible, the path is made relative
883 the configured directory for modules. If possible, the path is made relative
873 to the directory for scripts.
884 to the directory for scripts.
874 '''
885 '''
875
886
876 def initialize_options(self):
887 def initialize_options(self):
877 install_scripts.initialize_options(self)
888 install_scripts.initialize_options(self)
878
889
879 self.install_lib = None
890 self.install_lib = None
880
891
881 def finalize_options(self):
892 def finalize_options(self):
882 install_scripts.finalize_options(self)
893 install_scripts.finalize_options(self)
883 self.set_undefined_options('install',
894 self.set_undefined_options('install',
884 ('install_lib', 'install_lib'))
895 ('install_lib', 'install_lib'))
885
896
886 def run(self):
897 def run(self):
887 install_scripts.run(self)
898 install_scripts.run(self)
888
899
889 # It only makes sense to replace @LIBDIR@ with the install path if
900 # It only makes sense to replace @LIBDIR@ with the install path if
890 # the install path is known. For wheels, the logic below calculates
901 # the install path is known. For wheels, the logic below calculates
891 # the libdir to be "../..". This is because the internal layout of a
902 # the libdir to be "../..". This is because the internal layout of a
892 # wheel archive looks like:
903 # wheel archive looks like:
893 #
904 #
894 # mercurial-3.6.1.data/scripts/hg
905 # mercurial-3.6.1.data/scripts/hg
895 # mercurial/__init__.py
906 # mercurial/__init__.py
896 #
907 #
897 # When installing wheels, the subdirectories of the "<pkg>.data"
908 # When installing wheels, the subdirectories of the "<pkg>.data"
898 # directory are translated to system local paths and files therein
909 # directory are translated to system local paths and files therein
899 # are copied in place. The mercurial/* files are installed into the
910 # are copied in place. The mercurial/* files are installed into the
900 # site-packages directory. However, the site-packages directory
911 # site-packages directory. However, the site-packages directory
901 # isn't known until wheel install time. This means we have no clue
912 # isn't known until wheel install time. This means we have no clue
902 # at wheel generation time what the installed site-packages directory
913 # at wheel generation time what the installed site-packages directory
903 # will be. And, wheels don't appear to provide the ability to register
914 # will be. And, wheels don't appear to provide the ability to register
904 # custom code to run during wheel installation. This all means that
915 # custom code to run during wheel installation. This all means that
905 # we can't reliably set the libdir in wheels: the default behavior
916 # we can't reliably set the libdir in wheels: the default behavior
906 # of looking in sys.path must do.
917 # of looking in sys.path must do.
907
918
908 if (os.path.splitdrive(self.install_dir)[0] !=
919 if (os.path.splitdrive(self.install_dir)[0] !=
909 os.path.splitdrive(self.install_lib)[0]):
920 os.path.splitdrive(self.install_lib)[0]):
910 # can't make relative paths from one drive to another, so use an
921 # can't make relative paths from one drive to another, so use an
911 # absolute path instead
922 # absolute path instead
912 libdir = self.install_lib
923 libdir = self.install_lib
913 else:
924 else:
914 common = os.path.commonprefix((self.install_dir, self.install_lib))
925 common = os.path.commonprefix((self.install_dir, self.install_lib))
915 rest = self.install_dir[len(common):]
926 rest = self.install_dir[len(common):]
916 uplevel = len([n for n in os.path.split(rest) if n])
927 uplevel = len([n for n in os.path.split(rest) if n])
917
928
918 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
929 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
919
930
920 for outfile in self.outfiles:
931 for outfile in self.outfiles:
921 with open(outfile, 'rb') as fp:
932 with open(outfile, 'rb') as fp:
922 data = fp.read()
933 data = fp.read()
923
934
924 # skip binary files
935 # skip binary files
925 if b'\0' in data:
936 if b'\0' in data:
926 continue
937 continue
927
938
928 # During local installs, the shebang will be rewritten to the final
939 # During local installs, the shebang will be rewritten to the final
929 # install path. During wheel packaging, the shebang has a special
940 # install path. During wheel packaging, the shebang has a special
930 # value.
941 # value.
931 if data.startswith(b'#!python'):
942 if data.startswith(b'#!python'):
932 log.info('not rewriting @LIBDIR@ in %s because install path '
943 log.info('not rewriting @LIBDIR@ in %s because install path '
933 'not known' % outfile)
944 'not known' % outfile)
934 continue
945 continue
935
946
936 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
947 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
937 with open(outfile, 'wb') as fp:
948 with open(outfile, 'wb') as fp:
938 fp.write(data)
949 fp.write(data)
939
950
940 # virtualenv installs custom distutils/__init__.py and
951 # virtualenv installs custom distutils/__init__.py and
941 # distutils/distutils.cfg files which essentially proxy back to the
952 # distutils/distutils.cfg files which essentially proxy back to the
942 # "real" distutils in the main Python install. The presence of this
953 # "real" distutils in the main Python install. The presence of this
943 # directory causes py2exe to pick up the "hacked" distutils package
954 # directory causes py2exe to pick up the "hacked" distutils package
944 # from the virtualenv and "import distutils" will fail from the py2exe
955 # from the virtualenv and "import distutils" will fail from the py2exe
945 # build because the "real" distutils files can't be located.
956 # build because the "real" distutils files can't be located.
946 #
957 #
947 # We work around this by monkeypatching the py2exe code finding Python
958 # We work around this by monkeypatching the py2exe code finding Python
948 # modules to replace the found virtualenv distutils modules with the
959 # modules to replace the found virtualenv distutils modules with the
949 # original versions via filesystem scanning. This is a bit hacky. But
960 # original versions via filesystem scanning. This is a bit hacky. But
950 # it allows us to use virtualenvs for py2exe packaging, which is more
961 # it allows us to use virtualenvs for py2exe packaging, which is more
951 # deterministic and reproducible.
962 # deterministic and reproducible.
952 #
963 #
953 # It's worth noting that the common StackOverflow suggestions for this
964 # It's worth noting that the common StackOverflow suggestions for this
954 # problem involve copying the original distutils files into the
965 # problem involve copying the original distutils files into the
955 # virtualenv or into the staging directory after setup() is invoked.
966 # virtualenv or into the staging directory after setup() is invoked.
956 # The former is very brittle and can easily break setup(). Our hacking
967 # The former is very brittle and can easily break setup(). Our hacking
957 # of the found modules routine has a similar result as copying the files
968 # of the found modules routine has a similar result as copying the files
958 # manually. But it makes fewer assumptions about how py2exe works and
969 # manually. But it makes fewer assumptions about how py2exe works and
959 # is less brittle.
970 # is less brittle.
960
971
961 # This only catches virtualenvs made with virtualenv (as opposed to
972 # This only catches virtualenvs made with virtualenv (as opposed to
962 # venv, which is likely what Python 3 uses).
973 # venv, which is likely what Python 3 uses).
963 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
974 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
964
975
965 if py2exehacked:
976 if py2exehacked:
966 from distutils.command.py2exe import py2exe as buildpy2exe
977 from distutils.command.py2exe import py2exe as buildpy2exe
967 from py2exe.mf import Module as py2exemodule
978 from py2exe.mf import Module as py2exemodule
968
979
969 class hgbuildpy2exe(buildpy2exe):
980 class hgbuildpy2exe(buildpy2exe):
970 def find_needed_modules(self, mf, files, modules):
981 def find_needed_modules(self, mf, files, modules):
971 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
982 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
972
983
973 # Replace virtualenv's distutils modules with the real ones.
984 # Replace virtualenv's distutils modules with the real ones.
974 modules = {}
985 modules = {}
975 for k, v in res.modules.items():
986 for k, v in res.modules.items():
976 if k != 'distutils' and not k.startswith('distutils.'):
987 if k != 'distutils' and not k.startswith('distutils.'):
977 modules[k] = v
988 modules[k] = v
978
989
979 res.modules = modules
990 res.modules = modules
980
991
981 import opcode
992 import opcode
982 distutilsreal = os.path.join(os.path.dirname(opcode.__file__),
993 distutilsreal = os.path.join(os.path.dirname(opcode.__file__),
983 'distutils')
994 'distutils')
984
995
985 for root, dirs, files in os.walk(distutilsreal):
996 for root, dirs, files in os.walk(distutilsreal):
986 for f in sorted(files):
997 for f in sorted(files):
987 if not f.endswith('.py'):
998 if not f.endswith('.py'):
988 continue
999 continue
989
1000
990 full = os.path.join(root, f)
1001 full = os.path.join(root, f)
991
1002
992 parents = ['distutils']
1003 parents = ['distutils']
993
1004
994 if root != distutilsreal:
1005 if root != distutilsreal:
995 rel = os.path.relpath(root, distutilsreal)
1006 rel = os.path.relpath(root, distutilsreal)
996 parents.extend(p for p in rel.split(os.sep))
1007 parents.extend(p for p in rel.split(os.sep))
997
1008
998 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1009 modname = '%s.%s' % ('.'.join(parents), f[:-3])
999
1010
1000 if modname.startswith('distutils.tests.'):
1011 if modname.startswith('distutils.tests.'):
1001 continue
1012 continue
1002
1013
1003 if modname.endswith('.__init__'):
1014 if modname.endswith('.__init__'):
1004 modname = modname[:-len('.__init__')]
1015 modname = modname[:-len('.__init__')]
1005 path = os.path.dirname(full)
1016 path = os.path.dirname(full)
1006 else:
1017 else:
1007 path = None
1018 path = None
1008
1019
1009 res.modules[modname] = py2exemodule(modname, full,
1020 res.modules[modname] = py2exemodule(modname, full,
1010 path=path)
1021 path=path)
1011
1022
1012 if 'distutils' not in res.modules:
1023 if 'distutils' not in res.modules:
1013 raise SystemExit('could not find distutils modules')
1024 raise SystemExit('could not find distutils modules')
1014
1025
1015 return res
1026 return res
1016
1027
1017 cmdclass = {'build': hgbuild,
1028 cmdclass = {'build': hgbuild,
1018 'build_doc': hgbuilddoc,
1029 'build_doc': hgbuilddoc,
1019 'build_mo': hgbuildmo,
1030 'build_mo': hgbuildmo,
1020 'build_ext': hgbuildext,
1031 'build_ext': hgbuildext,
1021 'build_py': hgbuildpy,
1032 'build_py': hgbuildpy,
1022 'build_scripts': hgbuildscripts,
1033 'build_scripts': hgbuildscripts,
1023 'build_hgextindex': buildhgextindex,
1034 'build_hgextindex': buildhgextindex,
1024 'install': hginstall,
1035 'install': hginstall,
1025 'install_lib': hginstalllib,
1036 'install_lib': hginstalllib,
1026 'install_scripts': hginstallscripts,
1037 'install_scripts': hginstallscripts,
1027 'build_hgexe': buildhgexe,
1038 'build_hgexe': buildhgexe,
1028 }
1039 }
1029
1040
1030 if py2exehacked:
1041 if py2exehacked:
1031 cmdclass['py2exe'] = hgbuildpy2exe
1042 cmdclass['py2exe'] = hgbuildpy2exe
1032
1043
1033 packages = ['mercurial',
1044 packages = ['mercurial',
1034 'mercurial.cext',
1045 'mercurial.cext',
1035 'mercurial.cffi',
1046 'mercurial.cffi',
1036 'mercurial.hgweb',
1047 'mercurial.hgweb',
1037 'mercurial.pure',
1048 'mercurial.pure',
1038 'mercurial.thirdparty',
1049 'mercurial.thirdparty',
1039 'mercurial.thirdparty.attr',
1050 'mercurial.thirdparty.attr',
1040 'mercurial.thirdparty.zope',
1051 'mercurial.thirdparty.zope',
1041 'mercurial.thirdparty.zope.interface',
1052 'mercurial.thirdparty.zope.interface',
1042 'mercurial.utils',
1053 'mercurial.utils',
1043 'mercurial.revlogutils',
1054 'mercurial.revlogutils',
1044 'mercurial.testing',
1055 'mercurial.testing',
1045 'hgext', 'hgext.convert', 'hgext.fsmonitor',
1056 'hgext', 'hgext.convert', 'hgext.fsmonitor',
1046 'hgext.fastannotate',
1057 'hgext.fastannotate',
1047 'hgext.fsmonitor.pywatchman',
1058 'hgext.fsmonitor.pywatchman',
1048 'hgext.infinitepush',
1059 'hgext.infinitepush',
1049 'hgext.highlight',
1060 'hgext.highlight',
1050 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
1061 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
1051 'hgext.remotefilelog',
1062 'hgext.remotefilelog',
1052 'hgext.zeroconf', 'hgext3rd',
1063 'hgext.zeroconf', 'hgext3rd',
1053 'hgdemandimport']
1064 'hgdemandimport']
1054 if sys.version_info[0] == 2:
1065 if sys.version_info[0] == 2:
1055 packages.extend(['mercurial.thirdparty.concurrent',
1066 packages.extend(['mercurial.thirdparty.concurrent',
1056 'mercurial.thirdparty.concurrent.futures'])
1067 'mercurial.thirdparty.concurrent.futures'])
1057
1068
1058 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1069 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1059 # py2exe can't cope with namespace packages very well, so we have to
1070 # py2exe can't cope with namespace packages very well, so we have to
1060 # install any hgext3rd.* extensions that we want in the final py2exe
1071 # install any hgext3rd.* extensions that we want in the final py2exe
1061 # image here. This is gross, but you gotta do what you gotta do.
1072 # image here. This is gross, but you gotta do what you gotta do.
1062 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1073 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1063
1074
1064 common_depends = ['mercurial/bitmanipulation.h',
1075 common_depends = ['mercurial/bitmanipulation.h',
1065 'mercurial/compat.h',
1076 'mercurial/compat.h',
1066 'mercurial/cext/util.h']
1077 'mercurial/cext/util.h']
1067 common_include_dirs = ['mercurial']
1078 common_include_dirs = ['mercurial']
1068
1079
1069 osutil_cflags = []
1080 osutil_cflags = []
1070 osutil_ldflags = []
1081 osutil_ldflags = []
1071
1082
1072 # platform specific macros
1083 # platform specific macros
1073 for plat, func in [('bsd', 'setproctitle')]:
1084 for plat, func in [('bsd', 'setproctitle')]:
1074 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1085 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1075 osutil_cflags.append('-DHAVE_%s' % func.upper())
1086 osutil_cflags.append('-DHAVE_%s' % func.upper())
1076
1087
1077 for plat, macro, code in [
1088 for plat, macro, code in [
1078 ('bsd|darwin', 'BSD_STATFS', '''
1089 ('bsd|darwin', 'BSD_STATFS', '''
1079 #include <sys/param.h>
1090 #include <sys/param.h>
1080 #include <sys/mount.h>
1091 #include <sys/mount.h>
1081 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1092 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1082 '''),
1093 '''),
1083 ('linux', 'LINUX_STATFS', '''
1094 ('linux', 'LINUX_STATFS', '''
1084 #include <linux/magic.h>
1095 #include <linux/magic.h>
1085 #include <sys/vfs.h>
1096 #include <sys/vfs.h>
1086 int main() { struct statfs s; return sizeof(s.f_type); }
1097 int main() { struct statfs s; return sizeof(s.f_type); }
1087 '''),
1098 '''),
1088 ]:
1099 ]:
1089 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1100 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1090 osutil_cflags.append('-DHAVE_%s' % macro)
1101 osutil_cflags.append('-DHAVE_%s' % macro)
1091
1102
1092 if sys.platform == 'darwin':
1103 if sys.platform == 'darwin':
1093 osutil_ldflags += ['-framework', 'ApplicationServices']
1104 osutil_ldflags += ['-framework', 'ApplicationServices']
1094
1105
1095 xdiff_srcs = [
1106 xdiff_srcs = [
1096 'mercurial/thirdparty/xdiff/xdiffi.c',
1107 'mercurial/thirdparty/xdiff/xdiffi.c',
1097 'mercurial/thirdparty/xdiff/xprepare.c',
1108 'mercurial/thirdparty/xdiff/xprepare.c',
1098 'mercurial/thirdparty/xdiff/xutils.c',
1109 'mercurial/thirdparty/xdiff/xutils.c',
1099 ]
1110 ]
1100
1111
1101 xdiff_headers = [
1112 xdiff_headers = [
1102 'mercurial/thirdparty/xdiff/xdiff.h',
1113 'mercurial/thirdparty/xdiff/xdiff.h',
1103 'mercurial/thirdparty/xdiff/xdiffi.h',
1114 'mercurial/thirdparty/xdiff/xdiffi.h',
1104 'mercurial/thirdparty/xdiff/xinclude.h',
1115 'mercurial/thirdparty/xdiff/xinclude.h',
1105 'mercurial/thirdparty/xdiff/xmacros.h',
1116 'mercurial/thirdparty/xdiff/xmacros.h',
1106 'mercurial/thirdparty/xdiff/xprepare.h',
1117 'mercurial/thirdparty/xdiff/xprepare.h',
1107 'mercurial/thirdparty/xdiff/xtypes.h',
1118 'mercurial/thirdparty/xdiff/xtypes.h',
1108 'mercurial/thirdparty/xdiff/xutils.h',
1119 'mercurial/thirdparty/xdiff/xutils.h',
1109 ]
1120 ]
1110
1121
1111 class RustCompilationError(CCompilerError):
1122 class RustCompilationError(CCompilerError):
1112 """Exception class for Rust compilation errors."""
1123 """Exception class for Rust compilation errors."""
1113
1124
1114 class RustExtension(Extension):
1125 class RustExtension(Extension):
1115 """Base classes for concrete Rust Extension classes.
1126 """Base classes for concrete Rust Extension classes.
1116 """
1127 """
1117
1128
1118 rusttargetdir = os.path.join('rust', 'target', 'release')
1129 rusttargetdir = os.path.join('rust', 'target', 'release')
1119
1130
1120 def __init__(self, mpath, sources, rustlibname, subcrate,
1131 def __init__(self, mpath, sources, rustlibname, subcrate,
1121 py3_features=None, **kw):
1132 py3_features=None, **kw):
1122 Extension.__init__(self, mpath, sources, **kw)
1133 Extension.__init__(self, mpath, sources, **kw)
1123 if hgrustext is None:
1134 if hgrustext is None:
1124 return
1135 return
1125 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1136 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1126 self.py3_features = py3_features
1137 self.py3_features = py3_features
1127
1138
1128 # adding Rust source and control files to depends so that the extension
1139 # adding Rust source and control files to depends so that the extension
1129 # gets rebuilt if they've changed
1140 # gets rebuilt if they've changed
1130 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1141 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1131 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1142 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1132 if os.path.exists(cargo_lock):
1143 if os.path.exists(cargo_lock):
1133 self.depends.append(cargo_lock)
1144 self.depends.append(cargo_lock)
1134 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1145 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1135 self.depends.extend(os.path.join(dirpath, fname)
1146 self.depends.extend(os.path.join(dirpath, fname)
1136 for fname in fnames
1147 for fname in fnames
1137 if os.path.splitext(fname)[1] == '.rs')
1148 if os.path.splitext(fname)[1] == '.rs')
1138
1149
1139 def rustbuild(self):
1150 def rustbuild(self):
1140 if hgrustext is None:
1151 if hgrustext is None:
1141 return
1152 return
1142 env = os.environ.copy()
1153 env = os.environ.copy()
1143 if 'HGTEST_RESTOREENV' in env:
1154 if 'HGTEST_RESTOREENV' in env:
1144 # Mercurial tests change HOME to a temporary directory,
1155 # Mercurial tests change HOME to a temporary directory,
1145 # but, if installed with rustup, the Rust toolchain needs
1156 # but, if installed with rustup, the Rust toolchain needs
1146 # HOME to be correct (otherwise the 'no default toolchain'
1157 # HOME to be correct (otherwise the 'no default toolchain'
1147 # error message is issued and the build fails).
1158 # error message is issued and the build fails).
1148 # This happens currently with test-hghave.t, which does
1159 # This happens currently with test-hghave.t, which does
1149 # invoke this build.
1160 # invoke this build.
1150
1161
1151 # Unix only fix (os.path.expanduser not really reliable if
1162 # Unix only fix (os.path.expanduser not really reliable if
1152 # HOME is shadowed like this)
1163 # HOME is shadowed like this)
1153 import pwd
1164 import pwd
1154 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1165 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1155
1166
1156 cargocmd = ['cargo', 'build', '-vv', '--release']
1167 cargocmd = ['cargo', 'build', '-vv', '--release']
1157 if sys.version_info[0] == 3 and self.py3_features is not None:
1168 if sys.version_info[0] == 3 and self.py3_features is not None:
1158 cargocmd.extend(('--features', self.py3_features,
1169 cargocmd.extend(('--features', self.py3_features,
1159 '--no-default-features'))
1170 '--no-default-features'))
1160 try:
1171 try:
1161 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1172 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1162 except OSError as exc:
1173 except OSError as exc:
1163 if exc.errno == errno.ENOENT:
1174 if exc.errno == errno.ENOENT:
1164 raise RustCompilationError("Cargo not found")
1175 raise RustCompilationError("Cargo not found")
1165 elif exc.errno == errno.EACCES:
1176 elif exc.errno == errno.EACCES:
1166 raise RustCompilationError(
1177 raise RustCompilationError(
1167 "Cargo found, but permisssion to execute it is denied")
1178 "Cargo found, but permisssion to execute it is denied")
1168 else:
1179 else:
1169 raise
1180 raise
1170 except subprocess.CalledProcessError:
1181 except subprocess.CalledProcessError:
1171 raise RustCompilationError(
1182 raise RustCompilationError(
1172 "Cargo failed. Working directory: %r, "
1183 "Cargo failed. Working directory: %r, "
1173 "command: %r, environment: %r"
1184 "command: %r, environment: %r"
1174 % (self.rustsrcdir, cargocmd, env))
1185 % (self.rustsrcdir, cargocmd, env))
1175
1186
1176 class RustEnhancedExtension(RustExtension):
1187 class RustEnhancedExtension(RustExtension):
1177 """A C Extension, conditionally enhanced with Rust code.
1188 """A C Extension, conditionally enhanced with Rust code.
1178
1189
1179 If the HGRUSTEXT environment variable is set to something else
1190 If the HGRUSTEXT environment variable is set to something else
1180 than 'cpython', the Rust sources get compiled and linked within the
1191 than 'cpython', the Rust sources get compiled and linked within the
1181 C target shared library object.
1192 C target shared library object.
1182 """
1193 """
1183
1194
1184 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1195 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1185 RustExtension.__init__(self, mpath, sources, rustlibname, subcrate,
1196 RustExtension.__init__(self, mpath, sources, rustlibname, subcrate,
1186 **kw)
1197 **kw)
1187 if hgrustext != 'direct-ffi':
1198 if hgrustext != 'direct-ffi':
1188 return
1199 return
1189 self.extra_compile_args.append('-DWITH_RUST')
1200 self.extra_compile_args.append('-DWITH_RUST')
1190 self.libraries.append(rustlibname)
1201 self.libraries.append(rustlibname)
1191 self.library_dirs.append(self.rusttargetdir)
1202 self.library_dirs.append(self.rusttargetdir)
1192
1203
1193 class RustStandaloneExtension(RustExtension):
1204 class RustStandaloneExtension(RustExtension):
1194
1205
1195 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1206 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1196 RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate,
1207 RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate,
1197 **kw)
1208 **kw)
1198 self.dylibname = dylibname
1209 self.dylibname = dylibname
1199
1210
1200 def build(self, target_dir):
1211 def build(self, target_dir):
1201 self.rustbuild()
1212 self.rustbuild()
1202 target = [target_dir]
1213 target = [target_dir]
1203 target.extend(self.name.split('.'))
1214 target.extend(self.name.split('.'))
1204 ext = '.so' # TODO Unix only
1215 ext = '.so' # TODO Unix only
1205 target[-1] += ext
1216 target[-1] += ext
1206 shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext),
1217 shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext),
1207 os.path.join(*target))
1218 os.path.join(*target))
1208
1219
1209
1220
1210 extmodules = [
1221 extmodules = [
1211 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
1222 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
1212 include_dirs=common_include_dirs,
1223 include_dirs=common_include_dirs,
1213 depends=common_depends),
1224 depends=common_depends),
1214 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
1225 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
1215 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1226 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1216 include_dirs=common_include_dirs,
1227 include_dirs=common_include_dirs,
1217 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
1228 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
1218 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
1229 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
1219 'mercurial/cext/mpatch.c'],
1230 'mercurial/cext/mpatch.c'],
1220 include_dirs=common_include_dirs,
1231 include_dirs=common_include_dirs,
1221 depends=common_depends),
1232 depends=common_depends),
1222 RustEnhancedExtension(
1233 RustEnhancedExtension(
1223 'mercurial.cext.parsers', ['mercurial/cext/charencode.c',
1234 'mercurial.cext.parsers', ['mercurial/cext/charencode.c',
1224 'mercurial/cext/dirs.c',
1235 'mercurial/cext/dirs.c',
1225 'mercurial/cext/manifest.c',
1236 'mercurial/cext/manifest.c',
1226 'mercurial/cext/parsers.c',
1237 'mercurial/cext/parsers.c',
1227 'mercurial/cext/pathencode.c',
1238 'mercurial/cext/pathencode.c',
1228 'mercurial/cext/revlog.c'],
1239 'mercurial/cext/revlog.c'],
1229 'hgdirectffi',
1240 'hgdirectffi',
1230 'hg-direct-ffi',
1241 'hg-direct-ffi',
1231 include_dirs=common_include_dirs,
1242 include_dirs=common_include_dirs,
1232 depends=common_depends + ['mercurial/cext/charencode.h',
1243 depends=common_depends + ['mercurial/cext/charencode.h',
1233 'mercurial/cext/revlog.h',
1244 'mercurial/cext/revlog.h',
1234 'rust/hg-core/src/ancestors.rs',
1245 'rust/hg-core/src/ancestors.rs',
1235 'rust/hg-core/src/lib.rs']),
1246 'rust/hg-core/src/lib.rs']),
1236 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
1247 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
1237 include_dirs=common_include_dirs,
1248 include_dirs=common_include_dirs,
1238 extra_compile_args=osutil_cflags,
1249 extra_compile_args=osutil_cflags,
1239 extra_link_args=osutil_ldflags,
1250 extra_link_args=osutil_ldflags,
1240 depends=common_depends),
1251 depends=common_depends),
1241 Extension(
1252 Extension(
1242 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
1253 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
1243 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1254 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1244 ]),
1255 ]),
1245 Extension('hgext.fsmonitor.pywatchman.bser',
1256 Extension('hgext.fsmonitor.pywatchman.bser',
1246 ['hgext/fsmonitor/pywatchman/bser.c']),
1257 ['hgext/fsmonitor/pywatchman/bser.c']),
1247 ]
1258 ]
1248
1259
1249 if hgrustext == 'cpython':
1260 if hgrustext == 'cpython':
1250 extmodules.append(
1261 extmodules.append(
1251 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg',
1262 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg',
1252 py3_features='python3')
1263 py3_features='python3')
1253 )
1264 )
1254
1265
1255
1266
1256 sys.path.insert(0, 'contrib/python-zstandard')
1267 sys.path.insert(0, 'contrib/python-zstandard')
1257 import setup_zstd
1268 import setup_zstd
1258 extmodules.append(setup_zstd.get_c_extension(
1269 extmodules.append(setup_zstd.get_c_extension(
1259 name='mercurial.zstd',
1270 name='mercurial.zstd',
1260 root=os.path.abspath(os.path.dirname(__file__))))
1271 root=os.path.abspath(os.path.dirname(__file__))))
1261
1272
1262 try:
1273 try:
1263 from distutils import cygwinccompiler
1274 from distutils import cygwinccompiler
1264
1275
1265 # the -mno-cygwin option has been deprecated for years
1276 # the -mno-cygwin option has been deprecated for years
1266 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1277 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1267
1278
1268 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1279 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1269 def __init__(self, *args, **kwargs):
1280 def __init__(self, *args, **kwargs):
1270 mingw32compilerclass.__init__(self, *args, **kwargs)
1281 mingw32compilerclass.__init__(self, *args, **kwargs)
1271 for i in 'compiler compiler_so linker_exe linker_so'.split():
1282 for i in 'compiler compiler_so linker_exe linker_so'.split():
1272 try:
1283 try:
1273 getattr(self, i).remove('-mno-cygwin')
1284 getattr(self, i).remove('-mno-cygwin')
1274 except ValueError:
1285 except ValueError:
1275 pass
1286 pass
1276
1287
1277 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1288 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1278 except ImportError:
1289 except ImportError:
1279 # the cygwinccompiler package is not available on some Python
1290 # the cygwinccompiler package is not available on some Python
1280 # distributions like the ones from the optware project for Synology
1291 # distributions like the ones from the optware project for Synology
1281 # DiskStation boxes
1292 # DiskStation boxes
1282 class HackedMingw32CCompiler(object):
1293 class HackedMingw32CCompiler(object):
1283 pass
1294 pass
1284
1295
1285 if os.name == 'nt':
1296 if os.name == 'nt':
1286 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1297 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1287 # extra_link_args to distutils.extensions.Extension() doesn't have any
1298 # extra_link_args to distutils.extensions.Extension() doesn't have any
1288 # effect.
1299 # effect.
1289 from distutils import msvccompiler
1300 from distutils import msvccompiler
1290
1301
1291 msvccompilerclass = msvccompiler.MSVCCompiler
1302 msvccompilerclass = msvccompiler.MSVCCompiler
1292
1303
1293 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1304 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1294 def initialize(self):
1305 def initialize(self):
1295 msvccompilerclass.initialize(self)
1306 msvccompilerclass.initialize(self)
1296 # "warning LNK4197: export 'func' specified multiple times"
1307 # "warning LNK4197: export 'func' specified multiple times"
1297 self.ldflags_shared.append('/ignore:4197')
1308 self.ldflags_shared.append('/ignore:4197')
1298 self.ldflags_shared_debug.append('/ignore:4197')
1309 self.ldflags_shared_debug.append('/ignore:4197')
1299
1310
1300 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1311 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1301
1312
1302 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
1313 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
1303 'help/*.txt',
1314 'help/*.txt',
1304 'help/internals/*.txt',
1315 'help/internals/*.txt',
1305 'default.d/*.rc',
1316 'default.d/*.rc',
1306 'dummycert.pem']}
1317 'dummycert.pem']}
1307
1318
1308 def ordinarypath(p):
1319 def ordinarypath(p):
1309 return p and p[0] != '.' and p[-1] != '~'
1320 return p and p[0] != '.' and p[-1] != '~'
1310
1321
1311 for root in ('templates',):
1322 for root in ('templates',):
1312 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1323 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1313 curdir = curdir.split(os.sep, 1)[1]
1324 curdir = curdir.split(os.sep, 1)[1]
1314 dirs[:] = filter(ordinarypath, dirs)
1325 dirs[:] = filter(ordinarypath, dirs)
1315 for f in filter(ordinarypath, files):
1326 for f in filter(ordinarypath, files):
1316 f = os.path.join(curdir, f)
1327 f = os.path.join(curdir, f)
1317 packagedata['mercurial'].append(f)
1328 packagedata['mercurial'].append(f)
1318
1329
1319 datafiles = []
1330 datafiles = []
1320
1331
1321 # distutils expects version to be str/unicode. Converting it to
1332 # distutils expects version to be str/unicode. Converting it to
1322 # unicode on Python 2 still works because it won't contain any
1333 # unicode on Python 2 still works because it won't contain any
1323 # non-ascii bytes and will be implicitly converted back to bytes
1334 # non-ascii bytes and will be implicitly converted back to bytes
1324 # when operated on.
1335 # when operated on.
1325 assert isinstance(version, bytes)
1336 assert isinstance(version, bytes)
1326 setupversion = version.decode('ascii')
1337 setupversion = version.decode('ascii')
1327
1338
1328 extra = {}
1339 extra = {}
1329
1340
1330 py2exepackages = [
1341 py2exepackages = [
1331 'hgdemandimport',
1342 'hgdemandimport',
1332 'hgext3rd',
1343 'hgext3rd',
1333 'hgext',
1344 'hgext',
1334 'email',
1345 'email',
1335 # implicitly imported per module policy
1346 # implicitly imported per module policy
1336 # (cffi wouldn't be used as a frozen exe)
1347 # (cffi wouldn't be used as a frozen exe)
1337 'mercurial.cext',
1348 'mercurial.cext',
1338 #'mercurial.cffi',
1349 #'mercurial.cffi',
1339 'mercurial.pure',
1350 'mercurial.pure',
1340 ]
1351 ]
1341
1352
1342 py2exeexcludes = []
1353 py2exeexcludes = []
1343 py2exedllexcludes = ['crypt32.dll']
1354 py2exedllexcludes = ['crypt32.dll']
1344
1355
1345 if issetuptools:
1356 if issetuptools:
1346 extra['python_requires'] = supportedpy
1357 extra['python_requires'] = supportedpy
1347
1358
1348 if py2exeloaded:
1359 if py2exeloaded:
1349 extra['console'] = [
1360 extra['console'] = [
1350 {'script':'hg',
1361 {'script':'hg',
1351 'copyright':'Copyright (C) 2005-2019 Matt Mackall and others',
1362 'copyright':'Copyright (C) 2005-2019 Matt Mackall and others',
1352 'product_version':version}]
1363 'product_version':version}]
1353 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1364 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1354 # Need to override hgbuild because it has a private copy of
1365 # Need to override hgbuild because it has a private copy of
1355 # build.sub_commands.
1366 # build.sub_commands.
1356 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1367 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1357 # put dlls in sub directory so that they won't pollute PATH
1368 # put dlls in sub directory so that they won't pollute PATH
1358 extra['zipfile'] = 'lib/library.zip'
1369 extra['zipfile'] = 'lib/library.zip'
1359
1370
1360 # We allow some configuration to be supplemented via environment
1371 # We allow some configuration to be supplemented via environment
1361 # variables. This is better than setup.cfg files because it allows
1372 # variables. This is better than setup.cfg files because it allows
1362 # supplementing configs instead of replacing them.
1373 # supplementing configs instead of replacing them.
1363 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1374 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1364 if extrapackages:
1375 if extrapackages:
1365 py2exepackages.extend(extrapackages.split(' '))
1376 py2exepackages.extend(extrapackages.split(' '))
1366
1377
1367 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1378 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1368 if excludes:
1379 if excludes:
1369 py2exeexcludes.extend(excludes.split(' '))
1380 py2exeexcludes.extend(excludes.split(' '))
1370
1381
1371 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1382 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1372 if dllexcludes:
1383 if dllexcludes:
1373 py2exedllexcludes.extend(dllexcludes.split(' '))
1384 py2exedllexcludes.extend(dllexcludes.split(' '))
1374
1385
1375 if os.name == 'nt':
1386 if os.name == 'nt':
1376 # Windows binary file versions for exe/dll files must have the
1387 # Windows binary file versions for exe/dll files must have the
1377 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1388 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1378 setupversion = setupversion.split(r'+', 1)[0]
1389 setupversion = setupversion.split(r'+', 1)[0]
1379
1390
1380 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1391 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1381 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1392 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1382 if version:
1393 if version:
1383 version = version[0]
1394 version = version[0]
1384 if sys.version_info[0] == 3:
1395 if sys.version_info[0] == 3:
1385 version = version.decode('utf-8')
1396 version = version.decode('utf-8')
1386 xcode4 = (version.startswith('Xcode') and
1397 xcode4 = (version.startswith('Xcode') and
1387 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1398 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1388 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1399 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1389 else:
1400 else:
1390 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1401 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1391 # installed, but instead with only command-line tools. Assume
1402 # installed, but instead with only command-line tools. Assume
1392 # that only happens on >= Lion, thus no PPC support.
1403 # that only happens on >= Lion, thus no PPC support.
1393 xcode4 = True
1404 xcode4 = True
1394 xcode51 = False
1405 xcode51 = False
1395
1406
1396 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1407 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1397 # distutils.sysconfig
1408 # distutils.sysconfig
1398 if xcode4:
1409 if xcode4:
1399 os.environ['ARCHFLAGS'] = ''
1410 os.environ['ARCHFLAGS'] = ''
1400
1411
1401 # XCode 5.1 changes clang such that it now fails to compile if the
1412 # XCode 5.1 changes clang such that it now fails to compile if the
1402 # -mno-fused-madd flag is passed, but the version of Python shipped with
1413 # -mno-fused-madd flag is passed, but the version of Python shipped with
1403 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1414 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1404 # C extension modules, and a bug has been filed upstream at
1415 # C extension modules, and a bug has been filed upstream at
1405 # http://bugs.python.org/issue21244. We also need to patch this here
1416 # http://bugs.python.org/issue21244. We also need to patch this here
1406 # so Mercurial can continue to compile in the meantime.
1417 # so Mercurial can continue to compile in the meantime.
1407 if xcode51:
1418 if xcode51:
1408 cflags = get_config_var('CFLAGS')
1419 cflags = get_config_var('CFLAGS')
1409 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1420 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1410 os.environ['CFLAGS'] = (
1421 os.environ['CFLAGS'] = (
1411 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1422 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1412
1423
1413 setup(name='mercurial',
1424 setup(name='mercurial',
1414 version=setupversion,
1425 version=setupversion,
1415 author='Matt Mackall and many others',
1426 author='Matt Mackall and many others',
1416 author_email='mercurial@mercurial-scm.org',
1427 author_email='mercurial@mercurial-scm.org',
1417 url='https://mercurial-scm.org/',
1428 url='https://mercurial-scm.org/',
1418 download_url='https://mercurial-scm.org/release/',
1429 download_url='https://mercurial-scm.org/release/',
1419 description=('Fast scalable distributed SCM (revision control, version '
1430 description=('Fast scalable distributed SCM (revision control, version '
1420 'control) system'),
1431 'control) system'),
1421 long_description=('Mercurial is a distributed SCM tool written in Python.'
1432 long_description=('Mercurial is a distributed SCM tool written in Python.'
1422 ' It is used by a number of large projects that require'
1433 ' It is used by a number of large projects that require'
1423 ' fast, reliable distributed revision control, such as '
1434 ' fast, reliable distributed revision control, such as '
1424 'Mozilla.'),
1435 'Mozilla.'),
1425 license='GNU GPLv2 or any later version',
1436 license='GNU GPLv2 or any later version',
1426 classifiers=[
1437 classifiers=[
1427 'Development Status :: 6 - Mature',
1438 'Development Status :: 6 - Mature',
1428 'Environment :: Console',
1439 'Environment :: Console',
1429 'Intended Audience :: Developers',
1440 'Intended Audience :: Developers',
1430 'Intended Audience :: System Administrators',
1441 'Intended Audience :: System Administrators',
1431 'License :: OSI Approved :: GNU General Public License (GPL)',
1442 'License :: OSI Approved :: GNU General Public License (GPL)',
1432 'Natural Language :: Danish',
1443 'Natural Language :: Danish',
1433 'Natural Language :: English',
1444 'Natural Language :: English',
1434 'Natural Language :: German',
1445 'Natural Language :: German',
1435 'Natural Language :: Italian',
1446 'Natural Language :: Italian',
1436 'Natural Language :: Japanese',
1447 'Natural Language :: Japanese',
1437 'Natural Language :: Portuguese (Brazilian)',
1448 'Natural Language :: Portuguese (Brazilian)',
1438 'Operating System :: Microsoft :: Windows',
1449 'Operating System :: Microsoft :: Windows',
1439 'Operating System :: OS Independent',
1450 'Operating System :: OS Independent',
1440 'Operating System :: POSIX',
1451 'Operating System :: POSIX',
1441 'Programming Language :: C',
1452 'Programming Language :: C',
1442 'Programming Language :: Python',
1453 'Programming Language :: Python',
1443 'Topic :: Software Development :: Version Control',
1454 'Topic :: Software Development :: Version Control',
1444 ],
1455 ],
1445 scripts=scripts,
1456 scripts=scripts,
1446 packages=packages,
1457 packages=packages,
1447 ext_modules=extmodules,
1458 ext_modules=extmodules,
1448 data_files=datafiles,
1459 data_files=datafiles,
1449 package_data=packagedata,
1460 package_data=packagedata,
1450 cmdclass=cmdclass,
1461 cmdclass=cmdclass,
1451 distclass=hgdist,
1462 distclass=hgdist,
1452 options={
1463 options={
1453 'py2exe': {
1464 'py2exe': {
1454 'bundle_files': 3,
1465 'bundle_files': 3,
1455 'dll_excludes': py2exedllexcludes,
1466 'dll_excludes': py2exedllexcludes,
1456 'excludes': py2exeexcludes,
1467 'excludes': py2exeexcludes,
1457 'packages': py2exepackages,
1468 'packages': py2exepackages,
1458 },
1469 },
1459 'bdist_mpkg': {
1470 'bdist_mpkg': {
1460 'zipdist': False,
1471 'zipdist': False,
1461 'license': 'COPYING',
1472 'license': 'COPYING',
1462 'readme': 'contrib/packaging/macosx/Readme.html',
1473 'readme': 'contrib/packaging/macosx/Readme.html',
1463 'welcome': 'contrib/packaging/macosx/Welcome.html',
1474 'welcome': 'contrib/packaging/macosx/Welcome.html',
1464 },
1475 },
1465 },
1476 },
1466 **extra)
1477 **extra)
General Comments 0
You need to be logged in to leave comments. Login now