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