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