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