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