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