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