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