##// END OF EJS Templates
setup: work around old versions of distutils breaking setup.py...
Augie Fackler -
r45120:4c6189d4 default
parent child Browse files
Show More
@@ -1,1749 +1,1753 b''
1 #
1 #
2 # This is the mercurial setup script.
2 # This is the mercurial setup script.
3 #
3 #
4 # 'python setup.py install', or
4 # 'python setup.py install', or
5 # 'python setup.py --help' for more options
5 # 'python setup.py --help' for more options
6 import os
6 import os
7
7
8 # Mercurial 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 # Not all distutils versions in the wild have boolean_options.
493 # This should be cleaned up when we're Python 3 only.
494 command_obj.boolean_options = (
495 getattr(command_obj, 'boolean_options', []) + self.boolean_options
496 )
493 return Distribution._set_command_options(
497 return Distribution._set_command_options(
494 self, command_obj, option_dict=option_dict
498 self, command_obj, option_dict=option_dict
495 )
499 )
496
500
497 def parse_command_line(self):
501 def parse_command_line(self):
498 ret = Distribution.parse_command_line(self)
502 ret = Distribution.parse_command_line(self)
499 if not (self.rust or self.no_rust):
503 if not (self.rust or self.no_rust):
500 hgrustext = os.environ.get('HGWITHRUSTEXT')
504 hgrustext = os.environ.get('HGWITHRUSTEXT')
501 # TODO record it for proper rebuild upon changes
505 # TODO record it for proper rebuild upon changes
502 # (see mercurial/__modulepolicy__.py)
506 # (see mercurial/__modulepolicy__.py)
503 if hgrustext != 'cpython' and hgrustext is not None:
507 if hgrustext != 'cpython' and hgrustext is not None:
504 if hgrustext:
508 if hgrustext:
505 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
509 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
506 printf(msg, file=sys.stderr)
510 printf(msg, file=sys.stderr)
507 hgrustext = None
511 hgrustext = None
508 self.rust = hgrustext is not None
512 self.rust = hgrustext is not None
509 self.no_rust = not self.rust
513 self.no_rust = not self.rust
510 return ret
514 return ret
511
515
512 def has_ext_modules(self):
516 def has_ext_modules(self):
513 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
517 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
514 # too late for some cases
518 # too late for some cases
515 return not self.pure and Distribution.has_ext_modules(self)
519 return not self.pure and Distribution.has_ext_modules(self)
516
520
517
521
518 # This is ugly as a one-liner. So use a variable.
522 # This is ugly as a one-liner. So use a variable.
519 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
523 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
520 buildextnegops['no-zstd'] = 'zstd'
524 buildextnegops['no-zstd'] = 'zstd'
521 buildextnegops['no-rust'] = 'rust'
525 buildextnegops['no-rust'] = 'rust'
522
526
523
527
524 class hgbuildext(build_ext):
528 class hgbuildext(build_ext):
525 user_options = build_ext.user_options + [
529 user_options = build_ext.user_options + [
526 ('zstd', None, 'compile zstd bindings [default]'),
530 ('zstd', None, 'compile zstd bindings [default]'),
527 ('no-zstd', None, 'do not compile zstd bindings'),
531 ('no-zstd', None, 'do not compile zstd bindings'),
528 (
532 (
529 'rust',
533 'rust',
530 None,
534 None,
531 'compile Rust extensions if they are in use '
535 'compile Rust extensions if they are in use '
532 '(requires Cargo) [default]',
536 '(requires Cargo) [default]',
533 ),
537 ),
534 ('no-rust', None, 'do not compile Rust extensions'),
538 ('no-rust', None, 'do not compile Rust extensions'),
535 ]
539 ]
536
540
537 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
541 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
538 negative_opt = buildextnegops
542 negative_opt = buildextnegops
539
543
540 def initialize_options(self):
544 def initialize_options(self):
541 self.zstd = True
545 self.zstd = True
542 self.rust = True
546 self.rust = True
543
547
544 return build_ext.initialize_options(self)
548 return build_ext.initialize_options(self)
545
549
546 def finalize_options(self):
550 def finalize_options(self):
547 # Unless overridden by the end user, build extensions in parallel.
551 # Unless overridden by the end user, build extensions in parallel.
548 # Only influences behavior on Python 3.5+.
552 # Only influences behavior on Python 3.5+.
549 if getattr(self, 'parallel', None) is None:
553 if getattr(self, 'parallel', None) is None:
550 self.parallel = True
554 self.parallel = True
551
555
552 return build_ext.finalize_options(self)
556 return build_ext.finalize_options(self)
553
557
554 def build_extensions(self):
558 def build_extensions(self):
555 ruststandalones = [
559 ruststandalones = [
556 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
560 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
557 ]
561 ]
558 self.extensions = [
562 self.extensions = [
559 e for e in self.extensions if e not in ruststandalones
563 e for e in self.extensions if e not in ruststandalones
560 ]
564 ]
561 # Filter out zstd if disabled via argument.
565 # Filter out zstd if disabled via argument.
562 if not self.zstd:
566 if not self.zstd:
563 self.extensions = [
567 self.extensions = [
564 e for e in self.extensions if e.name != 'mercurial.zstd'
568 e for e in self.extensions if e.name != 'mercurial.zstd'
565 ]
569 ]
566
570
567 # Build Rust standalon extensions if it'll be used
571 # Build Rust standalon extensions if it'll be used
568 # and its build is not explictely disabled (for external build
572 # and its build is not explictely disabled (for external build
569 # as Linux distributions would do)
573 # as Linux distributions would do)
570 if self.distribution.rust and self.rust:
574 if self.distribution.rust and self.rust:
571 for rustext in ruststandalones:
575 for rustext in ruststandalones:
572 rustext.build('' if self.inplace else self.build_lib)
576 rustext.build('' if self.inplace else self.build_lib)
573
577
574 return build_ext.build_extensions(self)
578 return build_ext.build_extensions(self)
575
579
576 def build_extension(self, ext):
580 def build_extension(self, ext):
577 if (
581 if (
578 self.distribution.rust
582 self.distribution.rust
579 and self.rust
583 and self.rust
580 and isinstance(ext, RustExtension)
584 and isinstance(ext, RustExtension)
581 ):
585 ):
582 ext.rustbuild()
586 ext.rustbuild()
583 try:
587 try:
584 build_ext.build_extension(self, ext)
588 build_ext.build_extension(self, ext)
585 except CCompilerError:
589 except CCompilerError:
586 if not getattr(ext, 'optional', False):
590 if not getattr(ext, 'optional', False):
587 raise
591 raise
588 log.warn(
592 log.warn(
589 "Failed to build optional extension '%s' (skipping)", ext.name
593 "Failed to build optional extension '%s' (skipping)", ext.name
590 )
594 )
591
595
592
596
593 class hgbuildscripts(build_scripts):
597 class hgbuildscripts(build_scripts):
594 def run(self):
598 def run(self):
595 if os.name != 'nt' or self.distribution.pure:
599 if os.name != 'nt' or self.distribution.pure:
596 return build_scripts.run(self)
600 return build_scripts.run(self)
597
601
598 exebuilt = False
602 exebuilt = False
599 try:
603 try:
600 self.run_command('build_hgexe')
604 self.run_command('build_hgexe')
601 exebuilt = True
605 exebuilt = True
602 except (DistutilsError, CCompilerError):
606 except (DistutilsError, CCompilerError):
603 log.warn('failed to build optional hg.exe')
607 log.warn('failed to build optional hg.exe')
604
608
605 if exebuilt:
609 if exebuilt:
606 # Copying hg.exe to the scripts build directory ensures it is
610 # Copying hg.exe to the scripts build directory ensures it is
607 # installed by the install_scripts command.
611 # installed by the install_scripts command.
608 hgexecommand = self.get_finalized_command('build_hgexe')
612 hgexecommand = self.get_finalized_command('build_hgexe')
609 dest = os.path.join(self.build_dir, 'hg.exe')
613 dest = os.path.join(self.build_dir, 'hg.exe')
610 self.mkpath(self.build_dir)
614 self.mkpath(self.build_dir)
611 self.copy_file(hgexecommand.hgexepath, dest)
615 self.copy_file(hgexecommand.hgexepath, dest)
612
616
613 # Remove hg.bat because it is redundant with hg.exe.
617 # Remove hg.bat because it is redundant with hg.exe.
614 self.scripts.remove('contrib/win32/hg.bat')
618 self.scripts.remove('contrib/win32/hg.bat')
615
619
616 return build_scripts.run(self)
620 return build_scripts.run(self)
617
621
618
622
619 class hgbuildpy(build_py):
623 class hgbuildpy(build_py):
620 def finalize_options(self):
624 def finalize_options(self):
621 build_py.finalize_options(self)
625 build_py.finalize_options(self)
622
626
623 if self.distribution.pure:
627 if self.distribution.pure:
624 self.distribution.ext_modules = []
628 self.distribution.ext_modules = []
625 elif self.distribution.cffi:
629 elif self.distribution.cffi:
626 from mercurial.cffi import (
630 from mercurial.cffi import (
627 bdiffbuild,
631 bdiffbuild,
628 mpatchbuild,
632 mpatchbuild,
629 )
633 )
630
634
631 exts = [
635 exts = [
632 mpatchbuild.ffi.distutils_extension(),
636 mpatchbuild.ffi.distutils_extension(),
633 bdiffbuild.ffi.distutils_extension(),
637 bdiffbuild.ffi.distutils_extension(),
634 ]
638 ]
635 # cffi modules go here
639 # cffi modules go here
636 if sys.platform == 'darwin':
640 if sys.platform == 'darwin':
637 from mercurial.cffi import osutilbuild
641 from mercurial.cffi import osutilbuild
638
642
639 exts.append(osutilbuild.ffi.distutils_extension())
643 exts.append(osutilbuild.ffi.distutils_extension())
640 self.distribution.ext_modules = exts
644 self.distribution.ext_modules = exts
641 else:
645 else:
642 h = os.path.join(get_python_inc(), 'Python.h')
646 h = os.path.join(get_python_inc(), 'Python.h')
643 if not os.path.exists(h):
647 if not os.path.exists(h):
644 raise SystemExit(
648 raise SystemExit(
645 'Python headers are required to build '
649 'Python headers are required to build '
646 'Mercurial but weren\'t found in %s' % h
650 'Mercurial but weren\'t found in %s' % h
647 )
651 )
648
652
649 def run(self):
653 def run(self):
650 basepath = os.path.join(self.build_lib, 'mercurial')
654 basepath = os.path.join(self.build_lib, 'mercurial')
651 self.mkpath(basepath)
655 self.mkpath(basepath)
652
656
653 rust = self.distribution.rust
657 rust = self.distribution.rust
654 if self.distribution.pure:
658 if self.distribution.pure:
655 modulepolicy = 'py'
659 modulepolicy = 'py'
656 elif self.build_lib == '.':
660 elif self.build_lib == '.':
657 # in-place build should run without rebuilding and Rust extensions
661 # in-place build should run without rebuilding and Rust extensions
658 modulepolicy = 'rust+c-allow' if rust else 'allow'
662 modulepolicy = 'rust+c-allow' if rust else 'allow'
659 else:
663 else:
660 modulepolicy = 'rust+c' if rust else 'c'
664 modulepolicy = 'rust+c' if rust else 'c'
661
665
662 content = b''.join(
666 content = b''.join(
663 [
667 [
664 b'# this file is autogenerated by setup.py\n',
668 b'# this file is autogenerated by setup.py\n',
665 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
669 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
666 ]
670 ]
667 )
671 )
668 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
672 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
669
673
670 build_py.run(self)
674 build_py.run(self)
671
675
672
676
673 class buildhgextindex(Command):
677 class buildhgextindex(Command):
674 description = 'generate prebuilt index of hgext (for frozen package)'
678 description = 'generate prebuilt index of hgext (for frozen package)'
675 user_options = []
679 user_options = []
676 _indexfilename = 'hgext/__index__.py'
680 _indexfilename = 'hgext/__index__.py'
677
681
678 def initialize_options(self):
682 def initialize_options(self):
679 pass
683 pass
680
684
681 def finalize_options(self):
685 def finalize_options(self):
682 pass
686 pass
683
687
684 def run(self):
688 def run(self):
685 if os.path.exists(self._indexfilename):
689 if os.path.exists(self._indexfilename):
686 with open(self._indexfilename, 'w') as f:
690 with open(self._indexfilename, 'w') as f:
687 f.write('# empty\n')
691 f.write('# empty\n')
688
692
689 # here no extension enabled, disabled() lists up everything
693 # here no extension enabled, disabled() lists up everything
690 code = (
694 code = (
691 'import pprint; from mercurial import extensions; '
695 'import pprint; from mercurial import extensions; '
692 'ext = extensions.disabled();'
696 'ext = extensions.disabled();'
693 'ext.pop("__index__", None);'
697 'ext.pop("__index__", None);'
694 'pprint.pprint(ext)'
698 'pprint.pprint(ext)'
695 )
699 )
696 returncode, out, err = runcmd(
700 returncode, out, err = runcmd(
697 [sys.executable, '-c', code], localhgenv()
701 [sys.executable, '-c', code], localhgenv()
698 )
702 )
699 if err or returncode != 0:
703 if err or returncode != 0:
700 raise DistutilsExecError(err)
704 raise DistutilsExecError(err)
701
705
702 with open(self._indexfilename, 'wb') as f:
706 with open(self._indexfilename, 'wb') as f:
703 f.write(b'# this file is autogenerated by setup.py\n')
707 f.write(b'# this file is autogenerated by setup.py\n')
704 f.write(b'docs = ')
708 f.write(b'docs = ')
705 f.write(out)
709 f.write(out)
706
710
707
711
708 class buildhgexe(build_ext):
712 class buildhgexe(build_ext):
709 description = 'compile hg.exe from mercurial/exewrapper.c'
713 description = 'compile hg.exe from mercurial/exewrapper.c'
710 user_options = build_ext.user_options + [
714 user_options = build_ext.user_options + [
711 (
715 (
712 'long-paths-support',
716 'long-paths-support',
713 None,
717 None,
714 'enable support for long paths on '
718 'enable support for long paths on '
715 'Windows (off by default and '
719 'Windows (off by default and '
716 'experimental)',
720 'experimental)',
717 ),
721 ),
718 ]
722 ]
719
723
720 LONG_PATHS_MANIFEST = """
724 LONG_PATHS_MANIFEST = """
721 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
725 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
722 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
726 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
723 <application>
727 <application>
724 <windowsSettings
728 <windowsSettings
725 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
729 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
726 <ws2:longPathAware>true</ws2:longPathAware>
730 <ws2:longPathAware>true</ws2:longPathAware>
727 </windowsSettings>
731 </windowsSettings>
728 </application>
732 </application>
729 </assembly>"""
733 </assembly>"""
730
734
731 def initialize_options(self):
735 def initialize_options(self):
732 build_ext.initialize_options(self)
736 build_ext.initialize_options(self)
733 self.long_paths_support = False
737 self.long_paths_support = False
734
738
735 def build_extensions(self):
739 def build_extensions(self):
736 if os.name != 'nt':
740 if os.name != 'nt':
737 return
741 return
738 if isinstance(self.compiler, HackedMingw32CCompiler):
742 if isinstance(self.compiler, HackedMingw32CCompiler):
739 self.compiler.compiler_so = self.compiler.compiler # no -mdll
743 self.compiler.compiler_so = self.compiler.compiler # no -mdll
740 self.compiler.dll_libraries = [] # no -lmsrvc90
744 self.compiler.dll_libraries = [] # no -lmsrvc90
741
745
742 pythonlib = None
746 pythonlib = None
743
747
744 if getattr(sys, 'dllhandle', None):
748 if getattr(sys, 'dllhandle', None):
745 # Different Python installs can have different Python library
749 # Different Python installs can have different Python library
746 # names. e.g. the official CPython distribution uses pythonXY.dll
750 # names. e.g. the official CPython distribution uses pythonXY.dll
747 # and MinGW uses libpythonX.Y.dll.
751 # and MinGW uses libpythonX.Y.dll.
748 _kernel32 = ctypes.windll.kernel32
752 _kernel32 = ctypes.windll.kernel32
749 _kernel32.GetModuleFileNameA.argtypes = [
753 _kernel32.GetModuleFileNameA.argtypes = [
750 ctypes.c_void_p,
754 ctypes.c_void_p,
751 ctypes.c_void_p,
755 ctypes.c_void_p,
752 ctypes.c_ulong,
756 ctypes.c_ulong,
753 ]
757 ]
754 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
758 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
755 size = 1000
759 size = 1000
756 buf = ctypes.create_string_buffer(size + 1)
760 buf = ctypes.create_string_buffer(size + 1)
757 filelen = _kernel32.GetModuleFileNameA(
761 filelen = _kernel32.GetModuleFileNameA(
758 sys.dllhandle, ctypes.byref(buf), size
762 sys.dllhandle, ctypes.byref(buf), size
759 )
763 )
760
764
761 if filelen > 0 and filelen != size:
765 if filelen > 0 and filelen != size:
762 dllbasename = os.path.basename(buf.value)
766 dllbasename = os.path.basename(buf.value)
763 if not dllbasename.lower().endswith(b'.dll'):
767 if not dllbasename.lower().endswith(b'.dll'):
764 raise SystemExit(
768 raise SystemExit(
765 'Python DLL does not end with .dll: %s' % dllbasename
769 'Python DLL does not end with .dll: %s' % dllbasename
766 )
770 )
767 pythonlib = dllbasename[:-4]
771 pythonlib = dllbasename[:-4]
768
772
769 if not pythonlib:
773 if not pythonlib:
770 log.warn(
774 log.warn(
771 'could not determine Python DLL filename; assuming pythonXY'
775 'could not determine Python DLL filename; assuming pythonXY'
772 )
776 )
773
777
774 hv = sys.hexversion
778 hv = sys.hexversion
775 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
779 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
776
780
777 log.info('using %s as Python library name' % pythonlib)
781 log.info('using %s as Python library name' % pythonlib)
778 with open('mercurial/hgpythonlib.h', 'wb') as f:
782 with open('mercurial/hgpythonlib.h', 'wb') as f:
779 f.write(b'/* this file is autogenerated by setup.py */\n')
783 f.write(b'/* this file is autogenerated by setup.py */\n')
780 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
784 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
781
785
782 macros = None
786 macros = None
783 if sys.version_info[0] >= 3:
787 if sys.version_info[0] >= 3:
784 macros = [('_UNICODE', None), ('UNICODE', None)]
788 macros = [('_UNICODE', None), ('UNICODE', None)]
785
789
786 objects = self.compiler.compile(
790 objects = self.compiler.compile(
787 ['mercurial/exewrapper.c'],
791 ['mercurial/exewrapper.c'],
788 output_dir=self.build_temp,
792 output_dir=self.build_temp,
789 macros=macros,
793 macros=macros,
790 )
794 )
791 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
795 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
792 self.hgtarget = os.path.join(dir, 'hg')
796 self.hgtarget = os.path.join(dir, 'hg')
793 self.compiler.link_executable(
797 self.compiler.link_executable(
794 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
798 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
795 )
799 )
796 if self.long_paths_support:
800 if self.long_paths_support:
797 self.addlongpathsmanifest()
801 self.addlongpathsmanifest()
798
802
799 def addlongpathsmanifest(self):
803 def addlongpathsmanifest(self):
800 r"""Add manifest pieces so that hg.exe understands long paths
804 r"""Add manifest pieces so that hg.exe understands long paths
801
805
802 This is an EXPERIMENTAL feature, use with care.
806 This is an EXPERIMENTAL feature, use with care.
803 To enable long paths support, one needs to do two things:
807 To enable long paths support, one needs to do two things:
804 - build Mercurial with --long-paths-support option
808 - build Mercurial with --long-paths-support option
805 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
809 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
806 LongPathsEnabled to have value 1.
810 LongPathsEnabled to have value 1.
807
811
808 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
812 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
809 it happens because Mercurial uses mt.exe circa 2008, which is not
813 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).
814 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.
815 This does not stop mt.exe from embedding/merging the XML properly.
812
816
813 Why resource #1 should be used for .exe manifests? I don't know and
817 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.
818 wasn't able to find an explanation for mortals. But it seems to work.
815 """
819 """
816 exefname = self.compiler.executable_filename(self.hgtarget)
820 exefname = self.compiler.executable_filename(self.hgtarget)
817 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
821 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
818 os.close(fdauto)
822 os.close(fdauto)
819 with open(manfname, 'w') as f:
823 with open(manfname, 'w') as f:
820 f.write(self.LONG_PATHS_MANIFEST)
824 f.write(self.LONG_PATHS_MANIFEST)
821 log.info("long paths manifest is written to '%s'" % manfname)
825 log.info("long paths manifest is written to '%s'" % manfname)
822 inputresource = '-inputresource:%s;#1' % exefname
826 inputresource = '-inputresource:%s;#1' % exefname
823 outputresource = '-outputresource:%s;#1' % exefname
827 outputresource = '-outputresource:%s;#1' % exefname
824 log.info("running mt.exe to update hg.exe's manifest in-place")
828 log.info("running mt.exe to update hg.exe's manifest in-place")
825 # supplying both -manifest and -inputresource to mt.exe makes
829 # supplying both -manifest and -inputresource to mt.exe makes
826 # it merge the embedded and supplied manifests in the -outputresource
830 # it merge the embedded and supplied manifests in the -outputresource
827 self.spawn(
831 self.spawn(
828 [
832 [
829 'mt.exe',
833 'mt.exe',
830 '-nologo',
834 '-nologo',
831 '-manifest',
835 '-manifest',
832 manfname,
836 manfname,
833 inputresource,
837 inputresource,
834 outputresource,
838 outputresource,
835 ]
839 ]
836 )
840 )
837 log.info("done updating hg.exe's manifest")
841 log.info("done updating hg.exe's manifest")
838 os.remove(manfname)
842 os.remove(manfname)
839
843
840 @property
844 @property
841 def hgexepath(self):
845 def hgexepath(self):
842 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
846 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
843 return os.path.join(self.build_temp, dir, 'hg.exe')
847 return os.path.join(self.build_temp, dir, 'hg.exe')
844
848
845
849
846 class hgbuilddoc(Command):
850 class hgbuilddoc(Command):
847 description = 'build documentation'
851 description = 'build documentation'
848 user_options = [
852 user_options = [
849 ('man', None, 'generate man pages'),
853 ('man', None, 'generate man pages'),
850 ('html', None, 'generate html pages'),
854 ('html', None, 'generate html pages'),
851 ]
855 ]
852
856
853 def initialize_options(self):
857 def initialize_options(self):
854 self.man = None
858 self.man = None
855 self.html = None
859 self.html = None
856
860
857 def finalize_options(self):
861 def finalize_options(self):
858 # If --man or --html are set, only generate what we're told to.
862 # If --man or --html are set, only generate what we're told to.
859 # Otherwise generate everything.
863 # Otherwise generate everything.
860 have_subset = self.man is not None or self.html is not None
864 have_subset = self.man is not None or self.html is not None
861
865
862 if have_subset:
866 if have_subset:
863 self.man = True if self.man else False
867 self.man = True if self.man else False
864 self.html = True if self.html else False
868 self.html = True if self.html else False
865 else:
869 else:
866 self.man = True
870 self.man = True
867 self.html = True
871 self.html = True
868
872
869 def run(self):
873 def run(self):
870 def normalizecrlf(p):
874 def normalizecrlf(p):
871 with open(p, 'rb') as fh:
875 with open(p, 'rb') as fh:
872 orig = fh.read()
876 orig = fh.read()
873
877
874 if b'\r\n' not in orig:
878 if b'\r\n' not in orig:
875 return
879 return
876
880
877 log.info('normalizing %s to LF line endings' % p)
881 log.info('normalizing %s to LF line endings' % p)
878 with open(p, 'wb') as fh:
882 with open(p, 'wb') as fh:
879 fh.write(orig.replace(b'\r\n', b'\n'))
883 fh.write(orig.replace(b'\r\n', b'\n'))
880
884
881 def gentxt(root):
885 def gentxt(root):
882 txt = 'doc/%s.txt' % root
886 txt = 'doc/%s.txt' % root
883 log.info('generating %s' % txt)
887 log.info('generating %s' % txt)
884 res, out, err = runcmd(
888 res, out, err = runcmd(
885 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
889 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
886 )
890 )
887 if res:
891 if res:
888 raise SystemExit(
892 raise SystemExit(
889 'error running gendoc.py: %s' % '\n'.join([out, err])
893 'error running gendoc.py: %s' % '\n'.join([out, err])
890 )
894 )
891
895
892 with open(txt, 'wb') as fh:
896 with open(txt, 'wb') as fh:
893 fh.write(out)
897 fh.write(out)
894
898
895 def gengendoc(root):
899 def gengendoc(root):
896 gendoc = 'doc/%s.gendoc.txt' % root
900 gendoc = 'doc/%s.gendoc.txt' % root
897
901
898 log.info('generating %s' % gendoc)
902 log.info('generating %s' % gendoc)
899 res, out, err = runcmd(
903 res, out, err = runcmd(
900 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
904 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
901 os.environ,
905 os.environ,
902 cwd='doc',
906 cwd='doc',
903 )
907 )
904 if res:
908 if res:
905 raise SystemExit(
909 raise SystemExit(
906 'error running gendoc: %s' % '\n'.join([out, err])
910 'error running gendoc: %s' % '\n'.join([out, err])
907 )
911 )
908
912
909 with open(gendoc, 'wb') as fh:
913 with open(gendoc, 'wb') as fh:
910 fh.write(out)
914 fh.write(out)
911
915
912 def genman(root):
916 def genman(root):
913 log.info('generating doc/%s' % root)
917 log.info('generating doc/%s' % root)
914 res, out, err = runcmd(
918 res, out, err = runcmd(
915 [
919 [
916 sys.executable,
920 sys.executable,
917 'runrst',
921 'runrst',
918 'hgmanpage',
922 'hgmanpage',
919 '--halt',
923 '--halt',
920 'warning',
924 'warning',
921 '--strip-elements-with-class',
925 '--strip-elements-with-class',
922 'htmlonly',
926 'htmlonly',
923 '%s.txt' % root,
927 '%s.txt' % root,
924 root,
928 root,
925 ],
929 ],
926 os.environ,
930 os.environ,
927 cwd='doc',
931 cwd='doc',
928 )
932 )
929 if res:
933 if res:
930 raise SystemExit(
934 raise SystemExit(
931 'error running runrst: %s' % '\n'.join([out, err])
935 'error running runrst: %s' % '\n'.join([out, err])
932 )
936 )
933
937
934 normalizecrlf('doc/%s' % root)
938 normalizecrlf('doc/%s' % root)
935
939
936 def genhtml(root):
940 def genhtml(root):
937 log.info('generating doc/%s.html' % root)
941 log.info('generating doc/%s.html' % root)
938 res, out, err = runcmd(
942 res, out, err = runcmd(
939 [
943 [
940 sys.executable,
944 sys.executable,
941 'runrst',
945 'runrst',
942 'html',
946 'html',
943 '--halt',
947 '--halt',
944 'warning',
948 'warning',
945 '--link-stylesheet',
949 '--link-stylesheet',
946 '--stylesheet-path',
950 '--stylesheet-path',
947 'style.css',
951 'style.css',
948 '%s.txt' % root,
952 '%s.txt' % root,
949 '%s.html' % root,
953 '%s.html' % root,
950 ],
954 ],
951 os.environ,
955 os.environ,
952 cwd='doc',
956 cwd='doc',
953 )
957 )
954 if res:
958 if res:
955 raise SystemExit(
959 raise SystemExit(
956 'error running runrst: %s' % '\n'.join([out, err])
960 'error running runrst: %s' % '\n'.join([out, err])
957 )
961 )
958
962
959 normalizecrlf('doc/%s.html' % root)
963 normalizecrlf('doc/%s.html' % root)
960
964
961 # This logic is duplicated in doc/Makefile.
965 # This logic is duplicated in doc/Makefile.
962 sources = {
966 sources = {
963 f
967 f
964 for f in os.listdir('mercurial/helptext')
968 for f in os.listdir('mercurial/helptext')
965 if re.search(r'[0-9]\.txt$', f)
969 if re.search(r'[0-9]\.txt$', f)
966 }
970 }
967
971
968 # common.txt is a one-off.
972 # common.txt is a one-off.
969 gentxt('common')
973 gentxt('common')
970
974
971 for source in sorted(sources):
975 for source in sorted(sources):
972 assert source[-4:] == '.txt'
976 assert source[-4:] == '.txt'
973 root = source[:-4]
977 root = source[:-4]
974
978
975 gentxt(root)
979 gentxt(root)
976 gengendoc(root)
980 gengendoc(root)
977
981
978 if self.man:
982 if self.man:
979 genman(root)
983 genman(root)
980 if self.html:
984 if self.html:
981 genhtml(root)
985 genhtml(root)
982
986
983
987
984 class hginstall(install):
988 class hginstall(install):
985
989
986 user_options = install.user_options + [
990 user_options = install.user_options + [
987 (
991 (
988 'old-and-unmanageable',
992 'old-and-unmanageable',
989 None,
993 None,
990 'noop, present for eggless setuptools compat',
994 'noop, present for eggless setuptools compat',
991 ),
995 ),
992 (
996 (
993 'single-version-externally-managed',
997 'single-version-externally-managed',
994 None,
998 None,
995 'noop, present for eggless setuptools compat',
999 'noop, present for eggless setuptools compat',
996 ),
1000 ),
997 ]
1001 ]
998
1002
999 # Also helps setuptools not be sad while we refuse to create eggs.
1003 # Also helps setuptools not be sad while we refuse to create eggs.
1000 single_version_externally_managed = True
1004 single_version_externally_managed = True
1001
1005
1002 def get_sub_commands(self):
1006 def get_sub_commands(self):
1003 # Screen out egg related commands to prevent egg generation. But allow
1007 # Screen out egg related commands to prevent egg generation. But allow
1004 # mercurial.egg-info generation, since that is part of modern
1008 # mercurial.egg-info generation, since that is part of modern
1005 # packaging.
1009 # packaging.
1006 excl = {'bdist_egg'}
1010 excl = {'bdist_egg'}
1007 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1011 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1008
1012
1009
1013
1010 class hginstalllib(install_lib):
1014 class hginstalllib(install_lib):
1011 '''
1015 '''
1012 This is a specialization of install_lib that replaces the copy_file used
1016 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,
1017 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
1018 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
1019 system has a umask of something like 027, preserving the permissions when
1016 copying will lead to a broken install.
1020 copying will lead to a broken install.
1017
1021
1018 Note that just passing keep_permissions=False to copy_file would be
1022 Note that just passing keep_permissions=False to copy_file would be
1019 insufficient, as it might still be applying a umask.
1023 insufficient, as it might still be applying a umask.
1020 '''
1024 '''
1021
1025
1022 def run(self):
1026 def run(self):
1023 realcopyfile = file_util.copy_file
1027 realcopyfile = file_util.copy_file
1024
1028
1025 def copyfileandsetmode(*args, **kwargs):
1029 def copyfileandsetmode(*args, **kwargs):
1026 src, dst = args[0], args[1]
1030 src, dst = args[0], args[1]
1027 dst, copied = realcopyfile(*args, **kwargs)
1031 dst, copied = realcopyfile(*args, **kwargs)
1028 if copied:
1032 if copied:
1029 st = os.stat(src)
1033 st = os.stat(src)
1030 # Persist executable bit (apply it to group and other if user
1034 # Persist executable bit (apply it to group and other if user
1031 # has it)
1035 # has it)
1032 if st[stat.ST_MODE] & stat.S_IXUSR:
1036 if st[stat.ST_MODE] & stat.S_IXUSR:
1033 setmode = int('0755', 8)
1037 setmode = int('0755', 8)
1034 else:
1038 else:
1035 setmode = int('0644', 8)
1039 setmode = int('0644', 8)
1036 m = stat.S_IMODE(st[stat.ST_MODE])
1040 m = stat.S_IMODE(st[stat.ST_MODE])
1037 m = (m & ~int('0777', 8)) | setmode
1041 m = (m & ~int('0777', 8)) | setmode
1038 os.chmod(dst, m)
1042 os.chmod(dst, m)
1039
1043
1040 file_util.copy_file = copyfileandsetmode
1044 file_util.copy_file = copyfileandsetmode
1041 try:
1045 try:
1042 install_lib.run(self)
1046 install_lib.run(self)
1043 finally:
1047 finally:
1044 file_util.copy_file = realcopyfile
1048 file_util.copy_file = realcopyfile
1045
1049
1046
1050
1047 class hginstallscripts(install_scripts):
1051 class hginstallscripts(install_scripts):
1048 '''
1052 '''
1049 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1053 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
1054 the configured directory for modules. If possible, the path is made relative
1051 to the directory for scripts.
1055 to the directory for scripts.
1052 '''
1056 '''
1053
1057
1054 def initialize_options(self):
1058 def initialize_options(self):
1055 install_scripts.initialize_options(self)
1059 install_scripts.initialize_options(self)
1056
1060
1057 self.install_lib = None
1061 self.install_lib = None
1058
1062
1059 def finalize_options(self):
1063 def finalize_options(self):
1060 install_scripts.finalize_options(self)
1064 install_scripts.finalize_options(self)
1061 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1065 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1062
1066
1063 def run(self):
1067 def run(self):
1064 install_scripts.run(self)
1068 install_scripts.run(self)
1065
1069
1066 # It only makes sense to replace @LIBDIR@ with the install path if
1070 # It only makes sense to replace @LIBDIR@ with the install path if
1067 # the install path is known. For wheels, the logic below calculates
1071 # the install path is known. For wheels, the logic below calculates
1068 # the libdir to be "../..". This is because the internal layout of a
1072 # the libdir to be "../..". This is because the internal layout of a
1069 # wheel archive looks like:
1073 # wheel archive looks like:
1070 #
1074 #
1071 # mercurial-3.6.1.data/scripts/hg
1075 # mercurial-3.6.1.data/scripts/hg
1072 # mercurial/__init__.py
1076 # mercurial/__init__.py
1073 #
1077 #
1074 # When installing wheels, the subdirectories of the "<pkg>.data"
1078 # When installing wheels, the subdirectories of the "<pkg>.data"
1075 # directory are translated to system local paths and files therein
1079 # directory are translated to system local paths and files therein
1076 # are copied in place. The mercurial/* files are installed into the
1080 # are copied in place. The mercurial/* files are installed into the
1077 # site-packages directory. However, the site-packages directory
1081 # site-packages directory. However, the site-packages directory
1078 # isn't known until wheel install time. This means we have no clue
1082 # isn't known until wheel install time. This means we have no clue
1079 # at wheel generation time what the installed site-packages directory
1083 # at wheel generation time what the installed site-packages directory
1080 # will be. And, wheels don't appear to provide the ability to register
1084 # 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
1085 # custom code to run during wheel installation. This all means that
1082 # we can't reliably set the libdir in wheels: the default behavior
1086 # we can't reliably set the libdir in wheels: the default behavior
1083 # of looking in sys.path must do.
1087 # of looking in sys.path must do.
1084
1088
1085 if (
1089 if (
1086 os.path.splitdrive(self.install_dir)[0]
1090 os.path.splitdrive(self.install_dir)[0]
1087 != os.path.splitdrive(self.install_lib)[0]
1091 != os.path.splitdrive(self.install_lib)[0]
1088 ):
1092 ):
1089 # can't make relative paths from one drive to another, so use an
1093 # can't make relative paths from one drive to another, so use an
1090 # absolute path instead
1094 # absolute path instead
1091 libdir = self.install_lib
1095 libdir = self.install_lib
1092 else:
1096 else:
1093 libdir = os.path.relpath(self.install_lib, self.install_dir)
1097 libdir = os.path.relpath(self.install_lib, self.install_dir)
1094
1098
1095 for outfile in self.outfiles:
1099 for outfile in self.outfiles:
1096 with open(outfile, 'rb') as fp:
1100 with open(outfile, 'rb') as fp:
1097 data = fp.read()
1101 data = fp.read()
1098
1102
1099 # skip binary files
1103 # skip binary files
1100 if b'\0' in data:
1104 if b'\0' in data:
1101 continue
1105 continue
1102
1106
1103 # During local installs, the shebang will be rewritten to the final
1107 # During local installs, the shebang will be rewritten to the final
1104 # install path. During wheel packaging, the shebang has a special
1108 # install path. During wheel packaging, the shebang has a special
1105 # value.
1109 # value.
1106 if data.startswith(b'#!python'):
1110 if data.startswith(b'#!python'):
1107 log.info(
1111 log.info(
1108 'not rewriting @LIBDIR@ in %s because install path '
1112 'not rewriting @LIBDIR@ in %s because install path '
1109 'not known' % outfile
1113 'not known' % outfile
1110 )
1114 )
1111 continue
1115 continue
1112
1116
1113 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1117 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1114 with open(outfile, 'wb') as fp:
1118 with open(outfile, 'wb') as fp:
1115 fp.write(data)
1119 fp.write(data)
1116
1120
1117
1121
1118 # virtualenv installs custom distutils/__init__.py and
1122 # virtualenv installs custom distutils/__init__.py and
1119 # distutils/distutils.cfg files which essentially proxy back to the
1123 # distutils/distutils.cfg files which essentially proxy back to the
1120 # "real" distutils in the main Python install. The presence of this
1124 # "real" distutils in the main Python install. The presence of this
1121 # directory causes py2exe to pick up the "hacked" distutils package
1125 # directory causes py2exe to pick up the "hacked" distutils package
1122 # from the virtualenv and "import distutils" will fail from the py2exe
1126 # from the virtualenv and "import distutils" will fail from the py2exe
1123 # build because the "real" distutils files can't be located.
1127 # build because the "real" distutils files can't be located.
1124 #
1128 #
1125 # We work around this by monkeypatching the py2exe code finding Python
1129 # We work around this by monkeypatching the py2exe code finding Python
1126 # modules to replace the found virtualenv distutils modules with the
1130 # modules to replace the found virtualenv distutils modules with the
1127 # original versions via filesystem scanning. This is a bit hacky. But
1131 # original versions via filesystem scanning. This is a bit hacky. But
1128 # it allows us to use virtualenvs for py2exe packaging, which is more
1132 # it allows us to use virtualenvs for py2exe packaging, which is more
1129 # deterministic and reproducible.
1133 # deterministic and reproducible.
1130 #
1134 #
1131 # It's worth noting that the common StackOverflow suggestions for this
1135 # It's worth noting that the common StackOverflow suggestions for this
1132 # problem involve copying the original distutils files into the
1136 # problem involve copying the original distutils files into the
1133 # virtualenv or into the staging directory after setup() is invoked.
1137 # virtualenv or into the staging directory after setup() is invoked.
1134 # The former is very brittle and can easily break setup(). Our hacking
1138 # 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
1139 # 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
1140 # manually. But it makes fewer assumptions about how py2exe works and
1137 # is less brittle.
1141 # is less brittle.
1138
1142
1139 # This only catches virtualenvs made with virtualenv (as opposed to
1143 # This only catches virtualenvs made with virtualenv (as opposed to
1140 # venv, which is likely what Python 3 uses).
1144 # venv, which is likely what Python 3 uses).
1141 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1145 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1142
1146
1143 if py2exehacked:
1147 if py2exehacked:
1144 from distutils.command.py2exe import py2exe as buildpy2exe
1148 from distutils.command.py2exe import py2exe as buildpy2exe
1145 from py2exe.mf import Module as py2exemodule
1149 from py2exe.mf import Module as py2exemodule
1146
1150
1147 class hgbuildpy2exe(buildpy2exe):
1151 class hgbuildpy2exe(buildpy2exe):
1148 def find_needed_modules(self, mf, files, modules):
1152 def find_needed_modules(self, mf, files, modules):
1149 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1153 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1150
1154
1151 # Replace virtualenv's distutils modules with the real ones.
1155 # Replace virtualenv's distutils modules with the real ones.
1152 modules = {}
1156 modules = {}
1153 for k, v in res.modules.items():
1157 for k, v in res.modules.items():
1154 if k != 'distutils' and not k.startswith('distutils.'):
1158 if k != 'distutils' and not k.startswith('distutils.'):
1155 modules[k] = v
1159 modules[k] = v
1156
1160
1157 res.modules = modules
1161 res.modules = modules
1158
1162
1159 import opcode
1163 import opcode
1160
1164
1161 distutilsreal = os.path.join(
1165 distutilsreal = os.path.join(
1162 os.path.dirname(opcode.__file__), 'distutils'
1166 os.path.dirname(opcode.__file__), 'distutils'
1163 )
1167 )
1164
1168
1165 for root, dirs, files in os.walk(distutilsreal):
1169 for root, dirs, files in os.walk(distutilsreal):
1166 for f in sorted(files):
1170 for f in sorted(files):
1167 if not f.endswith('.py'):
1171 if not f.endswith('.py'):
1168 continue
1172 continue
1169
1173
1170 full = os.path.join(root, f)
1174 full = os.path.join(root, f)
1171
1175
1172 parents = ['distutils']
1176 parents = ['distutils']
1173
1177
1174 if root != distutilsreal:
1178 if root != distutilsreal:
1175 rel = os.path.relpath(root, distutilsreal)
1179 rel = os.path.relpath(root, distutilsreal)
1176 parents.extend(p for p in rel.split(os.sep))
1180 parents.extend(p for p in rel.split(os.sep))
1177
1181
1178 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1182 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1179
1183
1180 if modname.startswith('distutils.tests.'):
1184 if modname.startswith('distutils.tests.'):
1181 continue
1185 continue
1182
1186
1183 if modname.endswith('.__init__'):
1187 if modname.endswith('.__init__'):
1184 modname = modname[: -len('.__init__')]
1188 modname = modname[: -len('.__init__')]
1185 path = os.path.dirname(full)
1189 path = os.path.dirname(full)
1186 else:
1190 else:
1187 path = None
1191 path = None
1188
1192
1189 res.modules[modname] = py2exemodule(
1193 res.modules[modname] = py2exemodule(
1190 modname, full, path=path
1194 modname, full, path=path
1191 )
1195 )
1192
1196
1193 if 'distutils' not in res.modules:
1197 if 'distutils' not in res.modules:
1194 raise SystemExit('could not find distutils modules')
1198 raise SystemExit('could not find distutils modules')
1195
1199
1196 return res
1200 return res
1197
1201
1198
1202
1199 cmdclass = {
1203 cmdclass = {
1200 'build': hgbuild,
1204 'build': hgbuild,
1201 'build_doc': hgbuilddoc,
1205 'build_doc': hgbuilddoc,
1202 'build_mo': hgbuildmo,
1206 'build_mo': hgbuildmo,
1203 'build_ext': hgbuildext,
1207 'build_ext': hgbuildext,
1204 'build_py': hgbuildpy,
1208 'build_py': hgbuildpy,
1205 'build_scripts': hgbuildscripts,
1209 'build_scripts': hgbuildscripts,
1206 'build_hgextindex': buildhgextindex,
1210 'build_hgextindex': buildhgextindex,
1207 'install': hginstall,
1211 'install': hginstall,
1208 'install_lib': hginstalllib,
1212 'install_lib': hginstalllib,
1209 'install_scripts': hginstallscripts,
1213 'install_scripts': hginstallscripts,
1210 'build_hgexe': buildhgexe,
1214 'build_hgexe': buildhgexe,
1211 }
1215 }
1212
1216
1213 if py2exehacked:
1217 if py2exehacked:
1214 cmdclass['py2exe'] = hgbuildpy2exe
1218 cmdclass['py2exe'] = hgbuildpy2exe
1215
1219
1216 packages = [
1220 packages = [
1217 'mercurial',
1221 'mercurial',
1218 'mercurial.cext',
1222 'mercurial.cext',
1219 'mercurial.cffi',
1223 'mercurial.cffi',
1220 'mercurial.defaultrc',
1224 'mercurial.defaultrc',
1221 'mercurial.helptext',
1225 'mercurial.helptext',
1222 'mercurial.helptext.internals',
1226 'mercurial.helptext.internals',
1223 'mercurial.hgweb',
1227 'mercurial.hgweb',
1224 'mercurial.interfaces',
1228 'mercurial.interfaces',
1225 'mercurial.pure',
1229 'mercurial.pure',
1226 'mercurial.thirdparty',
1230 'mercurial.thirdparty',
1227 'mercurial.thirdparty.attr',
1231 'mercurial.thirdparty.attr',
1228 'mercurial.thirdparty.zope',
1232 'mercurial.thirdparty.zope',
1229 'mercurial.thirdparty.zope.interface',
1233 'mercurial.thirdparty.zope.interface',
1230 'mercurial.utils',
1234 'mercurial.utils',
1231 'mercurial.revlogutils',
1235 'mercurial.revlogutils',
1232 'mercurial.testing',
1236 'mercurial.testing',
1233 'hgext',
1237 'hgext',
1234 'hgext.convert',
1238 'hgext.convert',
1235 'hgext.fsmonitor',
1239 'hgext.fsmonitor',
1236 'hgext.fastannotate',
1240 'hgext.fastannotate',
1237 'hgext.fsmonitor.pywatchman',
1241 'hgext.fsmonitor.pywatchman',
1238 'hgext.git',
1242 'hgext.git',
1239 'hgext.highlight',
1243 'hgext.highlight',
1240 'hgext.hooklib',
1244 'hgext.hooklib',
1241 'hgext.infinitepush',
1245 'hgext.infinitepush',
1242 'hgext.largefiles',
1246 'hgext.largefiles',
1243 'hgext.lfs',
1247 'hgext.lfs',
1244 'hgext.narrow',
1248 'hgext.narrow',
1245 'hgext.remotefilelog',
1249 'hgext.remotefilelog',
1246 'hgext.zeroconf',
1250 'hgext.zeroconf',
1247 'hgext3rd',
1251 'hgext3rd',
1248 'hgdemandimport',
1252 'hgdemandimport',
1249 ]
1253 ]
1250 if sys.version_info[0] == 2:
1254 if sys.version_info[0] == 2:
1251 packages.extend(
1255 packages.extend(
1252 [
1256 [
1253 'mercurial.thirdparty.concurrent',
1257 'mercurial.thirdparty.concurrent',
1254 'mercurial.thirdparty.concurrent.futures',
1258 'mercurial.thirdparty.concurrent.futures',
1255 ]
1259 ]
1256 )
1260 )
1257
1261
1258 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1262 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1259 # py2exe can't cope with namespace packages very well, so we have to
1263 # 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
1264 # 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.
1265 # image here. This is gross, but you gotta do what you gotta do.
1262 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1266 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1263
1267
1264 common_depends = [
1268 common_depends = [
1265 'mercurial/bitmanipulation.h',
1269 'mercurial/bitmanipulation.h',
1266 'mercurial/compat.h',
1270 'mercurial/compat.h',
1267 'mercurial/cext/util.h',
1271 'mercurial/cext/util.h',
1268 ]
1272 ]
1269 common_include_dirs = ['mercurial']
1273 common_include_dirs = ['mercurial']
1270
1274
1271 common_cflags = []
1275 common_cflags = []
1272
1276
1273 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1277 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1274 # makes declarations not at the top of a scope in the headers.
1278 # makes declarations not at the top of a scope in the headers.
1275 if os.name != 'nt' and sys.version_info[1] < 9:
1279 if os.name != 'nt' and sys.version_info[1] < 9:
1276 common_cflags = ['-Werror=declaration-after-statement']
1280 common_cflags = ['-Werror=declaration-after-statement']
1277
1281
1278 osutil_cflags = []
1282 osutil_cflags = []
1279 osutil_ldflags = []
1283 osutil_ldflags = []
1280
1284
1281 # platform specific macros
1285 # platform specific macros
1282 for plat, func in [('bsd', 'setproctitle')]:
1286 for plat, func in [('bsd', 'setproctitle')]:
1283 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1287 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1284 osutil_cflags.append('-DHAVE_%s' % func.upper())
1288 osutil_cflags.append('-DHAVE_%s' % func.upper())
1285
1289
1286 for plat, macro, code in [
1290 for plat, macro, code in [
1287 (
1291 (
1288 'bsd|darwin',
1292 'bsd|darwin',
1289 'BSD_STATFS',
1293 'BSD_STATFS',
1290 '''
1294 '''
1291 #include <sys/param.h>
1295 #include <sys/param.h>
1292 #include <sys/mount.h>
1296 #include <sys/mount.h>
1293 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1297 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1294 ''',
1298 ''',
1295 ),
1299 ),
1296 (
1300 (
1297 'linux',
1301 'linux',
1298 'LINUX_STATFS',
1302 'LINUX_STATFS',
1299 '''
1303 '''
1300 #include <linux/magic.h>
1304 #include <linux/magic.h>
1301 #include <sys/vfs.h>
1305 #include <sys/vfs.h>
1302 int main() { struct statfs s; return sizeof(s.f_type); }
1306 int main() { struct statfs s; return sizeof(s.f_type); }
1303 ''',
1307 ''',
1304 ),
1308 ),
1305 ]:
1309 ]:
1306 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1310 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1307 osutil_cflags.append('-DHAVE_%s' % macro)
1311 osutil_cflags.append('-DHAVE_%s' % macro)
1308
1312
1309 if sys.platform == 'darwin':
1313 if sys.platform == 'darwin':
1310 osutil_ldflags += ['-framework', 'ApplicationServices']
1314 osutil_ldflags += ['-framework', 'ApplicationServices']
1311
1315
1312 xdiff_srcs = [
1316 xdiff_srcs = [
1313 'mercurial/thirdparty/xdiff/xdiffi.c',
1317 'mercurial/thirdparty/xdiff/xdiffi.c',
1314 'mercurial/thirdparty/xdiff/xprepare.c',
1318 'mercurial/thirdparty/xdiff/xprepare.c',
1315 'mercurial/thirdparty/xdiff/xutils.c',
1319 'mercurial/thirdparty/xdiff/xutils.c',
1316 ]
1320 ]
1317
1321
1318 xdiff_headers = [
1322 xdiff_headers = [
1319 'mercurial/thirdparty/xdiff/xdiff.h',
1323 'mercurial/thirdparty/xdiff/xdiff.h',
1320 'mercurial/thirdparty/xdiff/xdiffi.h',
1324 'mercurial/thirdparty/xdiff/xdiffi.h',
1321 'mercurial/thirdparty/xdiff/xinclude.h',
1325 'mercurial/thirdparty/xdiff/xinclude.h',
1322 'mercurial/thirdparty/xdiff/xmacros.h',
1326 'mercurial/thirdparty/xdiff/xmacros.h',
1323 'mercurial/thirdparty/xdiff/xprepare.h',
1327 'mercurial/thirdparty/xdiff/xprepare.h',
1324 'mercurial/thirdparty/xdiff/xtypes.h',
1328 'mercurial/thirdparty/xdiff/xtypes.h',
1325 'mercurial/thirdparty/xdiff/xutils.h',
1329 'mercurial/thirdparty/xdiff/xutils.h',
1326 ]
1330 ]
1327
1331
1328
1332
1329 class RustCompilationError(CCompilerError):
1333 class RustCompilationError(CCompilerError):
1330 """Exception class for Rust compilation errors."""
1334 """Exception class for Rust compilation errors."""
1331
1335
1332
1336
1333 class RustExtension(Extension):
1337 class RustExtension(Extension):
1334 """Base classes for concrete Rust Extension classes.
1338 """Base classes for concrete Rust Extension classes.
1335 """
1339 """
1336
1340
1337 rusttargetdir = os.path.join('rust', 'target', 'release')
1341 rusttargetdir = os.path.join('rust', 'target', 'release')
1338
1342
1339 def __init__(
1343 def __init__(
1340 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1344 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1341 ):
1345 ):
1342 Extension.__init__(self, mpath, sources, **kw)
1346 Extension.__init__(self, mpath, sources, **kw)
1343 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1347 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1344 self.py3_features = py3_features
1348 self.py3_features = py3_features
1345
1349
1346 # adding Rust source and control files to depends so that the extension
1350 # adding Rust source and control files to depends so that the extension
1347 # gets rebuilt if they've changed
1351 # gets rebuilt if they've changed
1348 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1352 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1349 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1353 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1350 if os.path.exists(cargo_lock):
1354 if os.path.exists(cargo_lock):
1351 self.depends.append(cargo_lock)
1355 self.depends.append(cargo_lock)
1352 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1356 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1353 self.depends.extend(
1357 self.depends.extend(
1354 os.path.join(dirpath, fname)
1358 os.path.join(dirpath, fname)
1355 for fname in fnames
1359 for fname in fnames
1356 if os.path.splitext(fname)[1] == '.rs'
1360 if os.path.splitext(fname)[1] == '.rs'
1357 )
1361 )
1358
1362
1359 @staticmethod
1363 @staticmethod
1360 def rustdylibsuffix():
1364 def rustdylibsuffix():
1361 """Return the suffix for shared libraries produced by rustc.
1365 """Return the suffix for shared libraries produced by rustc.
1362
1366
1363 See also: https://doc.rust-lang.org/reference/linkage.html
1367 See also: https://doc.rust-lang.org/reference/linkage.html
1364 """
1368 """
1365 if sys.platform == 'darwin':
1369 if sys.platform == 'darwin':
1366 return '.dylib'
1370 return '.dylib'
1367 elif os.name == 'nt':
1371 elif os.name == 'nt':
1368 return '.dll'
1372 return '.dll'
1369 else:
1373 else:
1370 return '.so'
1374 return '.so'
1371
1375
1372 def rustbuild(self):
1376 def rustbuild(self):
1373 env = os.environ.copy()
1377 env = os.environ.copy()
1374 if 'HGTEST_RESTOREENV' in env:
1378 if 'HGTEST_RESTOREENV' in env:
1375 # Mercurial tests change HOME to a temporary directory,
1379 # Mercurial tests change HOME to a temporary directory,
1376 # but, if installed with rustup, the Rust toolchain needs
1380 # but, if installed with rustup, the Rust toolchain needs
1377 # HOME to be correct (otherwise the 'no default toolchain'
1381 # HOME to be correct (otherwise the 'no default toolchain'
1378 # error message is issued and the build fails).
1382 # error message is issued and the build fails).
1379 # This happens currently with test-hghave.t, which does
1383 # This happens currently with test-hghave.t, which does
1380 # invoke this build.
1384 # invoke this build.
1381
1385
1382 # Unix only fix (os.path.expanduser not really reliable if
1386 # Unix only fix (os.path.expanduser not really reliable if
1383 # HOME is shadowed like this)
1387 # HOME is shadowed like this)
1384 import pwd
1388 import pwd
1385
1389
1386 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1390 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1387
1391
1388 cargocmd = ['cargo', 'rustc', '-vv', '--release']
1392 cargocmd = ['cargo', 'rustc', '-vv', '--release']
1389
1393
1390 feature_flags = []
1394 feature_flags = []
1391
1395
1392 if sys.version_info[0] == 3 and self.py3_features is not None:
1396 if sys.version_info[0] == 3 and self.py3_features is not None:
1393 feature_flags.append(self.py3_features)
1397 feature_flags.append(self.py3_features)
1394 cargocmd.append('--no-default-features')
1398 cargocmd.append('--no-default-features')
1395
1399
1396 rust_features = env.get("HG_RUST_FEATURES")
1400 rust_features = env.get("HG_RUST_FEATURES")
1397 if rust_features:
1401 if rust_features:
1398 feature_flags.append(rust_features)
1402 feature_flags.append(rust_features)
1399
1403
1400 cargocmd.extend(('--features', " ".join(feature_flags)))
1404 cargocmd.extend(('--features', " ".join(feature_flags)))
1401
1405
1402 cargocmd.append('--')
1406 cargocmd.append('--')
1403 if sys.platform == 'darwin':
1407 if sys.platform == 'darwin':
1404 cargocmd.extend(
1408 cargocmd.extend(
1405 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1409 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1406 )
1410 )
1407 try:
1411 try:
1408 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1412 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1409 except OSError as exc:
1413 except OSError as exc:
1410 if exc.errno == errno.ENOENT:
1414 if exc.errno == errno.ENOENT:
1411 raise RustCompilationError("Cargo not found")
1415 raise RustCompilationError("Cargo not found")
1412 elif exc.errno == errno.EACCES:
1416 elif exc.errno == errno.EACCES:
1413 raise RustCompilationError(
1417 raise RustCompilationError(
1414 "Cargo found, but permisssion to execute it is denied"
1418 "Cargo found, but permisssion to execute it is denied"
1415 )
1419 )
1416 else:
1420 else:
1417 raise
1421 raise
1418 except subprocess.CalledProcessError:
1422 except subprocess.CalledProcessError:
1419 raise RustCompilationError(
1423 raise RustCompilationError(
1420 "Cargo failed. Working directory: %r, "
1424 "Cargo failed. Working directory: %r, "
1421 "command: %r, environment: %r"
1425 "command: %r, environment: %r"
1422 % (self.rustsrcdir, cargocmd, env)
1426 % (self.rustsrcdir, cargocmd, env)
1423 )
1427 )
1424
1428
1425
1429
1426 class RustStandaloneExtension(RustExtension):
1430 class RustStandaloneExtension(RustExtension):
1427 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1431 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1428 RustExtension.__init__(
1432 RustExtension.__init__(
1429 self, pydottedname, [], dylibname, rustcrate, **kw
1433 self, pydottedname, [], dylibname, rustcrate, **kw
1430 )
1434 )
1431 self.dylibname = dylibname
1435 self.dylibname = dylibname
1432
1436
1433 def build(self, target_dir):
1437 def build(self, target_dir):
1434 self.rustbuild()
1438 self.rustbuild()
1435 target = [target_dir]
1439 target = [target_dir]
1436 target.extend(self.name.split('.'))
1440 target.extend(self.name.split('.'))
1437 target[-1] += DYLIB_SUFFIX
1441 target[-1] += DYLIB_SUFFIX
1438 shutil.copy2(
1442 shutil.copy2(
1439 os.path.join(
1443 os.path.join(
1440 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1444 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1441 ),
1445 ),
1442 os.path.join(*target),
1446 os.path.join(*target),
1443 )
1447 )
1444
1448
1445
1449
1446 extmodules = [
1450 extmodules = [
1447 Extension(
1451 Extension(
1448 'mercurial.cext.base85',
1452 'mercurial.cext.base85',
1449 ['mercurial/cext/base85.c'],
1453 ['mercurial/cext/base85.c'],
1450 include_dirs=common_include_dirs,
1454 include_dirs=common_include_dirs,
1451 extra_compile_args=common_cflags,
1455 extra_compile_args=common_cflags,
1452 depends=common_depends,
1456 depends=common_depends,
1453 ),
1457 ),
1454 Extension(
1458 Extension(
1455 'mercurial.cext.bdiff',
1459 'mercurial.cext.bdiff',
1456 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1460 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1457 include_dirs=common_include_dirs,
1461 include_dirs=common_include_dirs,
1458 extra_compile_args=common_cflags,
1462 extra_compile_args=common_cflags,
1459 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1463 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1460 ),
1464 ),
1461 Extension(
1465 Extension(
1462 'mercurial.cext.mpatch',
1466 'mercurial.cext.mpatch',
1463 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1467 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1464 include_dirs=common_include_dirs,
1468 include_dirs=common_include_dirs,
1465 extra_compile_args=common_cflags,
1469 extra_compile_args=common_cflags,
1466 depends=common_depends,
1470 depends=common_depends,
1467 ),
1471 ),
1468 Extension(
1472 Extension(
1469 'mercurial.cext.parsers',
1473 'mercurial.cext.parsers',
1470 [
1474 [
1471 'mercurial/cext/charencode.c',
1475 'mercurial/cext/charencode.c',
1472 'mercurial/cext/dirs.c',
1476 'mercurial/cext/dirs.c',
1473 'mercurial/cext/manifest.c',
1477 'mercurial/cext/manifest.c',
1474 'mercurial/cext/parsers.c',
1478 'mercurial/cext/parsers.c',
1475 'mercurial/cext/pathencode.c',
1479 'mercurial/cext/pathencode.c',
1476 'mercurial/cext/revlog.c',
1480 'mercurial/cext/revlog.c',
1477 ],
1481 ],
1478 include_dirs=common_include_dirs,
1482 include_dirs=common_include_dirs,
1479 extra_compile_args=common_cflags,
1483 extra_compile_args=common_cflags,
1480 depends=common_depends
1484 depends=common_depends
1481 + ['mercurial/cext/charencode.h', 'mercurial/cext/revlog.h',],
1485 + ['mercurial/cext/charencode.h', 'mercurial/cext/revlog.h',],
1482 ),
1486 ),
1483 Extension(
1487 Extension(
1484 'mercurial.cext.osutil',
1488 'mercurial.cext.osutil',
1485 ['mercurial/cext/osutil.c'],
1489 ['mercurial/cext/osutil.c'],
1486 include_dirs=common_include_dirs,
1490 include_dirs=common_include_dirs,
1487 extra_compile_args=common_cflags + osutil_cflags,
1491 extra_compile_args=common_cflags + osutil_cflags,
1488 extra_link_args=osutil_ldflags,
1492 extra_link_args=osutil_ldflags,
1489 depends=common_depends,
1493 depends=common_depends,
1490 ),
1494 ),
1491 Extension(
1495 Extension(
1492 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1496 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1493 [
1497 [
1494 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1498 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1495 ],
1499 ],
1496 extra_compile_args=common_cflags,
1500 extra_compile_args=common_cflags,
1497 ),
1501 ),
1498 Extension(
1502 Extension(
1499 'mercurial.thirdparty.sha1dc',
1503 'mercurial.thirdparty.sha1dc',
1500 [
1504 [
1501 'mercurial/thirdparty/sha1dc/cext.c',
1505 'mercurial/thirdparty/sha1dc/cext.c',
1502 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1506 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1503 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1507 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1504 ],
1508 ],
1505 extra_compile_args=common_cflags,
1509 extra_compile_args=common_cflags,
1506 ),
1510 ),
1507 Extension(
1511 Extension(
1508 'hgext.fsmonitor.pywatchman.bser',
1512 'hgext.fsmonitor.pywatchman.bser',
1509 ['hgext/fsmonitor/pywatchman/bser.c'],
1513 ['hgext/fsmonitor/pywatchman/bser.c'],
1510 extra_compile_args=common_cflags,
1514 extra_compile_args=common_cflags,
1511 ),
1515 ),
1512 RustStandaloneExtension(
1516 RustStandaloneExtension(
1513 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1517 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1514 ),
1518 ),
1515 ]
1519 ]
1516
1520
1517
1521
1518 sys.path.insert(0, 'contrib/python-zstandard')
1522 sys.path.insert(0, 'contrib/python-zstandard')
1519 import setup_zstd
1523 import setup_zstd
1520
1524
1521 zstd = setup_zstd.get_c_extension(
1525 zstd = setup_zstd.get_c_extension(
1522 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1526 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1523 )
1527 )
1524 zstd.extra_compile_args += common_cflags
1528 zstd.extra_compile_args += common_cflags
1525 extmodules.append(zstd)
1529 extmodules.append(zstd)
1526
1530
1527 try:
1531 try:
1528 from distutils import cygwinccompiler
1532 from distutils import cygwinccompiler
1529
1533
1530 # the -mno-cygwin option has been deprecated for years
1534 # the -mno-cygwin option has been deprecated for years
1531 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1535 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1532
1536
1533 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1537 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1534 def __init__(self, *args, **kwargs):
1538 def __init__(self, *args, **kwargs):
1535 mingw32compilerclass.__init__(self, *args, **kwargs)
1539 mingw32compilerclass.__init__(self, *args, **kwargs)
1536 for i in 'compiler compiler_so linker_exe linker_so'.split():
1540 for i in 'compiler compiler_so linker_exe linker_so'.split():
1537 try:
1541 try:
1538 getattr(self, i).remove('-mno-cygwin')
1542 getattr(self, i).remove('-mno-cygwin')
1539 except ValueError:
1543 except ValueError:
1540 pass
1544 pass
1541
1545
1542 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1546 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1543 except ImportError:
1547 except ImportError:
1544 # the cygwinccompiler package is not available on some Python
1548 # the cygwinccompiler package is not available on some Python
1545 # distributions like the ones from the optware project for Synology
1549 # distributions like the ones from the optware project for Synology
1546 # DiskStation boxes
1550 # DiskStation boxes
1547 class HackedMingw32CCompiler(object):
1551 class HackedMingw32CCompiler(object):
1548 pass
1552 pass
1549
1553
1550
1554
1551 if os.name == 'nt':
1555 if os.name == 'nt':
1552 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1556 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1553 # extra_link_args to distutils.extensions.Extension() doesn't have any
1557 # extra_link_args to distutils.extensions.Extension() doesn't have any
1554 # effect.
1558 # effect.
1555 from distutils import msvccompiler
1559 from distutils import msvccompiler
1556
1560
1557 msvccompilerclass = msvccompiler.MSVCCompiler
1561 msvccompilerclass = msvccompiler.MSVCCompiler
1558
1562
1559 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1563 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1560 def initialize(self):
1564 def initialize(self):
1561 msvccompilerclass.initialize(self)
1565 msvccompilerclass.initialize(self)
1562 # "warning LNK4197: export 'func' specified multiple times"
1566 # "warning LNK4197: export 'func' specified multiple times"
1563 self.ldflags_shared.append('/ignore:4197')
1567 self.ldflags_shared.append('/ignore:4197')
1564 self.ldflags_shared_debug.append('/ignore:4197')
1568 self.ldflags_shared_debug.append('/ignore:4197')
1565
1569
1566 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1570 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1567
1571
1568 packagedata = {
1572 packagedata = {
1569 'mercurial': [
1573 'mercurial': [
1570 'locale/*/LC_MESSAGES/hg.mo',
1574 'locale/*/LC_MESSAGES/hg.mo',
1571 'defaultrc/*.rc',
1575 'defaultrc/*.rc',
1572 'dummycert.pem',
1576 'dummycert.pem',
1573 ],
1577 ],
1574 'mercurial.helptext': ['*.txt',],
1578 'mercurial.helptext': ['*.txt',],
1575 'mercurial.helptext.internals': ['*.txt',],
1579 'mercurial.helptext.internals': ['*.txt',],
1576 }
1580 }
1577
1581
1578
1582
1579 def ordinarypath(p):
1583 def ordinarypath(p):
1580 return p and p[0] != '.' and p[-1] != '~'
1584 return p and p[0] != '.' and p[-1] != '~'
1581
1585
1582
1586
1583 for root in ('templates',):
1587 for root in ('templates',):
1584 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1588 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1585 curdir = curdir.split(os.sep, 1)[1]
1589 curdir = curdir.split(os.sep, 1)[1]
1586 dirs[:] = filter(ordinarypath, dirs)
1590 dirs[:] = filter(ordinarypath, dirs)
1587 for f in filter(ordinarypath, files):
1591 for f in filter(ordinarypath, files):
1588 f = os.path.join(curdir, f)
1592 f = os.path.join(curdir, f)
1589 packagedata['mercurial'].append(f)
1593 packagedata['mercurial'].append(f)
1590
1594
1591 datafiles = []
1595 datafiles = []
1592
1596
1593 # distutils expects version to be str/unicode. Converting it to
1597 # distutils expects version to be str/unicode. Converting it to
1594 # unicode on Python 2 still works because it won't contain any
1598 # unicode on Python 2 still works because it won't contain any
1595 # non-ascii bytes and will be implicitly converted back to bytes
1599 # non-ascii bytes and will be implicitly converted back to bytes
1596 # when operated on.
1600 # when operated on.
1597 assert isinstance(version, bytes)
1601 assert isinstance(version, bytes)
1598 setupversion = version.decode('ascii')
1602 setupversion = version.decode('ascii')
1599
1603
1600 extra = {}
1604 extra = {}
1601
1605
1602 py2exepackages = [
1606 py2exepackages = [
1603 'hgdemandimport',
1607 'hgdemandimport',
1604 'hgext3rd',
1608 'hgext3rd',
1605 'hgext',
1609 'hgext',
1606 'email',
1610 'email',
1607 # implicitly imported per module policy
1611 # implicitly imported per module policy
1608 # (cffi wouldn't be used as a frozen exe)
1612 # (cffi wouldn't be used as a frozen exe)
1609 'mercurial.cext',
1613 'mercurial.cext',
1610 #'mercurial.cffi',
1614 #'mercurial.cffi',
1611 'mercurial.pure',
1615 'mercurial.pure',
1612 ]
1616 ]
1613
1617
1614 py2exeexcludes = []
1618 py2exeexcludes = []
1615 py2exedllexcludes = ['crypt32.dll']
1619 py2exedllexcludes = ['crypt32.dll']
1616
1620
1617 if issetuptools:
1621 if issetuptools:
1618 extra['python_requires'] = supportedpy
1622 extra['python_requires'] = supportedpy
1619
1623
1620 if py2exeloaded:
1624 if py2exeloaded:
1621 extra['console'] = [
1625 extra['console'] = [
1622 {
1626 {
1623 'script': 'hg',
1627 'script': 'hg',
1624 'copyright': 'Copyright (C) 2005-2020 Matt Mackall and others',
1628 'copyright': 'Copyright (C) 2005-2020 Matt Mackall and others',
1625 'product_version': version,
1629 'product_version': version,
1626 }
1630 }
1627 ]
1631 ]
1628 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1632 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1629 # Need to override hgbuild because it has a private copy of
1633 # Need to override hgbuild because it has a private copy of
1630 # build.sub_commands.
1634 # build.sub_commands.
1631 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1635 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1632 # put dlls in sub directory so that they won't pollute PATH
1636 # put dlls in sub directory so that they won't pollute PATH
1633 extra['zipfile'] = 'lib/library.zip'
1637 extra['zipfile'] = 'lib/library.zip'
1634
1638
1635 # We allow some configuration to be supplemented via environment
1639 # We allow some configuration to be supplemented via environment
1636 # variables. This is better than setup.cfg files because it allows
1640 # variables. This is better than setup.cfg files because it allows
1637 # supplementing configs instead of replacing them.
1641 # supplementing configs instead of replacing them.
1638 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1642 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1639 if extrapackages:
1643 if extrapackages:
1640 py2exepackages.extend(extrapackages.split(' '))
1644 py2exepackages.extend(extrapackages.split(' '))
1641
1645
1642 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1646 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1643 if excludes:
1647 if excludes:
1644 py2exeexcludes.extend(excludes.split(' '))
1648 py2exeexcludes.extend(excludes.split(' '))
1645
1649
1646 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1650 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1647 if dllexcludes:
1651 if dllexcludes:
1648 py2exedllexcludes.extend(dllexcludes.split(' '))
1652 py2exedllexcludes.extend(dllexcludes.split(' '))
1649
1653
1650 if os.name == 'nt':
1654 if os.name == 'nt':
1651 # Windows binary file versions for exe/dll files must have the
1655 # Windows binary file versions for exe/dll files must have the
1652 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1656 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1653 setupversion = setupversion.split(r'+', 1)[0]
1657 setupversion = setupversion.split(r'+', 1)[0]
1654
1658
1655 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1659 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1656 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1660 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1657 if version:
1661 if version:
1658 version = version[0]
1662 version = version[0]
1659 if sys.version_info[0] == 3:
1663 if sys.version_info[0] == 3:
1660 version = version.decode('utf-8')
1664 version = version.decode('utf-8')
1661 xcode4 = version.startswith('Xcode') and StrictVersion(
1665 xcode4 = version.startswith('Xcode') and StrictVersion(
1662 version.split()[1]
1666 version.split()[1]
1663 ) >= StrictVersion('4.0')
1667 ) >= StrictVersion('4.0')
1664 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1668 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1665 else:
1669 else:
1666 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1670 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1667 # installed, but instead with only command-line tools. Assume
1671 # installed, but instead with only command-line tools. Assume
1668 # that only happens on >= Lion, thus no PPC support.
1672 # that only happens on >= Lion, thus no PPC support.
1669 xcode4 = True
1673 xcode4 = True
1670 xcode51 = False
1674 xcode51 = False
1671
1675
1672 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1676 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1673 # distutils.sysconfig
1677 # distutils.sysconfig
1674 if xcode4:
1678 if xcode4:
1675 os.environ['ARCHFLAGS'] = ''
1679 os.environ['ARCHFLAGS'] = ''
1676
1680
1677 # XCode 5.1 changes clang such that it now fails to compile if the
1681 # XCode 5.1 changes clang such that it now fails to compile if the
1678 # -mno-fused-madd flag is passed, but the version of Python shipped with
1682 # -mno-fused-madd flag is passed, but the version of Python shipped with
1679 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1683 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1680 # C extension modules, and a bug has been filed upstream at
1684 # C extension modules, and a bug has been filed upstream at
1681 # http://bugs.python.org/issue21244. We also need to patch this here
1685 # http://bugs.python.org/issue21244. We also need to patch this here
1682 # so Mercurial can continue to compile in the meantime.
1686 # so Mercurial can continue to compile in the meantime.
1683 if xcode51:
1687 if xcode51:
1684 cflags = get_config_var('CFLAGS')
1688 cflags = get_config_var('CFLAGS')
1685 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1689 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1686 os.environ['CFLAGS'] = (
1690 os.environ['CFLAGS'] = (
1687 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1691 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1688 )
1692 )
1689
1693
1690 setup(
1694 setup(
1691 name='mercurial',
1695 name='mercurial',
1692 version=setupversion,
1696 version=setupversion,
1693 author='Matt Mackall and many others',
1697 author='Matt Mackall and many others',
1694 author_email='mercurial@mercurial-scm.org',
1698 author_email='mercurial@mercurial-scm.org',
1695 url='https://mercurial-scm.org/',
1699 url='https://mercurial-scm.org/',
1696 download_url='https://mercurial-scm.org/release/',
1700 download_url='https://mercurial-scm.org/release/',
1697 description=(
1701 description=(
1698 'Fast scalable distributed SCM (revision control, version '
1702 'Fast scalable distributed SCM (revision control, version '
1699 'control) system'
1703 'control) system'
1700 ),
1704 ),
1701 long_description=(
1705 long_description=(
1702 'Mercurial is a distributed SCM tool written in Python.'
1706 'Mercurial is a distributed SCM tool written in Python.'
1703 ' It is used by a number of large projects that require'
1707 ' It is used by a number of large projects that require'
1704 ' fast, reliable distributed revision control, such as '
1708 ' fast, reliable distributed revision control, such as '
1705 'Mozilla.'
1709 'Mozilla.'
1706 ),
1710 ),
1707 license='GNU GPLv2 or any later version',
1711 license='GNU GPLv2 or any later version',
1708 classifiers=[
1712 classifiers=[
1709 'Development Status :: 6 - Mature',
1713 'Development Status :: 6 - Mature',
1710 'Environment :: Console',
1714 'Environment :: Console',
1711 'Intended Audience :: Developers',
1715 'Intended Audience :: Developers',
1712 'Intended Audience :: System Administrators',
1716 'Intended Audience :: System Administrators',
1713 'License :: OSI Approved :: GNU General Public License (GPL)',
1717 'License :: OSI Approved :: GNU General Public License (GPL)',
1714 'Natural Language :: Danish',
1718 'Natural Language :: Danish',
1715 'Natural Language :: English',
1719 'Natural Language :: English',
1716 'Natural Language :: German',
1720 'Natural Language :: German',
1717 'Natural Language :: Italian',
1721 'Natural Language :: Italian',
1718 'Natural Language :: Japanese',
1722 'Natural Language :: Japanese',
1719 'Natural Language :: Portuguese (Brazilian)',
1723 'Natural Language :: Portuguese (Brazilian)',
1720 'Operating System :: Microsoft :: Windows',
1724 'Operating System :: Microsoft :: Windows',
1721 'Operating System :: OS Independent',
1725 'Operating System :: OS Independent',
1722 'Operating System :: POSIX',
1726 'Operating System :: POSIX',
1723 'Programming Language :: C',
1727 'Programming Language :: C',
1724 'Programming Language :: Python',
1728 'Programming Language :: Python',
1725 'Topic :: Software Development :: Version Control',
1729 'Topic :: Software Development :: Version Control',
1726 ],
1730 ],
1727 scripts=scripts,
1731 scripts=scripts,
1728 packages=packages,
1732 packages=packages,
1729 ext_modules=extmodules,
1733 ext_modules=extmodules,
1730 data_files=datafiles,
1734 data_files=datafiles,
1731 package_data=packagedata,
1735 package_data=packagedata,
1732 cmdclass=cmdclass,
1736 cmdclass=cmdclass,
1733 distclass=hgdist,
1737 distclass=hgdist,
1734 options={
1738 options={
1735 'py2exe': {
1739 'py2exe': {
1736 'bundle_files': 3,
1740 'bundle_files': 3,
1737 'dll_excludes': py2exedllexcludes,
1741 'dll_excludes': py2exedllexcludes,
1738 'excludes': py2exeexcludes,
1742 'excludes': py2exeexcludes,
1739 'packages': py2exepackages,
1743 'packages': py2exepackages,
1740 },
1744 },
1741 'bdist_mpkg': {
1745 'bdist_mpkg': {
1742 'zipdist': False,
1746 'zipdist': False,
1743 'license': 'COPYING',
1747 'license': 'COPYING',
1744 'readme': 'contrib/packaging/macosx/Readme.html',
1748 'readme': 'contrib/packaging/macosx/Readme.html',
1745 'welcome': 'contrib/packaging/macosx/Welcome.html',
1749 'welcome': 'contrib/packaging/macosx/Welcome.html',
1746 },
1750 },
1747 },
1751 },
1748 **extra
1752 **extra
1749 )
1753 )
General Comments 0
You need to be logged in to leave comments. Login now