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