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