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