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