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