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