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