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