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