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