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