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